Passed
Push — master ( 4ee8fa...6eee97 )
by Tim
02:41
created

PasswordReset::validateMagicLink()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 59
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 26
nc 3
nop 1
dl 0
loc 59
rs 9.504
c 0
b 0
f 0

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 SimpleSAML\Module\ldapPasswordReset\Controller;
4
5
use Exception;
6
use SimpleSAML\Assert\Assert;
7
use SimpleSAML\{Auth, Configuration, Error, Logger, Module, Session};
8
use SimpleSAML\Module\ldapPasswordReset\MagicLink;
9
use SimpleSAML\Module\ldapPasswordReset\TokenStorage;
10
use SimpleSAML\Module\ldapPasswordReset\UserRepository;
11
use SimpleSAML\XHTML\Template;
12
use Symfony\Component\HttpFoundation\{RedirectResponse, Request};
13
14
use function sprintf;
15
16
/**
17
 * Controller class for the ldapPasswordReset module.
18
 *
19
 * This class serves the password reset code and error views available in the module.
20
 *
21
 * @package simplesamlphp/simplesamlphp-module-ldapPasswordReset
22
 */
23
class PasswordReset
24
{
25
    /** @var \SimpleSAML\Configuration */
26
    protected Configuration $config;
27
28
    /** @var \SimpleSAML\Logger */
29
    protected Logger $logger;
30
31
    /** @var \SimpleSAML\Configuration */
32
    protected Configuration $moduleConfig;
33
34
    /** @var \SimpleSAML\Session */
35
    protected Session $session;
36
37
    /** @var \SimpleSAML\Module\ldapPasswordReset\UserRepository */
38
    protected UserRepository $userRepository;
39
40
    /**
41
     * @var \SimpleSAML\Auth\State|string
42
     * @psalm-var \SimpleSAML\Auth\State|class-string
43
     */
44
    protected $authState = Auth\State::class;
45
46
47
    /**
48
     * Password reset Controller constructor.
49
     *
50
     * @param \SimpleSAML\Configuration $config The configuration to use.
51
     * @param \SimpleSAML\Session $session The current user session.
52
     */
53
    public function __construct(Configuration $config, Session $session)
54
    {
55
        $this->config = $config;
56
        $this->logger = new Logger();
57
        $this->moduleConfig = Configuration::getConfig('module_ldapPasswordReset.php');
58
        $this->session = $session;
59
        $this->userRepository = new UserRepository();
60
    }
61
62
63
    /**
64
     * Inject the \SimpleSAML\Logger dependency.
65
     *
66
     * @param \SimpleSAML\Logger $logger
67
     */
68
    public function setLogger(Logger $logger): void
69
    {
70
        $this->logger = $logger;
71
    }
72
73
74
    /**
75
     * Inject the \SimpleSAML\Auth\State dependency.
76
     *
77
     * @param \SimpleSAML\Auth\State $authState
78
     */
79
    public function setAuthState(Auth\State $authState): void
80
    {
81
        $this->authState = $authState;
82
    }
83
84
85
    /**
86
     * Display the page where the EMail address should be entered.
87
     *
88
     * @return \SimpleSAML\XHTML\Template
89
     */
90
    public function enterEmail(Request $request): Template
91
    {
92
        $t = new Template($this->config, 'ldapPasswordReset:enterEmail.twig');
93
        $t->data = [
94
            'mailSent' => false,
95
        ];
96
97
        if ($request->request->has('submit_button')) {
98
            /** @psalm-var string|null $id */
99
            $id = $request->query->get('AuthState', null);
100
            if ($id === null) {
101
                throw new Error\BadRequest('Missing AuthState parameter.');
102
            }
103
104
            $t->data['mailSent'] = true;
105
106
            /** @psalm-var string $email */
107
            $email = $request->request->get('email');
108
            Assert::stringNotEmpty($email);
109
110
            /** @var array $state */
111
            $state = $this->authState::loadState($id, 'ldapPasswordReset:request', false);
112
113
            $user = $this->userRepository->findUserByEmail($email);
114
            if ($user !== null) {
115
                $this->logger::info(sprintf('ldapPasswordReset: a password reset was requested for user %s', $email));
116
117
                $tokenStorage = new TokenStorage($this->config);
118
                $token = $tokenStorage->generateToken();
119
                $session = $this->session->getTrackID();
120
121
                $tokenStorage->storeToken($token, $email, $session, $state['ldapPasswordReset:referer'] ?? null);
122
                $this->logger::debug(sprintf('ldapPasswordReset: token %s was stored for %s', $token, $email));
123
124
                $mailer = new MagicLink($this->config);
125
                $mailer->sendMagicLink($email, $token);
126
                $this->logger::info(sprintf('ldapPasswordReset: token %s way e-mailed to user %s'));
127
            } else {
128
                $this->logger::warning(sprintf('ldapPasswordReset: a password reset was requested for non-existing user %s', $email));
129
            }
130
/**
131
        } else {
132
            $state = [];
133
134
            if ($request->server->has('HTTP_REFERER')) {
135
                $state['ldapPasswordReset:referer'] = $request->server->get('HTTP_REFERER');
136
            }
137
*/
138
        }
139
140
        $t->data['AuthState'] = $this->authState::saveState($state, 'ldapPasswordReset:request');
141
        return $t;
142
    }
143
144
145
    /**
146
     * Process a received magic link.
147
     *
148
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
149
     */
150
    public function validateMagicLink(Request $request): RedirectResponse
151
    {
152
        if (!$request->query->has('t')) {
153
            throw new Error\BadRequest('Missing token.');
154
        }
155
156
        /** @psalm-var string $t */
157
        $t = $request->query->get('t');
158
        Assert::uuid($t, 'Invalid token provided.', Error\BadRequest::class);
159
160
        $tokenStorage = new TokenStorage($this->config);
161
        $token = $tokenStorage->retrieveToken($t);
162
163
        Assert::nullOrIsArray($token);
164
165
        if ($token !== null) {
166
            Assert::keyExists($token, 'mail');
167
            Assert::keyExists($token, 'session');
168
//            Assert::keyExists($token, 'referer');
169
170
/**
171
            if (
172
                $this->moduleConfig->getOptionalBoolean('lockBrowserSession', true)
173
                && $token['session'] !== $this->session->getTrackID()
174
            ) {
175
                $this->logger::warning(sprintf(
176
                    "Token '%s' was used in a different browser session then where it was requested from.",
177
                    $t,
178
                ));
179
            } else {
180
*/
181
                $this->logger::info(sprintf(
182
                    "Preconditions for token '%s' were met. User '%s' may change it's password.",
183
                    $t,
184
                    $token['mail']
185
                ));
186
187
                // All pre-conditions met - Allow user to change password
188
                $state = [
189
                    'ldapPasswordReset:magicLinkValidated' => true,
190
                    'ldapPasswordReset:subject' => $token['mail'],
191
                    'ldapPasswordReset:session' => $token['session'],
192
//                    'ldapPasswordReset:referer' => $token['referer'],
193
                ];
194
195
                // Invalidate token - It may be used only once to reset a password
196
                $tokenStorage->deleteToken($t);
197
198
                $id = $this->authState::saveState($state, 'ldapPasswordReset:request');
199
                return new RedirectResponse(
200
                    Module::getModuleURL('ldapPasswordReset/resetPassword', ['AuthState' => $id])
201
                );
202
//            }
203
        } else {
204
            $this->logger::warning(sprintf("ldapPasswordReset: Could not find token '%s' in token storage.", $t));
205
        }
206
207
        $this->logger::debug(sprintf("ldapPasswordReset: an invalid magic link was used: %s", $t));
208
        return new RedirectResponse(Module::getModuleURL('ldapPasswordReset/invalidMagicLink'));
209
    }
210
211
212
    /**
213
     * Display an error message when an invalid magic link was used.
214
     *
215
     * @return \SimpleSAML\XHTML\Template
216
     */
217
    public function invalidMagicLink(Request $request): Template
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

217
    public function invalidMagicLink(/** @scrutinizer ignore-unused */ Request $request): Template

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
218
    {
219
        return new Template($this->config, 'ldapPasswordReset:invalidMagicLink.twig');
220
    }
221
222
223
    /**
224
     * Display the page where the user can set a new password.
225
     *
226
     * @return \SimpleSAML\XHTML\Template
227
     */
228
    public function resetPassword(Request $request): Template
229
    {
230
        /** @psalm-var string|null $id */
231
        $id = $request->query->get('AuthState', null);
232
        if ($id === null) {
233
            throw new Error\BadRequest('Missing AuthState parameter.');
234
        }
235
236
        /** @var array $state */
237
        $state = $this->authState::loadState($id, 'ldapPasswordReset:request', false);
238
239
        $t = new Template($this->config, 'ldapPasswordReset:resetPassword.twig');
240
        $t->data = [
241
            'AuthState' => $id,
242
            'passwordMismatch' => false,
243
            'emailAddress' => $state['ldapPasswordReset:subject'],
244
        ];
245
246
        // Check if the submit-button was hit, or whether this is a first visit
247
        if ($request->request->has('submit_button')) {
248
            $this->logger::debug(sprintf('ldapPasswordReset: a new password was entered for user %s', $state['ldapPasswordReset']));
249
250
            // See if the submitted passwords match
251
            /** @psalm-var string $newPassword */
252
            $newPassword = $request->request->get('new-password');
253
            /** @psalm-var string $retypePassword */
254
            $retypePassword = $request->request->get('password');
255
256
            if (strcmp($newPassword, $retypePassword) === 0) {
257
                $this->logger::debug(sprintf('ldapPasswordReset: new matching passwords were entered for user %s', $state['ldapPasswordReset']));
258
259
                $user = $this->userRepository->findUserByEmail($state['ldapPasswordReset:subject']);
260
                Assert::notNull($user); // Must exist
261
262
                $result = $this->userRepository->updatePassword($user, $newPassword);
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type null; however, parameter $user of SimpleSAML\Module\ldapPa...itory::updatePassword() does only seem to accept Symfony\Component\Ldap\Entry, 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

262
                $result = $this->userRepository->updatePassword(/** @scrutinizer ignore-type */ $user, $newPassword);
Loading history...
263
                if ($result === true) {
264
                    $this->logger::info(sprintf(
265
                        'Password was reset for user: %s',
266
                        $state['ldapPasswordReset:subject']
267
                    ));
268
269
                    $t = new Template($this->config, 'ldapPasswordReset:passwordChanged.twig');
270
/*
271
                    if (
272
                        isset($state['ldapPasswordReset:referer'])
273
                        && ($state['session'] === $this->session->getTrackID())
274
                    ) {
275
                        // If this isn't the same browser, it makes no sense to get back to the
276
                        // previous authentication-flow. It will fail relentlessly
277
                        $t->data['referer'] = $state['ldapPasswordReset:referer'];
278
                    }
279
*/
280
                    $t->data['passwordChanged'] = true;
281
                    return $t;
282
                } else {
283
                    $this->logger::warning(sprintf(
284
                        'Password reset has failed for user: %s',
285
                        $state['ldapPasswordReset:subject']
286
                    ));
287
288
                    $t->data['passwordChanged'] = false;
289
                }
290
            } else {
291
                $this->logger::debug(sprintf('ldapPasswordReset: mismatching passwords were entered for user %s', $state['ldapPasswordReset']));
292
                $t->data['passwordMismatch'] = true;
293
            }
294
        }
295
296
        return $t;
297
    }
298
}
299