RegistrationHandlerTrait   A
last analyzed

Complexity

Total Complexity 8

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 37
c 2
b 0
f 0
dl 0
loc 94
rs 10
wmc 8

2 Methods

Rating   Name   Duplication   Size   Complexity  
A completeRegistrationRequest() 0 41 5
A createStartRegistrationResponse() 0 30 3
1
<?php
2
3
namespace SilverStripe\MFA\RequestHandler;
4
5
use Exception;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Control\HTTPResponse;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\MFA\Method\MethodInterface;
10
use SilverStripe\MFA\Service\RegisteredMethodManager;
11
use SilverStripe\MFA\State\RegisteredMethodDetailsInterface;
12
use SilverStripe\MFA\State\Result;
13
use SilverStripe\MFA\Store\StoreInterface;
14
use SilverStripe\ORM\ValidationResult;
15
use SilverStripe\Security\SecurityToken;
16
17
/**
18
 * This trait encapsulates logic that can be added to a `RequestHandler` to work with registering MFA authenticators
19
 * using the MFA front-end app. It provides two main methods; @see createStartRegistrationResponse - creates a response
20
 * that can be easily consumed by the MFA app to start the registration process for a method, and
21
 * @see completeRegistrationRequest - used to complete the registration flow for a method using details sent back by the
22
 * MFA app.
23
 */
24
trait RegistrationHandlerTrait
25
{
26
    /**
27
     * Create a response that can be consumed by a front-end for starting a registration
28
     *
29
     * @param StoreInterface $store
30
     * @param MethodInterface $method
31
     * @param bool $allowReregistration By default this method will return an error response when registering methods
32
     *                                  that already have a registration.
33
     * @return HTTPResponse
34
     */
35
    public function createStartRegistrationResponse(
36
        StoreInterface $store,
37
        MethodInterface $method,
38
        bool $allowReregistration = false
39
    ): HTTPResponse {
40
        $member = $store->getMember();
41
42
        // Sanity check that the method hasn't already been registered
43
        $existingRegisteredMethod = RegisteredMethodManager::singleton()->getFromMember($member, $method);
44
45
        $response = HTTPResponse::create()
46
            ->addHeader('Content-Type', 'application/json');
47
48
        if (!$allowReregistration && $existingRegisteredMethod) {
49
            return $response->setBody(json_encode(['errors' => [_t(
50
                __CLASS__ . '.METHOD_ALREADY_REGISTERED',
51
                'That method has already been registered against this Member'
52
            )]]))->setStatusCode(400);
53
        }
54
55
        // Mark the given method as started within the session
56
        $store->setMethod($method->getURLSegment());
57
        // Allow the registration handler to begin the process and generate some data to pass through to the front-end
58
        $data = $method->getRegisterHandler()->start($store);
59
60
        // Add a CSRF token
61
        $token = SecurityToken::inst();
62
        $data[$token->getName()] = $token->getValue();
63
64
        return $response->setBody(json_encode($data));
65
    }
66
67
    /**
68
     * Complete a registration request, returning a result object with a message and context for the result of the
69
     * registration attempt.
70
     *
71
     * @param StoreInterface $store
72
     * @param MethodInterface $method
73
     * @param HTTPRequest $request
74
     * @return Result
75
     * @throws \SilverStripe\ORM\ValidationException
76
     */
77
    public function completeRegistrationRequest(
78
        StoreInterface $store,
79
        MethodInterface $method,
80
        HTTPRequest $request
81
    ): Result {
82
        if (!SecurityToken::inst()->checkRequest($request)) {
83
            return Result::create(false, _t(
84
                __CLASS__ . '.CSRF_FAILURE',
85
                'Your request timed out. Please refresh and try again'
86
            ), ['code' => 403]);
87
        }
88
89
        $storedMethodName = $store->getMethod();
90
91
        // If a registration process hasn't been initiated in a previous request, calling this method is invalid
92
        if (!$storedMethodName) {
93
            return Result::create(false, _t(__CLASS__ . '.NO_REGISTRATION_IN_PROGRESS', 'No registration in progress'));
94
        }
95
96
        // Assert the method in progress matches the request for completion
97
        if ($storedMethodName !== $method->getURLSegment()) {
98
            return Result::create(
99
                false,
100
                _t(__CLASS__ . '.METHOD_MISMATCH', 'Method does not match registration in progress')
101
            );
102
        }
103
104
        $registrationHandler = $method->getRegisterHandler();
105
        $result = $registrationHandler->register($request, $store);
106
107
        $member = $store->getMember();
108
        if ($result->isSuccessful()) {
109
            RegisteredMethodManager::singleton()
110
                ->registerForMember($member, $method, $result->getContext());
111
        } else {
112
            $this->extend('onRegisterMethodFailure', $member, $method);
0 ignored issues
show
Bug introduced by
It seems like extend() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

112
            $this->/** @scrutinizer ignore-call */ 
113
                   extend('onRegisterMethodFailure', $member, $method);
Loading history...
113
        }
114
115
        // Replace the context with detail of the registered method
116
        return $result->setContext([
117
            'registeredMethod' => Injector::inst()->create(RegisteredMethodDetailsInterface::class, $method)
118
        ]);
119
    }
120
}
121