ForgotYourPasswordController::resetAccess()   C
last analyzed

Complexity

Conditions 8
Paths 8

Size

Total Lines 51
Code Lines 34

Duplication

Lines 23
Ratio 45.1 %

Importance

Changes 0
Metric Value
dl 23
loc 51
c 0
b 0
f 0
rs 6.5978
cc 8
eloc 34
nc 8
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Mouf\Security\Password;
4
5
use Mouf\Html\HtmlElement\HtmlBlock;
6
use Mouf\Html\Template\TemplateInterface;
7
use Mouf\Mvc\Splash\Annotations\Get;
8
use Mouf\Mvc\Splash\Annotations\Post;
9
use Mouf\Mvc\Splash\Annotations\URL;
10
use Mouf\Mvc\Splash\HtmlResponse;
11
use Mouf\Security\Password\Api\PasswordStrengthCheck as PasswordStrengthCheckInterface;
12
use Mouf\Security\UserService\UserService;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Psr\Log\LoggerInterface;
16
use Twig_Environment;
17
use Mouf\Security\Password\Api\EmailNotFoundException;
18
use Zend\Diactoros\Response\JsonResponse;
19
use Zend\Diactoros\Response\RedirectResponse;
20
21
class ForgotYourPasswordController
22
{
23
    private $baseUrl = 'forgot';
24
25
    /**
26
     * The logger used by this controller.
27
     *
28
     * @var LoggerInterface
29
     */
30
    private $logger;
31
32
    /**
33
     * The template used by this controller.
34
     *
35
     * @var TemplateInterface
36
     */
37
    private $template;
38
39
    /**
40
     * The main content block of the page.
41
     *
42
     * @var HtmlBlock
43
     */
44
    private $content;
45
46
    /**
47
     * The Twig environment (used to render Twig templates).
48
     *
49
     * @var Twig_Environment
50
     */
51
    private $twig;
52
53
    /**
54
     * The service that will actually check the tokens.
55
     *
56
     * @var ForgotYourPasswordService
57
     */
58
    private $forgotYourPasswordService;
59
60
    /**
61
     * The userservice to log people when a token is found.
62
     *
63
     * @var UserService
64
     */
65
    private $userService;
66
67
    /**
68
     * Enable this option if you want the "forgot your password screen" to NOT show the "email not found message" and always show the "an email has been sent" message.
69
     * This improves security because an attacker typing an email address of someone else cannot not if this user is registered or not in the application.
70
     *
71
     * At the same time, it decreases usability.
72
     *
73
     * @var bool
74
     */
75
    private $noLeak = false;
76
77
    /**
78
     * @var PasswordStrengthCheckInterface
79
     */
80
    private $passwordStrengthCheck;
81
82
    /**
83
     * The URL to continue to when password has been changed.
84
     * Relative to the root path of the application.
85
     * Defaults to '/'.
86
     *
87
     * @var string
88
     */
89
    private $continueUrl = '/';
90
91
    /**
92
     * Controller's constructor.
93
     *
94
     * @param LoggerInterface   $logger   The logger
95
     * @param TemplateInterface $template The template used by this controller
96
     * @param HtmlBlock         $content  The main content block of the page
97
     * @param Twig_Environment  $twig     The Twig environment (used to render Twig templates)
98
     */
99
    public function __construct(LoggerInterface $logger, TemplateInterface $template, HtmlBlock $content, Twig_Environment $twig, ForgotYourPasswordService $forgotYourPasswordService, UserService $userService, PasswordStrengthCheckInterface $passwordStrengthCheck)
100
    {
101
        $this->logger = $logger;
102
        $this->template = $template;
103
        $this->content = $content;
104
        $this->twig = $twig;
105
        $this->forgotYourPasswordService = $forgotYourPasswordService;
106
        $this->userService = $userService;
107
        $this->passwordStrengthCheck = $passwordStrengthCheck;
108
    }
109
110
    /**
111
     * Enable this option if you want the "forgot your password screen" to NOT show the "email not found message" and always show the "an email has been sent" message.
112
     * This improves security because an attacker typing an email address of someone else cannot not if this user is registered or not in the application.
113
     *
114
     * At the same time, it decreases usability.
115
     *
116
     * @param bool $noLeak
117
     */
118
    public function setNoLeak(bool $noLeak)
119
    {
120
        $this->noLeak = $noLeak;
121
    }
122
123
    /**
124
     * The URL to continue to when password has been changed.
125
     * Relative to the root path of the application.
126
     * Defaults to '/'.
127
     *
128
     * @param string $continueUrl
129
     */
130
    public function setContinueUrl(string $continueUrl)
131
    {
132
        $this->continueUrl = $continueUrl;
133
    }
134
135
    /**
136
     * Displays the screen to enter the email.
137
     *
138
     * @URL("{$this->baseUrl}/password")
139
     * @Get
140
     *
141
     * @param string|null $email
142
     *
143
     * @return ResponseInterface
144
     */
145
    public function index(string $email = null) : ResponseInterface
146
    {
147
        return $this->displayMainForm($email);
148
    }
149
150
    private function displayMainForm(string $email = null, bool $notFoundMessage = false) : ResponseInterface
151
    {
152
        $view = new ForgotYourPasswordView();
153
        if ($email) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $email of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
154
            $view->setEmail($email);
155
        }
156
157
        $view->setDisplayEmailNotFound($notFoundMessage);
158
159
        $code = $notFoundMessage ? 404 : 200;
160
161
        // Let's add the twig file to the template.
162
        $this->content->addHtmlElement($view, $code);
0 ignored issues
show
Unused Code introduced by
The call to HtmlBlock::addHtmlElement() has too many arguments starting with $code.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
163
164
        return new HtmlResponse($this->template);
165
    }
166
167
    private function jsonNotFoundResponse() : ResponseInterface
168
    {
169
        return new JsonResponse([
170
            'error' => 'Email not found',
171
        ], 404);
172
    }
173
174
    /**
175
     * Displays the screen to enter the email.
176
     *
177
     * @URL("{$this->baseUrl}/password")
178
     * @Post
179
     *
180
     * @param string $email
181
     *
182
     * @return ResponseInterface
183
     */
184
    public function submit(string $email, ServerRequestInterface $request) : ResponseInterface
185
    {
186
        try {
187
            // Let's get the URL of the reset password
188
            $currentUrl = $request->getUri()->getPath();
189
            $url = substr($currentUrl, 0, strrpos($currentUrl, '/password')).'/reset';
190
191
            $this->forgotYourPasswordService->sendMail($email, $request->getUri()->withPath($url)->withQuery(''));
192
        } catch (EmailNotFoundException $exception) {
193
            if (!$this->noLeak) {
194
                if ($this->isJson($request)) {
195
                    return $this->jsonNotFoundResponse();
196
                } else {
197
                    return $this->displayMainForm($email, true);
198
                }
199
            }
200
        }
201
202
        if ($this->isJson($request)) {
203
            return $this->jsonSentResponse();
204
        } else {
205
            return new RedirectResponse(ROOT_URL.$this->baseUrl.'/mail_sent?email='.urlencode($email));
206
        }
207
    }
208
209
    private function isJson(ServerRequestInterface $request) : bool
210
    {
211
        return stripos($request->getHeaderLine('Content-Type'), "application/json") === 0;
212
    }
213
214
    private function jsonSentResponse() : ResponseInterface
215
    {
216
        return new JsonResponse([
217
            'status' => 'ok',
218
            'message' => 'An email was sent to your mailbox.',
219
        ]);
220
    }
221
222
    /**
223
     * @URL("{$this->baseUrl}/mail_sent")
224
     * @Get()
225
     *
226
     * @param string $email
227
     *
228
     * @return ResponseInterface
229
     */
230
    public function displayMailSentScreen(string $email) : ResponseInterface
231
    {
232
        $this->content->addHtmlElement(new EmailSentView($email));
233
234
        return new HtmlResponse($this->template);
235
    }
236
237
    /**
238
     * @URL("{$this->baseUrl}/reset")
239
     * @Get()
240
     *
241
     * @param string $token
242
     *
243
     * @return ResponseInterface
244
     */
245
    public function displayResetAccessPage(string $token) : ResponseInterface
246
    {
247
        $isValid = $this->forgotYourPasswordService->checkToken($token);
248
249
        if (!$isValid) {
250
            $this->content->addHtmlElement(new TokenNotFoundView($token));
251
252
            return new HtmlResponse($this->template, 404);
253
        } else {
254
            $this->content->addHtmlElement(new ResetPasswordView($token, $this->passwordStrengthCheck->getPasswordRules()));
255
256
            return new HtmlResponse($this->template);
257
        }
258
    }
259
260
    /**
261
     * @URL("{$this->baseUrl}/reset")
262
     * @Post()
263
     *
264
     * @param string                 $token
265
     * @param ServerRequestInterface $request
266
     *
267
     * @return ResponseInterface
268
     */
269
    public function resetAccess(string $token, string $password, string $confirmpassword, ServerRequestInterface $request) : ResponseInterface
270
    {
271
        $isValidToken = $this->forgotYourPasswordService->checkToken($token);
272
273
        if (!$isValidToken) {
274
            if ($this->isJson($request)) {
275
                return $this->jsonTokenNotFoundResponse();
276
            } else {
277
                $this->content->addHtmlElement(new TokenNotFoundView($token));
278
279
                return new HtmlResponse($this->template, 404);
280
            }
281
        }
282
283 View Code Duplication
        if ($password !== $confirmpassword) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
284
            if ($this->isJson($request)) {
285
                return new JsonResponse([
286
                    'error' => 'Mismatching passwords',
287
                ], 400);
288
            } else {
289
                $view = new ResetPasswordView($token, $this->passwordStrengthCheck->getPasswordRules());
290
                $this->content->addHtmlElement($view->withDisplayMismatchPassword());
291
292
                return new HtmlResponse($this->template, 400);
293
            }
294
        }
295
296
        if ($this->passwordStrengthCheck->checkPasswordStrength($password) === false) {
297 View Code Duplication
            if ($this->isJson($request)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
                return new JsonResponse([
299
                    'error' => 'Password strength too weak',
300
                    'rules' => $this->passwordStrengthCheck->getPasswordRules(),
301
                ], 400);
302
            } else {
303
                $view = new ResetPasswordView($token, $this->passwordStrengthCheck->getPasswordRules());
304
                $this->content->addHtmlElement($view->withDisplayPoorPassword());
305
306
                return new HtmlResponse($this->template, 400);
307
            }
308
        }
309
310
        $this->forgotYourPasswordService->useToken($token, $password);
311
312
        if ($this->isJson($request)) {
313
            return new JsonResponse([
314
                'status' => 'ok',
315
            ], 200);
316
        } else {
317
            return new RedirectResponse('reset_confirm');
318
        }
319
    }
320
321
    /**
322
     * @URL("{$this->baseUrl}/reset_confirm")
323
     * @Get()
324
     *
325
     * @param ServerRequestInterface $request
326
     *
327
     * @return ResponseInterface
328
     */
329
    public function confirmResetAccess(ServerRequestInterface $request) : ResponseInterface
330
    {
331
        $path = $request->getUri()->getPath();
332
        $path = substr($path, 0, strrpos($path, $this->baseUrl.'/reset'));
333
        $path .= ltrim($this->continueUrl, '/');
334
335
        $view = new ConfirmResetPasswordView($path);
336
        $this->content->addHtmlElement($view);
337
338
        return new HtmlResponse($this->template);
339
    }
340
341
    protected function jsonTokenNotFoundResponse() : ResponseInterface
342
    {
343
        return new JsonResponse([
344
            'error' => 'Token not found',
345
        ], 404);
346
    }
347
}
348