Passed
Push — master ( 119443...623119 )
by Pol
03:53 queued 01:21
created

ApiGwKeyLoader::loadKey()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 19
c 1
b 0
f 0
dl 0
loc 32
ccs 18
cts 18
cp 1
rs 9.0111
cc 6
nc 10
nop 1
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EcPhp\ApiGwAuthenticationBundle\Service\KeyLoader;
6
7
use EcPhp\ApiGwAuthenticationBundle\Exception\ApiGwAuthenticationException;
8
use EcPhp\ApiGwAuthenticationBundle\Service\KeyConverter\KeyConverterInterface;
9
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\RawKeyLoader;
10
use Symfony\Contracts\HttpClient\HttpClientInterface;
11
use Throwable;
12
13
use function array_key_exists;
14
15
/**
16
 * Class ApiGwKeyLoader.
17
 *
18
 * phpcs:disable Generic.Files.LineLength.TooLong
19
 */
20
final class ApiGwKeyLoader implements KeyLoaderInterface
21
{
22
    private array $configuration;
23
24
    private array $environment;
25
26
    private HttpClientInterface $httpClient;
27
28
    private KeyConverterInterface $keyConverter;
29
30
    private static array $mapping = [
31
        [
32
            'env' => 'production',
33
            KeyLoaderInterface::TYPE_PUBLIC => 'https://api.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json',
34
            KeyLoaderInterface::TYPE_PRIVATE => '',
35
            'failsafe' => [
36
                KeyLoaderInterface::TYPE_PUBLIC => __DIR__ . '/../../Resources/keys/production/jwks.json',
37
                KeyLoaderInterface::TYPE_PRIVATE => '',
38
            ],
39
        ],
40
        [
41
            'env' => 'intra',
42
            KeyLoaderInterface::TYPE_PUBLIC => 'https://intrapi.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json',
43
            KeyLoaderInterface::TYPE_PRIVATE => '',
44
            'failsafe' => [
45
                KeyLoaderInterface::TYPE_PUBLIC => __DIR__ . '/../../Resources/keys/intra/jwks.json',
46
                KeyLoaderInterface::TYPE_PRIVATE => '',
47
            ],
48
        ],
49
        [
50
            'env' => 'acceptance',
51
            KeyLoaderInterface::TYPE_PUBLIC => 'https://api.acceptance.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json',
52
            KeyLoaderInterface::TYPE_PRIVATE => '',
53
            'failsafe' => [
54
                KeyLoaderInterface::TYPE_PUBLIC => __DIR__ . '/../../Resources/keys/acceptance/jwks.json',
55
                KeyLoaderInterface::TYPE_PRIVATE => '',
56
            ],
57
        ],
58
    ];
59
60
    private string $projectDir;
61
62 8
    public function __construct(HttpClientInterface $httpClient, KeyConverterInterface $keyConverter, string $projectDir, array $configuration = [])
63
    {
64 8
        $this->httpClient = $httpClient;
65 8
        $this->keyConverter = $keyConverter;
66 8
        $this->projectDir = $projectDir;
67 8
        $this->configuration = $configuration;
68 8
        $this->environment = $this->getEnvironment($configuration['defaults']['env']);
69 8
    }
70
71 7
    public function getPassphrase(): string
72
    {
73 7
        return '';
74
    }
75
76 7
    public function getPublicKey(): string
77
    {
78 7
        return $this->environment[KeyLoaderInterface::TYPE_PUBLIC] ?? '';
79
    }
80
81 7
    public function getSigningKey(): string
82
    {
83 7
        return $this->environment[KeyLoaderInterface::TYPE_PRIVATE] ?? '';
84
    }
85
86 7
    public function loadKey($type): string
87
    {
88 7
        $publicKey = $this->getPublicKey();
89 7
        $signingKey = $this->getSigningKey();
90 7
        $passPhrase = $this->getPassphrase();
91
92 7
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ? $publicKey : $signingKey;
93
94 7
        if ('user' === $this->environment['env']) {
95
            $keyPathCandidates = [
96 4
                [$this->projectDir, $key], // Look in the App dir,
97 4
                [__DIR__, $key], // Look in this bundle dir,
98
            ];
99
100 4
            foreach ($keyPathCandidates as $keyPathCandidateParts) {
101 4
                if (true === file_exists(implode('', $keyPathCandidateParts))) {
102 3
                    $prefix = current($keyPathCandidateParts);
103
104 3
                    return (new RawKeyLoader($prefix . $signingKey, $prefix . $publicKey, $passPhrase))
105 3
                        ->loadKey($type);
106
                }
107
            }
108
        }
109
110
        try {
111 4
            $key = (new JWKSKeyLoader($this, $this->httpClient, $this->keyConverter))
112 4
                ->loadKey($type);
113 2
        } catch (Throwable $e) {
114 2
            $key = $this->loadFailsafeKey($type);
115
        }
116
117 4
        return $key;
118
    }
119
120 8
    private function getEnvironment(string $env): array
121
    {
122 8
        $envs = [];
123
124 8
        foreach ($this->configuration['envs'] as $name => $data) {
125 6
            $envs[] = [
126 6
                'env' => $name,
127 6
                KeyLoaderInterface::TYPE_PUBLIC => $data[KeyLoaderInterface::TYPE_PUBLIC],
128 6
                KeyLoaderInterface::TYPE_PRIVATE => $data[KeyLoaderInterface::TYPE_PRIVATE],
129
                'failsafe' => [
130 6
                    KeyLoaderInterface::TYPE_PUBLIC => $data['failsafe'][KeyLoaderInterface::TYPE_PUBLIC] ?? $data[KeyLoaderInterface::TYPE_PUBLIC],
131 6
                    KeyLoaderInterface::TYPE_PRIVATE => $data['failsafe'][KeyLoaderInterface::TYPE_PRIVATE] ?? $data[KeyLoaderInterface::TYPE_PRIVATE],
132
                ],
133
            ];
134
        }
135
136 8
        foreach (array_merge(self::$mapping, $envs) as $mapping) {
137 8
            if ($mapping['env'] === $env) {
138 8
                return $mapping;
139
            }
140
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
141
    }
142
143 1
    private function getFailsafePrivateKey(): string
144
    {
145 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PRIVATE];
146
    }
147
148 1
    private function getFailsafePublicKey(): string
149
    {
150 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PUBLIC];
151
    }
152
153 2
    private function loadFailsafeKey(string $type): string
154
    {
155 2
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ?
156 1
            $this->getFailsafePublicKey() :
157 2
            $this->getFailsafePrivateKey();
158
159
        // Todo: Remove duplicated code in here and JWKSKeyLoader.
160 2
        $jwksArray = json_decode(file_get_contents($key), true);
161
162 2
        if (false === array_key_exists('keys', $jwksArray)) {
163
            throw new ApiGwAuthenticationException(
164
                sprintf('Invalid JWKS format of %s key at %s.', $type, $key)
165
            );
166
        }
167
168 2
        if ([] === $jwksArray['keys']) {
169
            throw new ApiGwAuthenticationException(
170
                sprintf('Invalid JWKS format of %s key at %s, keys array is empty.', $type, $key)
171
            );
172
        }
173
174 2
        return current($this->keyConverter->fromJWKStoPEMS($jwksArray['keys']));
175
    }
176
}
177