Passed
Pull Request — master (#127)
by Garion
02:31
created

sendResetEmail()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 23
rs 9.7333
c 0
b 0
f 0
cc 2
nc 3
nop 1
1
<?php
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\Core\Injector\Injector;
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
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
29
        'reset',
30
    ];
31
32
    public function reset(HTTPRequest $request): HTTPResponse
33
    {
34
        if (!$request->isPOST() || !$request->param('ID')) {
35
            return $this->owner
36
                ->getResponse()
37
                ->setStatusCode(400)
38
                ->addHeader('Content-Type', 'application/json')
39
                ->setBody(json_encode(
40
                    [
41
                        'error' => _t(__CLASS__ . '.BAD_REQUEST', 'Invalid request')
42
                    ]
43
                ));
44
        }
45
46
        $body = json_decode($request->getBody(), true);
47
48
        if (!SecurityToken::inst()->check($body['csrf_token'] ?? null)) {
49
            return $this->owner
50
                ->getResponse()
51
                ->setStatusCode(400)
52
                ->addHeader('Content-Type', 'application/json')
53
                ->setBody(json_encode(
54
                    [
55
                        'error' => _t(__CLASS__ . '.INVALID_CSRF_TOKEN', 'Invalid or missing CSRF token')
56
                    ]
57
                ));
58
        }
59
60
        if (!Permission::check(MemberMFAExtension::MFA_ADMINISTER_REGISTERED_METHODS)) {
61
            return $this->owner
62
                ->getResponse()
63
                ->setStatusCode(403)
64
                ->addHeader('Content-Type', 'application/json')
65
                ->setBody(json_encode(
66
                    [
67
                        'error' => _t(
68
                            __CLASS__ . '.INSUFFICIENT_PERMISSIONS',
69
                            'Insufficient permissions to reset user'
70
                        )
71
                    ]
72
                ));
73
        }
74
75
        /** @var Member $memberToReset */
76
        $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

76
        $memberToReset = Member::get()->byID(/** @scrutinizer ignore-type */ $request->param('ID'));
Loading history...
77
78
        if ($memberToReset === null) {
79
            return $this->owner
80
                ->getResponse()
81
                ->setStatusCode(403)
82
                ->addHeader('Content-Type', 'application/json')
83
                ->setBody(json_encode(
84
                    [
85
                        'error' => _t(
86
                            __CLASS__ . '.INVALID_MEMBER',
87
                            'Requested member for reset not found'
88
                        )
89
                    ]
90
                ));
91
        }
92
93
        $sent = $this->sendResetEmail($memberToReset);
94
95
        if (!$sent) {
96
            return $this->owner
97
                ->getResponse()
98
                ->setStatusCode(500)
99
                ->addHeader('Content-Type', 'application/json')
100
                ->setBody(json_encode(
101
                    [
102
                        'error' => _t(
103
                            __CLASS__ . '.EMAIL_NOT_SENT',
104
                            'Email sending failed'
105
                        )
106
                    ]
107
                ));
108
        }
109
110
        return $this->owner
111
            ->getResponse()
112
            ->setStatusCode(200)
113
            ->addHeader('Content-Type', 'application/json')
114
            ->setBody(json_encode(
115
                [
116
                    'success' => true,
117
                ]
118
            ));
119
    }
120
121
    /**
122
     * @param Member&MemberResetExtension $member
123
     * @return bool
124
     * @throws ValidationException
125
     * @throws PasswordEncryptor_NotFoundException
126
     */
127
    protected function sendResetEmail($member)
128
    {
129
        // Generate / store / obtain reset token
130
        $token = $member->generateAccountResetTokenAndStoreHash();
131
132
        // Create email and fire
133
        try {
134
            $email = Email::create()
135
                ->setHTMLTemplate('SilverStripe\\MFA\\Email\\AccountReset')
136
                ->setData($member)
137
                ->setSubject(_t(
138
                    __CLASS__ . '.ACCOUNT_RESET_EMAIL_SUBJECT',
139
                    'Reset your account'
140
                ))
141
                ->addData('AccountResetLink', $this->getAccountResetLink($member, $token))
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getAccountResetLink($member, $token) targeting SilverStripe\MFA\Extensi...::getAccountResetLink() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
142
                ->addData('Member', $member)
143
                ->setFrom(Email::config()->admin_email)
144
                ->setTo($member->Email);
145
146
            return $email->send();
147
        } catch (Exception $e) {
148
            Injector::inst()->get(LoggerInterface::class)->info('WARNING: Account Reset Email failed to send');
149
            return false;
150
        }
151
    }
152
153
    protected function getAccountResetLink(Member $member, string $token)
0 ignored issues
show
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

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

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 $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

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

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