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();
}
}
Related
With the new Symfony 5.1 security system, I'm not able to fire the InteractiveLoginEvent.
I followed the configuration in the official documentation (here and here) and the system was working perfectly in the former security system.
Below is my security.yaml :
security:
enable_authenticator_manager: true
encoders:
App\Entity\User:
algorithm: auto
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
form_login:
login_path: login
check_path: login
entry_point: form_login
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: logout
And the UserLocalSuscriber :
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
class UserLocaleSubscriber implements EventSubscriberInterface
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if ($user->getLocale() !== null) {
$this->session->set('_locale', $user->getLocale());
}
}
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
];
}
}
How to configure it correctly? Without it, the user locale is not set properly once the user has been logged in.
For the Symfony's new security system, the SecurityEvents::INTERACTIVE_LOGIN has been replaced with Symfony\Component\Security\Http\Event\LoginSuccessEvent.
Change your subscriber to listen for this one:
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class UserLocaleSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
LoginSuccessEvent::class => 'onLoginSuccess',
];
}
public function onLoginSuccess(LoginSuccessEvent $event): void
{
//...
}
}
These new events are mentioned briefly in the blog with the announcement for the new authenticator system.
The rest of the documentation has not been updated yet, the new auth system will probably become the default on a future Symfony realese, but right now it still is an experimental feature.
Try using this method onSecurityInteractivelogin()
i'm new # symfony and absolutely new at doctrine. On the symfony page is an tutorial about build an login process in sf2. And i love it, BUT i cant work with doctrine and i dont wanna work with it (there are many thinks that dont work with doctrine... - e.g. enumĀ“s, etc).
How i create an login controller, firewall setup, etc.. is explained very good. BUT! i would like to create it without doctrine... i have an existing database, and i love plain sql. :-)
How can i use plain sql in an UserInterface... that will work with the build-in login from sf2?
Thx a lot...
What you want to do is to plug in your own UserProvider:
http://symfony.com/doc/current/cookbook/security/entity_provider.html
In place of the Doctrine 2 Object Relational Manager (ORM) you might consider using the Doctrine 2 Database Access Layer(DBAL). This is a thin sql layer built on top of PDO. Has some helper routines for building sql.
http://symfony.com/doc/current/cookbook/doctrine/dbal.html
Of course you can just use PDO directly:
http://symfony.com/doc/current/cookbook/configuration/pdo_session_storage.html
Here is a solution I propose on Symfony 3.0.
As I implement an existing postgresql database and I want to re-use a lot of an already existing PHP code, ORM was not a fit in my case (too much timecost to do the reverse engineering). Hence I have been actively searching how to use Symfony without ORM. (I did implement DBAL because what I wrote for PDO works with it... so far ... with some minor fixes).
Let's start:
The important concept I needed to understand were taken from this page:
http://symfony.com/doc/current/cookbook/security/custom_provider.html + all the things about the security.yml file.
About the security file, it took me a while to understand that the node 'providers' had only two native option:'entity' or 'memory'. And with 'entity', one was bound to work with ORM. So I figured out that I needed to develop my own provider that would work with regular PDO.
Here some part of my code to give you an idea on how I finally made it work, you'll find below the following files:
security.yml (There are probably still some room for improvement here, as I haven't dig too much the 'firewall' part organization);
CustomUsersProvider.php : The class implementing the interface
UserProviderInterface (The class PdoCustom and its method getUser() are my own PDO cooking to retrieve the user from my DB);
services.yml: The service giving access to the class CustomUsersProvider and hence providing access to the 'provider' in the security file (if I unsderstood it correctly), I pass the DBAL database config as an argument in the service so it can be used in the instance of my object CustomUsersProvider;
CustomUsers.php : a class acting as an entity of my table
'custom_users', to work with the PDO::FETCH_CLASS type of PDO fetch; it implements Symfony interfaces: UserInterface and EquatableInterface;
You still have to implement the Symfony Controller SecurityController (find it here: http://symfony.com/doc/2.0/book/security.html) and its route to /login and Twig file.
You'll probably notice when reading the code that $email is used as $username;
[projectname]\app\config\security.yml
encoders:
CustomBundle\Entity\CustomUsers:
algorithm: [enter an encoding algorithm, eg: MD5, sha512, etc.]
role_hierarchy:
ROLE_NAME2: ROLE_NAME1
ROLE_NAME3: ROLE_NAME2
providers:
custom_users_provider:
id: custom_users_provider
firewalls:
main_login:
pattern: ^/login$
anonymous: ~
main:
pattern: ^/
anonymous: true
provider: custom_users_provider
form_login:
check_path: /login_check
login_path: /login
provider: custom_users_provider
username_parameter: email
password_parameter: password
logout: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/secured_for_role_name, role: ROLE_NAME }
[projectname]\CustomBundle\Security\CustomUsersProvider.php
<?php
namespace CustomBundle\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use CustomBundle\DependencyInjection\PdoCustom;
use CustomBundle\Entity\CustomUsers;
class CustomUsersProvider implements UserProviderInterface
{
private $dbalConnection;
private $logger;
public function __construct($dbalConnection){
$this->dbalConnection = $dbalConnection;
////////////////EASTER EGG//////////////////////////
$this->logger = $GLOBALS['kernel']->getContainer()->get('logger');
$logger->info('EASTER EGG: YOU CAN ADD LOG THAT CAN BE FOUND UNDER [projectname]\var\logs\dev.log WHEN LAUNCHING THRU app_dev.php, WICH COULD BE USEFUL TOO');
////////////////////////////////////////////////////
}
public function loadUserByUsername($username)
{
$PdoCustom = new PdoCustom($this->dbalConnection);
$userData = $PdoCustom->getUser($username);
if ($userData) {
$password = $userData->password;
$salt = null;
$role = $userData->role;
$resToReturn = new CustomUsers();
$resToReturn->setCharact($username, $password, $role);
return $resToReturn;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof CustomUsers) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'CustomBundle\Entity\CustomUsers';
}
}
?>
[projectname]\src\CustomBundle\Ressources\config\services.yml
custom_bundle.custom_users_provider.class : CustomBundle\Security\CustomUsersProvider
custom_users_provider:
class: %custom_bundle.custom_users_provider.class%
arguments: ["#doctrine.dbal.default_connection"]
[projectname]\CustomBundle\Entity\CustomUsers.php
<?php
namespace CustomBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
class CustomUsers implements UserInterface, EquatableInterface {
public $email;
public $password;
public $role;
public function __construct(){
}
public function setCharact($email,$password,$role){
$this->email = $email;
$this->password = $password;
$this->status = $status;
}
public function getRoles(){
return $this->role;
}
public function getPassword(){
return $this->password;
}
public function getSalt(){
return null;
}
public function getUsername(){
return $this->email;
}
public function eraseCredentials(){
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof CustomUsers) {
return false;
}
if ($this->user_password !== $user->getPassword()) {
return false;
}
//if ($this->salt !== $user->getSalt()) {
// return false;
//}
if ($this->email !== $user->getUsername()) {
return false;
}
return true;
}
}
?>
That's it, you still have some work to figure it all out by yourself but I hope that will give you directions that I had a really hard time to find on HOW TO USE SYMFONY WITHOUT ORM:))
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.
in Symfony2, is it possible to check if user is authenticated to access the URl he requested.
What I want to do is, i dont want to allow a logged in user to go back to registration or login or recover password pages.
here is my security.yml:
access_control:
- { path: ^/signup/, roles: IS_AUTHENTICATED_ANONYMOUSLY && !IS_AUTHENTICATED_FULLY}
- { path: ^/register/, roles: IS_AUTHENTICATED_ANONYMOUSLY && !IS_AUTHENTICATED_FULLY}
- { path: ^/recover/, roles: IS_AUTHENTICATED_ANONYMOUSLY && !IS_AUTHENTICATED_FULLY}
but this is showing, access denied page to current user. So i think it would be nice if I can redirect the user to home page on such request, by checking if he is not allowed. Can I check by providing path that user is authenticated or not in listener?
public function onKernelResponse(FilterResponseEvent $event)
{
$request = $event->getRequest();
$path = $request->getPathInfo();
if($this->container->get('security.context')->getToken() != null) {
// To check if user is authenticated or anonymous
if( ($this->container->get('security.context')->getToken() instanceof UsernamePasswordToken) &&
($this->container->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY') == true) ) {
// HOW TO CHECK PATH ?
// set response to redirect to home page
}
}
}
The security.access_map service
The configuration of security.access_control is processed by ...
SecurityBundle\DependencyInjection\SecurityExtension
... which creates RequestMatchers for the routes (path,hosts,ip,...) and then invokes the service's add() method with the matcher, the allowed roles and the channel (i.e. https ).
The service is usually used by i.e. the AccessListener.
You can use the security.access_map service to access the
security.access_control parameters in your application.
The class used for the security.access_map service is defined by the parameter security.access_map.class and defaults to
Symfony\Component\Security\Http\AccessMap ( implements
AccessMapInterface )
You can use the parameter security.access_map.class to override the service with a custom class (must implement AccessMapInterface):
# i.e. app/config/config.yml
parameters:
security.access_map.class: My\Custom\AccessMap
How to access the service
The security.access_map service is a private service as you can see by it's definition here.
This means you can't request it from the container directly like this:
$this->container->get('security.access_map')
You will have to inject it into another service (i.e. a listener service) explicitly to be able to access it.
A listener example
services:
my_listener:
class: My\Bundle\MyListenerBundle\EventListener\ForbiddenRouteListener
arguments: [ #security.access_map ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Then you can call the getPatterns() method to obtain the RequestMatchers, allowed roles and required channel from there.
namespace My\Bundle\MyListenerBundle\EventListener;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class ForbiddenRouteListener
{
protected $accessMap;
public function __construct(AccessMapInterface $access_map)
{
$this->accessMap = $access_map;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$patterns = $this->accessMap->getPatterns($request);
// ...
Maybe this will help someone. I just catch route name and check if they are in array. If yes just redirect. This is event listener.
services.yml
project.loggedin_listener:
class: Project\FrontBundle\EventListener\LoggedInListener
arguments: [ "#router", "#service_container" ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
listener:
namespace Project\FrontBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
class LoggedInListener {
private $router;
private $container;
public function __construct($router, $container)
{
$this->router = $router;
$this->container = $container;
}
public function onKernelRequest(GetResponseEvent $event)
{
$container = $this->container;
$accountRouteName = "_homepage";
if( $container->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY') ){
$routeName = $container->get('request')->get('_route');
$routes = array("admin_login","fos_user_security_login","fos_user_registration_register","fos_user_resetting_request");
if(in_array($routeName, $routes)){
$url = $this->router->generate($accountRouteName);
$event->setResponse(new RedirectResponse($url));
}
}
}
}
You can do not only inside security.yml options, but also via controller, like this:
if($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
return $this->redirect($this->generateUrl('homepage'));
}
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