Passed
Pull Request — master (#165)
by Garion
02:14
created

SecurityExtension::doResetAccount()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 62
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 32
nc 4
nop 2
dl 0
loc 62
rs 9.408
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 declare(strict_types=1);
2
3
namespace SilverStripe\MFA\Extension\AccountReset;
4
5
use SilverStripe\Control\HTTPRequest;
6
use SilverStripe\Control\HTTPResponse;
7
use SilverStripe\Core\Extensible;
8
use SilverStripe\Core\Extension;
9
use SilverStripe\Forms\FieldList;
10
use SilverStripe\Forms\Form;
11
use SilverStripe\Forms\FormAction;
12
use SilverStripe\Forms\PasswordField;
13
use SilverStripe\Forms\RequiredFields;
14
use SilverStripe\MFA\JSONResponse;
15
use SilverStripe\MFA\RequestHandler\BaseHandlerTrait;
16
use SilverStripe\ORM\FieldType\DBDatetime;
17
use SilverStripe\ORM\ValidationResult;
18
use SilverStripe\Security\Member;
19
use SilverStripe\Security\Security;
20
21
/**
22
 * Extends the Security controller to support Account Resets. This extension can
23
 * itself be extended to add procedures to the reset action (such as removing
24
 * additional authentication factors, sending alerts, etc.)
25
 *
26
 * @package SilverStripe\MFA\Extension
27
 * @property Security owner
28
 */
29
class SecurityExtension extends Extension
30
{
31
    use BaseHandlerTrait;
32
    use Extensible;
33
    use JSONResponse;
34
35
    private static $url_handlers = [
0 ignored issues
show
introduced by
The private property $url_handlers is not used, and could be removed.
Loading history...
36
        'GET reset-account' => 'resetaccount',
37
    ];
38
39
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
40
        'resetaccount',
41
        'ResetAccountForm',
42
    ];
43
44
    public function resetaccount(HTTPRequest $request): HTTPResponse
45
    {
46
        if (Security::getCurrentUser()) {
47
            return $this->jsonResponse(['error' => 'Already authenticated'], 400);
48
        }
49
50
        ['m' => $memberID, 't' => $token] = $request->getVars();
51
52
        /** @var Member|MemberExtension $member */
53
        $member = Member::get()->byID($memberID);
54
55
        if (is_null($member) || $member->verifyAccountResetToken($token) === false) {
56
            return $this->jsonResponse(['error' => 'Invalid member or token'], 400);
57
        }
58
59
        $request->getSession()->set('MemberID', $member->ID);
60
61
        return $this->owner->getResponse()->setBody($this->owner->renderWith(
62
            'Security',
63
            [
64
                'Title' => _t(
65
                    __CLASS__ . '.ACCOUNT_RESET_TITLE',
66
                    'Reset Account'
67
                ),
68
                'Message' => _t(
69
                    __CLASS__ . '.ACCOUNT_RESET_DESCRIPTION',
70
                    'Your password will be changed, and any registered MFA methods will be removed.'
71
                ),
72
                'Form' => $this->ResetAccountForm(),
73
            ]
74
        ));
75
    }
76
77
    public function ResetAccountForm(): Form
78
    {
79
        $fields = FieldList::create([
80
            PasswordField::create(
81
                'NewPassword1',
82
                _t(
83
                    'SilverStripe\\Security\\Member.NEWPASSWORD',
84
                    'New Password'
85
                )
86
            ),
87
            PasswordField::create(
88
                'NewPassword2',
89
                _t(
90
                    'SilverStripe\\Security\\Member.CONFIRMNEWPASSWORD',
91
                    'Confirm New Password'
92
                )
93
            ),
94
        ]);
95
96
        $actions = FieldList::create([
97
            FormAction::create('doResetAccount', 'Reset Account'),
98
        ]);
99
100
        $validation = RequiredFields::create(['NewPassword1', 'NewPassword2']);
101
102
        $form = Form::create($this->owner, 'ResetAccountForm', $fields, $actions, $validation);
103
104
        $this->owner->extend('updateResetAccountForm', $form);
105
106
        return $form;
107
    }
108
109
    /**
110
     * Resets the user's password, and triggers other account reset procedures
111
     *
112
     * @param array $data
113
     * @param Form $form
114
     * @return HTTPResponse
115
     */
116
    public function doResetAccount(array $data, Form $form): HTTPResponse
117
    {
118
        $memberID = $this->owner->getRequest()->getSession()->get('MemberID');
119
120
        // If the ID isn't in the session, politely assume the session has expired
121
        if (!$memberID) {
122
            $form->sessionMessage(
123
                _t(
124
                    __CLASS__ . '.RESETTIMEDOUT',
125
                    "The account reset process timed out. Please click the link in the email and try again."
126
                ),
127
                ValidationResult::TYPE_ERROR
128
            );
129
130
            return $this->owner->redirectBack();
131
        }
132
133
        /** @var Member&MemberExtension $member */
134
        $member = Member::get()->byID(intval($memberID));
135
136
        // Fail if passwords do not match
137
        if ($data['NewPassword1'] !== $data['NewPassword2']) {
138
            $form->sessionMessage(
139
                _t(
140
                    'SilverStripe\\Security\\Member.ERRORNEWPASSWORD',
141
                    'You have entered your new password differently, try again'
142
                ),
143
                ValidationResult::TYPE_ERROR
144
            );
145
146
            return $this->owner->redirectBack();
147
        }
148
149
        // Check if the new password is accepted
150
        $validationResult = $member->changePassword($data['NewPassword1']);
151
        if (!$validationResult->isValid()) {
152
            $form->setSessionValidationResult($validationResult);
153
154
            return $this->owner->redirectBack();
155
        }
156
157
        // Clear locked out status
158
        $member->LockedOutUntil = null;
159
        $member->FailedLoginCount = null;
160
161
        // Clear account reset data
162
        $member->AccountResetHash = null;
163
        $member->AccountResetExpired = DBDatetime::create()->now();
164
        $member->write();
165
166
        // Pass off to extensions to perform any additional reset actions
167
        $this->extend('handleAccountReset', $member);
168
169
        // Send the user along to the login form (allowing any additional factors to kick in as needed)
170
        $this->owner->setSessionMessage(
171
            _t(
172
                __CLASS__ . '.RESETSUCCESSMESSAGE',
173
                'Reset complete. Please log in with your new password.'
174
            ),
175
            ValidationResult::TYPE_GOOD
176
        );
177
        return $this->owner->redirect($this->owner->Link('login'));
178
    }
179
}
180