U2fSubscriber::getSubscribedEvents()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/*
4
 * This file is part of the U2F Security bundle.
5
 *
6
 * (c) Michael Barbey <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Mbarbey\U2fSecurityBundle\EventSubscriber;
13
14
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
16
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
17
use Symfony\Component\Security\Http\SecurityEvents;
18
use Symfony\Component\HttpKernel\KernelEvents;
19
use Symfony\Component\HttpFoundation\RedirectResponse;
20
use Symfony\Component\Routing\RouterInterface;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
use Symfony\Component\HttpFoundation\Session\SessionInterface;
23
use Mbarbey\U2fSecurityBundle\Event\Authentication\U2fAuthenticationRequiredEvent;
24
use Mbarbey\U2fSecurityBundle\Model\User\U2fUserInterface;
25
26
/**
27
 * U2F event subscriber
28
 *
29
 * An event subscriber which will automatically detect when a user successfuly log in and define of the user
30
 * must be redirected it to the U2F authentication page or not.
31
 *
32
 * The listened events are :
33
 * - security.interactive_login : When a user successfully log in
34
 * - kernel.request             : When a page is requested
35
 *
36
 * @author Michael Barbey <[email protected]>
37
 */
38
class U2fSubscriber implements EventSubscriberInterface
39
{
40
    const U2F_SECURITY_KEY = 'u2f_must_validate';
41
42
    private $redirectToRoute;
43
    private $whitelistOfRoutes;
44
45
    private $router;
46
    private $session;
47
    private $dispatcher;
48
49
    /**
50
     * @param string $redirectToRoute               The route where the user must be redirect to perform the U2F authentication
51
     * @param array $whitelistOfRoutes              An array of routes to add to the whitelist
52
     * @param RouterInterface $router               The routing engine
53
     * @param SessionInterface $session             The session handler
54
     * @param EventDispatcherInterface $dispatcher  The event dispatcher
55
     */
56
    public function __construct($redirectToRoute, array $whitelistOfRoutes, RouterInterface $router, SessionInterface $session, EventDispatcherInterface $dispatcher)
57
    {
58
        $this->redirectToRoute = $redirectToRoute;
59
        $this->whitelistOfRoutes = $whitelistOfRoutes;
60
        $this->router = $router;
61
        $this->session = $session;
62
        $this->dispatcher = $dispatcher;
63
    }
64
65
    /**
66
     * This function is called when a user successfully log in with is basic credentials.
67
     *
68
     * This function will determine if the user must be redirected to the U2F authentication page or not.
69
     *
70
     * @param InteractiveLoginEvent $event
71
     */
72
    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
73
    {
74
        $user = $event->getAuthenticationToken()->getUser();
75
76
        /*
77
         * If the user has at least one security key linked to it account, he must be redirected
78
         * to the U2F authentication page.
79
         */
80
        if ($user instanceof U2fUserInterface && $user->getU2fKeys()->count()) {
81
            $authenticate = true;
82
83
            /*
84
             * We give a change to cancel this authentication request by dropping a bottle to the sea and waiting for
85
             * someone to respond.
86
             */
87
            if ($this->dispatcher->hasListeners(U2fAuthenticationRequiredEvent::getName())) {
88
                $shouldAuthenticate = new U2fAuthenticationRequiredEvent($user);
89
                $this->dispatcher->dispatch($shouldAuthenticate::getName(), $shouldAuthenticate);
90
                $authenticate = $shouldAuthenticate->mustAuthenticate();
91
            }
92
93
            /*
94
             * If the user must be redirected, we put a flag in his session.
95
             */
96
            if ($authenticate) {
97
                $event->getRequest()->getSession()->set(static::U2F_SECURITY_KEY, true);
98
            }
99
        }
100
    }
101
102
    /**
103
     * This function is called at every requests.
104
     *
105
     * This function will check if the user must be redirected and if the current route is not whitelisted. Then we return
106
     * a redirect response instead of the content the user expected.
107
     *
108
     * @param GetResponseEvent $event
109
     */
110
    public function onKernelRequest(GetResponseEvent $event)
111
    {
112
        /*
113
         * Do nothing if the request isn't a master request and if the securtiy flag hasn't been planted
114
         * by the "onSecurityInteractiveLogin" function.
115
         */
116
        if (
117
            $event->isMasterRequest() &&
118
            $event->getRequest()->getSession()->get(static::U2F_SECURITY_KEY)
119
        ) {
120
            $route = $event->getRequest()->get('_route');
121
            $whitelist = array_merge(
122
                [
123
                    $this->redirectToRoute
124
                ],
125
                $this->whitelistOfRoutes,
126
                [
127
                    '_wdt',
128
                    '_profiler_home',
129
                    '_profiler_search',
130
                    '_profiler_search_bar',
131
                    '_profiler_phpinfo',
132
                    '_profiler_search_results',
133
                    '_profiler_open_file',
134
                    '_profiler',
135
                    '_profiler_router',
136
                    '_profiler_exception',
137
                    '_profiler_exception_css'
138
                ]
139
            );
140
141
            /*
142
             * If the user isn't on a whitelisted route, he is redirected to the authentication page.
143
             */
144
            if (!in_array($route, $whitelist)) {
145
                $counter = $this->session->get('u2f_registration_error_counter', -1) +1;
146
                $this->session->set('u2f_registration_error_counter', $counter);
147
                $event->setResponse(new RedirectResponse($this->router->generate($this->redirectToRoute)));
148
                $event->stopPropagation();
149
            }
150
        }
151
    }
152
153
    /**
154
     * Return the list of listened events
155
     *
156
     * @return array
157
     */
158 1
    public static function getSubscribedEvents()
159
    {
160
        return [
161 1
            SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
162 1
            KernelEvents::REQUEST => 'onKernelRequest'
163
        ];
164
    }
165
}
166