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