diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 16c00e7..ddeee55 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -16,7 +16,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.3' + php-version: '8.0' tools: composer:v2 - name: Validate composer.json and composer.lock diff --git a/README.md b/README.md index cc54cb1..df443dc 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,45 @@ # Sign-in with Apple SDK + [![Latest Version](https://img.shields.io/github/v/release/AzimoLabs/apple-sign-in-php-sdk.svg?style=flat-square)](https://github.com/AzimoLabs/apple-sign-in-php-sdk/releases) [![Build Status](https://img.shields.io/github/workflow/status/AzimoLabs/apple-sign-in-php-sdk/CI?label=ci%20build&style=flat-square)](https://github.com/AzimoLabs/apple-sign-in-php-sdk/actions?query=workflow%3ACI) ## Installation + Recommended and easiest way to installing library is through [Composer](https://getcomposer.org/). - + `composer require azimolabs/apple-sign-in-php-sdk` ## Requirements + * PHP 7.1+ * OpenSSL Extension +## PHP support +|PHP version|Library version| +|---|---| +|`5.x`|`NOT SUPPORTED`| +| `> 7.0 <= 7.3`| `1.4.x` | +| `> 7.4 < 8.0`| `1.5.x` | +| `> 8.0 & ^7.4`| `2.0.x` | + ## How it works -This description assumes that you already have generated [identityToken](https://developer.apple.com/documentation/authenticationservices/asauthorizationsinglesignoncredential/3153080-identitytoken). -Remember that token is valid ONLY for 10 minutes. -The first step to verify the identity token is to generate a public key. To generate public key `exponent` and `modulus` values are required. -Both information are exposed in [Apple API endpoint](https://appleid.apple.com/auth/keys). Those values differ depending on the algorithm. +This description assumes that you already have +generated [identityToken](https://developer.apple.com/documentation/authenticationservices/asauthorizationsinglesignoncredential/3153080-identitytoken) +. Remember that token is valid ONLY for 10 minutes. + +The first step to verify the identity token is to generate a public key. To generate public key `exponent` and `modulus` +values are required. Both information are exposed in [Apple API endpoint](https://appleid.apple.com/auth/keys). Those +values differ depending on the algorithm. -The second step is verification if provided `identityToken` is valid against generated public key. If so we are sure that `identityToken` wasn't malformed. +The second step is verification if provided `identityToken` is valid against generated public key. If so we are sure +that `identityToken` wasn't malformed. -The third step is validation if token is not expired. Additionally it is worth to check `issuer` and `audience`, examples are shown below. +The third step is validation if token is not expired. Additionally it is worth to check `issuer` and `audience`, +examples are shown below. ## Basic usage + Once you have cloned repository, make sure that composer dependencies are installed running `composer install -o`. ```php @@ -31,29 +48,36 @@ $validationData->setIssuer('https://appleid.apple.com'); $validationData->setAudience('com.azimo'); $appleJwtFetchingService = new Auth\Service\AppleJwtFetchingService( - new Auth\Jwt\JwtParser(new Parser()), - new Auth\Jwt\JwtVerifier( - new Api\AppleApiClient( - new GuzzleHttp\Client( + new Auth\Jwt\JwtParser(new \Lcobucci\JWT\Token\Parser(new \Lcobucci\JWT\Encoding\JoseEncoder())), + new Auth\Jwt\JwtVerifier( + new Api\AppleApiClient( + new GuzzleHttp\Client( + [ + 'base_uri' => 'https://appleid.apple.com', + 'timeout' => 5, + 'connect_timeout' => 5, + ] + ), + new Api\Factory\ResponseFactory() + ), + new \Lcobucci\JWT\Validation\Validator(), + new \Lcobucci\JWT\Signer\Rsa\Sha256() + ), + new Auth\Jwt\JwtValidator( + new \Lcobucci\JWT\Validation\Validator(), [ - 'base_uri' => 'https://appleid.apple.com', - 'timeout' => 5, - 'connect_timeout' => 5, + new \Lcobucci\JWT\Validation\Constraint\IssuedBy('https://appleid.apple.com'), + new \Lcobucci\JWT\Validation\Constraint\PermittedFor('com.c.azimo.stage'), ] ), - new Api\Factory\ResponseFactory() - ), - new RSA(), - new Sha256() - ), - new Auth\Jwt\JwtValidator($validationData), - new Auth\Factory\AppleJwtStructFactory() -); + new Auth\Factory\AppleJwtStructFactory() + ); $appleJwtFetchingService->getJwtPayload('your.identity.token'); ``` -If you don't want to copy-paste above code you can paste freshly generated `identityToken` in `tests/E2e/Auth/AppleJwtFetchingServiceTest.php:53` +If you don't want to copy-paste above code you can paste freshly generated `identityToken` +in `tests/E2e/Auth/AppleJwtFetchingServiceTest.php:53` and run tests with simple command `php vendor/bin/phpunit tests/E2e`. ```shell script @@ -70,16 +94,24 @@ OK (1 test, 1 assertion) ``` ## Todo + It is welcome to open a pull request with a fix of any issue: -- [ ] Upgrade `lcobucci/jwt` to version `4.x`. Reported in: [Implicit conversion of keys from strings is deprecated. #2](https://github.com/AzimoLabs/apple-sign-in-php-sdk/issues/2) -- [x] Make library compatible with PHP `7.4.3`. Reported in [Uncaught JsonException: Malformed UTF-8 characters](https://github.com/AzimoLabs/apple-sign-in-php-sdk/issues/4) -- [ ] Make library compatible with PHP `8.0.0` +- [x] Upgrade `phpseclib/phpseclib` to version `3.0.7` +- [x] Upgrade `lcobucci/jwt` to version `4.x`. Reported + in: [Implicit conversion of keys from strings is deprecated. #2](https://github.com/AzimoLabs/apple-sign-in-php-sdk/issues/2) +- [x] Make library compatible with PHP `7.4.3`. Reported + in [Uncaught JsonException: Malformed UTF-8 characters](https://github.com/AzimoLabs/apple-sign-in-php-sdk/issues/4) +- [x] Make library compatible with PHP `8.0.0` ## Miscellaneous + * [JSON web token](https://jwt.io/) * [Sign in with Apple overwiew](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple) * [How backend token verification works](https://sarunw.com/posts/sign-in-with-apple-3/) # Towards financial services available to all -We’re working throughout the company to create faster, cheaper, and more available financial services all over the world, and here are some of the techniques that we’re utilizing. There’s still a long way ahead of us, and if you’d like to be part of that journey, check out our [careers page](https://bit.ly/3vajnu6). + +We’re working throughout the company to create faster, cheaper, and more available financial services all over the +world, and here are some of the techniques that we’re utilizing. There’s still a long way ahead of us, and if you’d like +to be part of that journey, check out our [careers page](https://bit.ly/3vajnu6). diff --git a/composer.json b/composer.json index 4496ab2..1e4f907 100644 --- a/composer.json +++ b/composer.json @@ -29,11 +29,11 @@ }, "minimum-stability": "stable", "require": { - "php": "^7.1", + "php": "^7.4 || ^8.0", "ext-mbstring": "*", "ext-openssl": "*", - "phpseclib/phpseclib": "^2.0", - "lcobucci/jwt": "3.3.3", + "phpseclib/phpseclib": "^3.0", + "lcobucci/jwt": "^4.0", "guzzlehttp/guzzle": "^6.0|^7.0" }, "require-dev": { diff --git a/src/Api/AppleApiClient.php b/src/Api/AppleApiClient.php index 6e7b358..537b2a5 100644 --- a/src/Api/AppleApiClient.php +++ b/src/Api/AppleApiClient.php @@ -5,18 +5,14 @@ use Azimo\Apple\Api\Exception\PublicKeyFetchingFailedException; use Azimo\Apple\Api\Factory\ResponseFactory; use GuzzleHttp; +use GuzzleHttp\Utils; +use InvalidArgumentException; -class AppleApiClient +final class AppleApiClient implements AppleApiClientInterface { - /** - * @var GuzzleHttp\ClientInterface - */ - private $httpClient; + private GuzzleHttp\ClientInterface $httpClient; - /** - * @var ResponseFactory - */ - private $responseFactory; + private ResponseFactory $responseFactory; public function __construct(GuzzleHttp\ClientInterface $httpClient, ResponseFactory $responseFactory) { @@ -34,9 +30,9 @@ public function getAuthKeys(): Response\JsonWebKeySetCollection try { return $this->responseFactory->createFromArray( - GuzzleHttp\json_decode($response->getBody()->getContents(), true) + Utils::jsonDecode($response->getBody()->getContents(), true) ); - } catch (\InvalidArgumentException $exception) { + } catch (InvalidArgumentException $exception) { throw new Exception\InvalidResponseException( 'Unable to decode response', $exception->getCode(), diff --git a/src/Api/AppleApiClientInterface.php b/src/Api/AppleApiClientInterface.php new file mode 100644 index 0000000..1aa3769 --- /dev/null +++ b/src/Api/AppleApiClientInterface.php @@ -0,0 +1,13 @@ +authKeys = $authKeys; } - public function getAuthKeys(): array - { - return $this->authKeys; - } - public function getByCryptographicAlgorithm(string $algorithm): ?JsonWebKeySet { if (!CryptographicAlgorithmEnum::isSupported($algorithm)) { diff --git a/src/Auth/Exception/InvalidCryptographicAlgorithmException.php b/src/Auth/Exception/InvalidCryptographicAlgorithmException.php index 6909b10..f38e876 100644 --- a/src/Auth/Exception/InvalidCryptographicAlgorithmException.php +++ b/src/Auth/Exception/InvalidCryptographicAlgorithmException.php @@ -4,6 +4,6 @@ use InvalidArgumentException; -class InvalidCryptographicAlgorithmException extends InvalidArgumentException implements AppleExceptionInterface +final class InvalidCryptographicAlgorithmException extends InvalidArgumentException implements AppleExceptionInterface { } diff --git a/src/Auth/Exception/InvalidJwtException.php b/src/Auth/Exception/InvalidJwtException.php index 591a1b7..f19236f 100644 --- a/src/Auth/Exception/InvalidJwtException.php +++ b/src/Auth/Exception/InvalidJwtException.php @@ -4,6 +4,6 @@ use InvalidArgumentException; -class InvalidJwtException extends InvalidArgumentException implements AppleExceptionInterface +final class InvalidJwtException extends InvalidArgumentException implements AppleExceptionInterface { } diff --git a/src/Auth/Exception/KeysFetchingFailedException.php b/src/Auth/Exception/KeysFetchingFailedException.php index 2a43c8d..8ce5367 100644 --- a/src/Auth/Exception/KeysFetchingFailedException.php +++ b/src/Auth/Exception/KeysFetchingFailedException.php @@ -4,6 +4,6 @@ use RuntimeException; -class KeysFetchingFailedException extends RuntimeException implements AppleExceptionInterface +final class KeysFetchingFailedException extends RuntimeException implements AppleExceptionInterface { } diff --git a/src/Auth/Exception/MissingClaimException.php b/src/Auth/Exception/MissingClaimException.php index 9c71e5f..931bc7a 100644 --- a/src/Auth/Exception/MissingClaimException.php +++ b/src/Auth/Exception/MissingClaimException.php @@ -4,6 +4,6 @@ use OutOfBoundsException; -class MissingClaimException extends OutOfBoundsException implements AppleExceptionInterface +final class MissingClaimException extends OutOfBoundsException implements AppleExceptionInterface { } diff --git a/src/Auth/Exception/NotSignedTokenException.php b/src/Auth/Exception/NotSignedTokenException.php deleted file mode 100644 index e1d1a02..0000000 --- a/src/Auth/Exception/NotSignedTokenException.php +++ /dev/null @@ -1,9 +0,0 @@ -getClaim('iss'), - $token->getClaim('aud'), - $token->getClaim('exp'), - $token->getClaim('iat'), - $token->getClaim('sub'), - $token->getClaim('c_hash'), - $token->getClaim('email', ''), - // For some reason Apple API returns boolean flag as a string - (string) $token->getClaim('email_verified', 'false') === 'true', - // For some reason Apple API returns boolean flag as a string - (string) $token->getClaim('is_private_email', 'false') === 'true', - $token->getClaim('auth_time'), - $token->getClaim('nonce_supported', false) - ); - } catch (OutOfBoundsException $exception) { - throw new MissingClaimException($exception->getMessage(), $exception->getCode(), $exception); - } + $claims = $token->claims(); + + return new JwtPayload( + $claims->get('iss'), + $claims->get('aud'), + $claims->get('exp'), + $claims->get('iat'), + $claims->get('sub'), + $claims->get('c_hash', ''), + $claims->get('email', ''), + // For some reason Apple API returns boolean flag as a string + (string) $claims->get('email_verified', 'false') === 'true', + // For some reason Apple API returns boolean flag as a string + (string) $claims->get('is_private_email', 'false') === 'true', + $claims->get('auth_time'), + $claims->get('nonce_supported', false) + ); } } diff --git a/src/Auth/Jwt/JwtParser.php b/src/Auth/Jwt/JwtParser.php index ea79328..a1331ad 100644 --- a/src/Auth/Jwt/JwtParser.php +++ b/src/Auth/Jwt/JwtParser.php @@ -9,10 +9,7 @@ class JwtParser { - /** - * @var JWT\Parser - */ - private $jwtParser; + private JWT\Parser $jwtParser; public function __construct(JWT\Parser $jwtParser) { diff --git a/src/Auth/Jwt/JwtValidator.php b/src/Auth/Jwt/JwtValidator.php index b72635b..c6f37b3 100644 --- a/src/Auth/Jwt/JwtValidator.php +++ b/src/Auth/Jwt/JwtValidator.php @@ -2,22 +2,23 @@ namespace Azimo\Apple\Auth\Jwt; +use DateTimeImmutable; use Lcobucci\JWT; class JwtValidator { - /** - * @var JWT\ValidationData - */ - private $validationData; + private JWT\Validator $validator; - public function __construct(JWT\ValidationData $validationData) + private array $constraints; + + public function __construct(JWT\Validator $validator, array $constraints) { - $this->validationData = $validationData; + $this->validator = $validator; + $this->constraints = $constraints; } public function isValid(JWT\Token $jwt): bool { - return $jwt->validate($this->validationData) && !$jwt->isExpired(); + return $this->validator->validate($jwt, ...$this->constraints) && !$jwt->isExpired(new DateTimeImmutable()); } } diff --git a/src/Auth/Jwt/JwtVerifier.php b/src/Auth/Jwt/JwtVerifier.php index 31cf840..16c40b6 100644 --- a/src/Auth/Jwt/JwtVerifier.php +++ b/src/Auth/Jwt/JwtVerifier.php @@ -2,54 +2,43 @@ namespace Azimo\Apple\Auth\Jwt; -use Azimo\Apple\Api\AppleApiClient; +use Azimo\Apple\Api\AppleApiClientInterface; use Azimo\Apple\Api\Exception as ApiException; use Azimo\Apple\Api\Response\JsonWebKeySet; use Azimo\Apple\Auth\Exception; -use BadMethodCallException; use Lcobucci\JWT; use OutOfBoundsException; -use phpseclib\Crypt\RSA; -use phpseclib\Math\BigInteger; +use phpseclib3\Crypt\RSA; +use phpseclib3\Math\BigInteger; class JwtVerifier { - /** - * @var AppleApiClient - */ - private $client; + private AppleApiClientInterface $client; - /** - * @var RSA - */ - private $rsa; + private JWT\Signer $signer; - /** - * @var JWT\Signer - */ - private $signer; + private JWT\Validator $validator; - public function __construct(AppleApiClient $client, RSA $rsa, JWT\Signer $signer) + public function __construct(AppleApiClientInterface $client, JWT\Validator $validator, JWT\Signer $signer) { $this->client = $client; - $this->rsa = $rsa; $this->signer = $signer; + $this->validator = $validator; } /** * @throws Exception\InvalidCryptographicAlgorithmException * @throws Exception\KeysFetchingFailedException - * @throws Exception\NotSignedTokenException */ public function verify(JWT\Token $jwt): bool { - $this->loadRsaKey($this->getAuthKey($jwt)); - - try { - return $jwt->verify($this->signer, $this->rsa->getPublicKey()); - } catch (BadMethodCallException $exception) { - throw new Exception\NotSignedTokenException($exception->getMessage(), $exception->getCode(), $exception); - } + return $this->validator->validate( + $jwt, + new JWT\Validation\Constraint\SignedWith( + $this->signer, + JWT\Signer\Key\InMemory::plainText($this->createPublicKey($this->getAuthKey($jwt))) + ) + ); } /** @@ -69,7 +58,7 @@ private function getAuthKey(JWT\Token $jwt): JsonWebKeySet } try { - $cryptographicAlgorithm = $jwt->getHeader('kid'); + $cryptographicAlgorithm = $jwt->headers()->get('kid'); $authKey = $authKeys->getByCryptographicAlgorithm($cryptographicAlgorithm); } catch (OutOfBoundsException | ApiException\UnsupportedCryptographicAlgorithmException $exception) { throw new Exception\InvalidCryptographicAlgorithmException( @@ -88,24 +77,11 @@ private function getAuthKey(JWT\Token $jwt): JsonWebKeySet return $authKey; } - private function loadRsaKey(JsonWebKeySet $authKey): void + private function createPublicKey(JsonWebKeySet $authKey): string { - /** - * Phpspeclib is parsing phpinfo(); output to determine OpenSSL Library and Header versions, - * basing on that set if MATH_BIGINTEGER_OPENSSL_ENABLED or MATH_BIGINTEGER_OPENSSL_DISABLED const. - * It crashes tests so it is possible that it might crash production, that is why constants are overwritten. - * - * @see vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php:273 - */ - if (!defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { - define('MATH_BIGINTEGER_OPENSSL_ENABLED', true); - } - - $this->rsa->loadKey( - [ - 'exponent' => new BigInteger(base64_decode(strtr($authKey->getExponent(), '-_', '+/')), 256), - 'modulus' => new BigInteger(base64_decode(strtr($authKey->getModulus(), '-_', '+/')), 256), - ] + return RSA\Formats\Keys\PKCS8::savePublicKey( + new BigInteger(base64_decode(strtr($authKey->getModulus(), '-_', '+/')), 256), + new BigInteger(base64_decode(strtr($authKey->getExponent(), '-_', '+/')), 256) ); } } diff --git a/src/Auth/Service/AppleJwtFetchingService.php b/src/Auth/Service/AppleJwtFetchingService.php index 8403eac..c266b17 100644 --- a/src/Auth/Service/AppleJwtFetchingService.php +++ b/src/Auth/Service/AppleJwtFetchingService.php @@ -7,27 +7,15 @@ use Azimo\Apple\Auth\Jwt; use Azimo\Apple\Auth\Struct\JwtPayload; -class AppleJwtFetchingService +final class AppleJwtFetchingService implements AppleJwtFetchingServiceInterface { - /** - * @var Jwt\JwtParser - */ - private $parser; + private Jwt\JwtParser $parser; - /** - * @var Jwt\JwtVerifier - */ - private $verifier; + private Jwt\JwtVerifier $verifier; - /** - * @var Jwt\JwtValidator - */ - private $validator; + private Jwt\JwtValidator $validator; - /** - * @var AppleJwtStructFactory - */ - private $factory; + private AppleJwtStructFactory $factory; public function __construct( Jwt\JwtParser $parser, @@ -46,7 +34,6 @@ public function __construct( * @throws Exception\InvalidJwtException * @throws Exception\KeysFetchingFailedException * @throws Exception\MissingClaimException - * @throws Exception\NotSignedTokenException * @throws Exception\ValidationFailedException * @throws Exception\VerificationFailedException */ diff --git a/src/Auth/Service/AppleJwtFetchingServiceInterface.php b/src/Auth/Service/AppleJwtFetchingServiceInterface.php new file mode 100644 index 0000000..c1aefc1 --- /dev/null +++ b/src/Auth/Service/AppleJwtFetchingServiceInterface.php @@ -0,0 +1,19 @@ +iss; } - public function getAud(): string + public function getAud(): array { return $this->aud; } - public function getExp(): int + public function getExp(): DateTimeInterface { return $this->exp; } - public function getIat(): int + public function getIat(): DateTimeInterface { return $this->iat; } diff --git a/tests/E2e/Auth/AppleJwtFetchingServiceTest.php b/tests/E2e/Auth/AppleJwtFetchingServiceTest.php index c9e1ed0..a2ab81d 100644 --- a/tests/E2e/Auth/AppleJwtFetchingServiceTest.php +++ b/tests/E2e/Auth/AppleJwtFetchingServiceTest.php @@ -5,29 +5,24 @@ use Azimo\Apple\Api; use Azimo\Apple\Auth; use GuzzleHttp; -use Lcobucci\JWT\Parser; +use Lcobucci\JWT\Encoding\JoseEncoder; use Lcobucci\JWT\Signer\Rsa\Sha256; -use Lcobucci\JWT\ValidationData; +use Lcobucci\JWT\Token\Parser; +use Lcobucci\JWT\Validation\Constraint\IssuedBy; +use Lcobucci\JWT\Validation\Constraint\PermittedFor; +use Lcobucci\JWT\Validation\Validator; use Mockery\Adapter\Phpunit\MockeryTestCase; -use phpseclib\Crypt\RSA; -class AppleJwtFetchingServiceTest extends MockeryTestCase +final class AppleJwtFetchingServiceTest extends MockeryTestCase { - /** - * @var Auth\Service\AppleJwtFetchingService - */ - private $appleJwtFetchingService; + private Auth\Service\AppleJwtFetchingService $appleJwtFetchingService; public function setUp(): void { parent::setUp(); - $validationData = new ValidationData(); - $validationData->setIssuer('https://appleid.apple.com'); - $validationData->setAudience('com.c.azimo.stage'); - $this->appleJwtFetchingService = new Auth\Service\AppleJwtFetchingService( - new Auth\Jwt\JwtParser(new Parser()), + new Auth\Jwt\JwtParser(new Parser(new JoseEncoder())), new Auth\Jwt\JwtVerifier( new Api\AppleApiClient( new GuzzleHttp\Client( @@ -39,10 +34,16 @@ public function setUp(): void ), new Api\Factory\ResponseFactory() ), - new RSA(), + new Validator(), new Sha256() ), - new Auth\Jwt\JwtValidator($validationData), + new Auth\Jwt\JwtValidator( + new Validator(), + [ + new IssuedBy('https://appleid.apple.com'), + new PermittedFor('com.c.azimo.stage'), + ] + ), new Auth\Factory\AppleJwtStructFactory() ); } @@ -50,7 +51,7 @@ public function setUp(): void public function testIfGetJwtPayloadReturnExpectedJwtPayload(): void { $jwtPayload = $this->appleJwtFetchingService->getJwtPayload( - 'eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmMuYXppbW8uc3RhZ2UiLCJleHAiOjE2MTMyMTIzNjIsImlhdCI6MTYxMzEyNTk2Miwic3ViIjoiMDAwNTYwLjE4MDM2YjI3MmI5MjRkYTg5ZWY3N2RjNDYyNDhkODRhLjA3MjEiLCJjX2hhc2giOiJ4SGpPV24zblpUa3JTS1dRSGRRZmFBIiwiYXV0aF90aW1lIjoxNjEzMTI1OTYyLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.YShVEmo-QGDnMxU_M9wkwOFcqC5vqvMvXDDlZvQ1VO-WA74_CYOBMbdMKvvTWWGgpvnykNVduvixuFkv_3vpRo2llydwllmVJtMxshTx-kIDmBnInP03lP2jdaDSonDmm0UiXtGEmOqqeFiT_sgUn5o0jfUUreNrXMBM9eLpzEDcjyMW_u3qBhds2SQlsJew6Hd9w16lMTngrJYrMq2H6gogWaCqoXdXexJGdQYfBiX2J14XEgGAyW_7ZupFKT0YCb_OwBQubocsdKbRiw7KlHZVH4vcCaz6e5as9Z-g9V8o4eOFMhuYaugmuGdpBruulyOgDgdPYmR3JCMdPeLFdA' + 'eyJraWQiOiJZdXlYb1kiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmMuYXppbW8uc3RhZ2UiLCJleHAiOjE2MjQ3MDE3MzUsImlhdCI6MTYyNDYxNTMzNSwic3ViIjoiMDAwNTYwLjE4MDM2YjI3MmI5MjRkYTg5ZWY3N2RjNDYyNDhkODRhLjA3MjEiLCJjX2hhc2giOiJzQWhpVmFTYXlKNlRSVFdoWFMxdGFBIiwiYXV0aF90aW1lIjoxNjI0NjE1MzM1LCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.osvYd0hNosZKWD85-CJmyNXivFgWhrNCOdOpiB7VuYsRRMFn5cxZCFg8fBEiaekeVtHXMsilxoE7FUKfyZ14smi7QNk87qAcZ_ivF52x_l6hkR0YCANdbcIrJqFJQ2GwL1DHN4hE628qEZBf_dj5SdTcixHM-8X3ibWDt4irzBACiXqWvbaeRqFdhwJl-yG_of-9zjYg98-Hlk18MphxCgqmVhFXlOi_al4sVHdqZtUjMgGyqszmoIURgU9lOXXDGKZ3LyBU7vXJIZY4FjcXsOtJ4PO8N2LD_2EN2hXRdiUV_A86Dki_O9w17ZjYxlLwpsxRm_m3SzTVptgL0DL_7Q' ); self::assertInstanceOf(Auth\Struct\JwtPayload::class, $jwtPayload); diff --git a/tests/Unit/Api/AppleApiClientTest.php b/tests/Unit/Api/AppleApiClientTest.php index ea8020d..6f56556 100644 --- a/tests/Unit/Api/AppleApiClientTest.php +++ b/tests/Unit/Api/AppleApiClientTest.php @@ -9,22 +9,13 @@ use Mockery\Adapter\Phpunit\MockeryTestCase; use Psr\Http\Message\RequestInterface; -class AppleApiClientTest extends MockeryTestCase +final class AppleApiClientTest extends MockeryTestCase { - /** - * @var Api\AppleApiClient - */ - private $client; - - /** - * @var GuzzleHttp\Client|\Mockery\MockInterface - */ - private $httpClientMock; - - /** - * @var Api\Factory\ResponseFactory|\Mockery\MockInterface - */ - private $responseFactoryMock; + private Api\AppleApiClient $client; + + private GuzzleHttp\Client $httpClientMock; + + private Api\Factory\ResponseFactory $responseFactoryMock; protected function setUp(): void { diff --git a/tests/Unit/Api/Factory/ResponseFactoryTest.php b/tests/Unit/Api/Factory/ResponseFactoryTest.php index 62457f1..38a3f01 100644 --- a/tests/Unit/Api/Factory/ResponseFactoryTest.php +++ b/tests/Unit/Api/Factory/ResponseFactoryTest.php @@ -8,12 +8,9 @@ use Azimo\Apple\Api\Response\JsonWebKeySetCollection; use Mockery\Adapter\Phpunit\MockeryTestCase; -class ResponseFactoryTest extends MockeryTestCase +final class ResponseFactoryTest extends MockeryTestCase { - /** - * @var ResponseFactory - */ - private $responseFactory; + private ResponseFactory $responseFactory; protected function setUp(): void { diff --git a/tests/Unit/Auth/Factory/AppleJwtStructFactoryTest.php b/tests/Unit/Auth/Factory/AppleJwtStructFactoryTest.php index fa37b77..66ccafd 100644 --- a/tests/Unit/Auth/Factory/AppleJwtStructFactoryTest.php +++ b/tests/Unit/Auth/Factory/AppleJwtStructFactoryTest.php @@ -2,18 +2,15 @@ namespace Azimo\Apple\Tests\Unit\Auth\Factory; -use Azimo\Apple\Auth\Exception\MissingClaimException; use Azimo\Apple\Auth\Factory\AppleJwtStructFactory; use Azimo\Apple\Auth\Struct\JwtPayload; -use Lcobucci\JWT\Claim\EqualsTo; +use DateTimeImmutable; use Lcobucci\JWT\Token; +use Mockery\Adapter\Phpunit\MockeryTestCase; -class AppleJwtStructFactoryTest extends \Mockery\Adapter\Phpunit\MockeryTestCase +final class AppleJwtStructFactoryTest extends MockeryTestCase { - /** - * @var AppleJwtStructFactory - */ - private $appleJwtStructFactory; + private AppleJwtStructFactory $appleJwtStructFactory; protected function setUp(): void { @@ -24,12 +21,14 @@ protected function setUp(): void public function testIfCreateJwtPayloadFromTokenReturnsExpectedJsonPayload(): void { - $this->assertEquals( + $currentDate = new DateTimeImmutable(); + + self::assertEquals( new JwtPayload( 'https://appleid.apple.com', - 'com.acme.app', - 1591622611, - 1591622011, + ['com.acme.app'], + $currentDate, + $currentDate, 'foo.bar.baz', 'qGzMhtsfTCom-bl1PJYLHk', 'foo@privaterelay.appleid.com', @@ -39,52 +38,31 @@ public function testIfCreateJwtPayloadFromTokenReturnsExpectedJsonPayload(): voi true ), $this->appleJwtStructFactory->createJwtPayloadFromToken( - new Token( - [ - 'kid' => 'eXaunmL', - 'alg' => 'RS256', - ], - [ - 'iss' => new EqualsTo('iss', 'https://appleid.apple.com'), - 'aud' => new EqualsTo('aud', 'com.acme.app'), - 'exp' => new EqualsTo('exp', 1591622611), - 'iat' => new EqualsTo('iat', 1591622011), - 'sub' => new EqualsTo('sub', 'foo.bar.baz'), - 'c_hash' => new EqualsTo('c_hash', 'qGzMhtsfTCom-bl1PJYLHk'), - 'email' => new EqualsTo('email', 'foo@privaterelay.appleid.com'), - 'email_verified' => new EqualsTo('email_verified', 'true'), - 'is_private_email' => new EqualsTo('is_private_email', 'true'), - 'auth_time' => new EqualsTo('auth_time', 1591622011), - 'nonce_supported' => new EqualsTo('nonce_supported', true), - ] + new Token\Plain( + new Token\DataSet( + [ + 'kid' => 'eXaunmL', + 'alg' => 'RS256', + ], '' + ), + new Token\DataSet( + [ + 'iss' => 'https://appleid.apple.com', + 'aud' => ['com.acme.app'], + 'exp' => $currentDate, + 'iat' => $currentDate, + 'sub' => 'foo.bar.baz', + 'c_hash' => 'qGzMhtsfTCom-bl1PJYLHk', + 'email' => 'foo@privaterelay.appleid.com', + 'email_verified' => 'true', + 'is_private_email' => 'true', + 'auth_time' => 1591622011, + 'nonce_supported' => true, + ], '' + ), + Token\Signature::fromEmptyData() ) ) ); } - - public function testIfCreateJwtPayloadFromTokenThrowsMissingClaimExceptionWhenAnyClaimIsMissing(): void - { - $this->expectExceptionMessage('Requested claim is not configured'); - $this->expectException(MissingClaimException::class); - $this->appleJwtStructFactory->createJwtPayloadFromToken( - new Token( - [ - 'kid' => 'eXaunmL', - 'alg' => 'RS256', - ], - [ - 'iss' => new EqualsTo('iss', 'https://appleid.apple.com'), - 'aud' => new EqualsTo('aud', 'com.acme.app'), - 'exp' => new EqualsTo('exp', 1591622611), - 'iat' => new EqualsTo('iat', 1591622011), - 'sub' => new EqualsTo('sub', 'foo.bar.baz'), - 'email' => new EqualsTo('email', 'foo@privaterelay.appleid.com'), - 'email_verified' => new EqualsTo('email_verified', 'true'), - 'is_private_email' => new EqualsTo('is_private_email', 'true'), - 'auth_time' => new EqualsTo('auth_time', 1591622011), - 'nonce_supported' => new EqualsTo('nonce_supported', true), - ] - ) - ); - } } diff --git a/tests/Unit/Auth/Jwt/JwtParserTest.php b/tests/Unit/Auth/Jwt/JwtParserTest.php index e8ee970..58b45b0 100644 --- a/tests/Unit/Auth/Jwt/JwtParserTest.php +++ b/tests/Unit/Auth/Jwt/JwtParserTest.php @@ -11,17 +11,11 @@ use Mockery\Adapter\Phpunit\MockeryTestCase; use RuntimeException; -class JwtParserTest extends MockeryTestCase +final class JwtParserTest extends MockeryTestCase { - /** - * @var Parser|Mockery\MockInterface - */ - private $jwtParserMock; + private Parser $jwtParserMock; - /** - * @var JwtParser - */ - private $parser; + private JwtParser $parser; protected function setUp(): void { @@ -34,7 +28,7 @@ protected function setUp(): void public function testIfParseReturnsExpectedTokenObjectWhenJwtIsCorrect(): void { - $token = new Token(); + $token = Mockery::mock(Token::class); $this->jwtParserMock->shouldReceive('parse') ->once() diff --git a/tests/Unit/Auth/Jwt/JwtValidatorTest.php b/tests/Unit/Auth/Jwt/JwtValidatorTest.php index 2430ad9..67d7772 100644 --- a/tests/Unit/Auth/Jwt/JwtValidatorTest.php +++ b/tests/Unit/Auth/Jwt/JwtValidatorTest.php @@ -3,46 +3,40 @@ namespace Azimo\Apple\Tests\Unit\Auth\Jwt; use Azimo\Apple\Auth\Jwt\JwtValidator; +use DateTimeImmutable; use Lcobucci\JWT; use Mockery; use Mockery\Adapter\Phpunit\MockeryTestCase; -class JwtValidatorTest extends MockeryTestCase +final class JwtValidatorTest extends MockeryTestCase { - /** - * @var JWT\ValidationData|Mockery\MockInterface - */ - private $validationDataMock; + private JWT\Token $jwtMock; - /** - * @var JWT\Token|Mockery\MockInterface - */ - private $jwtMock; + private array $constraints; - /** - * @var JwtValidator - */ - private $jwtValidator; + private JWT\Validator $validatorMock; + + private JwtValidator $jwtValidator; protected function setUp(): void { parent::setUp(); - $this->validationDataMock = Mockery::mock(JWT\ValidationData::class); $this->jwtMock = Mockery::mock(JWT\Token::class); - - $this->jwtValidator = new JwtValidator($this->validationDataMock); + $this->constraints = []; + $this->validatorMock = Mockery::mock(JWT\Validator::class); + $this->jwtValidator = new JwtValidator($this->validatorMock, $this->constraints); } public function testIfIsValidReturnsTrueWhenTokenDataAreValidAndTokenIsNotExpired(): void { - $this->jwtMock->shouldReceive('validate') + $this->validatorMock->shouldReceive('validate') ->once() - ->with($this->validationDataMock) + ->with($this->jwtMock, ...$this->constraints) ->andReturn(true); $this->jwtMock->shouldReceive('isExpired') ->once() - ->withNoArgs() + ->with(DateTimeImmutable::class) ->andReturn(false); self::assertTrue($this->jwtValidator->isValid($this->jwtMock)); @@ -50,13 +44,13 @@ public function testIfIsValidReturnsTrueWhenTokenDataAreValidAndTokenIsNotExpire public function testIfIsValidReturnsFalseWhenTokenDataAreValidAndTokenIsExpired(): void { - $this->jwtMock->shouldReceive('validate') + $this->validatorMock->shouldReceive('validate') ->once() - ->with($this->validationDataMock) + ->with($this->jwtMock, ...$this->constraints) ->andReturn(true); $this->jwtMock->shouldReceive('isExpired') ->once() - ->withNoArgs() + ->with(DateTimeImmutable::class) ->andReturn(true); self::assertFalse($this->jwtValidator->isValid($this->jwtMock)); @@ -64,9 +58,9 @@ public function testIfIsValidReturnsFalseWhenTokenDataAreValidAndTokenIsExpired( public function testIfIsValidReturnsFalseWhenTokenDataAreInvalidValidAndTokenIsNotExpired(): void { - $this->jwtMock->shouldReceive('validate') + $this->validatorMock->shouldReceive('validate') ->once() - ->with($this->validationDataMock) + ->with($this->jwtMock, ...$this->constraints) ->andReturn(false); $this->jwtMock->shouldNotReceive('isExpired'); diff --git a/tests/Unit/Auth/Jwt/JwtVerifierTest.php b/tests/Unit/Auth/Jwt/JwtVerifierTest.php index 4647b24..795e97e 100644 --- a/tests/Unit/Auth/Jwt/JwtVerifierTest.php +++ b/tests/Unit/Auth/Jwt/JwtVerifierTest.php @@ -5,51 +5,34 @@ use Azimo\Apple\APi; use Azimo\Apple\Auth\Exception; use Azimo\Apple\Auth\Jwt\JwtVerifier; -use BadMethodCallException; use Lcobucci\JWT; use Mockery; use Mockery\Adapter\Phpunit\MockeryTestCase; use OutOfBoundsException; -use phpseclib\Crypt\RSA; -use phpseclib\Math\BigInteger; class JwtVerifierTest extends MockeryTestCase { - /** - * @var APi\AppleApiClient|Mockery\MockInterface - */ - private $appleApiClient; + private APi\AppleApiClientInterface $appleApiClient; - /** - * @var RSA|Mockery\MockInterface - */ - private $rsaMock; + private JWT\Token $jwtTokenMock; - /** - * @var JWT\Signer\Rsa\Sha256|Mockery\MockInterface - */ - private $signerMock; + private JWT\Validator $validatorMock; - /** - * @var JWT\Token|Mockery\MockInterface - */ - private $jwtMock; - - /** - * @var JwtVerifier - */ - private $jwtVerifier; + private JwtVerifier $jwtVerifier; protected function setUp(): void { parent::setUp(); - $this->appleApiClient = Mockery::mock(Api\AppleApiClient::class); - $this->rsaMock = Mockery::mock(RSA::class); - $this->signerMock = Mockery::mock(JWT\Signer\Rsa\Sha256::class); - $this->jwtMock = Mockery::mock(JWT\Token::class); + $this->validatorMock = Mockery::mock(JWT\Validator::class); + $this->appleApiClient = Mockery::mock(Api\AppleApiClientInterface::class); + $this->jwtTokenMock = Mockery::mock(JWT\Token::class); - $this->jwtVerifier = new JwtVerifier($this->appleApiClient, $this->rsaMock, $this->signerMock); + $this->jwtVerifier = new JwtVerifier( + $this->appleApiClient, + $this->validatorMock, + new JWT\Signer\Rsa\Sha256() + ); } public function testIfVerifyThrowsKeysFetchingFailedExceptionWhenFailedToFetchJsonWebKeySet(): void @@ -61,7 +44,7 @@ public function testIfVerifyThrowsKeysFetchingFailedExceptionWhenFailedToFetchJs $this->expectException(Exception\KeysFetchingFailedException::class); $this->expectExceptionMessage('Connection error'); - $this->jwtVerifier->verify($this->jwtMock); + $this->jwtVerifier->verify($this->jwtTokenMock); } public function testIfVerifyThrowsInvalidCryptographicAlgorithmExceptionWhenKidHeaderNotExist(): void @@ -71,14 +54,14 @@ public function testIfVerifyThrowsInvalidCryptographicAlgorithmExceptionWhenKidH ->withNoArgs() ->andReturn(new Api\Response\JsonWebKeySetCollection([])); - $this->jwtMock->shouldReceive('getHeader') + $this->jwtTokenMock->shouldReceive('headers') ->once() - ->with('kid') + ->withNoArgs() ->andThrow(new OutOfBoundsException('`kid` header is missing')); $this->expectException(Exception\InvalidCryptographicAlgorithmException::class); $this->expectExceptionMessage('`kid` header is missing'); - $this->jwtVerifier->verify($this->jwtMock); + $this->jwtVerifier->verify($this->jwtTokenMock); } public function testIfVerifyThrowsInvalidCryptographicAlgorithmExceptionWhenAlgorithmIsNotSupported(): void @@ -88,16 +71,16 @@ public function testIfVerifyThrowsInvalidCryptographicAlgorithmExceptionWhenAlgo ->withNoArgs() ->andReturn(new Api\Response\JsonWebKeySetCollection([])); - $this->jwtMock->shouldReceive('getHeader') + $this->jwtTokenMock->shouldReceive('headers') ->once() - ->with('kid') - ->andReturn('foo'); + ->withNoArgs() + ->andReturn(new JWT\Token\DataSet(['kid' => 'foo'], '')); $this->expectException(Exception\InvalidCryptographicAlgorithmException::class); $this->expectExceptionMessage( 'Cryptographic algorithm `foo` is not supported. Supported algorithms: `86D88Kf,eXaunmL,YuyXoY`' ); - $this->jwtVerifier->verify($this->jwtMock); + $this->jwtVerifier->verify($this->jwtTokenMock); } public function testIfVerifyThrowsInvalidCryptographicAlgorithmExceptionWhenAuthKeyNotExistForGivenAlgorithm(): void @@ -107,68 +90,14 @@ public function testIfVerifyThrowsInvalidCryptographicAlgorithmExceptionWhenAuth ->withNoArgs() ->andReturn(new Api\Response\JsonWebKeySetCollection([])); - $this->jwtMock->shouldReceive('getHeader') + $this->jwtTokenMock->shouldReceive('headers') ->once() - ->with('kid') - ->andReturn('86D88Kf'); + ->withNoArgs() + ->andReturn(new JWT\Token\DataSet(['kid' => '86D88Kf'], '')); $this->expectException(Exception\InvalidCryptographicAlgorithmException::class); $this->expectExceptionMessage('Unsupported cryptographic algorithm passed `86D88Kf'); - $this->jwtVerifier->verify($this->jwtMock); - } - - public function testIfVerifyThrowsNotSignedTokenExceptionWhenTokenIsMissingSignature(): void - { - $this->appleApiClient->shouldReceive('getAuthKeys') - ->once() - ->withNoArgs() - ->andReturn( - new Api\Response\JsonWebKeySetCollection( - [ - '86D88Kf' => new Api\Response\JsonWebKeySet( - 'RSA', - '86D88Kf', - 'sig', - 'RS256', - 'iGaLqP6y-SJCCBq5Hv6pGDbG_SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY11qInqUvjJur--hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwCPygjLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk-ILjv1bORSRl8AK677-1T8isGfHKXGZ_ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBRZA2QuYw-zHLwQ', - 'AQAB' - ), - ] - ) - ); - - $this->jwtMock->shouldReceive('getHeader') - ->once() - ->with('kid') - ->andReturn('86D88Kf'); - - $this->rsaMock->shouldReceive('loadKey') - ->once() - ->with( - Mockery::on( - function (array $argument) { - $this->assertArrayHasKey('exponent', $argument); - $this->assertArrayHasKey('modulus', $argument); - $this->assertInstanceOf(BigInteger::class, $argument['exponent']); - $this->assertInstanceOf(BigInteger::class, $argument['modulus']); - - return true; - } - ) - ); - $this->rsaMock->shouldReceive('getPublicKey') - ->withNoArgs() - ->once() - ->andReturn('publicKey'); - - $this->jwtMock->shouldReceive('verify') - ->once() - ->with($this->signerMock, 'publicKey') - ->andThrow(new BadMethodCallException('This token is not signed')); - - $this->expectException(Exception\NotSignedTokenException::class); - $this->expectExceptionMessage('This token is not signed'); - $this->jwtVerifier->verify($this->jwtMock); + $this->jwtVerifier->verify($this->jwtTokenMock); } public function testIfVerifyReturnsTrueWhenTokenIsCorrect(): void @@ -191,36 +120,17 @@ public function testIfVerifyReturnsTrueWhenTokenIsCorrect(): void ) ); - $this->jwtMock->shouldReceive('getHeader') + $this->jwtTokenMock->shouldReceive('headers') ->once() - ->with('kid') - ->andReturn('86D88Kf'); - - $this->rsaMock->shouldReceive('loadKey') - ->once() - ->with( - Mockery::on( - function (array $argument) { - $this->assertArrayHasKey('exponent', $argument); - $this->assertArrayHasKey('modulus', $argument); - $this->assertInstanceOf(BigInteger::class, $argument['exponent']); - $this->assertInstanceOf(BigInteger::class, $argument['modulus']); - - return true; - } - ) - ); - $this->rsaMock->shouldReceive('getPublicKey') ->withNoArgs() - ->once() - ->andReturn('publicKey'); + ->andReturn(new JWT\Token\DataSet(['kid' => '86D88Kf'], '')); - $this->jwtMock->shouldReceive('verify') + $this->validatorMock->shouldReceive('validate') ->once() - ->with($this->signerMock, 'publicKey') + ->with($this->jwtTokenMock, JWT\Validation\Constraint\SignedWith::class) ->andReturn(true); - self::assertTrue($this->jwtVerifier->verify($this->jwtMock)); + self::assertTrue($this->jwtVerifier->verify($this->jwtTokenMock)); } public function testIfVerifyReturnsTrueWhenTokenIsCorrectMalformed(): void @@ -243,35 +153,16 @@ public function testIfVerifyReturnsTrueWhenTokenIsCorrectMalformed(): void ) ); - $this->jwtMock->shouldReceive('getHeader') + $this->jwtTokenMock->shouldReceive('headers') ->once() - ->with('kid') - ->andReturn('86D88Kf'); - - $this->rsaMock->shouldReceive('loadKey') - ->once() - ->with( - Mockery::on( - function (array $argument) { - $this->assertArrayHasKey('exponent', $argument); - $this->assertArrayHasKey('modulus', $argument); - $this->assertInstanceOf(BigInteger::class, $argument['exponent']); - $this->assertInstanceOf(BigInteger::class, $argument['modulus']); - - return true; - } - ) - ); - $this->rsaMock->shouldReceive('getPublicKey') ->withNoArgs() - ->once() - ->andReturn('publicKey'); + ->andReturn(new JWT\Token\DataSet(['kid' => '86D88Kf'], '')); - $this->jwtMock->shouldReceive('verify') + $this->validatorMock->shouldReceive('validate') ->once() - ->with($this->signerMock, 'publicKey') + ->with($this->jwtTokenMock, JWT\Validation\Constraint\SignedWith::class) ->andReturn(false); - self::assertFalse($this->jwtVerifier->verify($this->jwtMock)); + self::assertFalse($this->jwtVerifier->verify($this->jwtTokenMock)); } } diff --git a/tests/Unit/Auth/Service/AppleJwtFetchingServiceTest.php b/tests/Unit/Auth/Service/AppleJwtFetchingServiceTest.php index c279df6..8a92a16 100644 --- a/tests/Unit/Auth/Service/AppleJwtFetchingServiceTest.php +++ b/tests/Unit/Auth/Service/AppleJwtFetchingServiceTest.php @@ -7,36 +7,22 @@ use Azimo\Apple\Auth\Jwt; use Azimo\Apple\Auth\Service\AppleJwtFetchingService; use Azimo\Apple\Auth\Struct\JwtPayload; +use DateTimeImmutable; use Lcobucci\JWT\Token; use Mockery; use Mockery\Adapter\Phpunit\MockeryTestCase; class AppleJwtFetchingServiceTest extends MockeryTestCase { - /** - * @var Jwt\JwtParser|Mockery\MockInterface - */ - private $parserMock; - - /** - * @var Jwt\JwtVerifier|Mockery\MockInterface - */ - private $verifierMock; - - /** - * @var Jwt\JwtValidator|Mockery\MockInterface - */ - private $validatorMock; - - /** - * @var AppleJwtStructFactory|Mockery\MockInterface - */ - private $factoryMock; - - /** - * @var AppleJwtFetchingService - */ - private $jwtService; + private Jwt\JwtParser $parserMock; + + private Jwt\JwtVerifier $verifierMock; + + private Jwt\JwtValidator $validatorMock; + + private AppleJwtStructFactory $factoryMock; + + private AppleJwtFetchingService $jwtService; protected function setUp(): void { @@ -57,7 +43,7 @@ protected function setUp(): void public function testIfGetJwtPayloadThrowsVerificationFailedExceptionWhenVerificationFails(): void { - $token = new Token(); + $token = Mockery::mock(Token::class); $this->parserMock->shouldReceive('parse') ->once() ->with('json.web.token') @@ -78,7 +64,7 @@ public function testIfGetJwtPayloadThrowsVerificationFailedExceptionWhenVerifica public function testIfGetJwtPayloadThrowsValidationFailedExceptionWhenTokenIsInvalid(): void { - $token = new Token(); + $token = Mockery::mock(Token::class); $this->parserMock->shouldReceive('parse') ->once() ->with('json.web.token') @@ -101,7 +87,8 @@ public function testIfGetJwtPayloadThrowsValidationFailedExceptionWhenTokenIsInv public function testIfGetJwtPayloadReturnsExpectedJwtPayloadWhenTokenIsVerifiedAndValid(): void { - $token = new Token(); + $currentDate = new DateTimeImmutable(); + $token = Mockery::mock(Token::class); $this->parserMock->shouldReceive('parse') ->once() ->with('json.web.token') @@ -119,9 +106,9 @@ public function testIfGetJwtPayloadReturnsExpectedJwtPayloadWhenTokenIsVerifiedA $jwtPayload = new JwtPayload( 'https://appleid.apple.com', - 'com.acme.app', - 1591622611, - 1591622011, + ['com.acme.app'], + $currentDate, + $currentDate, 'foo.bar.baz', 'qGzMhtsfTCom-bl1PJYLHk', 'foo@privaterelay.appleid.com',