Passed
Push — master ( 23587f...1016f0 )
by Jan
04:38 queued 10s
created

PasswordChangeNeededSubscriber::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
c 1
b 0
f 1
nc 1
nop 3
dl 0
loc 5
rs 10
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
20
 */
21
22
namespace App\EventSubscriber;
23
24
25
use App\Entity\UserSystem\User;
26
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
27
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
28
use Symfony\Component\HttpKernel\Event\RequestEvent;
29
use Symfony\Component\HttpKernel\KernelEvents;
30
use Symfony\Component\Security\Core\Security;
31
use Symfony\Component\Security\Http\HttpUtils;
32
33
/**
34
 * This event subscriber redirects a user to its settings page, when it needs to change its password or is enforced
35
 * to setup a 2FA method (enforcement can be set per group).
36
 * In this cases the user is unable to access sites other than the whitelisted (see ALLOWED_ROUTES).
37
 * @package App\EventSubscriber
38
 */
39
class PasswordChangeNeededSubscriber implements EventSubscriberInterface
40
{
41
42
    protected $security;
43
    protected $flashBag;
44
    protected $httpUtils;
45
46
    /**
47
     * @var string[] The routes the user is allowed to access without being redirected.
48
     * This should be only routes related to login/logout and user settings
49
     */
50
    public const ALLOWED_ROUTES = [
51
        '2fa_login',
52
        '2fa_login_check',
53
        'user_settings',
54
        'club_base_register_u2f',
55
        'logout',
56
    ];
57
58
    /** @var string The route the user will redirected to, if he needs to change this password */
59
    public const REDIRECT_TARGET = 'user_settings';
60
61
    public function __construct(Security $security, FlashBagInterface $flashBag, HttpUtils $httpUtils)
62
    {
63
        $this->security = $security;
64
        $this->flashBag = $flashBag;
65
        $this->httpUtils = $httpUtils;
66
    }
67
68
    /**
69
     * This function is called when the kernel encounters a request.
70
     * It checks if the user must change its password or add an 2FA mehtod and redirect it to the user settings page,
71
     * if needed.
72
     * @param  RequestEvent  $event
73
     */
74
    public function redirectToSettingsIfNeeded(RequestEvent $event) : void
75
    {
76
        $user = $this->security->getUser();
77
        $request = $event->getRequest();
78
79
        if(!$event->isMasterRequest()) {
80
            return;
81
        }
82
        if(!$user instanceof User) {
83
            return;
84
        }
85
86
        //Abort if we dont need to redirect the user.
87
        if (!$user->isNeedPwChange() && !static::TFARedirectNeeded($user)) {
88
            return;
89
        }
90
91
        //Check for a whitelisted URL
92
        foreach (static::ALLOWED_ROUTES as $route) {
93
            //Dont do anything if we encounter an allowed route
94
            if ($this->httpUtils->checkRequestPath($request, $route)) {
95
                return;
96
            }
97
        }
98
99
        /* Dont redirect tree endpoints, as this would cause trouble and creates multiple flash
100
        warnigs for one page reload */
101
        if(strpos($request->getUri(), '/tree/') !== false) {
102
            return;
103
        }
104
105
        //Show appropriate message to user about the reason he was redirected
106
        if($user->isNeedPwChange()) {
107
            $this->flashBag->add('warning', 'user.pw_change_needed.flash');
108
        }
109
110
        if(static::TFARedirectNeeded($user)) {
111
            $this->flashBag->add('warning', 'user.2fa_needed.flash');
112
        }
113
114
        $event->setResponse($this->httpUtils->createRedirectResponse($request, static::REDIRECT_TARGET));
115
116
    }
117
118
    /**
119
     * Check if a redirect because of a missing 2FA method is needed.
120
     * That is the case if the group of the user enforces 2FA, but the user has neither Google Authenticator nor an
121
     * U2F key setup.
122
     * @param  User  $user The user for which should be checked if it needs to be redirected.
123
     * @return bool True if the user needs to be redirected.
124
     */
125
    public static function TFARedirectNeeded(User $user) : bool
126
    {
127
        $tfa_enabled = $user->isU2FAuthEnabled() || $user->isGoogleAuthenticatorEnabled();
128
129
        if ($user->getGroup() !== null && $user->getGroup()->isEnforce2FA() && !$tfa_enabled) {
130
            return true;
131
        }
132
133
        return false;
134
    }
135
136
    /**
137
     * @inheritDoc
138
     */
139
    public static function getSubscribedEvents()
140
    {
141
        return [
142
            KernelEvents::REQUEST => 'redirectToSettingsIfNeeded',
143
        ];
144
    }
145
}