silverstripe /
silverstripe-mfa
| 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
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 |