Skip to content

Commit

Permalink
tests: Add more tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
drupol committed Feb 10, 2021
1 parent 17d44b8 commit 75b7c0a
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 40 deletions.
4 changes: 2 additions & 2 deletions grumphp.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ parameters:
threads: 10
test_framework: phpspec
configuration: infection.json.dist
min_msi: 40
min_covered_msi: 40
min_msi: 50
min_covered_msi: 50
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace spec\EcPhp\ApiGwAuthenticationBundle\Service\KeyLoader;

use EcPhp\ApiGwAuthenticationBundle\Exception\ApiGwAuthenticationException;
use EcPhp\ApiGwAuthenticationBundle\Service\KeyConverter\KeyConverterInterface;
use EcPhp\ApiGwAuthenticationBundle\Service\KeyLoader\ApiGwKeyLoader;
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
Expand Down Expand Up @@ -99,8 +100,8 @@ public function it_can_get_the_api_gateway_production_key(HttpClientInterface $h

public function it_can_get_user_failsafe_private_key(HttpClientInterface $httpClient, KeyConverterInterface $keyConverter)
{
$publicKeyFilepath = __DIR__ . '/../../../../../tests/src/Resources/keys/user/public.jwks.json';
$privateKeyFilepath = __DIR__ . '/../../../../../tests/src/Resources/keys/user/private.jwks.json';
$publicKeyFilepath = '/../../../tests/src/Resources/keys/user/public.jwks.json';
$privateKeyFilepath = '/../../../tests/src/Resources/keys/user/private.jwks.json';
$configuration = [
'defaults' => [
'env' => 'user',
Expand All @@ -119,7 +120,7 @@ public function it_can_get_user_failsafe_private_key(HttpClientInterface $httpCl

$projectDir = __DIR__;

$jwks = json_decode(file_get_contents($privateKeyFilepath), true);
$jwks = json_decode(file_get_contents(__DIR__ . '/../..' . $privateKeyFilepath), true);

$keyConverter
->fromJWKStoPEMS($jwks['keys'])
Expand Down Expand Up @@ -269,6 +270,64 @@ public function it_make_sure_that_official_keys_cannot_be_overriden(HttpClientIn
->shouldReturn($key);
}

public function it_throws_an_exception_when_failsafe_key_is_empty(HttpClientInterface $httpClient, KeyConverterInterface $keyConverter)
{
$publicKeyFilepath = '/../../../tests/src/Resources/keys/user/public.jwks.empty';
$privateKeyFilepath = '/../../../tests/src/Resources/keys/user/private.jwks.json';
$configuration = [
'defaults' => [
'env' => 'user',
],
'envs' => [
'user' => [
KeyLoaderInterface::TYPE_PUBLIC => 'http://a.b.c.d.e.f',
KeyLoaderInterface::TYPE_PRIVATE => 'http://a.b.c.d.e.f',
'failsafe' => [
KeyLoaderInterface::TYPE_PUBLIC => $publicKeyFilepath,
KeyLoaderInterface::TYPE_PRIVATE => $privateKeyFilepath,
],
],
],
];

$projectDir = __DIR__;

$this->beConstructedWith($httpClient, $keyConverter, $projectDir, $configuration);

$this
->shouldThrow(new ApiGwAuthenticationException('Invalid JWKS format of public key at /../../../tests/src/Resources/keys/user/public.jwks.empty, keys array is empty.'))
->during('loadKey', [KeyLoaderInterface::TYPE_PUBLIC]);
}

public function it_throws_an_exception_when_failsafe_key_is_invalid(HttpClientInterface $httpClient, KeyConverterInterface $keyConverter)
{
$publicKeyFilepath = '/../../../tests/src/Resources/keys/user/public.jwks.invalid';
$privateKeyFilepath = '/../../../tests/src/Resources/keys/user/private.jwks.json';
$configuration = [
'defaults' => [
'env' => 'user',
],
'envs' => [
'user' => [
KeyLoaderInterface::TYPE_PUBLIC => 'http://a.b.c.d.e.f',
KeyLoaderInterface::TYPE_PRIVATE => 'http://a.b.c.d.e.f',
'failsafe' => [
KeyLoaderInterface::TYPE_PUBLIC => $publicKeyFilepath,
KeyLoaderInterface::TYPE_PRIVATE => $privateKeyFilepath,
],
],
],
];

$projectDir = __DIR__;

$this->beConstructedWith($httpClient, $keyConverter, $projectDir, $configuration);

$this
->shouldThrow(new ApiGwAuthenticationException('Invalid JWKS format of public key at /../../../tests/src/Resources/keys/user/public.jwks.invalid.'))
->during('loadKey', [KeyLoaderInterface::TYPE_PUBLIC]);
}

public function let(HttpClientInterface $httpClient, KeyConverterInterface $keyConverter)
{
$configuration = [
Expand All @@ -277,8 +336,8 @@ public function let(HttpClientInterface $httpClient, KeyConverterInterface $keyC
],
'envs' => [
'user' => [
KeyLoaderInterface::TYPE_PUBLIC => __DIR__ . '/../../../../../tests/src/Resources/user/public.key',
KeyLoaderInterface::TYPE_PRIVATE => __DIR__ . '/../../../../../tests/src/Resources/user/private.key',
KeyLoaderInterface::TYPE_PUBLIC => '/../../../tests/src/Resources/user/public.key',
KeyLoaderInterface::TYPE_PRIVATE => '/../../../tests/src/Resources/user/private.key',
],
],
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,6 @@ public function it_can_throw_when_status_code_is_not_200(KeyLoaderInterface $key
->during('loadKey', [KeyLoaderInterface::TYPE_PUBLIC]);
}

public function it_can_throw_when_the_request_failed(KeyLoaderInterface $keyLoader, HttpClientInterface $httpClient, KeyConverterInterface $keyConverter, ResponseInterface $response)
{
$this->prepareDeps($keyLoader, $httpClient, $keyConverter, $response);

$httpClient
->request('GET', KeyLoaderInterface::TYPE_PUBLIC)
->willThrow(new ApiGwAuthenticationException('foo'));

$this
->shouldThrow(ApiGwAuthenticationException::class)
->during('loadKey', [KeyLoaderInterface::TYPE_PUBLIC]);
}

public function it_is_initializable()
{
$this->shouldHaveType(JWKSKeyLoader::class);
Expand All @@ -65,6 +52,53 @@ public function it_is_initializable()
->shouldReturn('passphrase');
}

public function it_throw_an_exception_when_the_jwks_has_no_keys(KeyLoaderInterface $keyLoader, HttpClientInterface $httpClient, KeyConverterInterface $keyConverter, ResponseInterface $response)
{
$this->prepareDeps($keyLoader, $httpClient, $keyConverter, $response);

$response
->toArray()
->willReturn(['keys' => []]);

$this
->shouldThrow(
new ApiGwAuthenticationException(
'Invalid JWKS format of public key at public, keys array is empty.'
)
)
->during('loadKey', [KeyLoaderInterface::TYPE_PUBLIC]);
}

public function it_throw_an_exception_when_the_jwks_is_invalid(KeyLoaderInterface $keyLoader, HttpClientInterface $httpClient, KeyConverterInterface $keyConverter, ResponseInterface $response)
{
$this->prepareDeps($keyLoader, $httpClient, $keyConverter, $response);

$response
->toArray()
->willReturn(['foo' => 'bar']);

$this
->shouldThrow(
new ApiGwAuthenticationException(
'Invalid JWKS format of public key at public.'
)
)
->during('loadKey', [KeyLoaderInterface::TYPE_PUBLIC]);
}

public function it_throw_an_exception_when_the_request_failed(KeyLoaderInterface $keyLoader, HttpClientInterface $httpClient, KeyConverterInterface $keyConverter, ResponseInterface $response)
{
$this->prepareDeps($keyLoader, $httpClient, $keyConverter, $response);

$httpClient
->request('GET', KeyLoaderInterface::TYPE_PUBLIC)
->willThrow(new ApiGwAuthenticationException('foo'));

$this
->shouldThrow(ApiGwAuthenticationException::class)
->during('loadKey', [KeyLoaderInterface::TYPE_PUBLIC]);
}

public function let(KeyLoaderInterface $keyLoader, HttpClientInterface $httpClient, KeyConverterInterface $keyConverter, ResponseInterface $response)
{
$this->prepareDeps($keyLoader, $httpClient, $keyConverter, $response);
Expand Down Expand Up @@ -92,11 +126,11 @@ private function prepareDeps(KeyLoaderInterface $keyLoader, HttpClientInterface
$response
->toArray()
->willReturn(['keys' => [
'jwks array structure',
['jwks array structure'],
]]);

$keyConverter
->fromJWKStoPEMS(['jwks array structure'])
->fromJWKStoPEMS([['jwks array structure']])
->willReturn(['foo']);

$httpClient
Expand Down
40 changes: 28 additions & 12 deletions src/Service/KeyLoader/ApiGwKeyLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class ApiGwKeyLoader implements KeyLoaderInterface

private const API_GW_PRODUCTION = 'https://api.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json';

private const LOCAL_FAILSAFE_PATH = __DIR__ . '/../../Resources/keys';
private const LOCAL_FAILSAFE_PATH = '/../../Resources/keys';

private array $environment;

Expand Down Expand Up @@ -102,18 +102,13 @@ public function loadKey($type): string
$key = KeyLoaderInterface::TYPE_PUBLIC === $type ? $publicKey : $signingKey;

if ('user' === $this->environment['env']) {
$keyPathCandidates = [
[$this->projectDir, $key], // Look in the App dir,
[__DIR__, $key], // Look in this bundle dir,
];
$keyPathCandidateParts = $this->findFirstFileExist($key);

foreach ($keyPathCandidates as $keyPathCandidateParts) {
if (true === file_exists(implode('', $keyPathCandidateParts))) {
$prefix = current($keyPathCandidateParts);
if ([] !== $keyPathCandidateParts) {
$prefix = current($keyPathCandidateParts);

return (new RawKeyLoader($prefix . $signingKey, $prefix . $publicKey, $passPhrase))
->loadKey($type);
}
return (new RawKeyLoader($prefix . $signingKey, $prefix . $publicKey, $passPhrase))
->loadKey($type);
}
}

Expand All @@ -127,6 +122,25 @@ public function loadKey($type): string
return $key;
}

private function findFirstFileExist(string $key): array
{
$candidates = array_map(
static fn (string $directory): array => [$directory, $key],
[
$this->projectDir,
__DIR__,
]
);

foreach ($candidates as $candidate) {
if (true === file_exists(implode('', $candidate))) {
return $candidate;
}
}

return [];
}

private function getEnvironment(string $env, array $configuredEnvs): array
{
$envs = [];
Expand Down Expand Up @@ -166,8 +180,10 @@ private function loadFailsafeKey(string $type): string
$this->getFailsafePublicKey() :
$this->getFailsafePrivateKey();

$keyPathCandidateParts = $this->findFirstFileExist($key);

// Todo: Remove duplicated code in here and JWKSKeyLoader.
$jwksArray = json_decode(file_get_contents($key), true);
$jwksArray = json_decode(file_get_contents(implode('', $keyPathCandidateParts)), true);

if (false === array_key_exists('keys', $jwksArray)) {
throw new ApiGwAuthenticationException(
Expand Down
12 changes: 6 additions & 6 deletions src/Service/KeyLoader/JWKSKeyLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function loadKey($type): string
$key = $this->keyLoader->getPublicKey();

try {
$jwks = $this->httpClient->request('GET', $key);
$response = $this->httpClient->request('GET', $key);
} catch (Throwable $e) {
throw new ApiGwAuthenticationException(
sprintf('Unable to request uri(%s) for %s key.', $key, $type),
Expand All @@ -59,26 +59,26 @@ public function loadKey($type): string
);
}

if (200 !== $statusCode = $jwks->getStatusCode()) {
if (200 !== $statusCode = $response->getStatusCode()) {
throw new ApiGwAuthenticationException(
sprintf('Invalid code(%s) thrown while fetching the %s key at %s.', $statusCode, $type, $key)
);
}

$jwksArray = $jwks->toArray();
$jwks = $response->toArray();

if (false === array_key_exists('keys', $jwksArray)) {
if (false === array_key_exists('keys', $jwks)) {
throw new ApiGwAuthenticationException(
sprintf('Invalid JWKS format of %s key at %s.', $type, $key)
);
}

if ([] === $jwksArray['keys']) {
if ([] === $jwks['keys']) {
throw new ApiGwAuthenticationException(
sprintf('Invalid JWKS format of %s key at %s, keys array is empty.', $type, $key)
);
}

return current($this->keyConverter->fromJWKStoPEMS($jwksArray['keys']));
return current($this->keyConverter->fromJWKStoPEMS($jwks['keys']));
}
}
3 changes: 3 additions & 0 deletions tests/src/Resources/keys/user/public.jwks.empty
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"keys": []
}
3 changes: 3 additions & 0 deletions tests/src/Resources/keys/user/public.jwks.invalid
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": "bar"
}

0 comments on commit 75b7c0a

Please sign in to comment.