The "session.storage.factory.service" service is deprecated, - php

I have updated symfony to 5.3 and get deprecations logs like
User Deprecated: Since symfony/framework-bundle 5.3: The session.storage.factory.service service is deprecated, use session.storage.factory.native, session.storage.factory.php_bridge or session.storage.factory.mock_file instead.
I think it is caused by using TokenStrageInterface::getToken() but I cannot find the solution to solve it.
The code I use is like this.
<?php
namespace App\EventSubscriber;
use App\Entity\User;
use Gedmo\Loggable\LoggableListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
class DoctrineExtensionSubscriber implements EventSubscriberInterface
{
/**
* #var LoggableListener
*/
private LoggableListener $loggableListener;
/**
* #var TokenStorageInterface
*/
private TokenStorageInterface $tokenStorage;
public function __construct(LoggableListener $loggableListener,
TokenStorageInterface $tokenStorage
)
{
$this->loggableListener = $loggableListener;
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => [
'onKernelController',
-10,
],
];
}
public function onKernelController(ControllerEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
if ($this->tokenStorage?->getToken()?->isAuthenticated() === true) {
$user = $this->tokenStorage->getToken()->getUser();
$controller = $event->getController();
if (is_array($event->getController())) {
$controller = $event->getController()[0];
}
if ($user instanceof User) {
$this->loggableListener->setUsername($user->getFullName());
return;
}
$this->loggableListener->setUsername('Anonymous');
}
}
}
I've thought to edit config file, but I can't determine where to change. Please see the following code for config/packages/framework.yaml:
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
#http_method_override: true
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
#handler_id: null
cookie_secure: auto
cookie_samesite: lax
save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
#esi: true
#fragments: true
php_errors:
log: true

Your framework.yaml session section should look like:
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
storage_factory_id: session.storage.factory.native
This is the default config you get with a new 5.3 project. You can leave the save_path entry if you want. The storage_value_id was introduced in 5.3.
According to the storage_factory_id docs, the default value should already be factory.native. Which implies that you don't actually need the entry at all.
However, bin/console debug:config framework session shows different results if you leave it out. Not sure it that is an error or not.
In any event, add the storage_factory_id and the error should go away.

Related

Symfony 4 -- error when I try to create a Session Proxy

I follow the instructions in https://symfony.com/doc/master/session/proxy_examples.html,
I update my framework.yaml
framework:
secret: '%env(APP_SECRET)%'
#default_locale: en
#csrf_protection: ~
#http_method_override: true
# uncomment this entire section to enable sessions
session:
# With this config, PHP's native session handling is used
handler_id: App\Session\CookieEncryptedSession
#esi: ~
#fragments: ~
php_errors:
log: true
I also create my ownclass:
<?php
namespace App\Session;
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
class CookieEncryptedSession extends SessionHandlerProxy
{
private $key;
public function __construct(\SessionHandlerInterface $handler, Key $key)
{
$this->key = $key;
parent::__construct($handler);
}
public function read($id)
{
$data = parent::read($id);
return Crypto::decrypt($data, $this->key);
}
public function write($id, $data)
{
$data = Crypto::encrypt($data, $this->key);
return parent::write($id, $data);
}
}
When I try to run the server with the console I get this error:
In CheckCircularReferencesPass.php line 67:
Circular reference detected for service "App\Session\CookieEncryptedSession
", path: "App\Session\CookieEncryptedSession -> App\Session\CookieEncrypted
Session".
Any idea where is the mistake?
Thanks
Oskar
The autowiring is trying to inject the service to itself as the service implements the interface required on the constructor. CookieEncryptedSession implements SessionHandlerInterface via:
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
Setup in your services the service: CookieEncryptedSession manually so you can select the SessionHandlerInterface service you want.
NativeSessionHandler
NativeFileSessionHandler
DbalSessionHandler
Etc

PHP/Symfony: Cannot trigger the event for FOSUserBundle

I have a problem with subscribing events for FOSUserBundle (I am using this tutorial).
Basically, I am trying to just redirect a user to specified route after login. I made a class, just like in the tutorial:
//src:src/AppBundle/EventsListener/LoginListener.php
namespace AppBundle\EventsListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class LoginListener implements EventSubscriberInterface
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::SECURITY_IMPLICIT_LOGIN => 'onSecurityImplicitLogin',
);
}
public function onSecurityImplicitLogin(FormEvent $event)
{
return $this->redirectToRoute('profile');
}
}
And my services.yml looks like this:
services:
login_listener:
class: AppBundle\EventsListener\LoginListener
arguments: ['#router']
tags:
- { name: 'kernel.event_subscriber' }
But it still redirects me to the "\" route. Can somebody help me find what's wrong? Besides, is this really the only way to define routes in this bundle?
And, by the way, are there any resources/manuals besides this one, "official docs"? I've found this manual very unclear and rough.
Thank you for concern!
For that simple purpose you have just to add in security.yml:
firewalls:
main:
form_login:
always_use_default_target_path: true
default_target_path: profile

Redirect Symfony2 LogoutSuccessHandler to original logout target

I need to modify my user object on logout. To do this, I have a security.yml that contains the following (amongst other things) -
#...
logout:
success_handler: my.logout_success_handler
target: /
#...
...this defines a logout success handler, which is defined in services.yml like this -
my.security.logout_success_handler:
class: My\Security\LogoutSuccessHandler
arguments: ["#security.context", "#doctrine.orm.default_entity_manager"]
...finally, the business-end of my handler is like this -
// ...
public function onLogoutSuccess(Request $request)
{
$user = $this->securityContext->getToken()->getUser();
// ... do stuff with the user object....
$this->em->flush();
// now what?
}
// ...
So, where it says "now what?" I understand that I need to return a Response object. Ideally I want that response object to redirect the user to whatever is defined in logout.target in the security.yml.
Is there an easy way I can query that? Or, even better, is there another way of doing this kind of thing that doesn't require me to get involved with the request/response objects at all?
Thanks
You could define your target as a parameter in your parameters.yml or config.yml:
parameters:
logout.target: /
And then reference this value in your security.yml:
logout:
success_handler: my.logout_success_handler
target: %logout.target%
And/or inject it into your logout handler:
my.security.logout_success_handler:
class: My\Security\LogoutSuccessHandler
arguments: ["#security.context", "#doctrine.orm.default_entity_manager", %logout.target%]
And return a RedirectResponse with this value:
// Assign the 3. constructor parameter to the instance variable $logoutTarget
public function onLogoutSuccess(Request $request)
{
// ...
return new RedirectResponse($this->logoutTarget);
}
So, I think I've figured out the right answer -
Rather than implementing LogoutSuccessHandlerInterface and configuring a logout.success_handler, my security.yml now looks like this -
# ...
logout:
handlers: [my.bundle.security.logout_handler]
# ...
...and I'm implementing Symfony\Component\Security\Http\Logout\LogoutHandlerInterface. Confusing naming, but this seems to be the preferred way of doing post-logout operations without having to get involved with the response object. My implementation looks like this -
namespace My\Bundle\Security;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManager;
/**
* Do post logout stuff
*/
class LogoutHandler implements LogoutHandlerInterface
{
/**
* #var EntityManager
*/
protected $em;
/**
* Constructor
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Do post logout stuff
*/
public function logout(Request $request, Response $response, TokenInterface $authToken)
{
$user = $authToken->getUser();
// do stuff with the user object...
$this->em->flush();
return $response;
}
}
...as you can see, the LogoutHandlerInterface provides a pre-made $response object that I can just return when I'm finished.
You could use composition and inject the default LogoutSuccessHandler into your object and call the onLogoutSucces method on it.
The following pseudu code shows the idea of doing it.
class MyLogoutSuccessHandler implements \LogoutSuccessHandler
{
protected $original;
public function __construct(OriginalLogoutSuccesHandler $original)
{
$this->original = $original;
}
public function onLogoutSuccess(Request $request)
{
// do stuf your want and delegate to the original
return $this->original->onLogoutSuccess($request);
}
}
This is also the way HttpKernelInterface works in StackPHP and when you use HttpCache in your application.
Hopefully this helps, happy coding :)

symfony 2 set locale based on user preferences stored in DB

I am trying to set the locale based on the current user's preferences which are stored in the DB.
Our User class therefore has a getPreferredLanguage which returns a locale identify ('en', 'fr_FR', etc.).
I've considered the following approach:
register a "locale" listener service that subscribes to the KernelEvents::REQUEST event.
this service has access to the security context (via its constructor)
this service's onKernelRequest method attempts to get the user from the security context, get the user's preferred locale, and set it as the request's locale.
Unfortunately, this doesn't work. When the "locale" listener service's onRequestEvent method is invoked, the security context does not have a token. It seems that the context listener is invoked at a very late stage (with a priority of 0), and it is impossible to tell my "locale" listener to run before the security context.
Does anyone know how to fix this approach, or suggest another one?
You may be interested in the locale listener, which I posted in this answer: Symfony2 locale detection: not considering _locale in session
Edit: If a user changes his language in the profile, it's no problem. You can hook into profile edit success event if you're are using FOSUserBundle (master). Otherwise in your profile controller, if you're using a self made system. Here is a example for FOSUserBundle:
<?php
namespace Acme\UserBundle\EventListener;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ChangeLanguageListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::PROFILE_EDIT_SUCCESS => 'onProfileEditSuccess',
);
}
public function onProfileEditSuccess(FormEvent $event)
{
$request = $event->getRequest();
$session = $request->getSession();
$form = $event->getForm();
$user = $form->getData();
$lang = $user->getLanguage();
$session->set('_locale', $lang);
$request->setLocale($lang);
}
}
and in the services.yml
services:
acme.change_language:
class: Acme\UserBundle\EventListener\ChangeLanguageListener
tags:
- { name: kernel.event_subscriber }
for multiple sessions in multiple browser is no problem, as every new session requires a new login. Hmm, ok, not after changing the language, as only the current session would be updated. But you can modify the LanguageListener to support this.
And the case if an admin changes the language should be insignificant.
If you reach this answer through Google, I am currently using this solution.
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class SetLocaleEventSubscriber implements EventSubscriberInterface
{
private Security $security;
private TranslatorInterface $translator;
public function __construct(Security $security, TranslatorInterface $translator)
{
$this->security = $security;
$this->translator = $translator;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::CONTROLLER => [
['setLocale', 1]
]
];
}
public function setLocale(ControllerEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
/**
* #var \App\Entit\User
*/
$user = $this->security->getUser();
if ($user) {;
$request->setLocale($user->getLocale());
$this->translator->setLocale($user->getLocale());
}
}
}
In order to achieve this, you need to setup an event subscriber on the Kernel::REQUEST event with a higher priority than the default Locale listener as indicated in the documentation
At this time, you will unfortunately not be able to access to the current logged in user because this is something set in another Symfony event triggered after the Locale listener.
However, you can access to the session.
The solution is to save the user's locale in the session just after a successful login, and then set the locale in the request from the session.
// src/EventSubscriber/UserLocaleSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
/**
* Stores the locale of the user in the session after the
* login. This can be used by the LocaleSubscriber afterwards.
*/
class UserLocaleSubscriber implements EventSubscriberInterface
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if (null !== $user->getLocale()) {
$this->requestStack->getSession()->set('_locale', $user->getLocale());
}
}
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
];
}
}
// src/EventSubscriber/LocaleSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LocaleSubscriber implements EventSubscriberInterface
{
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
if ($request->getSession()) {
// Set user's locale from session
if ($locale = $request->getSession()->get('_locale')) {
$request->setLocale($locale);
}
}
}
public static function getSubscribedEvents()
{
return [
// must be registered before (i.e. with a higher priority than) the default Locale listener
KernelEvents::REQUEST => [['onKernelRequest', 20]],
];
}
}

Change language on login

Symfony 2.1.3-dev
SonataUserBundle
SonataAdminBundle
JMSI18nRoutingBundle
By default the language is french, but I enabled "en"
I installed this bundles and most things work fine.
But I would like to do the following :
A user XXX (SonataUserBundle) has in the field "locale" the value "en"
When this user logs in I want to show up the pages in english.
The user has not to switch manually.
I think this should be done on the autentification process.
The problem is that SonataUserBundle (based on FOSUser) does not do the authentification (seen HERE)
So I tried to do THIS, but there must be some configuration issues.
When applying the wsse_secured to the whole site :
wsse_secured:
pattern: ^/
wsse: true
I get the following error : A Token was not found in the SecurityContext.
When adding anonymous to my config.yml :
firewalls:
....
main:
pattern: ^/
wsse: true
anonymous: true
I can access the home page, but when trying to login I get this error :
You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.
When adding the checkpath for FOS it works but the systems does not work with my wsse-provider (I added code in WsseProvider.php to make me know)
So my question : How can I get work this WSSE authentification. I followed strictly the indications in the doc.
EDIT :
I perhaps made an error by implementing the wsse security files in my own bundle.
Now I moved it to sonata user bundle and I get the following error :
ErrorException: Catchable Fatal Error: Argument 1 passed to Application\Sonata\UserBundle\Security\Authentication\Provider\WsseProvider::__construct() must implement interface Symfony\Component\Security\Core\User\UserProviderInterface, string given, called in ..\app\cache\dev\appDevDebugProjectContainer.php on line 4413 and defined in ..\src\Application\Sonata\UserBundle\Security\Authentication\Provider\WsseProvider.php line 17
What's wrong with my UserProviderInterface in WsseProvider.php :
<?php
namespace Application\Sonata\UserBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Application\Sonata\UserBundle\Security\Authentication\Token\WsseUserToken;
class WsseProvider implements AuthenticationProviderInterface
{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir = $cacheDir;
}
...
I've solve this problem with a simple KernelRequestListener:
<?php
namespace ACME\DemoBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
class RequestListener
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function onKernelRequest(GetResponseEvent $event)
{
$userlocale = null;
$request = $event->getRequest();
$user = $this->container->get('security.context')->getToken()->getUser();
if (!is_object($user)) {
return;
}
$userlocale = $user->getLocale();
if($userlocale !== NULL AND $userlocale !== '')
{
$request->setLocale($userlocale);
}
}
}
Register service in Acme/Demo/Resources/service.yml:
ACME.demo.listener.request:
class: ACME\DemoBundle\Listener\RequestListener
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Other solution I've found there: Here

Categories