UserSettingsController::regenerateBackupCodes()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
nc 3
nop 3
dl 0
loc 20
rs 9.9
c 1
b 0
f 0
1
<?php
2
/*
3
 * Copyright (C) 2020  Jan Böhmer
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Affero General Public License as published
7
 * by the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Affero General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Affero General Public License
16
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 */
18
19
namespace App\Controller;
20
21
use App\Entity\User;
22
use App\Form\TFA\TFAGoogleSettingsType;
23
use App\Form\User\PasswordChangeType;
24
use App\Services\TFA\BackupCodeManager;
25
use Doctrine\ORM\EntityManagerInterface;
26
use Endroid\QrCode\QrCode;
27
use Endroid\QrCode\Writer\SvgWriter;
28
use RuntimeException;
29
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
30
use Scheb\TwoFactorBundle\Security\TwoFactor\QrCode\QrCodeGenerator;
0 ignored issues
show
Bug introduced by
The type Scheb\TwoFactorBundle\Se...\QrCode\QrCodeGenerator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
32
use Symfony\Component\HttpFoundation\Request;
33
use Symfony\Component\HttpFoundation\Response;
34
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
35
use Symfony\Component\Routing\Annotation\Route;
36
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
37
38
/**
39
 * @Route("/admin/user")
40
 */
41
class UserSettingsController extends AbstractController
42
{
43
    /**
44
     * @Route("/settings", name="user_settings")
45
     */
46
    public function userSettings(Request $request, UserPasswordHasherInterface $passwordEncoder, EntityManagerInterface $entityManager,
47
        GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager): Response
48
    {
49
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
50
51
        $user = $this->getUser();
52
        if (!$user instanceof User) {
53
            throw new RuntimeException('This controller can only manage App\Entity\User objects!');
54
        }
55
56
        $pw_form = $this->createForm(PasswordChangeType::class);
57
        $pw_form->handleRequest($request);
58
59
        if ($pw_form->isSubmitted() && $pw_form->isValid()) {
60
            //If form is valid, the old password was already validated, so we just have to encrypt the pw now
61
            $hashed_pw = $passwordEncoder->hashPassword($user, $pw_form['plain_password']->getData());
62
            $user->setPassword($hashed_pw);
63
            $user->setPasswordChangeNeeded(false);
64
65
            $entityManager->flush();
66
67
            $this->addFlash('success', 'password.changed_successful');
68
        }
69
70
        //Handle 2FA things
71
        $google_form = $this->createForm(TFAGoogleSettingsType::class, $user);
72
        $google_enabled = $user->isGoogleAuthenticatorEnabled();
73
        if (!$google_enabled && !$google_form->isSubmitted()) {
74
            $user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret());
75
            $google_form->get('googleAuthenticatorSecret')
76
                ->setData($user->getGoogleAuthenticatorSecret());
77
        }
78
        $google_form->handleRequest($request);
79
80
        if ($google_form->isSubmitted() && $google_form->isValid()) {
81
            if (!$google_enabled) {
82
                //Save 2FA settings (save secrets)
83
                $user->setGoogleAuthenticatorSecret($google_form->get('googleAuthenticatorSecret')->getData());
84
                $backupCodeManager->enableBackupCodes($user);
85
86
                $entityManager->flush();
87
                $this->addFlash('success', 'user.settings.2fa.google.activated');
88
89
                return $this->redirect($request->getUri());
90
            }
91
92
            //Remove secret to disable google authenticator
93
            $user->setGoogleAuthenticatorSecret(null);
94
            $backupCodeManager->disableBackupCodesIfUnused($user);
95
            $entityManager->flush();
96
            $this->addFlash('success', 'user.settings.2fa.google.disabled');
97
98
            return $this->redirect($request->getUri());
99
        }
100
101
102
        $qrCode = QrCode::create($googleAuthenticator->getQRContent($user));
103
104
        return $this->render('admin/user/settings.html.twig', [
105
            'user' => $user,
106
            'pw_form' => $pw_form->createView(),
107
            'google_form' => $google_form->createView(),
108
            'tfa_google' => [
109
                'enabled' => $google_enabled,
110
                'qrContent' => $googleAuthenticator->getQRContent($user),
111
                'secret' => $user->getGoogleAuthenticatorSecret(),
112
                'qrImageDataUri' => (new SvgWriter())->write($qrCode)->getDataUri(),
113
                'username' => $user->getGoogleAuthenticatorUsername(),
114
            ],
115
        ]);
116
    }
117
118
    /**
119
     * @Route("/2fa_backup_codes", name="show_backup_codes")
120
     */
121
    public function showBackupCodes(): Response
122
    {
123
        $user = $this->getUser();
124
125
        //When user change its settings, he should be logged  in fully.
126
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
127
128
        if (!$user instanceof User) {
129
            throw new RuntimeException('This controller only works only for Part-DB User objects!');
130
        }
131
132
        if (empty($user->getBackupCodes())) {
133
            $this->addFlash('error', 'tfa_backup.no_codes_enabled');
134
135
            throw new RuntimeException('You do not have any backup codes enabled, therefore you can not view them!');
136
        }
137
138
        return $this->render('admin/user/backup_codes.html.twig', [
139
            'user' => $user,
140
        ]);
141
    }
142
143
    /**
144
     * @Route("/regenerate_backup_codes", name="tfa_regenerate_backup_codes", methods={"DELETE"})
145
     */
146
    public function regenerateBackupCodes(Request $request, EntityManagerInterface $entityManager, BackupCodeManager $backupCodeManager): \Symfony\Component\HttpFoundation\RedirectResponse
147
    {
148
        $user = $this->getUser();
149
150
        //When user change its settings, he should be logged  in fully.
151
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
152
153
        if (!$user instanceof User) {
154
            throw new RuntimeException('This controller only works only for Part-DB User objects!');
155
        }
156
157
        if ($this->isCsrfTokenValid('regenerate_backup_codes'.$user->getId(), $request->request->get('_token'))) {
158
            $backupCodeManager->regenerateBackupCodes($user);
159
            $entityManager->flush();
160
            $this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated');
161
        } else {
162
            $this->addFlash('error', 'csfr_invalid');
163
        }
164
165
        return $this->redirect($request->request->get('_redirect'));
166
    }
167
168
    /**
169
     * @Route("/invalidate_trustedDevices", name="tfa_trustedDevices_invalidate", methods={"DELETE"})
170
     *
171
     * RedirectResponse
172
     */
173
    public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager): \Symfony\Component\HttpFoundation\RedirectResponse
174
    {
175
        $user = $this->getUser();
176
177
        //When user change its settings, he should be logged  in fully.
178
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
179
180
        if (!$user instanceof User) {
181
            throw new RuntimeException('This controller only works only for Part-DB User objects!');
182
        }
183
184
        if ($this->isCsrfTokenValid('devices_reset'.$user->getId(), $request->request->get('_token'))) {
185
            $user->invalidateTrustedDevices();
186
            $entityManager->flush();
187
            $this->addFlash('success', 'tfa_trustedDevice.invalidate.success');
188
        } else {
189
            $this->addFlash('error', 'csfr_invalid');
190
        }
191
192
        return $this->redirect($request->request->get('_redirect'));
193
    }
194
}
195