Completed
Push — 1.0 ( 4b07bc...bb4e0d )
by David
06:01
created

ForgotYourPasswordController::resetAccess()   C

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\ForgotYourPasswordDao;
12
use Mouf\Security\Password\Api\PasswordStrengthCheck;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Mouf\Security\Password\PasswordStrengthCheck.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use Mouf\Security\UserService\UserService;
14
use Psr\Http\Message\ResponseInterface;
15
use Psr\Http\Message\ServerRequestInterface;
16
use Psr\Log\LoggerInterface;
17
use Twig_Environment;
18
use Mouf\Security\Password\Api\EmailNotFoundException;
19
use Zend\Diactoros\Response\JsonResponse;
20
use Zend\Diactoros\Response\RedirectResponse;
21
22
class ForgotYourPasswordController
23
{
24
    private $baseUrl = 'forgot';
25
26
    /**
27
     * The logger used by this controller.
28
     *
29
     * @var LoggerInterface
30
     */
31
    private $logger;
32
33
    /**
34
     * The template used by this controller.
35
     *
36
     * @var TemplateInterface
37
     */
38
    private $template;
39
40
    /**
41
     * The main content block of the page.
42
     *
43
     * @var HtmlBlock
44
     */
45
    private $content;
46
47
    /**
48
     * The Twig environment (used to render Twig templates).
49
     *
50
     * @var Twig_Environment
51
     */
52
    private $twig;
53
54
    /**
55
     * The service that will actually check the tokens.
56
     *
57
     * @var ForgotYourPasswordDao
58
     */
59
    private $forgotYourPasswordService;
60
61
    /**
62
     * The userservice to log people when a token is found.
63
     *
64
     * @var UserService
65
     */
66
    private $userService;
67
68
    /**
69
     * 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.
70
     * 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.
71
     *
72
     * At the same time, it decreases usability.
73
     *
74
     * @var bool
75
     */
76
    private $noLeak = false;
77
78
    /**
79
     * @var PasswordStrengthCheck
80
     */
81
    private $passwordStrengthCheck;
82
83
    /**
84
     * The URL to continue to when password has been changed.
85
     * Relative to the root path of the application.
86
     * Defaults to '/'.
87
     *
88
     * @var string
89
     */
90
    private $continueUrl = '/';
91
92
    /**
93
     * Controller's constructor.
94
     *
95
     * @param LoggerInterface   $logger   The logger
96
     * @param TemplateInterface $template The template used by this controller
97
     * @param HtmlBlock         $content  The main content block of the page
98
     * @param Twig_Environment  $twig     The Twig environment (used to render Twig templates)
99
     */
100
    public function __construct(LoggerInterface $logger, TemplateInterface $template, HtmlBlock $content, Twig_Environment $twig, ForgotYourPasswordService $forgotYourPasswordService, UserService $userService, PasswordStrengthCheck $passwordStrengthCheck)
101
    {
102
        $this->logger = $logger;
103
        $this->template = $template;
104
        $this->content = $content;
105
        $this->twig = $twig;
106
        $this->forgotYourPasswordService = $forgotYourPasswordService;
0 ignored issues
show
Documentation Bug introduced by
It seems like $forgotYourPasswordService of type object<Mouf\Security\Pas...gotYourPasswordService> is incompatible with the declared type object<Mouf\Security\Pas...\ForgotYourPasswordDao> of property $forgotYourPasswordService.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
107
        $this->userService = $userService;
108
        $this->passwordStrengthCheck = $passwordStrengthCheck;
109
    }
110
111
    /**
112
     * 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.
113
     * 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.
114
     *
115
     * At the same time, it decreases usability.
116
     *
117
     * @param bool $noLeak
118
     */
119
    public function setNoLeak(bool $noLeak)
120
    {
121
        $this->noLeak = $noLeak;
122
    }
123
124
    /**
125
     * The URL to continue to when password has been changed.
126
     * Relative to the root path of the application.
127
     * Defaults to '/'.
128
     *
129
     * @param string $continueUrl
130
     */
131
    public function setContinueUrl(string $continueUrl)
132
    {
133
        $this->continueUrl = $continueUrl;
134
    }
135
136
    /**
137
     * Displays the screen to enter the email.
138
     *
139
     * @URL("{$this->baseUrl}/password")
140
     * @Get
141
     *
142
     * @param string|null $email
143
     *
144
     * @return ResponseInterface
145
     */
146
    public function index(string $email = null) : ResponseInterface
147
    {
148
        return $this->displayMainForm($email);
149
    }
150
151
    private function displayMainForm(string $email = null, bool $notFoundMessage = false) : ResponseInterface
152
    {
153
        $view = new ForgotYourPasswordView();
154
        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...
155
            $view->setEmail($email);
156
        }
157
158
        $view->setDisplayEmailNotFound($notFoundMessage);
159
160
        $code = $notFoundMessage ? 404 : 200;
161
162
        // Let's add the twig file to the template.
163
        $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...
164
165
        return new HtmlResponse($this->template);
166
    }
167
168
    private function jsonNotFoundResponse() : ResponseInterface
169
    {
170
        return new JsonResponse([
171
            'error' => 'Email not found',
172
        ], 404);
173
    }
174
175
    /**
176
     * Displays the screen to enter the email.
177
     *
178
     * @URL("{$this->baseUrl}/password")
179
     * @Post
180
     *
181
     * @param string $email
182
     *
183
     * @return ResponseInterface
184
     */
185
    public function submit(string $email, ServerRequestInterface $request) : ResponseInterface
186
    {
187
        try {
188
            // Let's get the URL of the reset password
189
            $currentUrl = $request->getUri()->getPath();
190
            $url = substr($currentUrl, 0, strrpos($currentUrl, '/password')).'/reset';
191
192
            $this->forgotYourPasswordService->sendMail($email, $request->getUri()->withPath($url)->withQuery(''));
0 ignored issues
show
Bug introduced by
The method sendMail() does not seem to exist on object<Mouf\Security\Pas...\ForgotYourPasswordDao>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
193
        } catch (EmailNotFoundException $exception) {
194
            if (!$this->noLeak) {
195
                if ($this->isJson($request)) {
196
                    return $this->jsonNotFoundResponse();
197
                } else {
198
                    return $this->displayMainForm($email, true);
199
                }
200
            }
201
        }
202
203
        if ($this->isJson($request)) {
204
            return $this->jsonSentResponse();
205
        } else {
206
            return new RedirectResponse(ROOT_URL.$this->baseUrl.'/mail_sent?email='.urlencode($email));
207
        }
208
    }
209
210
    private function isJson(ServerRequestInterface $request) : bool
211
    {
212
        return strpos($request->getHeaderLine('Accept'), 'json') !== false;
213
    }
214
215
    private function jsonSentResponse() : ResponseInterface
216
    {
217
        return new JsonResponse([
218
            'status' => 'ok',
219
            'message' => 'An email was sent to your mailbox.',
220
        ]);
221
    }
222
223
    /**
224
     * @URL("{$this->baseUrl}/mail_sent")
225
     * @Get()
226
     *
227
     * @param string $email
228
     *
229
     * @return ResponseInterface
230
     */
231
    public function displayMailSentScreen(string $email) : ResponseInterface
232
    {
233
        $this->content->addHtmlElement(new EmailSentView($email));
234
235
        return new HtmlResponse($this->template);
236
    }
237
238
    /**
239
     * @URL("{$this->baseUrl}/reset")
240
     * @Get()
241
     *
242
     * @param string $token
243
     *
244
     * @return ResponseInterface
245
     */
246
    public function displayResetAccessPage(string $token) : ResponseInterface
247
    {
248
        $isValid = $this->forgotYourPasswordService->checkToken($token);
0 ignored issues
show
Bug introduced by
The method checkToken() does not seem to exist on object<Mouf\Security\Pas...\ForgotYourPasswordDao>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
249
250
        if (!$isValid) {
251
            $this->content->addHtmlElement(new TokenNotFoundView($token));
252
253
            return new HtmlResponse($this->template, 404);
254
        } else {
255
            $this->content->addHtmlElement(new ResetPasswordView($token, $this->passwordStrengthCheck->getPasswordRules()));
256
257
            return new HtmlResponse($this->template);
258
        }
259
    }
260
261
    /**
262
     * @URL("{$this->baseUrl}/reset")
263
     * @Post()
264
     *
265
     * @param string                 $token
266
     * @param ServerRequestInterface $request
267
     *
268
     * @return ResponseInterface
269
     */
270
    public function resetAccess(string $token, string $password, string $confirmpassword, ServerRequestInterface $request) : ResponseInterface
271
    {
272
        $isValidToken = $this->forgotYourPasswordService->checkToken($token);
0 ignored issues
show
Bug introduced by
The method checkToken() does not seem to exist on object<Mouf\Security\Pas...\ForgotYourPasswordDao>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
273
274
        if (!$isValidToken) {
275
            if ($this->isJson($request)) {
276
                return $this->jsonTokenNotFoundResponse();
277
            } else {
278
                $this->content->addHtmlElement(new TokenNotFoundView($token));
279
280
                return new HtmlResponse($this->template, 404);
281
            }
282
        }
283
284 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...
285
            if ($this->isJson($request)) {
286
                return new JsonResponse([
287
                    'error' => 'Mismatching passwords',
288
                ], 400);
289
            } else {
290
                $view = new ResetPasswordView($token, $this->passwordStrengthCheck->getPasswordRules());
291
                $this->content->addHtmlElement($view->withDisplayMismatchPassword());
292
293
                return new HtmlResponse($this->template, 400);
294
            }
295
        }
296
297
        if ($this->passwordStrengthCheck->checkPasswordStrength($password) === false) {
298 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...
299
                return new JsonResponse([
300
                    'error' => 'Password strength too weak',
301
                    'rules' => $this->passwordStrengthCheck->getPasswordRules(),
302
                ], 400);
303
            } else {
304
                $view = new ResetPasswordView($token, $this->passwordStrengthCheck->getPasswordRules());
305
                $this->content->addHtmlElement($view->withDisplayPoorPassword());
306
307
                return new HtmlResponse($this->template, 400);
308
            }
309
        }
310
311
        $this->forgotYourPasswordService->useToken($token, $password);
0 ignored issues
show
Bug introduced by
The method useToken() does not seem to exist on object<Mouf\Security\Pas...\ForgotYourPasswordDao>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
312
313
        if ($this->isJson($request)) {
314
            return new JsonResponse([
315
                'status' => 'ok',
316
            ], 200);
317
        } else {
318
            return new RedirectResponse('reset_confirm');
319
        }
320
    }
321
322
    /**
323
     * @URL("{$this->baseUrl}/reset_confirm")
324
     * @Get()
325
     *
326
     * @param ServerRequestInterface $request
327
     *
328
     * @return ResponseInterface
329
     */
330
    public function confirmResetAccess(ServerRequestInterface $request) : ResponseInterface
331
    {
332
        $path = $request->getUri()->getPath();
333
        $path = substr($path, 0, strrpos($path, $this->baseUrl.'/reset'));
334
        $path .= ltrim($this->continueUrl, '/');
335
336
        $view = new ConfirmResetPasswordView($path);
337
        $this->content->addHtmlElement($view);
338
339
        return new HtmlResponse($this->template);
340
    }
341
342
    protected function jsonTokenNotFoundResponse() : ResponseInterface
343
    {
344
        return new JsonResponse([
345
            'error' => 'Token not found',
346
        ], 404);
347
    }
348
}
349