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

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

Related

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

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.

How to get firewall name from Request in Symfony 5?

The question is simple. I am implmenting AccessDeniedListener and I get an ExceptionEvent object. From this I can get request. I want to apply certain logic ONLY if I am inside one of my firewalls defined in security.yaml.
How can I get the Firewall name from ExceptionEvent or Request instances?
EDIT:
I have found this code "works"
$firewall_context_name = $event->getRequest()->attributes->get('_firewall_context');
However Im not very happy about it. There should be a FirewallContext or FirewallConfig objects retrieveable somehow, no?
Thanks
class AccessDeniedListener implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
// the priority must be greater than the Security HTTP
// ExceptionListener, to make sure it's called before
// the default exception listener
KernelEvents::EXCEPTION => ['onKernelException', 2],
];
}
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
if (!$exception instanceof AccessDeniedException) {
return;
}
$request = $event->getRequest();
// HOW TO GET FIREWALL NAME HERE???
security.yaml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api/
security: false
main:
custom_authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
lazy: true
provider: app_user_provider
As stated in the docs you linked:
This object can be accessed through the getFirewallConfig(Request $request) method of the
Symfony\Bundle\SecurityBundle\Security\FirewallMap class
This class cannot be injected directly, so you'll have to configure your dependency in services.yaml using the service alias security.firewall.map (or create a service alias if you plan to use it somewhere else).
services:
# ...
App\Listener\AccessDeniedListener:
arguments:
$firewallMap: '#security.firewall.map'
Now modify your listener to receive this parameter:
class AccessDeniedListener implements EventSubscriberInterface
{
private $firewallMap;
public function __construct(FirewallMapInterface $firewallMap)
{
$this->firewallMap = $firewallMap;
}
// Ommited getSubscribedEvents
public function onKernelException(ExceptionEvent $event): void
{
$request = $event->getRequest();
$firewallConfig = $this->firewallMap->getFirewallConfig($request);
if (null === $firewallConfig) {
return;
}
$firewallName = $firewallConfig->getName();
}
}

Showing current user in log

I'm trying to create a custom monolog processor to attach the current user to an error mailer.
When declaring a service like so:
monolog.processor.mail:
class: MyVendor\Monolog\Processor\MailProcessor
arguments:
- #mailer
- #security.context
tags:
- { name: monolog.processor, method: processRecord }
I get a circular reference:
[Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException]
Circular reference detected for service "monolog.processor.mail",
path: "router -> monolog.logger.router -> monolog.processor.mail
-> security.context -> security.authentication.manager
-> fos_user.user_provider.username_email-> fos_user.user_manager
-> doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection
-> doctrine.dbal.logger -> monolog.logger.doctrine".
What would be the best practice solution here?
A related forum thread:
http://forum.symfony-project.org/viewtopic.php?t=40306&p=131081#p131143
This thread shows that:
Setter injection doesn't solve the issue (i tried this as well)
Injecting the container causes an infinitive recursion (this i have not confirmed)
Also tried this script http://pastebin.com/AuvFgTY3 to get the user from the session.
if ($this->session !== null) {
if ($this->session->has($this->securityKey)) {
$token = unserialize($this->session->get($this->securityKey));
$this->currentUser = $token->getUser();
}
}
This gave the following error:
Warning: ini_set(): A session is active. You cannot change the session module's ini settings at this time in
C:\inetpub\symfony23\vendor\symfony\symfony\src\Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler.php
on line 56
I do understand that the security.context has not yet been build for services which request the logger very early on. For my class it's not a problem since i will set the user to undefined. So ideally the security.context would be setter injected AFTER the security.context service has been created. However i can not change the priority on the logger to be constructed very late because it's needed early on.
So maybe the question resolves to: how to recreate the service again after security.context has been initialized? Not sure if scope prototype would help here??
Create handler on kernel request and extract user:
// src/AppBundle/Monolog/UserProcessor.php
namespace AppBundle\Monolog;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class UserProcessor
{
private $tokenStorage;
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function __invoke(array $record)
{
if (null !== $this->user) {
$record['extra']['user'] = $this->user->getUsername();
}
return $record;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (null === $token = $this->tokenStorage->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
$this->user = $user;
}
}
Register new processor:
# app/config/services.yml
services:
monolog.processor.user:
class: AppBundle\Monolog\UserProcessor
arguments: ["#security.token_storage"]
tags:
- { name: monolog.processor }
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Symfony Documentation has problem

Symfony2 ContextErrorException in Custom Authentication Provider

I'm trying to make a How to create a custom Authentication Provider, after a full reading and looking into the Symfony code, I thought that just with creating a Factory, Authentication Provider and using the symfony default class will be enough but actually I'm missing something and I'm getting this error
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Acme\DemoBundle\Provider\MyProvider::__construct() must implement interface Symfony\Component\Security\Core\User\UserProviderInterface, string given, called in D:\wamp\www\sf2ldap\app\cache\dev\appDevDebugProjectContainer.php on line 1383 and defined in D:\wamp\www\sf2ldap\src\Acme\DemoBundle\Provider\MyProvider.php line 20
The Factory
namespace Acme\DemoBundle\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
class MyFactory extends AbstractFactory
{
public function getPosition()
{
return 'form';
}
public function getKey()
{
return 'kstr';
}
protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
{
$providerId = 'security.authentication.provider.kstr.' . $id;
$container
->setDefinition($providerId, new DefinitionDecorator('kstr.security.authentication.provider'))
->replaceArgument(0, new Reference($userProviderId));
return $providerId;
}
protected function getListenerId()
{
return 'security.authentication.listener.form';
}
}
My Provider
namespace Acme\DemoBundle\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\Authentication\Token\TokenInterface;
class MyProvider implements AuthenticationProviderInterface
{
private $_userProvider;
public function __construct(UserProviderInterface $userProvider)
{
$this->_userProvider = $userProvider;
}
public function authenticate(TokenInterface $token)
{
try
{
$user = $this->_userProvider->loadUserByUsername($token->getUsername());
//custom auth steps
$token = new UsernamePasswordToken(
$token->getUsername(), null, $token->getProviderKey(), $user->getRoles()
);
return $token;
}
} catch (\Exception $exc)
{
throw new AuthenticationException('Invalid username or password. ', 0, $e);
}
throw new AuthenticationException('Invalid username or password asdfasd');
}
public function supports(TokenInterface $token)
{
return $token instanceof UsernamePasswordToken;
}
}
services.yml
services:
kstr.security.authentication.provider:
class: Acme\DemoBundle\Provider\MyProvider
arguments: [""]
security.yml
security:
encoders:
Acme\DemoBundle\Entity\SecureUser: plaintext
providers:
multiples:
chain:
providers: [entity_provider, ldap]
entity_provider:
entity: { class: AcmeDemoBundle:SecureUser, property: username }
ldap:
id: kstr.security.authentication.provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/demo/secured/login$
security: false
secured_area:
pattern: ^/demo/secured/
kstr:
check_path: _security_check
login_path: _demo_login
provider: ldap
logout:
path: _demo_logout
target: _demo
need some help to figure this out, what I'm missing here? do I need to create a custom listener even if the default "security.authentication.listener.form" fulfil my needs?
You're passing a string arguments: [""] as first argument to the constructor of the service.
That's why the typehint in __construct(UserProviderInterface $userProvider) fails.
Inject a UserProviderInterface properly and the exception will disappear.

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