Passed
Pull Request — master (#127)
by Garion
03:26
created

SecurityAdminAccountResetExtension::reset()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 64
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 64
rs 8.5066
c 0
b 0
f 0
cc 7
nc 6
nop 1

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

81
        $memberToReset = Member::get()->byID(/** @scrutinizer ignore-type */ $request->param('ID'));
Loading history...
82
83
        if ($memberToReset === null) {
84
            return $this->jsonResponse(
85
                [
86
                    'error' => _t(
87
                        __CLASS__ . '.INVALID_MEMBER',
88
                        'Requested member for reset not found'
89
                    )
90
                ],
91
                403
92
            );
93
        }
94
95
        $sent = $this->sendResetEmail($memberToReset);
96
97
        if (!$sent) {
98
            return $this->jsonResponse(
99
                [
100
                    'error' => _t(
101
                        __CLASS__ . '.EMAIL_NOT_SENT',
102
                        'Email sending failed'
103
                    )
104
                ],
105
                500
106
            );
107
        }
108
109
        return $this->jsonResponse(['success' => true], 200);
110
    }
111
112
    /**
113
     * @param Member&MemberResetExtension $member
114
     * @return bool
115
     * @throws ValidationException
116
     * @throws PasswordEncryptor_NotFoundException
117
     */
118
    protected function sendResetEmail($member)
119
    {
120
        // Generate / store / obtain reset token
121
        $token = $member->generateAccountResetTokenAndStoreHash();
122
123
        // Create email and fire
124
        try {
125
            $email = Email::create()
126
                ->setHTMLTemplate('SilverStripe\\MFA\\Email\\AccountReset')
127
                ->setData($member)
128
                ->setSubject(_t(
129
                    __CLASS__ . '.ACCOUNT_RESET_EMAIL_SUBJECT',
130
                    'Reset your account'
131
                ))
132
                ->addData('AccountResetLink', $this->getAccountResetLink($member, $token))
133
                ->addData('Member', $member)
134
                ->setFrom(Email::config()->admin_email)
135
                ->setTo($member->Email);
136
137
            return $email->send();
138
        } catch (Exception $e) {
139
            $this->logger->info('WARNING: Account Reset Email failed to send');
140
            return false;
141
        }
142
    }
143
144
    /**
145
     * Generates a link to the Account Reset Handler endpoint to be sent to a Member.
146
     *
147
     * @param Member $member
148
     * @param string $token
149
     * @return string
150
     * @todo Implement when Account Reset Handler is built
151
     */
152
    protected function getAccountResetLink(Member $member, string $token): string
0 ignored issues
show
Unused Code introduced by
The parameter $token 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

152
    protected function getAccountResetLink(Member $member, /** @scrutinizer ignore-unused */ string $token): string

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...
Unused Code introduced by
The parameter $member 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

152
    protected function getAccountResetLink(/** @scrutinizer ignore-unused */ Member $member, string $token): string

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...
153
    {
154
        return '';
155
    }
156
}
157