Completed
Push — master ( f0b03a...fa0cb8 )
by Robbie
27s queued 11s
created

SecurityAdminExtension::reset()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 64
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 30
nc 6
nop 1
dl 0
loc 64
rs 8.5066
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 Exception;
6
use Psr\Log\LoggerInterface;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Control\Email\Email;
9
use SilverStripe\Control\HTTPRequest;
10
use SilverStripe\Control\HTTPResponse;
11
use SilverStripe\Core\Extension;
12
use SilverStripe\Admin\SecurityAdmin;
13
use SilverStripe\MFA\Extension\MemberExtension as BaseMFAMemberExtension;
14
use SilverStripe\MFA\JSONResponse;
15
use SilverStripe\ORM\ValidationException;
16
use SilverStripe\Security\Member;
17
use SilverStripe\Security\PasswordEncryptor_NotFoundException;
18
use SilverStripe\Security\Permission;
19
use SilverStripe\Security\Security;
20
use SilverStripe\Security\SecurityToken;
21
22
/**
23
 * This extension is applied to SecurityAdmin to provide an additional endpoint
24
 * for sending account reset requests.
25
 *
26
 * @package SilverStripe\MFA\Extension
27
 * @property SecurityAdmin $owner
28
 */
29
class SecurityAdminExtension extends Extension
30
{
31
    use JSONResponse;
32
33
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
34
        'reset',
35
    ];
36
37
    /**
38
     * @var string[]
39
     */
40
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
41
        'Logger' => '%$' . LoggerInterface::class . '.account_reset',
42
    ];
43
44
    /**
45
     * @var LoggerInterface
46
     */
47
    protected $logger;
48
49
    public function reset(HTTPRequest $request): HTTPResponse
50
    {
51
        if (!$request->isPOST() || !$request->param('ID')) {
52
            return $this->jsonResponse(
53
                [
54
                    'error' => _t(__CLASS__ . '.BAD_REQUEST', 'Invalid request')
55
                ],
56
                400
57
            );
58
        }
59
60
        $body = json_decode($request->getBody() ?? '', true);
61
62
        if (!SecurityToken::inst()->check($body['csrf_token'] ?? null)) {
63
            return $this->jsonResponse(
64
                [
65
                    'error' => _t(__CLASS__ . '.INVALID_CSRF_TOKEN', 'Invalid or missing CSRF token')
66
                ],
67
                400
68
            );
69
        }
70
71
        if (!Permission::check(BaseMFAMemberExtension::MFA_ADMINISTER_REGISTERED_METHODS)) {
72
            return $this->jsonResponse(
73
                [
74
                    'error' => _t(
75
                        __CLASS__ . '.INSUFFICIENT_PERMISSIONS',
76
                        'Insufficient permissions to reset user'
77
                    )
78
                ],
79
                403
80
            );
81
        }
82
83
        /** @var Member $memberToReset */
84
        $memberToReset = Member::get()->byID($request->param('ID'));
0 ignored issues
show
Bug introduced by
$request->param('ID') of type string is incompatible with the type integer expected by parameter $id of SilverStripe\ORM\DataList::byID(). ( Ignorable by Annotation )

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

84
        $memberToReset = Member::get()->byID(/** @scrutinizer ignore-type */ $request->param('ID'));
Loading history...
85
86
        if ($memberToReset === null) {
87
            return $this->jsonResponse(
88
                [
89
                    'error' => _t(
90
                        __CLASS__ . '.INVALID_MEMBER',
91
                        'Requested member for reset not found'
92
                    )
93
                ],
94
                403
95
            );
96
        }
97
98
        $sent = $this->sendResetEmail($memberToReset);
99
100
        if (!$sent) {
101
            return $this->jsonResponse(
102
                [
103
                    'error' => _t(
104
                        __CLASS__ . '.EMAIL_NOT_SENT',
105
                        'Email sending failed'
106
                    )
107
                ],
108
                500
109
            );
110
        }
111
112
        return $this->jsonResponse(['success' => true], 200);
113
    }
114
115
    /**
116
     * Prepares and attempts to send the Account Reset request email.
117
     *
118
     * @param Member&MemberExtension $member
119
     * @return bool
120
     */
121
    protected function sendResetEmail($member)
122
    {
123
        // Generate / store / obtain reset token
124
        $token = $member->generateAccountResetTokenAndStoreHash();
125
126
        // Create email and fire
127
        try {
128
            $email = Email::create()
129
                ->setHTMLTemplate('SilverStripe\\MFA\\Email\\AccountReset')
130
                ->setData($member)
131
                ->setSubject(_t(
132
                    __CLASS__ . '.ACCOUNT_RESET_EMAIL_SUBJECT',
133
                    'Reset your account'
134
                ))
135
                ->addData('AccountResetLink', $this->getAccountResetLink($member, $token))
136
                ->addData('Member', $member)
137
                ->setFrom(Email::config()->get('admin_email'))
138
                ->setTo($member->Email);
139
140
            return $email->send();
141
        } catch (Exception $e) {
142
            $this->logger->info('WARNING: Account Reset Email failed to send: ' . $e->getMessage());
143
            return false;
144
        }
145
    }
146
147
    /**
148
     * Generates a link to the Account Reset Handler endpoint to be sent to a Member.
149
     *
150
     * @param Member $member
151
     * @param string $token
152
     * @return string
153
     */
154
    public function getAccountResetLink(Member $member, string $token): string
155
    {
156
        return Controller::join_links(
157
            Security::singleton()->Link('resetaccount'),
158
            "?m={$member->ID}&t={$token}"
159
        );
160
    }
161
}
162