Completed
Push — master ( a08ba5...c0fb6f )
by
unknown
28s queued 12s
created

VerifyHandler::setLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\WebAuthn;
4
5
use CBOR\Decoder;
6
use CBOR\OtherObject\OtherObjectManager;
7
use CBOR\Tag\TagObjectManager;
8
use Exception;
9
use GuzzleHttp\Psr7\ServerRequest;
10
use Psr\Log\LoggerInterface;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\MFA\Method\Handler\VerifyHandlerInterface;
13
use SilverStripe\MFA\Model\RegisteredMethod;
14
use SilverStripe\MFA\State\Result;
15
use SilverStripe\MFA\Store\StoreInterface;
16
use Webauthn\AttestationStatement\AttestationObjectLoader;
17
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
18
use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport;
19
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
20
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
21
use Webauthn\AuthenticatorAssertionResponse;
22
use Webauthn\AuthenticatorAssertionResponseValidator;
23
use Webauthn\PublicKeyCredentialDescriptor;
24
use Webauthn\PublicKeyCredentialLoader;
25
use Webauthn\PublicKeyCredentialRequestOptions;
26
use Webauthn\TokenBinding\TokenBindingNotSupportedHandler;
27
28
class VerifyHandler implements VerifyHandlerInterface
29
{
30
    /**
31
     * Dependency injection configuration
32
     *
33
     * @config
34
     * @var array
35
     */
36
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
37
        'Logger' => LoggerInterface::class . '.mfa',
38
    ];
39
40
    /**
41
     * @var LoggerInterface
42
     */
43
    protected $logger = null;
44
45
    /**
46
     * Sets the {@see $logger} member variable
47
     *
48
     * @param LoggerInterface|null $logger
49
     * @return self
50
     */
51
    public function setLogger(?LoggerInterface $logger): self
52
    {
53
        $this->logger = $logger;
54
        return $this;
55
    }
56
57
    /**
58
     * Stores any data required to handle a login process with a method, and returns relevant state to be applied to the
59
     * front-end application managing the process.
60
     *
61
     * @param StoreInterface $store An object that hold session data (and the Member) that can be mutated
62
     * @param RegisteredMethod $method The RegisteredMethod instance that is being verified
63
     * @return array Props to be passed to a front-end component
64
     */
65
    public function start(StoreInterface $store, RegisteredMethod $method): array
66
    {
67
        return [
68
            'publicKey' => $this->getCredentialRequestOptions($store, $method, true),
69
        ];
70
    }
71
72
    /**
73
     * Verify the request has provided the right information to verify the member that aligns with any sessions state
74
     * that may have been set prior
75
     *
76
     * @param HTTPRequest $request
77
     * @param StoreInterface $store
78
     * @param RegisteredMethod $registeredMethod The RegisteredMethod instance that is being verified
79
     * @return Result
80
     * @throws Exception
81
     */
82
    public function verify(HTTPRequest $request, StoreInterface $store, RegisteredMethod $registeredMethod): Result
83
    {
84
        $options = $this->getCredentialRequestOptions($store, $registeredMethod);
85
86
        $data = json_decode($request->getBody(), true);
87
88
        // CBOR
89
        $decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
90
91
        // Attestation statement support manager
92
        $attestationStatementSupportManager = new AttestationStatementSupportManager();
93
        $attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
94
        $attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport($decoder));
95
96
        // Attestation object loader
97
        $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager, $decoder);
98
99
        $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader, $decoder);
100
101
        $credentialRepository = new CredentialRepository($store->getMember(), $registeredMethod);
0 ignored issues
show
Bug introduced by
It seems like $store->getMember() can also be of type null; however, parameter $member of SilverStripe\WebAuthn\Cr...pository::__construct() 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

101
        $credentialRepository = new CredentialRepository(/** @scrutinizer ignore-type */ $store->getMember(), $registeredMethod);
Loading history...
102
103
        $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
104
            $credentialRepository,
105
            $decoder,
106
            new TokenBindingNotSupportedHandler(),
107
            new ExtensionOutputCheckerHandler()
108
        );
109
110
        // Create a PSR-7 request
111
        $psrRequest = ServerRequest::fromGlobals();
112
113
        try {
114
            $publicKeyCredential = $publicKeyCredentialLoader->load(base64_decode($data['credentials']));
115
            $response = $publicKeyCredential->getResponse();
116
117
            if (!$response instanceof AuthenticatorAssertionResponse) {
118
                throw new ResponseTypeException('Unexpected response type found');
119
            }
120
121
            $authenticatorAssertionResponseValidator->check(
122
                $publicKeyCredential->getRawId(),
123
                $publicKeyCredential->getResponse(),
124
                $options,
125
                $psrRequest,
126
                (string) $store->getMember()->ID
127
            );
128
129
            return Result::create();
130
        } catch (Exception $e) {
131
            $this->logger->error($e->getMessage());
132
            throw $e;
133
        }
134
    }
135
136
    /**
137
     * Provide a localised string that serves as a lead in for choosing this option for authentication
138
     *
139
     * eg. "Enter one of your recovery codes"
140
     *
141
     * @return string
142
     */
143
    public function getLeadInLabel(): string
144
    {
145
        return _t(__CLASS__ . '.LEAD_IN', 'Verify with security key');
146
    }
147
148
    /**
149
     * Get the key that a React UI component is registered under (with @silverstripe/react-injector on the front-end)
150
     *
151
     * @return string
152
     */
153
    public function getComponent(): string
154
    {
155
        return 'WebAuthnVerify';
156
    }
157
158
    protected function getCredentialRequestOptions(
159
        StoreInterface $store,
160
        RegisteredMethod $registeredMethod,
161
        $reset = false
162
    ): PublicKeyCredentialRequestOptions {
163
        $state = $store->getState();
164
165
        if (!$reset && !empty($state) && !empty($state['credentialOptions'])) {
166
            return PublicKeyCredentialRequestOptions::createFromArray($state['credentialOptions']);
167
        }
168
169
        $data = json_decode($registeredMethod->Data, true);
170
        $descriptor = PublicKeyCredentialDescriptor::createFromArray($data['descriptor']);
171
172
        $options = new PublicKeyCredentialRequestOptions(
173
            random_bytes(32),
174
            40000,
175
            null,
176
            [$descriptor],
177
            PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED
178
        );
179
180
        $state['credentialOptions'] = $options;
181
        $store->setState($state);
182
183
        return $options;
184
    }
185
}
186