Why is my User Login no longer working after upgrading to Symfony3 - authentication

I came along a strange Problem with Symfony 3.
Under Symfony 2 everyhting worked out of the Box (Login).
But under Symfony 3 it doesn't validate at all.
The Doctrine Layer is not Loading my User Object nor the Repository.
Whats going on?

UserProviderInterface was changed to UserLoaderInterface in 2.8 (see doc)
class UserRepository extends EntityRepository implements
UserProviderInterface
class UserRepository extends EntityRepository
implements UserLoaderInterface
This wil fix the problem, you can also delete these functions:
public function refreshUser(UserInterface $user)
public function supportsClass($class)

Ok, short update.
I was able to fix this and would like to share what happened.
After Debuging the complete Login Prozess I stumbled accross the main cause for not beeing able to login.
<?php
// src/AppBundle/Entity/UserRepository.php
namespace AppBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$user = $this->createQueryBuilder('u')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery()
->getOneOrNullResult();
if (null === $user) {
$message = sprintf(
'Unable to find an active admin AppBundle:User object identified by "%s".',
$username
);
throw new UsernameNotFoundException($message);
}
return $user;
}
public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(
sprintf(
'Instances of "%s" are not supported.',
$class
)
);
}
return $this->find($user->getId());
}
public function supportsClass($class)
{
return $this->getEntityName() === $class
|| is_subclass_of($class, $this->getEntityName());
}
}
Ok,
this Repository Query Class is actually the reason why it is not working.
After Debuging and Testing I came Along this Code Block in the Class:
Symfony\Bridge\Doctrine\Security\User
/**
* {#inheritdoc}
*/
public function loadUserByUsername($username)
{
if (null !== $this->property) {
$user = $this->repository->findOneBy(array($this->property => $username));
} else {
if (!$this->repository instanceof UserLoaderInterface) {
throw new \InvalidArgumentException(sprintf('The Doctrine repository "%s" must implement Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface.', get_class($this->repository)));
}
$user = $this->repository->loadUserByUsername($username);
}
if (null === $user) {
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
}
return $user;
}
It states that the Repository Class must be an instance of UserLoaderInterface.
But the Documentation from
http://symfony.com/doc/current/cookbook/security/entity_provider.html
states it is an Instance of UserProviderInterface.
so the Login fails as it is not the right Interface implemented.
The Documentation (Cookbook) has an old Information in it, or the Symfony Team just simply forgot about it. ^^(can happen)
Hope this helps someone ^^

For me the issue was that the isEqualTo method of the EquatableInterface (on my User entity) was returning false when it should have been returning true.

Related

Symfony 3 get current user inside entity

I was wondering if there is a way that i can initialize the property owner with an entity User of FOSUserBundle so that it contains the user who created the Post
I want to do this inside the constructor as shown below.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post
{
/* here are defined some attributs */
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="posts")
* #ORM\JoinColumn(name="owner", referencedColumnName="id")
*/
private $owner;
public function __construct()
{
$this->owner = /* get current user */ ;
}
}
Is there a way to do this by replacing the comment in the constructor with something ?
Thank you for your answers
No, there isn't. [*]
There are at least two ways to deal with this:
Create your Post entities through a factory service which populates the
owner property:
namespace My\Bundle\EntityFactory;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostFactory
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function createPost()
{
$user = $this->tokenStorage()->getToken()->getUser();
$post = new Post($user);
}
}
(for this example, you will have to modify your Post constructor to
accept the owner as a parameter)
In services.yml:
services:
post_factory:
class: My\Bundle\EntityFactory\PostFactory
arguments: [#security.token_storage]
To create an entity from your controller:
$post = $this->container->get('post_factory')->createPost();
If you can tolerate that the owner will only be set once you persist the
entity, you can use a doctrine event listener:
namespace My\Bundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostOwnerAssignmentListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
if ($entity instanceof Post && !$entity->getOwner()) {
$entity->setOwner($this->tokenStorage->getToken()->getUser());
}
}
}
In services.yml:
services:
post_owner_assignment_listener:
class: My\Bundle\EventListener\PostOwnerAssignmentListener
arguments: [#security.token_storage]
tags:
- { name: doctrine.event_listener, event: prePersit }
The advantage here is that the owner gets assigned no matter how and where
the Post is created.
[*]: Well, technically with the default app.php you could access the
kernel by declaring global $kernel; in your constructor and go from there,
however this is very strongly discouraged and may break in strange and subtle
ways.
I think you are way over-complicating this issue. When you create a new Post in your controller, either in the controller or in the repository do something like this:
use AppBundle\Entity\Post; //at top of controller
$em = $this->getDoctrine()->getManager();
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$post = new Post();
$em->persist( $post );
$post->setOwner( $user );
// set other fields in your post entity
$em->flush();
For Symfony 4+ with Autowiring and Entity event listener:
In /EventListener/PostPrePersistListener.php:
namespace App\EventListener;
use App\Entity\Post;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class PostPrePersistListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(Post $post, LifecycleEventArgs $event)
{
$post->setOwner($this->tokenStorage->getToken()->getUser());
}
}
In services.yaml:
services:
App\EventListener\PostPrePersistListener:
autowire: true
tags:
- { name: doctrine.orm.entity_listener, entity: 'App\Entity\Post', event: prePersist }
Modifying services.yaml is required as Symfony cannot know that this custom service is tagged to hook on doctrine.event_listener
This works at Entity-level as asked, to ensure Controller do not handle the owner value.

Laravel Testing Error

I just started with learning how to test within Laravel. I came across some problems though..
I'm testing my controller and want to check if a View has a variable assigned.
My controller code:
class PagesController extends \BaseController {
protected $post;
public function __construct(Post $post) {
$this->post = $post;
}
public function index() {
$posts = $this->post->all();
return View::make('hello', ['posts' => $posts]);
}
}
And my view contains a foreach loop to display all posts:
#foreach ($posts as $post)
{{post->id}}
#endforeach
Last but not least my test file:
class PostControllerTest extends TestCase {
public function __construct()
{
// We have no interest in testing Eloquent
$this->mock = Mockery::mock('Eloquent', 'Post');
}
public function tearDown()
{
Mockery::close();
}
public function testIndex() {
$this->mock->shouldReceive('all')->once()->andReturn('foo');
$this->app->instance('Post', $this->mock);
$this->call('GET', '/');
$this->assertViewHas('posts');
}
}
Now comes the problem, when I run "phpunit" the following error appears:
ErrorException: Invalid argument supplied for foreach()
Any ideas why phpunit returns this error?
Your problem is here:
$this->mock->shouldReceive('all')->once()->andReturn('foo');
$this->post->all() (which is what you're mocking) should return an array, and that's what your view expects. You're returning a string.
$this->mock->shouldReceive('all')->once()->andReturn(array('foo'));
should take care of the error you have, though you'll then get an error of the "Getting property of non-object" type.
You could do this:
$mockPost = new stdClass();
$mockPost->id = 1;
$this->mock->shouldReceive('all')->once()->andReturn(array($mockpost));
You should mock the view as well:
public function testIndex() {
$this->mock->shouldReceive('all')->once()->andReturn('foo');
$this->app->instance('Post', $this->mock);
View::shouldReceive('make')->with('hello', array('posts', 'foo'))->once();
$this->call('GET', '/');
}

how to use SimpleSAMLphp in yii framework?

I have two project in yii framework and I want to use both project using SimpleSAMLphp with SSO. The condition, I need is if I login from the first project, i want access to the second project.
Thank you in advance.
First you load the SAML library by temporarily disabling the Yii autoloader. This is just to let you use the SAML classes and methods:
<?php
class YiiSAML extends CComponent {
private $_yiiSAML = null;
static private function pre() {
require_once (Yii::app()->params['simpleSAML'] . '/lib/_autoload.php');
// temporary disable Yii autoloader
spl_autoload_unregister(array(
'YiiBase',
'autoload'
));
}
static private function post() {
// enable Yii autoloader
spl_autoload_register(array(
'YiiBase',
'autoload'
));
}
public function __construct() {
self::pre();
//We select our authentication source:
$this->_yiiSAML = new SimpleSAML_Auth_Simple(Yii::app()->params['authSource']);
self::post();
}
static public function loggedOut($param, $stage) {
self::pre();
$state = SimpleSAML_Auth_State::loadState($param, $stage);
self::post();
if (isset($state['saml:sp:LogoutStatus'])) {
$ls = $state['saml:sp:LogoutStatus']; /* Only for SAML SP */
} else return true;
return $ls['Code'] === 'urn:oasis:names:tc:SAML:2.0:status:Success' && !isset($ls['SubCode']);
}
public function __call($method, $args) {
$params = (is_array($args) and !empty($args)) ? $args[0] : $args;
if (method_exists($this->_yiiSAML, $method)) return $this->_yiiSAML->$method($params);
else throw new YiiSAMLException(Yii::t('app', 'The method {method} does not exist in the SAML class', array(
'{method}' => $method
)));
}
}
class YiiSAMLException extends CException {
}
Then you define a filter extending the CFilter Yii class:
<?php
Yii::import('lib.YiiSAML');
class SAMLControl extends CFilter {
protected function preFilter($filterChain) {
$msg = Yii::t('yii', 'You are not authorized to perform this action.');
$saml = new YiiSAML();
if (Yii::app()->user->isGuest) {
Yii::app()->user->loginRequired();
return false;
} else {
$saml_attributes = $saml->getAttributes();
if (!$saml->isAuthenticated() or Yii::app()->user->id != $saml_attributes['User.id'][0]) {
Yii::app()->user->logout();
Yii::app()->user->loginRequired();
return false;
}
return true;
}
}
}
And finally, in the controllers you are interested to restrict, you override the filters() method:
public function filters() {
return array(
array(
'lib.SAMLControl'
) , // perform access control for CRUD operations
...
);
}
Hope it helps.
It can be done simply using "vendors" directory.
Download PHP Library from https://simplesamlphp.org/
Implement it in Yii Framework as a vendor library. (http://www.yiiframework.com/doc/guide/1.1/en/extension.integration)
Good Luck :)
I came across an Yii Extension for SimpleSAMLphp in github
https://github.com/asasmoyo/yii-simplesamlphp
You can load the simplesamlphp as a vendor library and then specify the autoload file in the extension.
Apart from the extension you can copy all the necessary configs and metadatas into the application and configure SimpleSAML Configuration to load the configurations from your directory, so you can keep the vendor package untouched for future updates.

Symfony authenticate user with external api key

I am trying to authenticate a user via external api key request following this http://symfony.com/doc/current/cookbook/security/api_key_authentication.html#cookbook-security-api-key-config
What is ["#your_api_key_user_provider"] ?
If I put something like ["test"] I get an error.
[UPDATE]
This is my ApiKeyAuthenticator.php:
// src/Acme/HelloBundle/Security/ApiKeyAuthenticator.php
namespace Acme\HelloBundle\Security;
use ////
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
protected $userProvider;
public function __construct(ApiKeyUserProvider $userProvider)
{
$this->userProvider = $userProvider;
}
public function createToken(Request $request, $providerKey)
{
if (!$request->query->has('apikey')) {
throw new BadCredentialsException('No API key found');
}
return new PreAuthenticatedToken(
'anon.',
$request->query->get('apikey'),
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
$apiKey = $token->getCredentials();
$username = $this->userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
throw new AuthenticationException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
$user = $this->userProvider->loadUserByUsername($username);
return new PreAuthenticatedToken(
$user,
$apiKey,
$providerKey,
$user->getRoles()
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
}
While the user provider is this:
// src/Acme/HelloBundle/Security/ApiKeyUserProvider.php
namespace Acme\HelloBundle\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
public function getUsernameForApiKey($apiKey)
{
// Look up the username based on the token in the database, via
// an API call, or do something entirely different
$username = ...;
return $username;
}
public function loadUserByUsername($username)
{
return new User(
$username,
null,
// the roles for the user - you may choose to determine
// these dynamically somehow based on the user
array('ROLE_USER')
);
}
public function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public function supportsClass($class)
{
return 'Symfony\Component\Security\Core\User\User' === $class;
}
}
The service should be just this:
services:
# ...
apikey_authenticator:
class: Acme\SeedBundle\Security\ApiKeyAuthenticator
arguments: ["#ApiKeyUserProvider"]
But i got this error: The service "apikey_authenticator" has a dependency on a non-existent service "apikeyuserprovider".
Thanks
That is the user provider service that you should have created following this doc:
http://symfony.com/doc/current/cookbook/security/custom_provider.html
So you register your user provider as a service IE: apikey_userprovider
http://symfony.com/doc/current/cookbook/security/custom_provider.html#create-a-service-for-the-user-provider
Then pass it using ["#apikey_userprovider"]
So your Services File should look like:
parameters:
apikey_userprovider.class: Acme\HelloBundle\Security\ApiKeyUserProvider
apikey_authenticator.class: Acme\SeedBundle\Security\ApiKeyAuthenticator
services:
apikey_userprovider:
class: %apikey_userprovider.class%
apikey_authenticator:
class: %apikey_authenticator.class%
arguments: ["#apikey_userprovider"]
You need to define your user provider as a service. This is what the # operator is telling symfony to look for. Defining your classes in the parameters is just part of Symfony Coding Standards

Access doctrine from authentication failure handler in Symfony2

I'm trying to write some loggin failure info in database from a custom authentication handler.
My problem is to gain access to the database since I don't know where the Doctrine object might be stored
Here's my code for now :
namespace MyApp\FrontBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request as Request;
use Symfony\Component\HttpFoundation\RedirectResponse as RedirectResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Security\Http\Authentication as Auth;
use Symfony\Component\Security\Core\Exception\AuthenticationException as AuthException;
class SecurityHandler implements Auth\AuthenticationFailureHandlerInterface
{
public function onAuthenticationFailure(Request $request, AuthException $token)
{
try
{
$lastLoginFailure = new DateTime();
// get database object here
}
catch(\Exception $ex)
{
}
}
}
Any ideas ?
Turn your SecurityHandler into a service and then inject the doctrine entity manager into it.
http://symfony.com/doc/current/book/service_container.html
Start command php app/console container:debug.
Copy doctrine.orm.entity_manager and paste to your hadler constructor arguments like
[...., #doctrine.orm.entity_manager].
In hadler use Doctrine\ORM\EntityManager;
I think you should extends your class "SecurityHandler" with ContainerAware if you want to use service since your Security Handler is not a controller.
class SecurityHandler extend ContainerAware implements Auth\AuthenticationFailureHandlerInterface{
public function onAuthenticationFailure(Request $request, AuthException $token)
{
try
{
$lastLoginFailure = new DateTime();
// get database object here
$doctrine = $this->container->get('doctrine');
$repository = $doctrine->getRepository('*NAME OF REPO*');
}
catch(\Exception $ex)
{
}
}
}