VerifyHandlerTest   A
last analyzed

Complexity

Total Complexity 8

Size/Duplication

Total Lines 175
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 76
c 3
b 0
f 1
dl 0
loc 175
rs 10
wmc 8

6 Methods

Rating   Name   Duplication   Size   Complexity  
A testStart() 0 4 1
A testStartThrowsExceptionWithMissingData() 0 4 1
A testVerifyReturnsErrorWhenRequiredInformationIsMissing() 0 7 1
A verifyProvider() 0 23 1
A setUp() 0 35 1
A testVerify() 0 39 3
1
<?php
2
3
namespace SilverStripe\WebAuthn\Tests;
4
5
use Exception;
6
use PHPUnit_Framework_MockObject_MockObject;
7
use Psr\Log\LoggerInterface;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\Dev\SapphireTest;
11
use SilverStripe\MFA\Model\RegisteredMethod;
12
use SilverStripe\MFA\State\Result;
13
use SilverStripe\MFA\Store\SessionStore;
14
use SilverStripe\Security\Member;
15
use SilverStripe\WebAuthn\VerifyHandler;
16
use Webauthn\AuthenticatorAssertionResponse;
17
use Webauthn\AuthenticatorAssertionResponseValidator;
18
use Webauthn\AuthenticatorAttestationResponse;
19
use Webauthn\AuthenticatorResponse;
20
use Webauthn\PublicKeyCredential;
21
use Webauthn\PublicKeyCredentialLoader;
22
23
class VerifyHandlerTest extends SapphireTest
24
{
25
    protected $usesDatabase = true;
26
27
    /**
28
     * @var VerifyHandler
29
     */
30
    protected $handler;
31
32
    /**
33
     * @var Member
34
     */
35
    protected $member;
36
37
    /**
38
     * @var HTTPRequest
39
     */
40
    protected $request;
41
42
    /**
43
     * @var SessionStore
44
     */
45
    protected $store;
46
47
    /**
48
     * @var RegisteredMethod
49
     */
50
    protected $registeredMethod;
51
52
    /**
53
     * @var array
54
     */
55
    protected $mockData = [];
56
57
    protected function setUp()
58
    {
59
        parent::setUp();
60
61
        $this->request = new HTTPRequest('GET', '/');
62
        $this->handler = Injector::inst()->create(VerifyHandler::class);
63
64
        $memberID = $this->logInWithPermission();
65
        /** @var Member $member */
66
        $this->member = Member::get()->byID($memberID);
67
68
        $this->store = new SessionStore($this->member);
69
70
        $this->registeredMethod = new RegisteredMethod();
71
72
        // phpcs:disable
73
        $this->registeredMethod->Data = json_encode([
74
            'g8e1UH4B1gUYl\/7AiDXHTp8SE3cxYnpC6jF3Fo0KMm79FNN\/e34hDE1Mnd4FSOoNW6B+p7xB2tqj28svkJQh1Q==' => [
75
                'source' => [
76
                    'publicKeyCredentialId' => 'g8e1UH4B1gUYl_7AiDXHTp8SE3cxYnpC6jF3Fo0KMm79FNN_e34hDE1Mnd4FSOoNW6B-p7xB2tqj28svkJQh1Q',
77
                    'type' => 'public-key',
78
                    'transports' =>
79
                        array (
80
                        ),
81
                    'attestationType' => 'none',
82
                    'trustPath' =>
83
                        array (
84
                            'type' => 'empty',
85
                        ),
86
                    'aaguid' => 'AAAAAAAAAAAAAAAAAAAAAA',
87
                    'credentialPublicKey' => 'pQECAyYgASFYII3gDdvOBje5JfjNO0VhxE2RrV5XoKqWmCZAmR0f9nFaIlggZOUvkovGH9cfeyfXEpJAVOzR1d-rVRZJvwWJf444aLo',
88
                    'userHandle' => 'MQ',
89
                    'counter' => 268,
90
                ],
91
                'counter' => 0,
92
            ]
93
        ]);
94
        // phpcs:enable
95
    }
96
97
    /**
98
     * @expectedException \SilverStripe\MFA\Exception\AuthenticationFailedException
99
     */
100
    public function testStartThrowsExceptionWithMissingData()
101
    {
102
        $this->registeredMethod->Data = '';
103
        $this->handler->start($this->store, $this->registeredMethod);
104
    }
105
106
    public function testStart()
107
    {
108
        $result = $this->handler->start($this->store, $this->registeredMethod);
109
        $this->assertArrayHasKey('publicKey', $result);
110
    }
111
112
    public function testVerifyReturnsErrorWhenRequiredInformationIsMissing()
113
    {
114
        $this->registeredMethod->Data = null;
115
        $result = $this->handler->verify($this->request, $this->store, $this->registeredMethod);
116
117
        $this->assertFalse($result->isSuccessful());
118
        $this->assertContains('Incomplete data', $result->getMessage());
119
    }
120
121
    /**
122
     * @param AuthenticatorResponse $mockResponse
123
     * @param Result $expectedResult
124
     * @param callable $responseValidatorMockCallback
125
     * @dataProvider verifyProvider
126
     */
127
    public function testVerify(
128
        $mockResponse,
129
        $expectedResult,
130
        callable $responseValidatorMockCallback = null
131
    ) {
132
        /** @var VerifyHandler&PHPUnit_Framework_MockObject_MockObject $handlerMock */
133
        $handlerMock = $this->getMockBuilder(VerifyHandler::class)
134
            ->setMethods(['getPublicKeyCredentialLoader', 'getAuthenticatorAssertionResponseValidator'])
135
            ->getMock();
136
137
        $responseValidatorMock = $this->createMock(AuthenticatorAssertionResponseValidator::class);
138
        // Allow the data provider to customise the validation check handling
139
        if ($responseValidatorMockCallback) {
140
            $responseValidatorMockCallback($responseValidatorMock);
141
        }
142
        $handlerMock->expects($this->any())->method('getAuthenticatorAssertionResponseValidator')
143
            ->willReturn($responseValidatorMock);
144
145
        $loggerMock = $this->createMock(LoggerInterface::class);
146
        $handlerMock->setLogger($loggerMock);
147
148
        $loaderMock = $this->createMock(PublicKeyCredentialLoader::class);
149
        $handlerMock->expects($this->once())->method('getPublicKeyCredentialLoader')->willReturn($loaderMock);
150
151
        $publicKeyCredentialMock = $this->createMock(PublicKeyCredential::class);
152
        $loaderMock->expects($this->once())->method('load')->with('example')->willReturn(
153
            $publicKeyCredentialMock
154
        );
155
156
        $publicKeyCredentialMock->expects($this->once())->method('getResponse')->willReturn($mockResponse);
157
158
        $this->request->setBody(json_encode([
159
            'credentials' => base64_encode('example'),
160
        ]));
161
        $result = $handlerMock->verify($this->request, $this->store, $this->registeredMethod);
0 ignored issues
show
Bug introduced by
The method verify() does not exist on PHPUnit_Framework_MockObject_MockObject. Did you maybe mean __phpunit_verify()? ( Ignorable by Annotation )

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

161
        /** @scrutinizer ignore-call */ 
162
        $result = $handlerMock->verify($this->request, $this->store, $this->registeredMethod);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
162
163
        $this->assertSame($expectedResult->isSuccessful(), $result->isSuccessful());
164
        if ($expectedResult->getMessage()) {
165
            $this->assertContains($expectedResult->getMessage(), $result->getMessage());
166
        }
167
    }
168
169
    /**
170
     * Some centralised or reusable logic for testVerify. Note that some of the mocks are only used in some of the
171
     * provided data scenarios, but any expected call numbers are based on all scenarios being run.
172
     *
173
     * @return array[]
174
     */
175
    public function verifyProvider()
176
    {
177
        return [
178
            'wrong response return type' => [
179
                // Deliberately the wrong child implementation of \Webauthn\AuthenticatorResponse
180
                $this->createMock(AuthenticatorAttestationResponse::class),
181
                new Result(false, 'Unexpected response type found'),
182
            ],
183
            'valid response' => [
184
                $this->createMock(AuthenticatorAssertionResponse::class),
185
                new Result(true),
186
                function (PHPUnit_Framework_MockObject_MockObject $responseValidatorMock) {
187
                    // Specifically setting expectations for the result of the response validator's "check" call
188
                    $responseValidatorMock->expects($this->once())->method('check')->willReturn(true);
189
                },
190
            ],
191
            'invalid response' => [
192
                $this->createMock(AuthenticatorAssertionResponse::class),
193
                new Result(false, 'I am a test'),
194
                function (PHPUnit_Framework_MockObject_MockObject $responseValidatorMock) {
195
                    // Specifically setting expectations for the result of the response validator's "check" call
196
                    $responseValidatorMock->expects($this->once())->method('check')
197
                        ->willThrowException(new Exception('I am a test'));
198
                },
199
            ],
200
        ];
201
    }
202
}
203