@@ -15,8 +15,7 @@ |
||
| 15 | 15 | use MaxBeckers\AmazonAlexa\Response\Directives\Display\TextContent; |
| 16 | 16 | use PHPUnit\Framework\TestCase; |
| 17 | 17 | |
| 18 | -class DisplayTest extends TestCase |
|
| 19 | -{ |
|
| 18 | +class DisplayTest extends TestCase { |
|
| 20 | 19 | public function testText(): void |
| 21 | 20 | { |
| 22 | 21 | $text = Text::create('Test'); |
@@ -13,8 +13,7 @@ |
||
| 13 | 13 | use MaxBeckers\AmazonAlexa\Response\Directives\GameEngine\StopInputHandlerDirective; |
| 14 | 14 | use PHPUnit\Framework\TestCase; |
| 15 | 15 | |
| 16 | -class GameEngineTest extends TestCase |
|
| 17 | -{ |
|
| 16 | +class GameEngineTest extends TestCase { |
|
| 18 | 17 | public function testStartInputHandlerDirective(): void |
| 19 | 18 | { |
| 20 | 19 | $pattern = Pattern::create(Pattern::ACTION_UP, ['gadgetId1', 'gadgetId2'], ['blue']); |
@@ -11,8 +11,7 @@ |
||
| 11 | 11 | use MaxBeckers\AmazonAlexa\Response\Directives\Display\TextContent; |
| 12 | 12 | use PHPUnit\Framework\TestCase; |
| 13 | 13 | |
| 14 | -class TemplateTest extends TestCase |
|
| 15 | -{ |
|
| 14 | +class TemplateTest extends TestCase { |
|
| 16 | 15 | public function testSerializeTypeAndToken(): void |
| 17 | 16 | { |
| 18 | 17 | $type = 'BodyTemplate1'; |
@@ -9,8 +9,7 @@ |
||
| 9 | 9 | use MaxBeckers\AmazonAlexa\Response\Directives\Display\TextContent; |
| 10 | 10 | use PHPUnit\Framework\TestCase; |
| 11 | 11 | |
| 12 | -class TextContentTest extends TestCase |
|
| 13 | -{ |
|
| 12 | +class TextContentTest extends TestCase { |
|
| 14 | 13 | public function testSerializePrimaryOnly(): void |
| 15 | 14 | { |
| 16 | 15 | $primaryText = Text::create('primaryText'); |
@@ -9,8 +9,7 @@ |
||
| 9 | 9 | use MaxBeckers\AmazonAlexa\Response\Directives\VideoApp\VideoLaunchDirective; |
| 10 | 10 | use PHPUnit\Framework\TestCase; |
| 11 | 11 | |
| 12 | -class VideoAppTest extends TestCase |
|
| 13 | -{ |
|
| 12 | +class VideoAppTest extends TestCase { |
|
| 14 | 13 | public function testMetadata(): void |
| 15 | 14 | { |
| 16 | 15 | $meta = Metadata::create(); |
@@ -10,8 +10,7 @@ |
||
| 10 | 10 | use MaxBeckers\AmazonAlexa\Response\CardImage; |
| 11 | 11 | use PHPUnit\Framework\TestCase; |
| 12 | 12 | |
| 13 | -class CardTest extends TestCase |
|
| 14 | -{ |
|
| 13 | +class CardTest extends TestCase { |
|
| 15 | 14 | public function testSimpleCard(): void |
| 16 | 15 | { |
| 17 | 16 | $title = 'title'; |
@@ -10,8 +10,7 @@ |
||
| 10 | 10 | use MaxBeckers\AmazonAlexa\Response\OutputSpeech; |
| 11 | 11 | use PHPUnit\Framework\TestCase; |
| 12 | 12 | |
| 13 | -class ResponseHelperTest extends TestCase |
|
| 14 | -{ |
|
| 13 | +class ResponseHelperTest extends TestCase { |
|
| 15 | 14 | private const RESPOND = 'Respond'; |
| 16 | 15 | private const REPROMPT = 'Reprompt'; |
| 17 | 16 | |
@@ -9,8 +9,7 @@ |
||
| 9 | 9 | use MaxBeckers\AmazonAlexa\Response\CanFulfill\CanFulfillSlot; |
| 10 | 10 | use PHPUnit\Framework\TestCase; |
| 11 | 11 | |
| 12 | -class CanFulfillResponseBodyTest extends TestCase |
|
| 13 | -{ |
|
| 12 | +class CanFulfillResponseBodyTest extends TestCase { |
|
| 14 | 13 | public function testJsonSerialize(): void |
| 15 | 14 | { |
| 16 | 15 | $slot1 = CanFulfillSlot::create(CanFulfillSlot::CAN_UNDERSTAND_YES, CanFulfillSlot::CAN_FULFILL_YES); |
@@ -54,7 +54,7 @@ discard block |
||
| 54 | 54 | $requestValidator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
| 55 | 55 | |
| 56 | 56 | $client->method('request') |
| 57 | - ->willReturn($apiResponse); |
|
| 57 | + ->willReturn($apiResponse); |
|
| 58 | 58 | $apiResponse->method('getStatusCode') |
| 59 | 59 | ->willReturn(200); |
| 60 | 60 | $apiResponse->method('getBody') |
@@ -82,7 +82,7 @@ discard block |
||
| 82 | 82 | $requestValidator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
| 83 | 83 | |
| 84 | 84 | $client->method('request') |
| 85 | - ->willReturn($apiResponse); |
|
| 85 | + ->willReturn($apiResponse); |
|
| 86 | 86 | $apiResponse->method('getStatusCode') |
| 87 | 87 | ->willReturn(400); |
| 88 | 88 | |
@@ -14,8 +14,7 @@ discard block |
||
| 14 | 14 | use Psr\Http\Message\ResponseInterface; |
| 15 | 15 | use Psr\Http\Message\StreamInterface; |
| 16 | 16 | |
| 17 | -class RequestValidatorTest extends TestCase |
|
| 18 | -{ |
|
| 17 | +class RequestValidatorTest extends TestCase { |
|
| 19 | 18 | public function testInvalidRequestTime(): void |
| 20 | 19 | { |
| 21 | 20 | $requestValidator = new RequestValidator(); |
@@ -112,8 +111,7 @@ discard block |
||
| 112 | 111 | |
| 113 | 112 | // validateSignature will fail (no real cert), so disable signature path by overriding property |
| 114 | 113 | $intentWithNoSignature = new class ($intent) extends IntentRequest { |
| 115 | - public function __construct(IntentRequest $base) |
|
| 116 | - { |
|
| 114 | + public function __construct(IntentRequest $base) { |
|
| 117 | 115 | $this->timestamp = $base->timestamp; |
| 118 | 116 | $this->type = $base->type; |
| 119 | 117 | } |
@@ -138,8 +136,7 @@ discard block |
||
| 138 | 136 | $r = new Request(); |
| 139 | 137 | $r->request = $intent; |
| 140 | 138 | $intent = new class ($intent) extends IntentRequest { |
| 141 | - public function __construct(IntentRequest $base) |
|
| 142 | - { |
|
| 139 | + public function __construct(IntentRequest $base) { |
|
| 143 | 140 | $this->timestamp = $base->timestamp; |
| 144 | 141 | $this->type = $base->type; |
| 145 | 142 | } |
@@ -172,8 +169,7 @@ discard block |
||
| 172 | 169 | { |
| 173 | 170 | $validator = new RequestValidator(); |
| 174 | 171 | $intent = new class () extends IntentRequest { |
| 175 | - public function __construct() |
|
| 176 | - { |
|
| 172 | + public function __construct() { |
|
| 177 | 173 | $this->timestamp = new \DateTime('-5 hours'); // would normally fail |
| 178 | 174 | $this->type = 'test'; |
| 179 | 175 | } |
@@ -201,8 +197,7 @@ discard block |
||
| 201 | 197 | $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
| 202 | 198 | |
| 203 | 199 | $intent = new class () extends IntentRequest { |
| 204 | - public function __construct() |
|
| 205 | - { |
|
| 200 | + public function __construct() { |
|
| 206 | 201 | $this->timestamp = new \DateTime(); |
| 207 | 202 | $this->type = 'test'; |
| 208 | 203 | } |
@@ -235,8 +230,7 @@ discard block |
||
| 235 | 230 | $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
| 236 | 231 | |
| 237 | 232 | $intent = new class () extends IntentRequest { |
| 238 | - public function __construct() |
|
| 239 | - { |
|
| 233 | + public function __construct() { |
|
| 240 | 234 | $this->timestamp = new \DateTime(); |
| 241 | 235 | $this->type = 'test'; |
| 242 | 236 | } |
@@ -268,8 +262,7 @@ discard block |
||
| 268 | 262 | $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
| 269 | 263 | |
| 270 | 264 | $intent = new class () extends IntentRequest { |
| 271 | - public function __construct() |
|
| 272 | - { |
|
| 265 | + public function __construct() { |
|
| 273 | 266 | $this->timestamp = new \DateTime(); |
| 274 | 267 | $this->type = 'test'; |
| 275 | 268 | } |
@@ -1,302 +1,302 @@ |
||
| 1 | -<?php |
|
| 1 | + <?php |
|
| 2 | 2 | |
| 3 | -declare(strict_types=1); |
|
| 3 | + declare(strict_types=1); |
|
| 4 | 4 | |
| 5 | -namespace MaxBeckers\AmazonAlexa\Test\Validation; |
|
| 5 | + namespace MaxBeckers\AmazonAlexa\Test\Validation; |
|
| 6 | 6 | |
| 7 | -use GuzzleHttp\Client; |
|
| 8 | -use MaxBeckers\AmazonAlexa\Exception\RequestInvalidSignatureException; |
|
| 9 | -use MaxBeckers\AmazonAlexa\Exception\RequestInvalidTimestampException; |
|
| 10 | -use MaxBeckers\AmazonAlexa\Request\Request; |
|
| 11 | -use MaxBeckers\AmazonAlexa\Request\Request\Standard\IntentRequest; |
|
| 12 | -use MaxBeckers\AmazonAlexa\Validation\RequestValidator; |
|
| 13 | -use PHPUnit\Framework\TestCase; |
|
| 14 | -use Psr\Http\Message\ResponseInterface; |
|
| 15 | -use Psr\Http\Message\StreamInterface; |
|
| 7 | + use GuzzleHttp\Client; |
|
| 8 | + use MaxBeckers\AmazonAlexa\Exception\RequestInvalidSignatureException; |
|
| 9 | + use MaxBeckers\AmazonAlexa\Exception\RequestInvalidTimestampException; |
|
| 10 | + use MaxBeckers\AmazonAlexa\Request\Request; |
|
| 11 | + use MaxBeckers\AmazonAlexa\Request\Request\Standard\IntentRequest; |
|
| 12 | + use MaxBeckers\AmazonAlexa\Validation\RequestValidator; |
|
| 13 | + use PHPUnit\Framework\TestCase; |
|
| 14 | + use Psr\Http\Message\ResponseInterface; |
|
| 15 | + use Psr\Http\Message\StreamInterface; |
|
| 16 | 16 | |
| 17 | -class RequestValidatorTest extends TestCase |
|
| 18 | -{ |
|
| 19 | - public function testInvalidRequestTime(): void |
|
| 17 | + class RequestValidatorTest extends TestCase |
|
| 20 | 18 | { |
| 21 | - $requestValidator = new RequestValidator(); |
|
| 22 | - |
|
| 23 | - $intentRequest = new IntentRequest(); |
|
| 24 | - $intentRequest->type = 'test'; |
|
| 25 | - $intentRequest->timestamp = new \DateTime('-1 hour'); |
|
| 26 | - $request = new Request(); |
|
| 27 | - $request->request = $intentRequest; |
|
| 28 | - |
|
| 29 | - $this->expectException(RequestInvalidTimestampException::class); |
|
| 30 | - $requestValidator->validate($request); |
|
| 31 | - } |
|
| 32 | - |
|
| 33 | - public function testInvalidSignatureCertChainUrl(): void |
|
| 34 | - { |
|
| 35 | - $requestValidator = new RequestValidator(); |
|
| 36 | - |
|
| 37 | - $intentRequest = new IntentRequest(); |
|
| 38 | - $intentRequest->type = 'test'; |
|
| 39 | - $intentRequest->timestamp = new \DateTime(); |
|
| 40 | - $request = new Request(); |
|
| 41 | - $request->request = $intentRequest; |
|
| 42 | - $request->signatureCertChainUrl = 'wrong path'; |
|
| 43 | - $request->signature = 'none'; |
|
| 44 | - |
|
| 45 | - $this->expectException(RequestInvalidSignatureException::class); |
|
| 46 | - $requestValidator->validate($request); |
|
| 47 | - } |
|
| 48 | - |
|
| 49 | - public function testWrongSignatureCertChainUrl(): void |
|
| 50 | - { |
|
| 51 | - $client = $this->createMock(Client::class); |
|
| 52 | - $apiResponse = $this->createMock(ResponseInterface::class); |
|
| 53 | - $apiResponseBody = $this->createMock(StreamInterface::class); |
|
| 54 | - $requestValidator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 55 | - |
|
| 56 | - $client->method('request') |
|
| 57 | - ->willReturn($apiResponse); |
|
| 58 | - $apiResponse->method('getStatusCode') |
|
| 59 | - ->willReturn(200); |
|
| 60 | - $apiResponse->method('getBody') |
|
| 61 | - ->willReturn($apiResponseBody); |
|
| 62 | - $apiResponseBody->method('getContents') |
|
| 63 | - ->willReturn('cert content'); |
|
| 64 | - |
|
| 65 | - $intentRequest = new IntentRequest(); |
|
| 66 | - $intentRequest->type = 'test'; |
|
| 67 | - $intentRequest->timestamp = new \DateTime(); |
|
| 68 | - $request = new Request(); |
|
| 69 | - $request->request = $intentRequest; |
|
| 70 | - $request->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/test.pem'; |
|
| 71 | - $request->signature = 'none'; |
|
| 72 | - $request->amazonRequestBody = ''; |
|
| 73 | - |
|
| 74 | - $this->expectException(RequestInvalidSignatureException::class); |
|
| 75 | - $requestValidator->validate($request); |
|
| 76 | - } |
|
| 77 | - |
|
| 78 | - public function testWrongSignatureCertChainUrlCallError(): void |
|
| 79 | - { |
|
| 80 | - $client = $this->createMock(Client::class); |
|
| 81 | - $apiResponse = $this->createMock(ResponseInterface::class); |
|
| 82 | - $requestValidator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 83 | - |
|
| 84 | - $client->method('request') |
|
| 85 | - ->willReturn($apiResponse); |
|
| 86 | - $apiResponse->method('getStatusCode') |
|
| 87 | - ->willReturn(400); |
|
| 88 | - |
|
| 89 | - $intentRequest = new IntentRequest(); |
|
| 90 | - $intentRequest->type = 'test'; |
|
| 91 | - $intentRequest->timestamp = new \DateTime(); |
|
| 92 | - $request = new Request(); |
|
| 93 | - $request->request = $intentRequest; |
|
| 94 | - $request->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/test.pem'; |
|
| 95 | - $request->signature = 'none'; |
|
| 96 | - $request->amazonRequestBody = ''; |
|
| 97 | - |
|
| 98 | - $this->expectException(RequestInvalidSignatureException::class); |
|
| 99 | - $requestValidator->validate($request); |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - public function testValidTimestampWithinTolerance(): void |
|
| 103 | - { |
|
| 104 | - $validator = new RequestValidator(); |
|
| 105 | - $intent = new IntentRequest(); |
|
| 106 | - $intent->timestamp = new \DateTime('-10 seconds'); |
|
| 107 | - $intent->type = 'test'; |
|
| 108 | - $r = new Request(); |
|
| 109 | - $r->request = $intent; |
|
| 110 | - $r->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; |
|
| 111 | - $r->signature = 'SGVsbG8='; // base64 "Hello" |
|
| 112 | - |
|
| 113 | - // validateSignature will fail (no real cert), so disable signature path by overriding property |
|
| 114 | - $intentWithNoSignature = new class ($intent) extends IntentRequest { |
|
| 115 | - public function __construct(IntentRequest $base) |
|
| 116 | - { |
|
| 117 | - $this->timestamp = $base->timestamp; |
|
| 118 | - $this->type = $base->type; |
|
| 119 | - } |
|
| 120 | - public function validateSignature(): bool |
|
| 121 | - { |
|
| 122 | - return false; |
|
| 123 | - } |
|
| 124 | - }; |
|
| 125 | - $r->request = $intentWithNoSignature; |
|
| 126 | - |
|
| 127 | - $validator->validate($r); |
|
| 128 | - $this->assertTrue(true); |
|
| 129 | - } |
|
| 19 | + public function testInvalidRequestTime(): void |
|
| 20 | + { |
|
| 21 | + $requestValidator = new RequestValidator(); |
|
| 22 | + |
|
| 23 | + $intentRequest = new IntentRequest(); |
|
| 24 | + $intentRequest->type = 'test'; |
|
| 25 | + $intentRequest->timestamp = new \DateTime('-1 hour'); |
|
| 26 | + $request = new Request(); |
|
| 27 | + $request->request = $intentRequest; |
|
| 28 | + |
|
| 29 | + $this->expectException(RequestInvalidTimestampException::class); |
|
| 30 | + $requestValidator->validate($request); |
|
| 31 | + } |
|
| 130 | 32 | |
| 131 | - public function testTimestampExactlyOnToleranceBoundaryPasses(): void |
|
| 132 | - { |
|
| 133 | - $tolerance = 50; |
|
| 134 | - $validator = new RequestValidator($tolerance); |
|
| 135 | - $intent = new IntentRequest(); |
|
| 136 | - $intent->timestamp = new \DateTime("-{$tolerance} seconds"); |
|
| 137 | - $intent->type = 'test'; |
|
| 138 | - $r = new Request(); |
|
| 139 | - $r->request = $intent; |
|
| 140 | - $intent = new class ($intent) extends IntentRequest { |
|
| 141 | - public function __construct(IntentRequest $base) |
|
| 142 | - { |
|
| 143 | - $this->timestamp = $base->timestamp; |
|
| 144 | - $this->type = $base->type; |
|
| 145 | - } |
|
| 146 | - public function validateSignature(): bool |
|
| 147 | - { |
|
| 148 | - return false; |
|
| 149 | - } |
|
| 150 | - }; |
|
| 151 | - $r->request = $intent; |
|
| 33 | + public function testInvalidSignatureCertChainUrl(): void |
|
| 34 | + { |
|
| 35 | + $requestValidator = new RequestValidator(); |
|
| 152 | 36 | |
| 153 | - $validator->validate($r); |
|
| 154 | - $this->assertTrue(true); |
|
| 155 | - } |
|
| 37 | + $intentRequest = new IntentRequest(); |
|
| 38 | + $intentRequest->type = 'test'; |
|
| 39 | + $intentRequest->timestamp = new \DateTime(); |
|
| 40 | + $request = new Request(); |
|
| 41 | + $request->request = $intentRequest; |
|
| 42 | + $request->signatureCertChainUrl = 'wrong path'; |
|
| 43 | + $request->signature = 'none'; |
|
| 156 | 44 | |
| 157 | - public function testTimestampJustOverToleranceFails(): void |
|
| 158 | - { |
|
| 159 | - $tolerance = 30; |
|
| 160 | - $validator = new RequestValidator($tolerance); |
|
| 161 | - $intent = new IntentRequest(); |
|
| 162 | - $intent->timestamp = new \DateTime('-' . ($tolerance + 1) . ' seconds'); |
|
| 163 | - $intent->type = 'test'; |
|
| 164 | - $r = new Request(); |
|
| 165 | - $r->request = $intent; |
|
| 166 | - |
|
| 167 | - $this->expectException(RequestInvalidTimestampException::class); |
|
| 168 | - $validator->validate($r); |
|
| 169 | - } |
|
| 170 | - |
|
| 171 | - public function testSkipTimestampValidationWhenRequestDisablesIt(): void |
|
| 172 | - { |
|
| 173 | - $validator = new RequestValidator(); |
|
| 174 | - $intent = new class () extends IntentRequest { |
|
| 175 | - public function __construct() |
|
| 176 | - { |
|
| 177 | - $this->timestamp = new \DateTime('-5 hours'); // would normally fail |
|
| 178 | - $this->type = 'test'; |
|
| 179 | - } |
|
| 180 | - public function validateTimestamp(): bool |
|
| 181 | - { |
|
| 182 | - return false; |
|
| 183 | - } |
|
| 184 | - public function validateSignature(): bool |
|
| 185 | - { |
|
| 186 | - return false; |
|
| 187 | - } |
|
| 188 | - }; |
|
| 189 | - $r = new Request(); |
|
| 190 | - $r->request = $intent; |
|
| 191 | - |
|
| 192 | - $validator->validate($r); |
|
| 193 | - $this->assertTrue(true); |
|
| 194 | - } |
|
| 195 | - |
|
| 196 | - public function testSkipSignatureValidationWhenRequestDisablesIt(): void |
|
| 197 | - { |
|
| 198 | - $client = $this->createMock(Client::class); |
|
| 199 | - $client->expects($this->never())->method('request'); |
|
| 45 | + $this->expectException(RequestInvalidSignatureException::class); |
|
| 46 | + $requestValidator->validate($request); |
|
| 47 | + } |
|
| 200 | 48 | |
| 201 | - $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 49 | + public function testWrongSignatureCertChainUrl(): void |
|
| 50 | + { |
|
| 51 | + $client = $this->createMock(Client::class); |
|
| 52 | + $apiResponse = $this->createMock(ResponseInterface::class); |
|
| 53 | + $apiResponseBody = $this->createMock(StreamInterface::class); |
|
| 54 | + $requestValidator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 55 | + |
|
| 56 | + $client->method('request') |
|
| 57 | + ->willReturn($apiResponse); |
|
| 58 | + $apiResponse->method('getStatusCode') |
|
| 59 | + ->willReturn(200); |
|
| 60 | + $apiResponse->method('getBody') |
|
| 61 | + ->willReturn($apiResponseBody); |
|
| 62 | + $apiResponseBody->method('getContents') |
|
| 63 | + ->willReturn('cert content'); |
|
| 64 | + |
|
| 65 | + $intentRequest = new IntentRequest(); |
|
| 66 | + $intentRequest->type = 'test'; |
|
| 67 | + $intentRequest->timestamp = new \DateTime(); |
|
| 68 | + $request = new Request(); |
|
| 69 | + $request->request = $intentRequest; |
|
| 70 | + $request->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/test.pem'; |
|
| 71 | + $request->signature = 'none'; |
|
| 72 | + $request->amazonRequestBody = ''; |
|
| 73 | + |
|
| 74 | + $this->expectException(RequestInvalidSignatureException::class); |
|
| 75 | + $requestValidator->validate($request); |
|
| 76 | + } |
|
| 202 | 77 | |
| 203 | - $intent = new class () extends IntentRequest { |
|
| 204 | - public function __construct() |
|
| 205 | - { |
|
| 206 | - $this->timestamp = new \DateTime(); |
|
| 207 | - $this->type = 'test'; |
|
| 208 | - } |
|
| 209 | - public function validateSignature(): bool |
|
| 210 | - { |
|
| 211 | - return false; |
|
| 212 | - } |
|
| 213 | - }; |
|
| 78 | + public function testWrongSignatureCertChainUrlCallError(): void |
|
| 79 | + { |
|
| 80 | + $client = $this->createMock(Client::class); |
|
| 81 | + $apiResponse = $this->createMock(ResponseInterface::class); |
|
| 82 | + $requestValidator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 83 | + |
|
| 84 | + $client->method('request') |
|
| 85 | + ->willReturn($apiResponse); |
|
| 86 | + $apiResponse->method('getStatusCode') |
|
| 87 | + ->willReturn(400); |
|
| 88 | + |
|
| 89 | + $intentRequest = new IntentRequest(); |
|
| 90 | + $intentRequest->type = 'test'; |
|
| 91 | + $intentRequest->timestamp = new \DateTime(); |
|
| 92 | + $request = new Request(); |
|
| 93 | + $request->request = $intentRequest; |
|
| 94 | + $request->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/test.pem'; |
|
| 95 | + $request->signature = 'none'; |
|
| 96 | + $request->amazonRequestBody = ''; |
|
| 97 | + |
|
| 98 | + $this->expectException(RequestInvalidSignatureException::class); |
|
| 99 | + $requestValidator->validate($request); |
|
| 100 | + } |
|
| 214 | 101 | |
| 215 | - $r = new Request(); |
|
| 216 | - $r->request = $intent; |
|
| 217 | - $r->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; |
|
| 218 | - $r->signature = 'AA=='; |
|
| 219 | - $validator->validate($r); |
|
| 102 | + public function testValidTimestampWithinTolerance(): void |
|
| 103 | + { |
|
| 104 | + $validator = new RequestValidator(); |
|
| 105 | + $intent = new IntentRequest(); |
|
| 106 | + $intent->timestamp = new \DateTime('-10 seconds'); |
|
| 107 | + $intent->type = 'test'; |
|
| 108 | + $r = new Request(); |
|
| 109 | + $r->request = $intent; |
|
| 110 | + $r->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; |
|
| 111 | + $r->signature = 'SGVsbG8='; // base64 "Hello" |
|
| 112 | + |
|
| 113 | + // validateSignature will fail (no real cert), so disable signature path by overriding property |
|
| 114 | + $intentWithNoSignature = new class ($intent) extends IntentRequest { |
|
| 115 | + public function __construct(IntentRequest $base) |
|
| 116 | + { |
|
| 117 | + $this->timestamp = $base->timestamp; |
|
| 118 | + $this->type = $base->type; |
|
| 119 | + } |
|
| 120 | + public function validateSignature(): bool |
|
| 121 | + { |
|
| 122 | + return false; |
|
| 123 | + } |
|
| 124 | + }; |
|
| 125 | + $r->request = $intentWithNoSignature; |
|
| 220 | 126 | |
| 221 | - $this->assertTrue(true); |
|
| 222 | - } |
|
| 127 | + $validator->validate($r); |
|
| 128 | + $this->assertTrue(true); |
|
| 129 | + } |
|
| 223 | 130 | |
| 224 | - public function testSignatureValidationPerformsHttpRequest(): void |
|
| 225 | - { |
|
| 226 | - $client = $this->createMock(Client::class); |
|
| 227 | - $response = $this->createMock(ResponseInterface::class); |
|
| 228 | - $stream = $this->createMock(StreamInterface::class); |
|
| 131 | + public function testTimestampExactlyOnToleranceBoundaryPasses(): void |
|
| 132 | + { |
|
| 133 | + $tolerance = 50; |
|
| 134 | + $validator = new RequestValidator($tolerance); |
|
| 135 | + $intent = new IntentRequest(); |
|
| 136 | + $intent->timestamp = new \DateTime("-{$tolerance} seconds"); |
|
| 137 | + $intent->type = 'test'; |
|
| 138 | + $r = new Request(); |
|
| 139 | + $r->request = $intent; |
|
| 140 | + $intent = new class ($intent) extends IntentRequest { |
|
| 141 | + public function __construct(IntentRequest $base) |
|
| 142 | + { |
|
| 143 | + $this->timestamp = $base->timestamp; |
|
| 144 | + $this->type = $base->type; |
|
| 145 | + } |
|
| 146 | + public function validateSignature(): bool |
|
| 147 | + { |
|
| 148 | + return false; |
|
| 149 | + } |
|
| 150 | + }; |
|
| 151 | + $r->request = $intent; |
|
| 229 | 152 | |
| 230 | - // Use a unique URL to avoid cached certificates |
|
| 231 | - $uniqueUrl = 'https://s3.amazonaws.com/echo.api/echo-api-cert-' . uniqid() . '.pem'; |
|
| 232 | - $localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . md5($uniqueUrl) . '.pem'; |
|
| 153 | + $validator->validate($r); |
|
| 154 | + $this->assertTrue(true); |
|
| 155 | + } |
|
| 233 | 156 | |
| 234 | - // Ensure no cached certificate exists |
|
| 235 | - @unlink($localPath); |
|
| 157 | + public function testTimestampJustOverToleranceFails(): void |
|
| 158 | + { |
|
| 159 | + $tolerance = 30; |
|
| 160 | + $validator = new RequestValidator($tolerance); |
|
| 161 | + $intent = new IntentRequest(); |
|
| 162 | + $intent->timestamp = new \DateTime('-' . ($tolerance + 1) . ' seconds'); |
|
| 163 | + $intent->type = 'test'; |
|
| 164 | + $r = new Request(); |
|
| 165 | + $r->request = $intent; |
|
| 166 | + |
|
| 167 | + $this->expectException(RequestInvalidTimestampException::class); |
|
| 168 | + $validator->validate($r); |
|
| 169 | + } |
|
| 236 | 170 | |
| 237 | - $client->expects($this->once())->method('request')->with('GET', $uniqueUrl)->willReturn($response); |
|
| 238 | - $response->method('getStatusCode')->willReturn(200); |
|
| 239 | - $response->method('getBody')->willReturn($stream); |
|
| 240 | - $stream->method('getContents')->willReturn("-----BEGIN CERTIFICATE-----\nFAKE\n-----END CERTIFICATE-----"); |
|
| 171 | + public function testSkipTimestampValidationWhenRequestDisablesIt(): void |
|
| 172 | + { |
|
| 173 | + $validator = new RequestValidator(); |
|
| 174 | + $intent = new class () extends IntentRequest { |
|
| 175 | + public function __construct() |
|
| 176 | + { |
|
| 177 | + $this->timestamp = new \DateTime('-5 hours'); // would normally fail |
|
| 178 | + $this->type = 'test'; |
|
| 179 | + } |
|
| 180 | + public function validateTimestamp(): bool |
|
| 181 | + { |
|
| 182 | + return false; |
|
| 183 | + } |
|
| 184 | + public function validateSignature(): bool |
|
| 185 | + { |
|
| 186 | + return false; |
|
| 187 | + } |
|
| 188 | + }; |
|
| 189 | + $r = new Request(); |
|
| 190 | + $r->request = $intent; |
|
| 241 | 191 | |
| 242 | - $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 192 | + $validator->validate($r); |
|
| 193 | + $this->assertTrue(true); |
|
| 194 | + } |
|
| 243 | 195 | |
| 244 | - $intent = new class () extends IntentRequest { |
|
| 245 | - public function __construct() |
|
| 246 | - { |
|
| 247 | - $this->timestamp = new \DateTime(); |
|
| 248 | - $this->type = 'test'; |
|
| 249 | - } |
|
| 250 | - public function validateSignature(): bool |
|
| 251 | - { |
|
| 252 | - return true; // Enable signature validation to trigger HTTP request |
|
| 253 | - } |
|
| 254 | - }; |
|
| 196 | + public function testSkipSignatureValidationWhenRequestDisablesIt(): void |
|
| 197 | + { |
|
| 198 | + $client = $this->createMock(Client::class); |
|
| 199 | + $client->expects($this->never())->method('request'); |
|
| 200 | + |
|
| 201 | + $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 202 | + |
|
| 203 | + $intent = new class () extends IntentRequest { |
|
| 204 | + public function __construct() |
|
| 205 | + { |
|
| 206 | + $this->timestamp = new \DateTime(); |
|
| 207 | + $this->type = 'test'; |
|
| 208 | + } |
|
| 209 | + public function validateSignature(): bool |
|
| 210 | + { |
|
| 211 | + return false; |
|
| 212 | + } |
|
| 213 | + }; |
|
| 214 | + |
|
| 215 | + $r = new Request(); |
|
| 216 | + $r->request = $intent; |
|
| 217 | + $r->signatureCertChainUrl = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; |
|
| 218 | + $r->signature = 'AA=='; |
|
| 219 | + $validator->validate($r); |
|
| 255 | 220 | |
| 256 | - $r = new Request(); |
|
| 257 | - $r->request = $intent; |
|
| 258 | - $r->amazonRequestBody = 'BODY'; |
|
| 259 | - $r->signature = base64_encode('sig'); |
|
| 260 | - $r->signatureCertChainUrl = $uniqueUrl; |
|
| 221 | + $this->assertTrue(true); |
|
| 222 | + } |
|
| 261 | 223 | |
| 262 | - $this->expectException(RequestInvalidSignatureException::class); |
|
| 263 | - $validator->validate($r); |
|
| 264 | - } |
|
| 224 | + public function testSignatureValidationPerformsHttpRequest(): void |
|
| 225 | + { |
|
| 226 | + $client = $this->createMock(Client::class); |
|
| 227 | + $response = $this->createMock(ResponseInterface::class); |
|
| 228 | + $stream = $this->createMock(StreamInterface::class); |
|
| 265 | 229 | |
| 266 | - public function testSignatureValidationUsesCachedCertWithoutHttpCall(): void |
|
| 267 | - { |
|
| 268 | - $url = 'https://s3.amazonaws.com/echo.api/cached-cert.pem'; |
|
| 269 | - $localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . md5($url) . '.pem'; |
|
| 270 | - file_put_contents($localPath, "-----BEGIN CERTIFICATE-----\nCACHED\n-----END CERTIFICATE-----"); |
|
| 230 | + // Use a unique URL to avoid cached certificates |
|
| 231 | + $uniqueUrl = 'https://s3.amazonaws.com/echo.api/echo-api-cert-' . uniqid() . '.pem'; |
|
| 232 | + $localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . md5($uniqueUrl) . '.pem'; |
|
| 271 | 233 | |
| 272 | - $client = $this->createMock(Client::class); |
|
| 273 | - $client->expects($this->never())->method('request'); |
|
| 234 | + // Ensure no cached certificate exists |
|
| 235 | + @unlink($localPath); |
|
| 274 | 236 | |
| 275 | - $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 237 | + $client->expects($this->once())->method('request')->with('GET', $uniqueUrl)->willReturn($response); |
|
| 238 | + $response->method('getStatusCode')->willReturn(200); |
|
| 239 | + $response->method('getBody')->willReturn($stream); |
|
| 240 | + $stream->method('getContents')->willReturn("-----BEGIN CERTIFICATE-----\nFAKE\n-----END CERTIFICATE-----"); |
|
| 241 | + |
|
| 242 | + $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 243 | + |
|
| 244 | + $intent = new class () extends IntentRequest { |
|
| 245 | + public function __construct() |
|
| 246 | + { |
|
| 247 | + $this->timestamp = new \DateTime(); |
|
| 248 | + $this->type = 'test'; |
|
| 249 | + } |
|
| 250 | + public function validateSignature(): bool |
|
| 251 | + { |
|
| 252 | + return true; // Enable signature validation to trigger HTTP request |
|
| 253 | + } |
|
| 254 | + }; |
|
| 255 | + |
|
| 256 | + $r = new Request(); |
|
| 257 | + $r->request = $intent; |
|
| 258 | + $r->amazonRequestBody = 'BODY'; |
|
| 259 | + $r->signature = base64_encode('sig'); |
|
| 260 | + $r->signatureCertChainUrl = $uniqueUrl; |
|
| 261 | + |
|
| 262 | + $this->expectException(RequestInvalidSignatureException::class); |
|
| 263 | + $validator->validate($r); |
|
| 264 | + } |
|
| 276 | 265 | |
| 277 | - $intent = new class () extends IntentRequest { |
|
| 278 | - public function __construct() |
|
| 279 | - { |
|
| 280 | - $this->timestamp = new \DateTime(); |
|
| 281 | - $this->type = 'test'; |
|
| 282 | - } |
|
| 283 | - public function validateSignature(): bool |
|
| 284 | - { |
|
| 285 | - return true; |
|
| 266 | + public function testSignatureValidationUsesCachedCertWithoutHttpCall(): void |
|
| 267 | + { |
|
| 268 | + $url = 'https://s3.amazonaws.com/echo.api/cached-cert.pem'; |
|
| 269 | + $localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . md5($url) . '.pem'; |
|
| 270 | + file_put_contents($localPath, "-----BEGIN CERTIFICATE-----\nCACHED\n-----END CERTIFICATE-----"); |
|
| 271 | + |
|
| 272 | + $client = $this->createMock(Client::class); |
|
| 273 | + $client->expects($this->never())->method('request'); |
|
| 274 | + |
|
| 275 | + $validator = new RequestValidator(RequestValidator::TIMESTAMP_VALID_TOLERANCE_SECONDS, $client); |
|
| 276 | + |
|
| 277 | + $intent = new class () extends IntentRequest { |
|
| 278 | + public function __construct() |
|
| 279 | + { |
|
| 280 | + $this->timestamp = new \DateTime(); |
|
| 281 | + $this->type = 'test'; |
|
| 282 | + } |
|
| 283 | + public function validateSignature(): bool |
|
| 284 | + { |
|
| 285 | + return true; |
|
| 286 | + } |
|
| 287 | + }; |
|
| 288 | + |
|
| 289 | + $r = new Request(); |
|
| 290 | + $r->request = $intent; |
|
| 291 | + $r->amazonRequestBody = 'BODY'; |
|
| 292 | + $r->signature = base64_encode('sig'); |
|
| 293 | + $r->signatureCertChainUrl = $url; |
|
| 294 | + |
|
| 295 | + $this->expectException(RequestInvalidSignatureException::class); |
|
| 296 | + try { |
|
| 297 | + $validator->validate($r); |
|
| 298 | + } finally { |
|
| 299 | + @unlink($localPath); |
|
| 286 | 300 | } |
| 287 | - }; |
|
| 288 | - |
|
| 289 | - $r = new Request(); |
|
| 290 | - $r->request = $intent; |
|
| 291 | - $r->amazonRequestBody = 'BODY'; |
|
| 292 | - $r->signature = base64_encode('sig'); |
|
| 293 | - $r->signatureCertChainUrl = $url; |
|
| 294 | - |
|
| 295 | - $this->expectException(RequestInvalidSignatureException::class); |
|
| 296 | - try { |
|
| 297 | - $validator->validate($r); |
|
| 298 | - } finally { |
|
| 299 | - @unlink($localPath); |
|
| 300 | 301 | } |
| 301 | - } |
|
| 302 | 302 | } |