LoginHandler   F
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 561
Duplicated Lines 0 %

Importance

Changes 15
Bugs 0 Features 1
Metric Value
eloc 228
dl 0
loc 561
rs 2.64
c 15
b 0
f 1
wmc 72

16 Methods

Rating   Name   Duplication   Size   Complexity  
B finishRegistration() 0 48 9
A mfa() 0 12 4
A getLogger() 0 3 1
B redirectAfterSuccessfulLogin() 0 55 7
A getBackURL() 0 12 4
A getSchema() 0 18 2
A getMethodRegistry() 0 3 1
A setLogger() 0 4 1
A doPerformLogin() 0 13 3
C startRegistration() 0 56 13
A startVerification() 0 19 4
A getMember() 0 16 4
A skipRegistration() 0 28 3
A doLogin() 0 46 6
A jsonResponse() 0 5 1
B finishVerification() 0 58 9

How to fix   Complexity   

Complex Class

Complex classes like LoginHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LoginHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SilverStripe\MFA\Authenticator;
6
7
use Psr\Log\LoggerInterface;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\HTTPResponse;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\MFA\Exception\InvalidMethodException;
12
use SilverStripe\MFA\Exception\MemberNotFoundException;
13
use SilverStripe\MFA\Extension\MemberExtension;
14
use SilverStripe\MFA\Method\MethodInterface;
15
use SilverStripe\MFA\RequestHandler\BaseHandlerTrait;
16
use SilverStripe\MFA\RequestHandler\RegistrationHandlerTrait;
17
use SilverStripe\MFA\RequestHandler\VerificationHandlerTrait;
18
use SilverStripe\MFA\Service\EnforcementManager;
19
use SilverStripe\MFA\Service\MethodRegistry;
20
use SilverStripe\MFA\Service\SchemaGenerator;
21
use SilverStripe\ORM\ValidationException;
22
use SilverStripe\ORM\ValidationResult;
23
use SilverStripe\Security\IdentityStore;
24
use SilverStripe\Security\Member;
25
use SilverStripe\Security\MemberAuthenticator\LoginHandler as BaseLoginHandler;
26
use SilverStripe\Security\MemberAuthenticator\MemberLoginForm;
27
use SilverStripe\Security\Security;
28
29
class LoginHandler extends BaseLoginHandler
30
{
31
    use BaseHandlerTrait;
32
    use VerificationHandlerTrait;
0 ignored issues
show
Bug introduced by
The trait SilverStripe\MFA\Request...erificationHandlerTrait requires the property $DefaultRegisteredMethod which is not provided by SilverStripe\MFA\Authenticator\LoginHandler.
Loading history...
33
    use RegistrationHandlerTrait;
34
35
    public const SESSION_KEY = 'MFALogin';
36
37
    private static $url_handlers = [
0 ignored issues
show
introduced by
The private property $url_handlers is not used, and could be removed.
Loading history...
38
        'GET mfa/schema' => 'getSchema', // Provides details about existing registered methods, etc.
39
        'GET mfa/register/$Method' => 'startRegistration', // Initiates registration process for $Method
40
        'POST mfa/register/$Method' => 'finishRegistration', // Completes registration process for $Method
41
        'GET mfa/skip' => 'skipRegistration', // Allows the user to skip MFA registration
42
        'GET mfa/verify/$Method' => 'startVerification', // Initiates verify process for $Method
43
        'POST mfa/verify/$Method' => 'finishVerification', // Verifies verify via $Method
44
        'GET mfa/complete' => 'redirectAfterSuccessfulLogin',
45
        'GET mfa' => 'mfa', // Renders the MFA Login Page to init the app
46
    ];
47
48
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
49
        'mfa',
50
        'getSchema',
51
        'startRegistration',
52
        'finishRegistration',
53
        'skipRegistration',
54
        'startVerification',
55
        'finishVerification',
56
        'redirectAfterSuccessfulLogin',
57
    ];
58
59
    /**
60
     * Provide a user help link that will be available on the Introduction UI
61
     *
62
     * @config
63
     * @var string
64
     */
65
    // phpcs:disable
66
    private static $user_help_link = 'https://userhelp.silverstripe.org/en/4/optional_features/multi-factor_authentication/';
0 ignored issues
show
introduced by
The private property $user_help_link is not used, and could be removed.
Loading history...
67
    // phpcs:enable
68
69
    /**
70
     * @var string[]
71
     */
72
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
73
        'Logger' => '%$' . LoggerInterface::class . '.mfa',
74
    ];
75
76
    /**
77
     * @var LoggerInterface
78
     */
79
    protected $logger;
80
81
    /**
82
     * Override the parent "doLogin" to insert extra steps into the flow
83
     *
84
     * @inheritdoc
85
     */
86
    public function doLogin($data, MemberLoginForm $form, HTTPRequest $request)
87
    {
88
        /** @var Member&MemberExtension $member */
89
        $member = $this->checkLogin($data, $request, $result);
90
        $enforcementManager = EnforcementManager::singleton();
91
92
        // If:
93
        //  - there's no member it's an invalid login, or
94
        //  - the enforcement manager determines that MFA should not be shown
95
        // then we can delegate to the parent as this will just be the normal login flow (without MFA)
96
        if (!$member || !$enforcementManager->shouldRedirectToMFA($member)) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
97
            return parent::doLogin($data, $form, $request);
98
        }
99
100
        // Enable sudo mode. This would usually be done by the default login handler's afterLogin() hook.
101
        $this->getSudoModeService()->activate($request->getSession());
102
103
        // Create a store for handling MFA for this member
104
        $store = $this->createStore($member);
105
        // We don't need to store the user's password
106
        $request->offsetUnset('Password');
107
        // User code may adjust the request properties further if they have their own sensitive data which
108
        // should be excluded from the store.
109
        $this->extend('onBeforeSaveRequestToStore', $request, $store);
110
        $store->save($request);
111
112
        // Store the BackURL for use after the process is complete
113
        if (!empty($data)) {
114
            $request->getSession()->set(static::SESSION_KEY . '.additionalData', $data);
115
        }
116
117
        // If there is at least one MFA method registered then the user MUST login with it
118
        $request->getSession()->clear(static::SESSION_KEY . '.mustLogin');
119
        if ($member->RegisteredMFAMethods()->count() > 0) {
120
            $request->getSession()->set(static::SESSION_KEY . '.mustLogin', true);
121
        } else {
122
            // When there are no methods then the user will be promted to register. We re-generate the session ID to
123
            // prevent session fixation on the MFA setup
124
            // NB: There's no SilverStripe API for this
125
            if (!headers_sent()) {
126
                @session_regenerate_id(true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_regenerate_id(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

126
                /** @scrutinizer ignore-unhandled */ @session_regenerate_id(true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
127
            }
128
        }
129
130
        // Redirect to the MFA step
131
        return $this->redirect($this->link('mfa'));
132
    }
133
134
    /**
135
     * Action handler for loading the MFA authentication React app
136
     * Template variables defined here will be used by the rendering controller's template - normally Page.ss
137
     *
138
     * @return HTTPResponse|array
139
     */
140
    public function mfa(HTTPRequest $request)
141
    {
142
        $store = $this->getStore();
143
        if (!$store || !$store->getMember() || !$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...
144
            return $this->redirectBack();
145
        }
146
147
        $this->applyRequirements();
148
149
        return [
150
            'Form' => $this->renderWith($this->getViewerTemplates()),
151
            'ClassName' => 'mfa',
152
        ];
153
    }
154
155
    /**
156
     * Provides information about the current Member's MFA state
157
     *
158
     * @return HTTPResponse
159
     */
160
    public function getSchema(): HTTPResponse
161
    {
162
        try {
163
            $member = $this->getMember();
164
            $schema = SchemaGenerator::create()->getSchema($member);
165
            return $this->jsonResponse(
166
                $schema + [
167
                    'endpoints' => [
168
                        'register' => $this->Link('mfa/register/{urlSegment}'),
169
                        'verify' => $this->Link('mfa/verify/{urlSegment}'),
170
                        'complete' => $this->Link('mfa/complete'),
171
                        'skip' => $this->Link('mfa/skip'),
172
                    ],
173
                ]
174
            );
175
        } catch (MemberNotFoundException $exception) {
176
            // If we don't have a valid member we shouldn't be here...
177
            return $this->redirectBack();
178
        }
179
    }
180
181
    /**
182
     * Handles the request to start a registration
183
     *
184
     * @param HTTPRequest $request
185
     * @return HTTPResponse
186
     */
187
    public function startRegistration(HTTPRequest $request): HTTPResponse
188
    {
189
        $store = $this->getStore();
190
        $sessionMember = $store ? $store->getMember() : null;
0 ignored issues
show
introduced by
$store is of type SilverStripe\MFA\Store\StoreInterface, thus it always evaluated to true.
Loading history...
191
        $loggedInMember = Security::getCurrentUser();
192
193
        if (
194
            ($loggedInMember === null && $sessionMember === null)
195
            || !$this->getSudoModeService()->check($request->getSession())
196
        ) {
197
            return $this->jsonResponse(
198
                ['errors' => [
199
                    _t(
200
                        __CLASS__ . '.NOT_AUTHENTICATING',
201
                        'You must be logged in or logging in. Please refresh the page and try again.'
202
                    )
203
                ]],
204
                403
205
            );
206
        }
207
208
        $method = $this->getMethodRegistry()->getMethodByURLSegment($request->param('Method'));
209
210
        // If the user isn't fully logged in and they already have a registered method, they can't register another
211
        // provided that they're not registering a backup method
212
        $registeredMethodCount = $sessionMember && $sessionMember->RegisteredMFAMethods()->count();
213
        $isRegisteringBackupMethod =
214
            $method instanceof MethodInterface && $this->getMethodRegistry()->isBackupMethod($method);
215
216
        if ($loggedInMember === null && $sessionMember && $registeredMethodCount > 0 && !$isRegisteringBackupMethod) {
217
            return $this->jsonResponse(
218
                ['errors' => [_t(__CLASS__ . '.MUST_USE_EXISTING_METHOD', 'This member already has an MFA method')]],
219
                400
220
            );
221
        }
222
223
        // Handle the case where the request hasn't provided an appropriate method to register
224
        if ($method === null) {
225
            return $this->jsonResponse(
226
                ['errors' => [_t(__CLASS__ . '.INVALID_METHOD', 'No such method is available')]],
227
                400
228
            );
229
        }
230
231
        // Ensure a store is available using the logged in member if the store doesn't exist
232
        if (!$store) {
0 ignored issues
show
introduced by
$store is of type SilverStripe\MFA\Store\StoreInterface, thus it always evaluated to true.
Loading history...
233
            $store = $this->createStore($loggedInMember);
234
        }
235
236
        // Delegate to the trait for common handling
237
        $response = $this->createStartRegistrationResponse($store, $method);
238
239
        // Ensure details are saved to the session
240
        $store->save($request);
241
242
        return $response;
243
    }
244
245
    /**
246
     * Handles the request to verify and process a new registration
247
     *
248
     * @param HTTPRequest $request
249
     * @return HTTPResponse
250
     */
251
    public function finishRegistration(HTTPRequest $request): HTTPResponse
252
    {
253
        $store = $this->getStore();
254
        $sessionMember = $store ? $store->getMember() : null;
0 ignored issues
show
introduced by
$store is of type SilverStripe\MFA\Store\StoreInterface, thus it always evaluated to true.
Loading history...
255
        $loggedInMember = Security::getCurrentUser();
256
257
        if (
258
            ($loggedInMember === null && $sessionMember === null)
259
            || !$this->getSudoModeService()->check($request->getSession())
260
        ) {
261
            return $this->jsonResponse(
262
                ['errors' => [
263
                    _t(
264
                        __CLASS__ . '.NOT_AUTHENTICATING',
265
                        'You must be logged in or logging in. Please refresh the page and try again.'
266
                    )
267
                ]],
268
                403
269
            );
270
        }
271
272
        $method = $this->getMethodRegistry()->getMethodByURLSegment($request->param('Method'));
273
        $result = $this->completeRegistrationRequest($store, $method, $request);
0 ignored issues
show
Bug introduced by
It seems like $method can also be of type null; however, parameter $method of SilverStripe\MFA\Authent...teRegistrationRequest() does only seem to accept SilverStripe\MFA\Method\MethodInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

273
        $result = $this->completeRegistrationRequest($store, /** @scrutinizer ignore-type */ $method, $request);
Loading history...
274
275
        if (!$result->isSuccessful()) {
276
            return $this->jsonResponse(
277
                ['errors' => [$result->getMessage()]],
278
                $result->getContext()['code'] ?? 400
279
            );
280
        }
281
282
        // If we've completed registration and the member is not already logged in then we need to log them in
283
        /** @var EnforcementManager $enforcementManager */
284
        $enforcementManager = EnforcementManager::create();
285
        $mustLogin = $request->getSession()->get(static::SESSION_KEY . '.mustLogin');
286
287
        // If the user has a valid registration at this point then we can log them in. We must ensure that they're not
288
        // required to log in though. The "mustLogin" flag is set at the beginning of the MFA process if they have at
289
        // least one method registered. They should always do that first. In that case we should assert
290
        // "isLoginComplete"
291
        if (
292
            (!$mustLogin || $this->isVerificationComplete($store))
293
            && $enforcementManager->hasCompletedRegistration($sessionMember)
0 ignored issues
show
Bug introduced by
It seems like $sessionMember can also be of type null; however, parameter $member of SilverStripe\MFA\Service...CompletedRegistration() does only seem to accept SilverStripe\Security\Member, maybe add an additional type check? ( Ignorable by Annotation )

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

293
            && $enforcementManager->hasCompletedRegistration(/** @scrutinizer ignore-type */ $sessionMember)
Loading history...
294
        ) {
295
            $this->doPerformLogin($request, $sessionMember);
0 ignored issues
show
Bug introduced by
It seems like $sessionMember can also be of type null; however, parameter $member of SilverStripe\MFA\Authent...ndler::doPerformLogin() does only seem to accept SilverStripe\Security\Member, maybe add an additional type check? ( Ignorable by Annotation )

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

295
            $this->doPerformLogin($request, /** @scrutinizer ignore-type */ $sessionMember);
Loading history...
296
        }
297
298
        return $this->jsonResponse(['success' => true], 201);
299
    }
300
301
    /**
302
     * Handle an HTTP request to skip MFA registration
303
     *
304
     * @param HTTPRequest $request
305
     * @return HTTPResponse
306
     * @throws ValidationException
307
     */
308
    public function skipRegistration(HTTPRequest $request): HTTPResponse
309
    {
310
        $loginUrl = Security::login_url();
311
312
        try {
313
            $member = $this->getMember();
314
            $enforcementManager = EnforcementManager::create();
315
316
            if (!$enforcementManager->canSkipMFA($member)) {
317
                Security::singleton()->setSessionMessage(
318
                    _t(__CLASS__ . '.CANNOT_SKIP', 'You cannot skip MFA registration'),
319
                    ValidationResult::TYPE_ERROR
320
                );
321
                return $this->redirect($loginUrl);
322
            }
323
324
            $member->update(['HasSkippedMFARegistration' => true])->write();
325
            $this->extend('onSkipRegistration', $member);
326
            $this->doPerformLogin($request, $member);
327
328
            // Redirect the user back to wherever they originally came from when they started the login process
329
            return $this->redirectAfterSuccessfulLogin();
330
        } catch (MemberNotFoundException $exception) {
331
            Security::singleton()->setSessionMessage(
332
                _t(__CLASS__ . '.CANNOT_SKIP', 'You cannot skip MFA registration'),
333
                ValidationResult::TYPE_ERROR
334
            );
335
            return $this->redirect($loginUrl);
336
        }
337
    }
338
339
    /**
340
     * Handles the request to start an authentication process with an authenticator (possibly specified by the request)
341
     *
342
     * @param HTTPRequest $request
343
     * @return HTTPResponse
344
     */
345
    public function startVerification(HTTPRequest $request): HTTPResponse
346
    {
347
        $store = $this->getStore();
348
        // If we don't have a valid member we shouldn't be here, or if sudo mode is not active yet.
349
        if (!$store || !$store->getMember() || !$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...
350
            return $this->jsonResponse(['message' => 'Forbidden'], 403);
351
        }
352
353
        // Use the provided trait method for handling login
354
        $response = $this->createStartVerificationResponse(
355
            $store,
356
            $this->getMethodRegistry()->getMethodByURLSegment($request->param('Method'))
357
        );
358
359
        // Ensure detail is saved to the store
360
        $store->save($request);
361
362
        // Respond with our method
363
        return $response;
364
    }
365
366
    /**
367
     * Handles requests to authenticate from any MFA method, directing verification to the Method supplied.
368
     *
369
     * @param HTTPRequest $request
370
     * @return HTTPResponse
371
     */
372
    public function finishVerification(HTTPRequest $request): HTTPResponse
373
    {
374
        $store = $this->getStore();
375
        // Enforce sudo mode
376
        if (!$this->getSudoModeService()->check($request->getSession())) {
377
            return $this->jsonResponse([
378
                'message' => _t(
379
                    __CLASS__ . '.SUDO_MODE_REQUIRED',
380
                    'You need to re-verify your account before continuing. Please reload and try again.'
381
                ),
382
            ], 403);
383
        }
384
385
        if ($store && ($member = $store->getMember()) && $member->isLockedOut()) {
386
            return $this->jsonResponse([
387
                'message' => _t(
388
                    __CLASS__ . '.LOCKED_OUT',
389
                    'Your account is temporarily locked. Please try again later.'
390
                ),
391
            ], 403);
392
        }
393
394
        try {
395
            $result = $this->completeVerificationRequest($store, $request);
396
        } catch (InvalidMethodException $e) {
397
            // Invalid method usually means a timeout. A user might be trying to verify before "starting"
398
            return $this->jsonResponse(['message' => 'Forbidden'], 403);
399
        }
400
401
        if (!$result->isSuccessful()) {
402
            $store->getMember()->registerFailedLogin();
403
            $code = $result->getContext()['code'] ?? 401;
404
405
            return $this->jsonResponse([
406
                'message' => $result->getMessage(),
407
            ], $code);
408
        }
409
410
        if (!$this->isVerificationComplete($store)) {
411
            return $this->jsonResponse([
412
                'message' => 'Additional authentication required',
413
            ], 202);
414
        }
415
416
        // Actually log in the member if the registration is complete
417
        $member = $store->getMember();
418
419
        if (EnforcementManager::create()->hasCompletedRegistration($member)) {
420
            $this->doPerformLogin($request, $member);
0 ignored issues
show
Bug introduced by
It seems like $member can also be of type null; however, parameter $member of SilverStripe\MFA\Authent...ndler::doPerformLogin() does only seem to accept SilverStripe\Security\Member, maybe add an additional type check? ( Ignorable by Annotation )

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

420
            $this->doPerformLogin($request, /** @scrutinizer ignore-type */ $member);
Loading history...
421
422
            // And also clear the session
423
            $store->clear($request);
424
        }
425
426
        // We still indicate login has been completed here. The finalisation of registration should take care of it
427
        return $this->jsonResponse([
428
            'message' => 'Login complete',
429
        ], 200);
430
    }
431
432
    public function redirectAfterSuccessfulLogin(): HTTPResponse
433
    {
434
        // Assert that we have a member logged in already. We explicitly don't use ->getMember as that will pull from
435
        // session during the MFA process
436
        $member = Security::getCurrentUser();
437
        $loginUrl = Security::login_url();
438
439
        if (!$member) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
440
            Security::singleton()->setSessionMessage(
441
                _t(__CLASS__ . '.MFA_LOGIN_INCOMPLETE', 'You must provide MFA login details'),
442
                ValidationResult::TYPE_ERROR
443
            );
444
            return $this->redirect($this->getBackURL() ?: $loginUrl);
445
        }
446
447
        $request = $this->getRequest();
448
        /** @var EnforcementManager $enforcementManager */
449
        $enforcementManager = EnforcementManager::create();
450
451
        // Assert that the member has a valid registration.
452
        // This is potentially redundant logic as the member should only be logged in if they've fully registered.
453
        // They're allowed to login if they can skip - so only do assertions if they're not allowed to skip
454
        // We'll also check that they've registered the required MFA details
455
        if (
456
            !$enforcementManager->canSkipMFA($member)
457
            && !$enforcementManager->hasCompletedRegistration($member)
458
        ) {
459
            // Log them out again
460
            /** @var IdentityStore $identityStore */
461
            $identityStore = Injector::inst()->get(IdentityStore::class);
462
            $identityStore->logOut($request);
463
464
            Security::singleton()->setSessionMessage(
465
                _t(__CLASS__ . '.INVALID_REGISTRATION', 'You must complete MFA registration'),
466
                ValidationResult::TYPE_ERROR
467
            );
468
            return $this->redirect($this->getBackURL() ?: $loginUrl);
469
        }
470
471
        // Redirecting after successful login expects a getVar to be set, store it before clearing the session data
472
        /** @see HTTPRequest::offsetSet */
473
        $request['BackURL'] = $this->getBackURL();
474
475
        // Clear the "additional data"
476
        $request->getSession()->clear(static::SESSION_KEY . '.additionalData');
477
478
        // Ensure any left over session state is cleaned up
479
        $store = $this->getStore();
480
        if ($store) {
0 ignored issues
show
introduced by
$store is of type SilverStripe\MFA\Store\StoreInterface, thus it always evaluated to true.
Loading history...
481
            $store->clear($request);
482
        }
483
        $request->getSession()->clear(static::SESSION_KEY . '.mustLogin');
484
485
        // Delegate to parent logic
486
        return parent::redirectAfterSuccessfulLogin();
487
    }
488
489
    /**
490
     * @return Member&MemberExtension
491
     * @throws MemberNotFoundException
492
     */
493
    public function getMember()
494
    {
495
        $store = $this->getStore();
496
497
        if ($store && $store->getMember()) {
498
            return $store->getMember();
499
        }
500
501
        $member = Security::getCurrentUser();
502
503
        // If we don't have a valid member we shouldn't be here...
504
        if (!$member) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
505
            throw new MemberNotFoundException();
506
        }
507
508
        return $member;
509
    }
510
511
    /**
512
     * @param LoggerInterface $logger
513
     * @return $this
514
     */
515
    public function setLogger(LoggerInterface $logger): self
516
    {
517
        $this->logger = $logger;
518
        return $this;
519
    }
520
521
    /**
522
     * @return LoggerInterface
523
     */
524
    public function getLogger(): ?LoggerInterface
525
    {
526
        return $this->logger;
527
    }
528
529
    /**
530
     * Adds more options for the back URL - to be returned from a current MFA session store
531
     *
532
     * @return string|null
533
     */
534
    public function getBackURL(): ?string
535
    {
536
        $backURL = parent::getBackURL();
537
538
        if (!$backURL && $this->getRequest()) {
539
            $data = $this->getRequest()->getSession()->get(static::SESSION_KEY . '.additionalData');
540
            if (isset($data['BackURL'])) {
541
                $backURL = $data['BackURL'];
542
            }
543
        }
544
545
        return $backURL;
546
    }
547
548
    /**
549
     * Respond with the given array as a JSON response
550
     *
551
     * @param array $response
552
     * @param int $code The HTTP response code to set on the response
553
     * @return HTTPResponse
554
     */
555
    public function jsonResponse(array $response, int $code = 200): HTTPResponse
556
    {
557
        return HTTPResponse::create(json_encode($response))
558
            ->addHeader('Content-Type', 'application/json')
559
            ->setStatusCode($code);
560
    }
561
562
    /**
563
     * Complete the login process for the given member by calling "performLogin" on the parent class
564
     *
565
     * @param HTTPRequest $request
566
     * @param Member&MemberExtension $member
567
     */
568
    protected function doPerformLogin(HTTPRequest $request, Member $member)
569
    {
570
        // Load the previously stored data from session and perform the login using it...
571
        $data = $request->getSession()->get(static::SESSION_KEY . '.additionalData') ?: [];
572
573
        // Check that we don't have a logged in member before actually performing a login
574
        $currentMember = Security::getCurrentUser();
575
576
        if (!$currentMember) {
0 ignored issues
show
introduced by
$currentMember is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
577
            // These next two lines are pulled from "parent::doLogin()"
578
            $this->performLogin($member, $data, $request);
579
            // Allow operations on the member after successful login
580
            parent::extend('afterLogin', $member);
581
        }
582
    }
583
584
    /**
585
     * @return MethodRegistry
586
     */
587
    protected function getMethodRegistry(): MethodRegistry
588
    {
589
        return MethodRegistry::singleton();
590
    }
591
}
592