Issues (257)

src/Controller/SecurityController.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published
9
 * by the Free Software Foundation, either version 3 of the License, or
10
 * (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 Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
declare(strict_types=1);
22
23
namespace App\Controller;
24
25
use App\Entity\UserSystem\User;
26
use App\Events\SecurityEvent;
27
use App\Events\SecurityEvents;
28
use App\Services\UserSystem\PasswordResetManager;
29
use Doctrine\ORM\EntityManagerInterface;
30
use Gregwar\CaptchaBundle\Type\CaptchaType;
31
use RuntimeException;
32
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
33
use Symfony\Component\EventDispatcher\EventDispatcher;
34
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
35
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
36
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
37
use Symfony\Component\Form\Extension\Core\Type\TextType;
38
use Symfony\Component\HttpFoundation\RedirectResponse;
39
use Symfony\Component\HttpFoundation\Request;
40
use Symfony\Component\HttpFoundation\Response;
41
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
42
use Symfony\Component\Routing\Annotation\Route;
43
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
44
use Symfony\Component\Validator\Constraints\Length;
45
use Symfony\Component\Validator\Constraints\NotBlank;
46
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
47
use Symfony\Contracts\Translation\TranslatorInterface;
48
49
class SecurityController extends AbstractController
50
{
51
    protected TranslatorInterface $translator;
52
    protected bool $allow_email_pw_reset;
53
54
    public function __construct(TranslatorInterface $translator, bool $allow_email_pw_reset)
55
    {
56
        $this->translator = $translator;
57
        $this->allow_email_pw_reset = $allow_email_pw_reset;
58
    }
59
60
    /**
61
     * @Route("/login", name="login", methods={"GET", "POST"})
62
     */
63
    public function login(AuthenticationUtils $authenticationUtils): Response
64
    {
65
        // get the login error if there is one
66
        $error = $authenticationUtils->getLastAuthenticationError();
67
68
        // last username entered by the user
69
        $lastUsername = $authenticationUtils->getLastUsername();
70
71
        return $this->render('security/login.html.twig', [
72
            'last_username' => $lastUsername,
73
            'error' => $error,
74
        ]);
75
    }
76
77
    /**
78
     * @Route("/pw_reset/request", name="pw_reset_request")
79
     *
80
     * @return RedirectResponse|Response
81
     */
82
    public function requestPwReset(PasswordResetManager $passwordReset, Request $request)
83
    {
84
        if (!$this->allow_email_pw_reset) {
85
            throw new AccessDeniedHttpException('The password reset via email is disabled!');
86
        }
87
88
        if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
89
            throw new AccessDeniedHttpException('You are already logged in, so you can not reset your password!');
90
        }
91
92
        $builder = $this->createFormBuilder();
93
        $builder->add('user', TextType::class, [
94
            'label' => $this->translator->trans('pw_reset.user_or_email'),
95
            'constraints' => [new NotBlank()],
96
        ]);
97
        $builder->add('captcha', CaptchaType::class, [
98
            'width' => 200,
99
            'height' => 50,
100
            'length' => 6,
101
        ]);
102
        $builder->add('submit', SubmitType::class, [
103
            'label' => 'pw_reset.submit',
104
        ]);
105
106
        $form = $builder->getForm();
107
        $form->handleRequest($request);
108
109
        if ($form->isSubmitted() && $form->isValid()) {
110
            $passwordReset->request($form->getData()['user']);
111
            $this->addFlash('success', 'pw_reset.request.success');
112
113
            return $this->redirectToRoute('login');
114
        }
115
116
        return $this->renderForm('security/pw_reset_request.html.twig', [
117
            'form' => $form,
118
        ]);
119
    }
120
121
    /**
122
     * @Route("/pw_reset/new_pw/{user}/{token}", name="pw_reset_new_pw")
123
     *
124
     * @return RedirectResponse|Response
125
     */
126
    public function pwResetNewPw(PasswordResetManager $passwordReset, Request $request, EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, ?string $user = null, ?string $token = null)
127
    {
128
        if (!$this->allow_email_pw_reset) {
129
            throw new AccessDeniedHttpException('The password reset via email is disabled!');
130
        }
131
132
        if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
133
            throw new AccessDeniedHttpException('You are already logged in, so you can not reset your password!');
134
        }
135
136
        $data = [
137
            'username' => $user,
138
            'token' => $token,
139
        ];
140
        $builder = $this->createFormBuilder($data);
141
        $builder->add('username', TextType::class, [
142
            'label' => $this->translator->trans('pw_reset.username'),
143
            'disabled' => true,
144
        ]);
145
        $builder->add('token', TextType::class, [
146
            'label' => $this->translator->trans('pw_reset.token'),
147
            'disabled' => true,
148
        ]);
149
        $builder->add('new_password', RepeatedType::class, [
150
            'type' => PasswordType::class,
151
            'first_options' => [
152
                'label' => 'user.settings.pw_new.label',
153
            ],
154
            'second_options' => [
155
                'label' => 'user.settings.pw_confirm.label',
156
            ],
157
            'invalid_message' => 'password_must_match',
158
            'constraints' => [new Length([
159
                'min' => 6,
160
                'max' => 128,
161
            ])],
162
        ]);
163
164
        $builder->add('submit', SubmitType::class, [
165
            'label' => 'pw_reset.submit',
166
        ]);
167
168
        $form = $builder->getForm();
169
        $form->handleRequest($request);
170
171
        if ($form->isSubmitted() && $form->isValid()) {
172
            $data = $form->getData();
173
            //Try to set the new password
174
            $success = $passwordReset->setNewPassword($data['username'], $data['token'], $data['new_password']);
175
            if (!$success) {
176
                $this->addFlash('error', 'pw_reset.new_pw.error');
177
            } else {
178
                $this->addFlash('success', 'pw_reset.new_pw.success');
179
180
                $repo = $em->getRepository(User::class);
181
                $u = $repo->findOneBy(['name' => $data['username']]);
182
                $event = new SecurityEvent($u);
0 ignored issues
show
It seems like $u can also be of type null; however, parameter $targetUser of App\Events\SecurityEvent::__construct() does only seem to accept App\Entity\UserSystem\User, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

182
                $event = new SecurityEvent(/** @scrutinizer ignore-type */ $u);
Loading history...
183
                /** @var EventDispatcher $eventDispatcher */
184
                $eventDispatcher->dispatch($event, SecurityEvents::PASSWORD_RESET);
185
186
                return $this->redirectToRoute('login');
187
            }
188
        }
189
190
        return $this->renderForm('security/pw_reset_new_pw.html.twig', [
191
            'form' => $form,
192
        ]);
193
    }
194
195
    /**
196
     * @Route("/logout", name="logout")
197
     */
198
    public function logout(): void
199
    {
200
        throw new RuntimeException('Will be intercepted before getting here');
201
    }
202
}
203