Symfony Service Configurator: getToken with security.context is NULL - symfony

I'm using Symfony Service Configurator in my project to configure a service after its instantiation (Docs),
in Configure method I need the current user logged so I inject the container and I tried to get the token form Security.context service but I got always NULL.
I tried also to inject only Security.context in my Configurator construct but I got same result.
Any ideas pls
Thanks.
class MyConfigurator
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function configure()
{
$user = $this->container->get('security.context')->getToken();
var_dump($user); // = NULL
}
}

I resolve the problem by getting the UserId from the session and fetch the current User from Database.
The UserId is set previously by a AuthenticationListener in my project.
So I modify my Configurator construct to be like this:
/**
* #param EntityManager $em
* #param Session $session
*/
public function __construct(EntityManager $em, Session $session)
{
$this->em = $em;
$this->session = $session;
}

A better way should be:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class MyConfigurator
{
private $tokenStorage;
public function __construct(EntityManager $em, TokenStorage $tokenStorage)
{
$this->em = $em;
$this->tokenStorage= $tokenStorage;
}
public function configure()
{
$user = $this->tokenStorage->getToken()->getUser();
}
....

Related

Token Storage Problems Symfony 5 Custom Login Authenticator

When the user logs in to the system, I need to fill a class variable (Login-> testInfo) with information, but in the controller the variable always returns null.
Here is a generic example.
The Login class
class Login extends UserInterface
{
private $testInfo = null;
public function setTestInfo(string $testInfo)
{
$this->testInfo = $testInfo;
}
public function getTestInfo() : ?string
{
return $this->testInfo;
}
}
The Authenticator:
class FormAuthenticator extends AbstractFormLoginAuthenticator
{
...
public function getUser($credentials, UserProviderInterface $userProvider)
{
$user = $this->entityManager->getRepository(Login::class)->findByUsername(credentials['username']);
if (!$user)
{
throw new CustomUserMessageAuthenticationException('Username could not be found.');
}
//this prints NULL
dd($user->getTestInfo());
$user->setTestInfo('testing the string');
//this prints 'testing the string'
dd($user->getTestInfo());
return $user;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
//this prints 'testing the string'
dd($token->getUser()->getTestInfo());
}
...
}
The Controller Class:
class MyController extends AbstractController
{
private $login = null;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->login = $tokenStorage->getToken() ? $tokenStorage->getToken()->getUser() : null;
}
public function home()
{
//this prints null
dd($this->login->getTestInfo());
}
}
If $user goes to the tokenStorage with the new value ('testing the string'), why, when I try to use it on the controller, does the variable always return null? what am I doing wrong?
Is testInfo a transient variable? Because you gotta know that there is UserProvider that tries to refresh user from token (maybe it could be "changed" somehow between requests). I'm pretty sure you're losing those infos right in this process.
Are you sure your controller constructor isn't being executed too soon, prior to the authentication success event writing the token to the token storage service? I'd dd() the token in the constructor to verify if the token and Login instance are present at that point.
You may need to use setContainer() instead of __construct() in your controller to retrieve the authenticated token, which would look something like this:
private $tokenStorage = null;
private $login = null;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param ContainerInterface $container Symfony service container interface
* #return ContainerInterface|null
*/
public function setContainer(\Psr\Container\ContainerInterface $container): ?\Psr\Container\ContainerInterface
{
if ($this->tokenStorage instanceof TokenStorageInterface && $this->tokenStorage->getToken() instanceof TokenInterface && $this->tokenStorage->getToken()->getUser() instanceof Login) {
$this->login = $this->tokenStorage->getToken()->getUser();
}
return $container;
}

Symfony Worker - Consume Messages - EntityManager

I use Symfony 4 with messenger and I use a worker who consumes my messages as a long-running process.
I have a bug with doctrine if I delete my post and recreate a new one and I dispatch my message. $post have the old data and not the new one.
I have tried a lot of things and nothing work, it works when I restart my worker.
class ChannelMessageHandler implements MessageHandlerInterface
{
private $channel;
private $bus;
public function __construct(ChannelService $channel, MessageBusInterface $commandBus)
{
$this->channel = $channel;
$this->bus = $commandBus;
}
public function __invoke(ChannelMessage $channelMessage)
{
$error = $this->channel->handleChannel($channelMessage->getUser(), $channelMessage->getService());
if ($error) {
throw new Exception($error[0]);
}
$this->bus->dispatch(new FeedMessage($channelMessage->getUser(), $channelMessage->getService()));
}
}
}
My MessageHandler call a service :
class ChannelService implements ContainerAwareInterface
{
use ContainerTrait;
protected $em;
protected $logger;
public function __construct(EntityManagerInterface $entityManager, LoggerInterface $logger)
{
$this->em = $entityManager;
$this->logger = $logger;
}
public function handleChannel($userId, $serviceName)
{
$user = $this->em->getRepository('App:User\Authentication\User')->findOneById($userId);
$post = $user->getPost();
return $this->getUserAnalyticBy($post, $serviceName);
}
thanks a lot

Symfony 3.4: Access doctrine within an EventListener

I am trying to access to doctrine within an EventListener which tests a database table, if it is invalid then the user will be redirected to a config page where he will fix the issues!
The test will be executed before every call to a controller, so I will use the Event kernel.controller:
[EDITED: SOLVED, TESTING AND REDIRECTING VERY FINE]
<?php
namespace AdminBundle\EventListener;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
class BuildReferencesEventListener
{
/**
* #var EntityManager
*/
protected $em;
/**
* #var Router
*/
protected $router;
protected $references = [
1 => 'oxygen',
2 => 'pH',
3 => 'pCO2'
];
protected $redirect_route = 'references';
protected $requestStack;
protected $session;
public function __construct(EntityManager $entityManager, Router $router, RequestStack $requestStack, Session $session)
{
$this->em = $entityManager;
$this->router = $router;
$this->requestStack = $requestStack;
$this->session = $session;
}
public function onKernelController()
{
$em = $this->em;
$savedReferences = $em->getRepository('AdminBundle:ParamReference')->findAll();
$references = $this->references;
if (count($savedReferences) <= 0){
$this->redirect();
}
for ($i =0; $i<count($savedReferences); $i++){
if ($savedReferences[$i] !== $references[$i]) {
$this->redirect();
}
}
}
public function redirect()
{
$request = $this->requestStack->getCurrentRequest();
$route = $request->get('_route');
$this->session->getFlashBag()->add('warning', 'You need to setup the references for the parameters before starting the work');
if ($route != 'references'){
$url = $this->router->generate('references');
$redirect = new RedirectResponse($url);
$redirect->send();
}
}
}
Here is services.yml
build.references:
class: AdminBundle\EventListener\BuildReferencesEventListener
arguments: ['#doctrine.orm.entity_manager', '#router', '#request_stack', '#session']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Add argument inside your service.yml event listner service section
Your.service:
Class: AdminBundle\EventListener\BuildReferencesEventListener
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: kernel.event_listener, event: kernel.controller }
And add this __cunstruct code inside your listner:
namespace AdminBundle\EventListener;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Doctrine\ORM\EntityManager;
class BuildReferencesEventListener
{
protected $em;
function __construct(EntityManager $em)
{
$this->em = $em;
}
public function onKernelController(FilterControllerEvent $event)
{
//TO DO call doctrine
// $em = Enity Manager...
// $em->getRepository...
// I hope its clear enough what im trying to do
}
In Symfony 3.4 and above, autowiring is enabled by default, so you just have to add a type-hinted argument to your service's constructor method and a private property to assign the argument's value to.
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class BuildReferencesEventListener
{
private $em;
public __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function onKernelController(FilterControllerEvent $event)
{
$em = this->em;
// [...]
}
}
If you need to
declare arguments explicitly, you could do this in your service configuration:
# config/services.yaml
services:
# ... same code as before
# explicitly configure the service
AdminBundle\EventListener\BuildReferencesEventListener:
arguments:
$em: '#doctrine.orm.entity_manager_interface'
This could be useful to pass parameters that the service container doesn't know.
To learn more about the service container, https://symfony.com/doc/3.4/service_container.html#injecting-services-config-into-a-service.

Get User in a Doctrine EventListener

when I register a new Plasmid Entity, I want give him an automatic name (like: p0001, p0002, p0003), to do this, I need to select in the database the last Plasmid entity for a specific User, get its autoName, and use this previous name to define the new one.
But, when I inject the token_storage in my listener, the token is null... In the controller, I can have the user, it's work.
The service.yml
app.event_listener.plasmid:
class: AppBundle\EventListener\PlasmidListener
arguments: ["#security.token_storage"]
tags:
- { name: doctrine.event_listener, event: prePersist }
And, the PlasmidListener
class PlasmidListener
{
private $user;
public function __construct(TokenStorage $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
// If the entity is not a Plasmid, return
if (!$entity instanceof Plasmid) {
return;
}
// Else, we have a Plasmid, get the entity manager
$em = $args->getEntityManager();
// Get the last plasmid Name
$lastPlasmid = $em->getRepository('AppBundle:Plasmid')->findLastPlasmid($this->user);
// Do something with the last plasmid in the database
}
}
If someone know why I can get the actual user in the Doctrine Listener ?
Thanks
I think that you should store pointer to tokenStorage class in your service instead of user object:
class PlasmidListener
{
private $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(LifecycleEventArgs $args)
{
$user = $this->tokenStorage->getToken()->getUser();
//...
}
}
To avoid error in Symfony4 and above, use TokenStorageInterface instead of TokenStorage
For example
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
And in your constructor :
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
To get the user and its details in prePersist :
$user = $this->tokenStorage->getToken()->getUser();

SecurityContext->isGranted() working in controller but not in service

I have a public area of my app accesible with no login or authentication, and when I run this code in a controller if ($securityContext->isGranted('IS_AUTHENTICATED_ANONYMOUSLY')) I get a true as expected.
Then I have a service defined like this:
main.services:
class: App\MainBundle\Services\MainServices
arguments: [ #doctrine.orm.entity_manager, #security.context, #service_container ]
But when I run this code:
public function __construct(EntityManager $em, SecurityContext $securityContext, Container $container) {
$this->em = $em;
$this->container = $container;
$this->securityContext = $securityContext;
error_log("MAIN");
if ($securityContext->isGranted('IS_AUTHENTICATED_ANONYMOUSLY'))
error_log("MAIN Anon");
else
error_log("MAIN no anon");
}
I get an exception:
Uncaught exception 'Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException' with message 'The security context contains no authentication token. One possible reason may be that there is no firewall configured for this URL.'
The service is invoked right after the first command in controller.
Thank you
This error could occur when service initializes before security token created. Try not to check access in constructor, move this check to method, called from your controller.
Use it only if a token is defined and #Ziumin is wright.
public function __construct(EntityManager $em, SecurityContext $securityContext, Container $container) {
$this->em = $em;
$this->container = $container;
$this->securityContext = $securityContext;
error_log("MAIN");
$token = $this->securityContext->getToken();
if (is_object($token)) {
if ($securityContext->isGranted('IS_AUTHENTICATED_ANONYMOUSLY'))
error_log("MAIN Anon");
else
error_log("MAIN no anon");
}
}

Resources