Completed
Pull Request — master (#7007)
by Simon
08:19
created

ChangePasswordHandler::doChangePassword()   C

Complexity

Conditions 12
Paths 24

Size

Total Lines 96
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 48
nc 24
nop 2
dl 0
loc 96
rs 5.034
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
4
namespace SilverStripe\Security\MemberAuthenticator;
5
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\HTTPResponse;
8
use SilverStripe\Control\RequestHandler;
9
use SilverStripe\Control\Session;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\ORM\FieldType\DBDatetime;
12
use SilverStripe\ORM\FieldType\DBField;
13
use SilverStripe\Security\Authenticator;
14
use SilverStripe\Security\IdentityStore;
15
use SilverStripe\Security\Member;
16
use SilverStripe\Security\Security;
17
18
class ChangePasswordHandler extends RequestHandler
19
{
20
    /**
21
     * @var Authenticator
22
     */
23
    protected $authenticator;
24
25
    /**
26
     * @var string
27
     */
28
    protected $link;
29
30
    /**
31
     * @var array Allowed Actions
32
     */
33
    private static $allowed_actions = [
34
        'changepassword',
35
        'changePasswordForm',
36
    ];
37
38
    /**
39
     * @var array URL Handlers. All should point to changepassword
40
     */
41
    private static $url_handlers = [
42
        '' => 'changepassword',
43
    ];
44
45
    /**
46
     * @param string $link The URL to recreate this request handler
47
     * @param MemberAuthenticator $authenticator
48
     */
49
    public function __construct($link, MemberAuthenticator $authenticator)
50
    {
51
        $this->link = $link;
52
        $this->authenticator = $authenticator;
53
        parent::__construct();
54
    }
55
56
    /**
57
     * Handle the change password request
58
     * @todo this could use some spring cleaning
59
     *
60
     * @return array|HTTPResponse
61
     */
62
    public function changepassword()
63
    {
64
        $request = $this->getRequest();
65
66
        // Extract the member from the URL.
67
        /** @var Member $member */
68
        $member = null;
69
        if ($request->getVar('m') !== null) {
70
            $member = Member::get()->filter(['ID' => (int)$request->getVar('m')])->first();
71
        }
72
        $token = $request->getVar('t');
73
74
        // Check whether we are merely changin password, or resetting.
75
        if ($token !== null && $member && $member->validateAutoLoginToken($token)) {
76
            $this->setSessionToken($member, $token);
0 ignored issues
show
Compatibility introduced by
$member of type object<SilverStripe\ORM\DataObject> is not a sub-type of object<SilverStripe\Security\Member>. It seems like you assume a child class of the class SilverStripe\ORM\DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
77
78
            // Redirect to myself, but without the hash in the URL
79
            return $this->redirect($this->link);
80
        }
81
82
        if (Session::get('AutoLoginHash')) {
83
            $message = DBField::create_field(
84
                'HTMLFragment',
85
                '<p>' . _t(
86
                    'SilverStripe\\Security\\Security.ENTERNEWPASSWORD',
87
                    'Please enter a new password.'
88
                ) . '</p>'
89
            );
90
91
            // Subsequent request after the "first load with hash" (see previous if clause).
92
            return [
93
                'Content' => $message,
94
                'Form'    => $this->changePasswordForm()
95
            ];
96
        }
97
98
        if (Security::getCurrentUser()) {
99
            // Logged in user requested a password change form.
100
            $message = DBField::create_field(
101
                'HTMLFragment',
102
                '<p>' . _t(
103
                    'SilverStripe\\Security\\Security.CHANGEPASSWORDBELOW',
104
                    'You can change your password below.'
105
                ) . '</p>'
106
            );
107
108
            return [
109
                'Content' => $message,
110
                'Form'    => $this->changePasswordForm()
111
            ];
112
        }
113
        // Show a friendly message saying the login token has expired
114
        if ($token !== null && $member && !$member->validateAutoLoginToken($token)) {
115
            $message = [
116
                'Content' => DBField::create_field(
117
                    'HTMLFragment',
118
                    _t(
119
                        'SilverStripe\\Security\\Security.NOTERESETLINKINVALID',
120
                        '<p>The password reset link is invalid or expired.</p>'
121
                        . '<p>You can request a new one <a href="{link1}">here</a> or change your password after'
122
                        . ' you <a href="{link2}">logged in</a>.</p>',
123
                        [
124
                            'link1' => $this->link('lostpassword'),
125
                            'link2' => $this->link('login')
126
                        ]
127
                    )
128
                )
129
            ];
130
131
            return [
132
                'Content' => $message,
133
            ];
134
        }
135
136
        // Someone attempted to go to changepassword without token or being logged in
137
        return Security::permissionFailure(
138
            Controller::curr(),
139
            _t(
140
                'SilverStripe\\Security\\Security.ERRORPASSWORDPERMISSION',
141
                'You must be logged in in order to change your password!'
142
            )
143
        );
144
    }
145
146
147
    /**
148
     * @param Member $member
149
     * @param string $token
150
     */
151
    protected function setSessionToken($member, $token)
152
    {
153
        // if there is a current member, they should be logged out
154
        if ($curMember = Security::getCurrentUser()) {
155
            /** @var LogoutHandler $handler */
156
            Injector::inst()->get(IdentityStore::class)->logOut();
157
        }
158
159
        // Store the hash for the change password form. Will be unset after reload within the ChangePasswordForm.
160
        Session::set('AutoLoginHash', $member->encryptWithUserSettings($token));
161
    }
162
163
    /**
164
     * Return a link to this request handler.
165
     * The link returned is supplied in the constructor
166
     * @param null $action
167
     * @return string
168
     */
169
    public function link($action = null)
170
    {
171
        if ($action) {
172
            return Controller::join_links($this->link, $action);
173
        }
174
175
        return $this->link;
176
    }
177
178
    /**
179
     * Factory method for the lost password form
180
     *
181
     * @skipUpgrade
182
     * @return ChangePasswordForm Returns the lost password form
183
     */
184
    public function changePasswordForm()
185
    {
186
        return ChangePasswordForm::create(
187
            $this,
188
            'ChangePasswordForm'
189
        );
190
    }
191
192
    /**
193
     * Change the password
194
     *
195
     * @param array $data The user submitted data
196
     * @param ChangePasswordForm $form
197
     * @return HTTPResponse
198
     */
199
    public function doChangePassword(array $data, $form)
200
    {
201
        $member = Security::getCurrentUser();
202
        // The user was logged in, check the current password
203
        if ($member && (
204
                empty($data['OldPassword']) ||
205
                !$member->checkPassword($data['OldPassword'])->isValid()
206
            )
207
        ) {
208
            $form->sessionMessage(
209
                _t(
210
                    'SilverStripe\\Security\\Member.ERRORPASSWORDNOTMATCH',
211
                    'Your current password does not match, please try again'
212
                ),
213
                'bad'
214
            );
215
216
            // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
217
            return $this->redirectBackToForm();
218
        }
219
220
        if (!$member) {
221
            if (Session::get('AutoLoginHash')) {
222
                $member = Member::member_from_autologinhash(Session::get('AutoLoginHash'));
223
            }
224
225
            // The user is not logged in and no valid auto login hash is available
226
            if (!$member) {
227
                Session::clear('AutoLoginHash');
228
229
                return $this->redirect($this->addBackURLParam(Security::singleton()->Link('login')));
230
            }
231
        }
232
233
        // Check the new password
234
        if (empty($data['NewPassword1'])) {
235
            $form->sessionMessage(
236
                _t(
237
                    'SilverStripe\\Security\\Member.EMPTYNEWPASSWORD',
238
                    "The new password can't be empty, please try again"
239
                ),
240
                'bad'
241
            );
242
243
            // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
244
            return $this->redirectBackToForm();
245
        }
246
247
        // Fail if passwords do not match
248
        if ($data['NewPassword1'] !== $data['NewPassword2']) {
249
            $form->sessionMessage(
250
                _t(
251
                    'SilverStripe\\Security\\Member.ERRORNEWPASSWORD',
252
                    'You have entered your new password differently, try again'
253
                ),
254
                'bad'
255
            );
256
257
            // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
258
            return $this->redirectBackToForm();
259
        }
260
261
        // Check if the new password is accepted
262
        $validationResult = $member->changePassword($data['NewPassword1']);
263
        if (!$validationResult->isValid()) {
264
            $form->setSessionValidationResult($validationResult);
265
266
            return $this->redirectBackToForm();
267
        }
268
269
        // Clear locked out status
270
        $member->LockedOutUntil = null;
271
        $member->FailedLoginCount = null;
272
        // Clear the members login hashes
273
        $member->AutoLoginHash = null;
274
        $member->AutoLoginExpired = DBDatetime::create()->now();
275
        $member->write();
276
277
        if ($member->canLogIn()->isValid()) {
278
            Injector::inst()->get(IdentityStore::class)->logIn($member, false, $this->getRequest());
279
        }
280
281
        // TODO Add confirmation message to login redirect
282
        Session::clear('AutoLoginHash');
283
284
        // Redirect to backurl
285
        $backURL = $this->getBackURL();
286
        if ($backURL) {
287
            return $this->redirect($backURL);
288
        }
289
290
        // Redirect to default location - the login form saying "You are logged in as..."
291
        $url = Security::singleton()->Link('login');
292
293
        return $this->redirect($url);
294
    }
295
296
    /**
297
     * Something went wrong, go back to the changepassword
298
     *
299
     * @return HTTPResponse
300
     */
301
    public function redirectBackToForm()
302
    {
303
        // Redirect back to form
304
        $url = $this->addBackURLParam(Security::singleton()->Link('changepassword'));
305
306
        return $this->redirect($url);
307
    }
308
}
309