silverstripe /
silverstripe-webauthn-authenticator
| 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
|
|||
| 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 |
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.