Completed
Push — master ( 036b0a...0ab8bd )
by Garion
15s queued 11s
created

AdminRegistrationController::setLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\MFA\Controller;
4
5
use Psr\Log\LoggerInterface;
6
use SilverStripe\Admin\LeftAndMain;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\MFA\Extension\MemberExtension;
11
use SilverStripe\MFA\RequestHandler\BaseHandlerTrait;
12
use SilverStripe\MFA\RequestHandler\RegistrationHandlerTrait;
13
use SilverStripe\MFA\Service\MethodRegistry;
14
use SilverStripe\MFA\Service\RegisteredMethodManager;
15
use SilverStripe\MFA\State\AvailableMethodDetailsInterface;
16
use SilverStripe\ORM\ValidationException;
17
use SilverStripe\Security\Member;
18
use SilverStripe\Security\Security;
19
use SilverStripe\Security\SecurityToken;
20
21
/**
22
 * This controller handles actions that a user may perform on MFA methods registered on their own account while logged
23
 * in. This includes deleting methods, registering new methods and replacing (re-registering) existing methods.
24
 */
25
class AdminRegistrationController extends LeftAndMain
26
{
27
    use RegistrationHandlerTrait;
28
    use BaseHandlerTrait;
29
30
    private static $url_segment = 'mfa';
0 ignored issues
show
introduced by
The private property $url_segment is not used, and could be removed.
Loading history...
31
32
    private static $ignore_menuitem = true;
0 ignored issues
show
introduced by
The private property $ignore_menuitem is not used, and could be removed.
Loading history...
33
34
    private static $url_handlers = [
0 ignored issues
show
introduced by
The private property $url_handlers is not used, and could be removed.
Loading history...
35
        'GET register/$Method' => 'startRegistration',
36
        'POST register/$Method' => 'finishRegistration',
37
        'DELETE method/$Method' => 'removeRegisteredMethod',
38
        'PUT method/$Method/default' => 'setDefaultRegisteredMethod',
39
    ];
40
41
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
42
        'startRegistration',
43
        'finishRegistration',
44
        'removeRegisteredMethod',
45
        'setDefaultRegisteredMethod',
46
    ];
47
48
    private static $required_permission_codes = false;
0 ignored issues
show
introduced by
The private property $required_permission_codes is not used, and could be removed.
Loading history...
49
50
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
51
        'Logger' => '%$' . LoggerInterface::class . '.mfa',
52
    ];
53
54
    /**
55
     * @var LoggerInterface
56
     */
57
    protected $logger;
58
59
    /**
60
     * Start a registration for a method on the currently logged in user
61
     *
62
     * @param HTTPRequest $request
63
     * @return HTTPResponse
64
     */
65
    public function startRegistration(HTTPRequest $request): HTTPResponse
66
    {
67
        // Create a fresh store from the current logged in user
68
        $member = Security::getCurrentUser();
69
        $store = $this->createStore($member);
70
71
        if (!$this->getSudoModeService()->check($request->getSession())) {
72
            return $this->jsonResponse(
73
                ['errors' => [_t(__CLASS__ . '.INVALID_SESSION', 'Invalid session. Please refresh and try again.')]],
74
                400
75
            );
76
        }
77
78
        // Get the specified method
79
        $method = MethodRegistry::singleton()->getMethodByURLSegment($request->param('Method'));
80
81
        if (!$method) {
82
            return $this->jsonResponse(
83
                ['errors' => [_t(__CLASS__ . '.INVALID_METHOD', 'No such method is available')]],
84
                400
85
            );
86
        }
87
88
        $response = $this->createStartRegistrationResponse($store, $method, true);
89
        $store->save($request);
90
91
        return $response;
92
    }
93
94
    /**
95
     * Complete a registration for a method for the currently logged in user
96
     *
97
     * @param HTTPRequest $request
98
     * @return HTTPResponse
99
     */
100
    public function finishRegistration(HTTPRequest $request): HTTPResponse
101
    {
102
        $store = $this->getStore();
103
104
        if (!$store || !$this->getSudoModeService()->check($request->getSession())) {
0 ignored issues
show
introduced by
$store is of type SilverStripe\MFA\Store\StoreInterface, thus it always evaluated to true.
Loading history...
105
            return $this->jsonResponse(
106
                ['errors' => [_t(__CLASS__ . '.INVALID_SESSION', 'Invalid session. Please try again')]],
107
                400
108
            );
109
        }
110
111
        $method = MethodRegistry::singleton()->getMethodByURLSegment($request->param('Method'));
112
113
        if (!$method) {
114
            return $this->jsonResponse(
115
                ['errors' => [_t(__CLASS__ . '.INVALID_METHOD', 'No such method is available')]],
116
                400
117
            );
118
        }
119
120
        $result = $this->completeRegistrationRequest($store, $method, $request);
121
122
        if (!$result->isSuccessful()) {
123
            return $this->jsonResponse(['errors' => [$result->getMessage()]], 400);
124
        }
125
126
        $store::clear($request);
127
128
        return $this->jsonResponse([
129
            'success' => true,
130
            'method' => $result->getContext()['registeredMethod'] ?? null,
131
        ], 201);
132
    }
133
134
    /**
135
     * Remove the specified method from the currently logged in user
136
     *
137
     * @param HTTPRequest $request
138
     * @return HTTPResponse
139
     */
140
    public function removeRegisteredMethod(HTTPRequest $request): HTTPResponse
141
    {
142
        // Ensure CSRF protection
143
        if (!SecurityToken::inst()->checkRequest($request)) {
144
            return $this->jsonResponse(
145
                ['errors' => [_t(__CLASS__ . '.CSRF_FAILURE', 'Request timed out, please try again')]],
146
                400
147
            );
148
        }
149
150
        // Get the specified method
151
        $methodRegistry = MethodRegistry::singleton();
152
        $specifiedMethod = $request->param('Method');
153
154
        if (!$specifiedMethod || !($method = $methodRegistry->getMethodByURLSegment($specifiedMethod))) {
155
            return $this->jsonResponse(
156
                ['errors' => [_t(__CLASS__ . '.INVALID_METHOD', 'No such method is available')]],
157
                400
158
            );
159
        }
160
161
        // Remove the method from the user
162
        $member = Security::getCurrentUser();
163
        $registeredMethodManager = RegisteredMethodManager::singleton();
164
        $result = $registeredMethodManager->deleteFromMember($member, $method);
165
166
        if (!$result) {
167
            return $this->jsonResponse(
168
                ['errors' => [_t(
169
                    __CLASS__ . '.COULD_NOT_DELETE',
170
                    'Could not delete the specified method from the user'
171
                )]],
172
                400
173
            );
174
        }
175
176
        $backupMethod = $methodRegistry->getBackupMethod();
177
        return $this->jsonResponse([
178
            'success' => true,
179
            'availableMethod' => Injector::inst()->create(AvailableMethodDetailsInterface::class, $method),
180
            // Indicate if the user has a backup method registered to keep the UI up to date
181
            // Deleting methods may remove the backup method if there are no other methods remaining.
182
            'hasBackupMethod' => $backupMethod && $registeredMethodManager->getFromMember(
183
                $member,
184
                $backupMethod
185
            ),
186
        ]);
187
    }
188
189
    /**
190
     * Set the default registered method for the current user to that provided by the MethodID parameter.
191
     *
192
     * @param HTTPRequest $request
193
     * @return HTTPResponse
194
     */
195
    public function setDefaultRegisteredMethod(HTTPRequest $request): HTTPResponse
196
    {
197
        // Ensure CSRF and sudo-mode protection
198
        if (!SecurityToken::inst()->checkRequest($request)
199
            || !$this->getSudoModeService()->check($request->getSession())
200
        ) {
201
            return $this->jsonResponse(
202
                ['errors' => [_t(__CLASS__ . '.CSRF_FAILURE', 'Request timed out, please try again')]],
203
                400
204
            );
205
        }
206
207
        // Get the specified method
208
        $methodRegistry = MethodRegistry::singleton();
209
        $specifiedMethod = $request->param('Method');
210
211
        if (!$specifiedMethod || !($method = $methodRegistry->getMethodByURLSegment($specifiedMethod))) {
212
            return $this->jsonResponse(
213
                ['errors' => [_t(__CLASS__ . '.INVALID_METHOD', 'No such method is available')]],
214
                400
215
            );
216
        }
217
218
        // Set the method as the default
219
        /** @var Member&MemberExtension $member */
220
        $member = Security::getCurrentUser();
221
        $registeredMethodManager = RegisteredMethodManager::singleton();
222
        $registeredMethod = $registeredMethodManager->getFromMember($member, $method);
223
        if (!$registeredMethod) {
224
            return $this->jsonResponse(
225
                ['errors' => [_t(__CLASS__ . '.INVALID_METHOD', 'No such registered method is available')]],
226
                400
227
            );
228
        }
229
        try {
230
            $member->setDefaultRegisteredMethod($registeredMethod);
231
            $member->write();
232
        } catch (ValidationException $exception) {
233
            $this->logger->debug(
234
                'Failed to set default registered method for user #' . $member->ID . ' to ' . $specifiedMethod
235
                . ': ' . $exception->getMessage()
236
            );
237
238
            return $this->jsonResponse(
239
                ['errors' => [_t(
240
                    __CLASS__ . '.COULD_NOT_SET_DEFAULT',
241
                    'Could not set the default method for the user'
242
                )]],
243
                400
244
            );
245
        }
246
247
        return $this->jsonResponse(['success' => true]);
248
    }
249
250
    /**
251
     * Respond with the given array as a JSON response
252
     *
253
     * @param array $response
254
     * @param int $code The HTTP response code to set on the response
255
     * @return HTTPResponse
256
     */
257
    protected function jsonResponse(array $response, int $code = 200): HTTPResponse
258
    {
259
        return HTTPResponse::create(json_encode($response))
260
            ->addHeader('Content-Type', 'application/json')
261
            ->setStatusCode($code);
262
    }
263
264
    /**
265
     * @param LoggerInterface|null $logger
266
     * @return $this
267
     */
268
    public function setLogger(?LoggerInterface $logger): self
269
    {
270
        $this->logger = $logger;
271
        return $this;
272
    }
273
}
274