Search code examples
phplistenersymfonytwo-factor-authentication

scheb/two_factor_bundle: how to use exclude_pattern for authenticated route?


I'm using Scheb's two factor bundle in a Symfony3 project and I'd like to handle the exclude_pattern parameter differently than it does, but I don't know how.

Normally, exclude_pattern is used to exclude an unauthenticated route from the two-factor authentication, like debug pages or static content:

# config/config.yml

scheb_two_factor:
    ...
    exclude_pattern: ^/(_(profiler|wdt)|css|images|js)/

its behavior being implemented like this:

/* vendor/scheb/two-factor-bundle/Security/TwoFactor/EventListener/RequestListener.php */

public function onCoreRequest(GetResponseEvent $event)
{
    $request = $event->getRequest();

    // Exclude path
    if ($this->excludePattern !== null && preg_match('#'.$this->excludePattern.'#', $request->getPathInfo())) {
        return;
    }

    ...

}

I'd like to handle exclude_pattern also for authenticated routes, so that I can skip two-factor authentication when I call them. For authenticated I mean within access_control section under security.yml, like this:

# app/config/security.yml
security:
    ...
    access_control:
        - { path: ^/test, role: ROLE_USER }

Right now, if I add an authenticated route under exclude_pattern, all I get is a AccessDeniedException, probably because the bundle requires that the access_decision_manager parameter to be set as strategy: unanimous.

The purpose is long to tell and English is not my native language, but if you really need to know it I can try to explain.

I tagged the question with both symfony3 and symfony2 because I'm using Symfony 3.0 but I'm pretty sure it's identical in Symfony 2.8.


Solution

  • I found a solution by overriding the Voter class from the bundle:

    // AppBundle/Security/TwoFactor/Voter.php
    
    namespace AppBundle\Security\TwoFactor;
    use Scheb\TwoFactorBundle\Security\TwoFactor\Session\SessionFlagManager;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    
    class Voter extends \Scheb\TwoFactorBundle\Security\TwoFactor\Voter
    {
    
    
        /**
         * @var string
         */
        protected $excludePattern;
    
        /**
         * Voter constructor.
         * @param SessionFlagManager $sessionFlagManager
         * @param array $providers
         * @param $excludePattern
         */
        public function __construct(SessionFlagManager $sessionFlagManager, array $providers, $excludePattern)
        {
    
            parent::__construct($sessionFlagManager, $providers);
            $this->excludePattern = $excludePattern;
        }
    
        /**
         * @param TokenInterface $token
         * @param mixed          $object
         * @param array          $attributes
         *
         * @return mixed result
         */
        public function vote(TokenInterface $token, $object, array $attributes)
        {
    
            if ($this->excludePattern !== null && preg_match('#'.$this->excludePattern.'#', $object->getPathInfo()))
            {
                return true;
            }
    
            parent::vote($token, $object, $attributes);
        }
    
    }
    

    # app/config/services.yml
    
    services:
        ...
        scheb_two_factor.security_voter:
        class: 'AppBundle\Security\TwoFactor\Voter'
        arguments:
            - '@scheb_two_factor.session_flag_manager'
            - ~
            - '%scheb_two_factor.exclude_pattern%'
    

    This way, whenever the GetResponseEvent is triggered, the right Voter is invoked which votes true if the exclude_pattern matches the path.