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']
Related
As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.
In Symfony 2.8/3.0, with our fancy new security components, how do I get the currently logged User (i.e. FOSUser) object in a service without injecting the whole container?
Is it even possible in a non-hacky way?
PS: Let's not consider the "pass it to the service function as a parameter" for being trivially obvious. Also, dirty.
Inject security.token_storage service into your service, and then use:
$this->token_storage->getToken()->getUser();
as described here: http://symfony.com/doc/current/book/security.html#retrieving-the-user-object and here: http://symfony.com/doc/current/book/service_container.html#referencing-injecting-services
Works with Symfony 3.4, 4.x, 5.x & above. The Security utility class was introduced in Symfony 3.4.
use Symfony\Component\Security\Core\Security;
public function indexAction(Security $security)
{
$user = $security->getUser();
}
https://symfony.com/doc/3.4/security.html#always-check-if-the-user-is-logged-in
Using constructor dependency injection, you can do it this way:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class A
{
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function foo()
{
dump($this->user);
}
}
In symfo 4 :
use Symfony\Component\Security\Core\Security;
class ExampleService
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function someMethod()
{
$user = $this->security->getUser();
}
}
See doc : https://symfony.com/doc/current/security.html#retrieving-the-user-object
From Symfony 3.3, from a Controller only, according this blog post: https://symfony.com/blog/new-in-symfony-3-2-user-value-resolver-for-controllers
It's easy as:
use Symfony\Component\Security\Core\User\UserInterface
public function indexAction(UserInterface $user)
{...}
With Symfony 5.2+ and PHP 8.0+ you can also get the logged user using the #[CurrentUser] attribute
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
class FooController extends AbstractController
{
public function index(#[CurrentUser] ?User $user)
{
// ...
}
}
Blog post: https://symfony.com/blog/new-in-symfony-5-2-controller-argument-attributes
Documentation: https://symfony.com/doc/current/security.html
Symfony does this in Symfony\Bundle\FrameworkBundle\ControllerControllerTrait
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application.');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
return $user;
}
So if you simply inject and replace security.token_storage, you're good to go.
if you class extend of Controller
$this->get('security.context')->getToken()->getUser();
Or, if you has access to container element..
$container = $this->configurationPool->getContainer();
$user = $container->get('security.context')->getToken()->getUser();
http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
All of my query in Entity Repository needs to be filtered by user.
Now I want to know how can I access the currently logged in user in Entity Repository directly.
What I did today is to get the currently logged in user in my controller, through the use of $this->getUser() and then pass it to Entity Repository and this is not efficient.
You need to inject security.token_storage service into another one to get the current user, but as of Repository classes belong to Doctrine project, not Symfony, it is not recommended to do this.. May be there is a way to achieve it by creating custom entityManager class as described here, but I don't think it would a good solution..
Instead of customizing an entityManager better create a service which calls repository classes' methods, inject desired services into it.. Let Repository classes do their job.
Implementation would be something like this:
RepositoryClass:
class MyRepository extends EntityRepository
{
public function fetchSomeDataByUser(UserInterface $user)
{
// query
}
}
Service:
class MyService
{
private $tokenStorage;
public function _construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
// other services
}
public function getSomeDataByUser()
{
$user = $this->tokenStorage->getToken()->getUser();
return $this->entityManager->getRepository(MyREPOSITORY)->fetchSomeDataByUser($user);
}
}
Usage:
public function someAction()
{
$dataByUser = $this->get(MYSERVICE)->getSomeDataByUser();
}
If you use JMSDiExtraBundle it can be done by adding setter injection:
use Doctrine\ORM\EntityRepository;
use JMS\DiExtraBundle\Annotation as DI;
class YourRepository extends EntityRepository
{
/** #var User current user entity */
protected $user;
/**
* #DI\InjectParams({
* "token_storage" = #DI\Inject("security.token_storage")
* })
*/
public function setSimplaManager(TokenStorageInterface $tokenStorage)
{
$token = $tokenStorage->getToken();
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
$this->user = $user;
}
}
How can I define a constructor in Symfony2 controller. I want to get the the logged in user data available in all the methods of my controller, Currently I do something like this in every action of my controller to get the logged in user.
$em = $this->getDoctrine()->getEntityManager("pp_userdata");
$user = $this->get("security.context")->getToken()->getUser();
I want to do it once in a constructor and make this logged in user available on all my actions
For a general solution for executing code before every controller action you can attach an event listener to the kernel.controller event like so:
<service id="your_app.listener.before_controller" class="App\CoreBundle\EventListener\BeforeControllerListener" scope="request">
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/>
<argument type="service" id="security.context"/>
</service>
Then in your BeforeControllerListener you will check the controller to see if it implements an interface, if it does, you will call a method from the interface and pass in the security context.
<?php
namespace App\CoreBundle\EventListener;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use App\CoreBundle\Model\InitializableControllerInterface;
/**
* #author Matt Drollette <matt#drollette.com>
*/
class BeforeControllerListener
{
protected $security_context;
public function __construct(SecurityContextInterface $security_context)
{
$this->security_context = $security_context;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
// not a object but a different kind of callable. Do nothing
return;
}
$controllerObject = $controller[0];
// skip initializing for exceptions
if ($controllerObject instanceof ExceptionController) {
return;
}
if ($controllerObject instanceof InitializableControllerInterface) {
// this method is the one that is part of the interface.
$controllerObject->initialize($event->getRequest(), $this->security_context);
}
}
}
Then, any controllers that you want to have the user always available you will just implement that interface and set the user like so:
use App\CoreBundle\Model\InitializableControllerInterface;
class DefaultController implements InitializableControllerInterface
{
/**
* Current user.
*
* #var User
*/
private $user;
/**
* {#inheritdoc}
*/
public function initialize(Request $request, SecurityContextInterface $security_context)
{
$this->user = $security_context->getToken()->getUser();
}
// ....
}
The interface is nothing more than
namespace App\CoreBundle\Model;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
interface InitializableControllerInterface
{
public function initialize(Request $request, SecurityContextInterface $security_context);
}
I'm runnig a bit late, but in a controller you can just access the user:
$this->getUser();
Should be working since 2.1
My approach to this was:
Make an empty Interface InitializableControllerInterface
Make event Listener for
namespace ACMEBundle\Event;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ControllerConstructor
{
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
// not a object but a different kind of callable. Do nothing
return;
}
$controllerObject = $controller[0];
if ($controllerObject instanceof InitializableControllerInterface) {
$controllerObject->__init($event->getRequest());
}
}
}
In your controller add:
class ProfileController extends Controller implements
InitializableControllerInterface
{
public function __init()
{
$this->user = $security_context->getToken()->getUser();
}
And you will be able to get the $this->user in each action.
Regards
I'm creating a website thanks to Symfony2 with FOSUserBundle.
I'm triyng to deny multiple connections on the same login (but from different computers for example).
I've 2 solutions :
Create an event listner on authentification but I didn't manage to make it. (even with the cookbook).
override the login_check method but my FOSUserBundle doesn't work if I do it.
Do you have any better options?
Or any solutions?
Got it finaly. There is just one last update to make to solve it all.
You need to add an other field to the User entity. sessionId (string).
Then update your LoginListener class like that :
// YourSite\UserBundle\Listener\YourSiteLoginListener.php
//...
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$session = $request->getSession();
$user = $event->getAuthenticationToken()->getUser();
$has_session = is_file ( '/path_to_your_php_session_file/'.'sess_'.$user->getSessionId() );
if($user->getLogged() && $has_session){
throw new AuthenticationException('this user is already logged');
}else{
$user->setLogged(true);
$user->setSessionId($session->getId());
$this->userManager->updateUser($user);
}
}
Maybe this will help people to solve this problem.
It's kind of a solution but there is still a problem :
If the user session is killed by php (after too mush time without action for example), you will have to go into your database to reset the "logged" value to 0.
So my solution is :
-add the field "logged" (boolean) to you User entity.
-in YourSite\UserBundle\Listener create a : YourSiteLoginListener.php with this code
namespace YourSite\UserBundle\Listener;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContext;
class YourSiteLoginListener
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if($user->getLogged()){
throw new AuthenticationException('this user is already logged');
}else{
$user->setLogged(true);
$this->userManager->updateUser($user);
}
}
}
-then in the same directory, create a logout handler : YourSiteLogoutHandler.php
namespace YourSite\UserBundle\Listener;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
class YourSiteLogoutHandler implements LogoutHandlerInterface
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public function logout (Request $request, Response $response, TokenInterface $token){
$user = $token->getUser();
if($user->getLogged()){
$user->setLogged(false);
$this->userManager->updateUser($user);
}
}
}
-finaly declare those services in your app/config.yml for example:
services:
yoursite_login_listener:
class: YourSite\UserBundle\Listener\YourSiteLoginListener
arguments: [#fos_user.user_manager]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method :onSecurityInteractiveLogin }
yoursite_logout_handler:
class: YourSite\UserBundle\Listener\YourSiteLogoutHandler
arguments: [#fos_user.user_manager]
In Symfony3, the logout handler was not trigged by the code above.
I rebuild the code so the system is updated when the user is logging out.
namespace YourSite\UserBundle\Listener;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public function onLogoutSuccess(Request $request){
global $kernel;
$user = $kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
if($user->getLogged()){
$user->setLogged(false);
$this->userManager->updateUser($user);
}
$referer = $request->headers->get('referer');
return new RedirectResponse($referer);
}
}