how to get custom user class - symfony

Maybe the question is very simple but here it is :
I authenticated using my own User class and UserProvider class. I extended DefaultAuthenticationSuccessHandler to modify the connected user.It should happen in this method :
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
In my custom class User, I have a addRole method.
How can I reach this method from the token given as a parameter in the onAuthenticationSuccess function?

First of all, you need to retrieve the authenticated User :
$user = $token->getUser();
Then, you should be able to call $user->addRole().
But, you need to store the changes in db.
For that, you need to inject the doctrine EntityManager in your service.
Change your service declaration :
# services.yml
your_authentication_success_handler:
# ...
arguments:
entityManager: "#doctrine.orm.entity_manager"
Set the entityManager in the constructor of your service:
// Authentication success handler
public function __construct(\Doctrine\ORM\EntityManager $entityManager = null)
{
$this->_em = $entityManager;
}
Now you can update your User in the onAuthenticationSuccess method like follows:
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
$user->addRole('YOUR_ROLE');
$this->_em->flush();
}
Update
Because the User returned is not an instance of your custom User entity, you have to retrieve it using the Repository of your entity.
use YourBundle\Entity\User;
// ...
$user = $token->getUser();
if (!($tokenUser instanceof User) {
$repository = $this->_em->getRepository('YourBundle:User');
$user = $repository->findBy(array(
'username' => $tokenUser->getUsername(), // Assuming the 'username' property is unique
));
}
$user->addRole('YOUR_ROLE'); // Now you can access the method
$this->_em->flush();

Related

Doctrine query outside the controller Symfony 2

I have some trouble since two days to do a query using a UserRepository outside a controller. I am trying to get a user from the database from a class that I named ApiKeyAuthenticator. I want to execute the query in the function getUsernameForApiKey like in the docs. I think I am suppose to use donctrine as a service but I don't get how to do this.
Thanks for you help in advance!
<?php
// src/AppBundle/Security/ApiKeyUserProvider.php
namespace AppBundle\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_API')
);
}
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 User::class === $class;
}
}
You have to make your ApiKeyUserProvider a service and inject the UserRepository as a dependency. Not sure if repositories are services in 2.8, so maybe you'll have to inject the EntityManager .
class ApiKeyUserProvider implements UserProviderInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function loadUserByUsername($username)
{
$repository = $this->em->getRepository(User::class);
// ...
Now register your class as a service in your services.yml file
services:
app.api_key_user_provider:
class: AppBundle\Security\ApiKeyUserProvider
arguments: ['#doctrine.orm.entity_manager']

FOSUserBundle - PHPUnit - Mock a user

I am using Symfony with the FOSUserBundle and now I like to test some things like:
Doctrine lifecycle
Controller behind firewall
For those tests I need to be a specific user or at least in a user group.
How do I mock a user session so that ...
The lifecycle field like "createdAt" will use the logged in user
The Controller act like some mocked user is logged in
Example:
class FooTest extends ... {
function setUp() {
$user = $this->getMock('User', ['getId', 'getName']);
$someWhereGlobal->user = $user;
// after this you should be logged in as a mocked user
// all operations should run using this user.
}
}
You can do this with LiipFunctionalTestBundle. Once you have installed and configured the Bundle, creating and user and log in in tests is easy.
Create a fixture for your user
This creates a user which will be loaded during tests:
<?php
// Filename: DataFixtures/ORM/LoadUserData.php
namespace Acme\MyBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\MyBundle\Entity\User;
class LoadUserData extends AbstractFixture implements FixtureInterface
{
public function load(ObjectManager $manager)
{
$user = new User();
$user
->setId(1)
->setName('foo bar')
->setEmail('foo#bar.com')
->setPassword('12341234')
->setAlgorithm('plaintext')
->setEnabled(true)
->setConfirmationToken(null)
;
$manager->persist($user);
$manager->flush();
// Create a reference for this user.
$this->addReference('user', $user);
}
}
If you want to use groups of users, you can see the official documentation.
Log in as this user in your test
As explained in LiipFunctionalTestBundle's documentation, here is how to load the user in the database and log in as this user:
/**
* Log in as the user defined in the Data Fixture.
*/
public function testWithUserLoggedIn()
{
$fixtures = $this->loadFixtures(array(
'Acme\MyBundle\DataFixtures\ORM\LoadUserData',
));
$repository = $fixtures->getReferenceRepository();
// Get the user from its reference.
$user = $repository->getReference('user')
// You can perform operations on this user.
// ...
// And perform functional tests:
// Create a new Client which will be logged in.
$this->loginAs($user, 'YOUR_FIREWALL_NAME');
$this->client = static::makeClient();
// The user is logged in: do whatever you want.
$path = '/';
$crawler = $this->client->request('GET', $path);
}
What I would do in this case is to create a CustomWebTestCase which extends the Symfony WebTestCase. In the class I would create a method which does the authentication for me.
Here is an example code:
namespace Company\MyBundle\Classes;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\User\User;
abstract class CustomWebTestCase extends WebTestCase
{
/**
* #param array|null $roles
* #return \Symfony\Bundle\FrameworkBundle\Client
*/
protected static function createAuthenticatedClient(array $roles = null) {
// Assign default user roles if no roles have been passed.
if($roles == null) {
$role = new Role('ROLE_SUPER_ADMIN');
$roles = array($role);
} else {
$tmpRoles = array();
foreach($roles as $role)
{
$role = new Role($role, $role);
$tmpRoles[] = $role;
}
$roles = $tmpRoles;
}
$user = new User('test_super_admin', 'passwd', $roles);
return self::createAuthentication(static::createClient(), $user);
}
private static function createAuthentication(Client $client, User $user) {
// Read below regarding config_test.yml!
$session = $client->getContainer()->get('session');
// Authenticate
$firewall = 'user_area'; // This MUST MATCH the name in your security.firewalls.->user_area<-
$token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
$session->set('_security_'.$firewall, serialize($token));
$session->save();
// Save authentication
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
return $client;
}
}
The code above will directly create a valid user session and will skip the firewall entirely. Therefore you can create whatever $user you want and it will still be valid. The important part of the code is located in the method createAuthentication. This is what does the authentication magic.
One more thing worth mentioning - make sure you have set framework.session.storage_id to session.storage.mock_file in your config_test.yml so that Symfony will automatically mock sessions instead of you having to deal with that in each test case:
framework:
session:
storage_id: session.storage.mock_file
Now in your test case you would simply extend MyWebTestCase and call the createAuthenticatedClient() method:
class MyTest extends CustomWebTestCase {
public function testSomething() {
//Create authoried and unauthorized clients.
$authenticatedClient = self::createAuthenticatedClient(array("ROLE_SUPER_ADMIN"));
$unauthorizedClient = self::createAuthenticatedClient(array("ROLE_INSUFFICIENT_PERMISSIONS"));
// Check if the page behaves properly when the user doesn't have necessary role(s).
$unauthorizedClient->request('GET', '/secured-page');
$response = $unauthorizedClient->getResponse();
$this->assertFalse($response->isSuccessful());
$this->assertEquals(403, $response->getStatusCode(), "This request should have failed!");
// Check if the page behaves properly when the user HAS the necessary role(s)
$authenticatedClient->request('GET', '/secured-page');
$response = $authenticatedClient->getResponse();
$this->assertTrue($response->isSuccessful());
$this->assertEquals(200, $response->getStatusCode(), "This request should be working!");
}
}
You can see an example in the Symfony official documentation as well.
You can easily do that with LiipFunctionalTestBundle which authorize you lot of shortcut for create Unit Test.
If already you have a form user for create or edit you can use this for your test unit workflow user in your application :
use the makeClient method for logging test
$credentials = array(
'username' => 'a valid username',
'password' => 'a valid password'
);
$client = static::makeClient($credentials);
use your form for test your creation
$crawler = $client->request('GET', '/profile');
$form = $crawler->selectButton('adding')->form();
$form['fos_user_profile_form[firstName]'] = 'Toto';
$form['fos_user_profile_form[lastName]'] = 'Tata';
$form['fos_user_profile_form[username]'] = 'dfgdgdgdgf';
$form['fos_user_profile_form[email]'] = 'testfgdf#grgreger.fr';
$form['fos_user_profile_form[current_password]'] = 'gfgfgdgpk5dfgddf';
testing "createdAt" with just call findOneBy in repository user like this
$user = $this->getObjectManager()
->getRepository('AcmeSecurityBundle:User')
->findOneBy(array('username' => 'testCreateUserUsername'));
$this->assertTrue($user->getCreatedAt() == now());

Symfony 2.6 get Service LoggedUser Catchable Fatal Error: Argument 3 must be an instance

I want to implement the security.token_storage as a service to get the user that is logged in; so when the user writes a post or a comment the field "Author" is automatically set.
I cannot get it working:
Attempted to call method "get" on class "Blog\BlogBundle\Services\PostManager".
How can I implement it as a service and use it?
The UserManager (as a service):
namespace Usuarios\UsersBundle\Services;
class UserManager
public function getloggedUser()
{
$loggedUser = $this->get('security.token_storage')->getToken()->getUser();
return $loggedUser;
}
The service.yml for the UserManager config:
services:
user_manager:
class: %user_manager.class%
arguments:
- #doctrine.orm.entity_manager
The PostManager, that uses the getloggedUser function:
namespace Blog\BlogBundle\Services;
class PostManager
private $em;
private $formFactory;
private $um;
/**
* #param EntityManager $em
* #param formFactoryInterface $formFactory
* #param UserManager $um
*/
public function __construct(EntityManager $em, FormFactoryInterface $formFactory, UserManager $um)
{
$this->em = $em;
$this->formFactory = $formFactory;
$this->um = $um;
public function createComment (Post $post, Request $request)
{
$comment = new Comment();
$comment->setPost($post);
//this is the line failing:
$comment->setAuthorName($this->um->getloggedUser());
$form = $this->formFactory->create(new CommentType(), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
$this->em->persist($comment);
$this->em->flush();
return true;
}
return $form;
Note "user_manager" is defined as a service and is fully functional since other functions using it are working. Why cannot I call the UserManager service from the PostManager service? The error I get is:
Catchable Fatal Error: Argument 3 passed to Blog\BlogBundle\Services\PostManager::__construct() must be an instance of Usuarios\UsersBundle\Services\UserManager, none given, called in C:\xampp\htdocs\eScribely2\app\cache\dev\appDevDebugProjectContainer.php on line 2535 and defined
services do not have the get convenience method that controllers do. You need to pass the container in as an argument when you build the service and store it as a member variable and then call you service.
$this->container->get('user_manager');
Optionally you can just pass in your user manger and not have to use the container at all.

Symfony2 access user and doctrine in a service

I'm running the equivalent of this code in lots and lots of controller actions, basically it grabs the user's username, and if that username is attached to a blog entity it will allow the user to see the blog entity(s):
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
return $this->render('MySiteBundle:Blog:index.html.twig', array(
'entities' => $entities,
I want to move it into a service so I can cut down on code repetition. I want to avoid doing as much logic in my controllers as possible.
That being said, I'm not sure how I can access the user session and doctrine in a service.
Here's my services.yml:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
And here's how I was attempting to call it in the controller:
public function testAction() {
$response = $this->get('mysite.user.blog');
return new Response($response);
}
I did try using an event subscriber/listener tag, but that doesn't seem to accomplish the task I want.
Here is my completely horrible attempt at a service. I couldn't get any response from it without using a constructor.
namespace MySite\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
protected $entities;
public function __construct(){
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Am I going about this the completely wrong way? Is there a better way that I'm missing?
EDIT/ANSWER:
modified my naming convention a little:
//services.yml
mysite.user.blog.entities:
class: Mysite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]
In the controller action:
$userEntities = $this->get('mysite.user.blog.entities');
$entities = $userEntities->getEntities();
In the service itself:
class BlogUser {
protected $entities;
public function __construct($em, $securityContext){
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
public function getEntities(){
return $this->entities;
}
}
Still needs two lines to get the $entities variable in the controller, but this is way better than defining the same thing over and over.
"Security.context" has been deprecated since Symfony 2.6
After some community discussions, it was decided that SecurityContext gives too many dependencies to retrieve a simple Token/User object. That's why, starting with Symfony 2.6, thesecurity.context service has been deprecated and split into two new services:security.authorization_checker and security.token_storage.
Source
Thus, the new way to do it would be, first configure your service as:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.token_storage"]
Then in the service class constructor:
class BlogUser
{
protected $user;
protected $entities;
public function __construct(EntityManager $em, TokenStorage $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Yes, you are doing it in wrong way. Let's look at your code:
# call to undefined object method getDoctrine()
$em = $this->getDoctrine()->getManager();
# call to undefined object method get()
$user = $this->get('security.context')->getToken()->getUser();
You cannot call getting entitymanager and security.context in your service in the same way like in your controller. Instead, you have to inject entitymanager and security.context services. Example:
# services.yml
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
calls:
- [ setUserFromSecurityContext, [ #security.context ]]
- [ setEntityManager, [ #doctrine.orm.entity_manager ]]
And improved service:
namespace Catablog\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
private $entityManager;
private $user;
public function setEntityManager(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function setUserFromSecurityContext(SecurityContext $securityContext)
{
# notice, there are a cases when `getToken()` returns null, so improve this
$this->user = $securityContext->getToken()->getUser();
}
public function getEntities(){
# your code here
}
}
More info about Dependency injection
You are looking on how to 'inject' other services into your custom service. Take a look at Service Container documentation.
In your case, you can inject doctrine.orm.entity_manager and security.context services into your BlogUser class via constructor injection. For example:
class BlogUser {
public function __construct($em, $securityContext) {
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
And configure your service as the following:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]

Custom repository, this user in symfony2

How you can access to the context user (i.e.: $this->getUser()) from inside a repository?
Something like:
public function findInviteRequestByUser(\Fundacity\UserBundle\Entity\User $user)
{
$contextUser = $this->getUser();
...
// HERE GOES THE QUERY
...
return $query->getResult();
}
you should pass security context as an argument.
Your controller
$securityContext = $this->get('security.context');
$em->getRepository('YourBundle')->findInviteRequestByUser($securityContext);
Your entity repository
public function findInviteRequestByUser(\Symfony\Component\Security\Core\SecurityContext $context)
{
$user = $context->getToken()->getUser();
...
}

Resources