ApiGwKeyLoader   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 22
eloc 86
dl 0
loc 185
ccs 64
cts 64
cp 1
rs 10
c 3
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getFailsafePrivateKey() 0 3 1
A getPassphrase() 0 4 1
A loadKey() 0 27 5
A getFailsafePublicKey() 0 3 1
A findFirstFileExist() 0 17 3
A getPublicKey() 0 3 1
A getSigningKey() 0 3 1
A loadFailsafeKey() 0 24 4
A __construct() 0 12 1
A getEnvironment() 0 19 4
1
<?php
2
3
/**
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @see https://github.com/ecphp
8
 */
9
10
declare(strict_types=1);
11
12
namespace EcPhp\ApiGwAuthenticationBundle\Service\KeyLoader;
13
14
use EcPhp\ApiGwAuthenticationBundle\Exception\ApiGwAuthenticationException;
15
use EcPhp\ApiGwAuthenticationBundle\Service\KeyConverter\KeyConverterInterface;
16
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\RawKeyLoader;
17
use Psr\Http\Client\ClientInterface;
18
use Psr\Http\Message\RequestFactoryInterface;
19
use Throwable;
20
21
use function array_key_exists;
22
23
/**
24
 * Class ApiGwKeyLoader.
25
 *
26
 * phpcs:disable Generic.Files.LineLength.TooLong
27
 */
28
final class ApiGwKeyLoader implements KeyLoaderInterface
29
{
30
    private const API_GW_ACCEPTANCE = 'https://api.acceptance.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json';
31
32
    private const API_GW_INTRA = 'https://intrapi.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json';
33
34
    private const API_GW_PRODUCTION = 'https://api.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json';
35
36
    private const LOCAL_FAILSAFE_PATH = '/../../Resources/keys';
37
38
    private array $environment;
39
40
    private ClientInterface $httpClient;
41
42
    private KeyConverterInterface $keyConverter;
43
44
    private static array $mapping = [
45
        [
46
            'env' => 'production',
47
            KeyLoaderInterface::TYPE_PUBLIC => self::API_GW_PRODUCTION,
48
            KeyLoaderInterface::TYPE_PRIVATE => '',
49
            'failsafe' => [
50
                KeyLoaderInterface::TYPE_PUBLIC => self::LOCAL_FAILSAFE_PATH . '/production/jwks.json',
51
                KeyLoaderInterface::TYPE_PRIVATE => '',
52
            ],
53
        ],
54
        [
55
            'env' => 'intra',
56
            KeyLoaderInterface::TYPE_PUBLIC => self::API_GW_INTRA,
57
            KeyLoaderInterface::TYPE_PRIVATE => '',
58
            'failsafe' => [
59
                KeyLoaderInterface::TYPE_PUBLIC => self::LOCAL_FAILSAFE_PATH . '/intra/jwks.json',
60
                KeyLoaderInterface::TYPE_PRIVATE => '',
61
            ],
62
        ],
63
        [
64
            'env' => 'acceptance',
65
            KeyLoaderInterface::TYPE_PUBLIC => self::API_GW_ACCEPTANCE,
66
            KeyLoaderInterface::TYPE_PRIVATE => '',
67
            'failsafe' => [
68
                KeyLoaderInterface::TYPE_PUBLIC => self::LOCAL_FAILSAFE_PATH . '/acceptance/jwks.json',
69
                KeyLoaderInterface::TYPE_PRIVATE => '',
70
            ],
71
        ],
72
    ];
73
74
    private string $projectDir;
75
76
    private RequestFactoryInterface $requestFactory;
77
78 10
    public function __construct(
79
        ClientInterface $httpClient,
80
        RequestFactoryInterface $requestFactory,
81
        KeyConverterInterface $keyConverter,
82
        string $projectDir,
83
        array $configuration
84
    ) {
85 10
        $this->httpClient = $httpClient;
86 10
        $this->requestFactory = $requestFactory;
87 10
        $this->keyConverter = $keyConverter;
88 10
        $this->projectDir = $projectDir;
89 10
        $this->environment = $this->getEnvironment($configuration['defaults']['env'], $configuration['envs']);
90
    }
91
92 9
    public function getPassphrase(): string
93
    {
94
        // Todo: Not supported yet.
95 9
        return '';
96
    }
97
98 9
    public function getPublicKey(): string
99
    {
100 9
        return $this->environment[KeyLoaderInterface::TYPE_PUBLIC] ?? '';
101
    }
102
103 9
    public function getSigningKey(): string
104
    {
105 9
        return $this->environment[KeyLoaderInterface::TYPE_PRIVATE] ?? '';
106
    }
107
108 9
    public function loadKey($type): string
109
    {
110 9
        $publicKey = $this->getPublicKey();
111 9
        $signingKey = $this->getSigningKey();
112 9
        $passPhrase = $this->getPassphrase();
113
114 9
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ? $publicKey : $signingKey;
115
116 9
        if ('user' === $this->environment['env']) {
117 6
            $keyPathCandidateParts = $this->findFirstFileExist($key);
118
119 6
            if ([] !== $keyPathCandidateParts) {
120 3
                $prefix = current($keyPathCandidateParts);
121
122 3
                return (new RawKeyLoader($prefix . $signingKey, $prefix . $publicKey, $passPhrase))
123 3
                    ->loadKey($type);
124
            }
125
        }
126
127
        try {
128 6
            $key = (new JWKSKeyLoader($this, $this->httpClient, $this->requestFactory, $this->keyConverter))
129 6
                ->loadKey($type);
130 6
        } catch (Throwable $e) {
131 6
            $key = $this->loadFailsafeKey($type);
132
        }
133
134 4
        return $key;
135
    }
136
137 9
    private function findFirstFileExist(string $key): array
138
    {
139 9
        $candidates = array_map(
140 9
            static fn (string $directory): array => [$directory, $key],
141
            [
142 9
                $this->projectDir,
143
                __DIR__,
144
            ]
145
        );
146
147 9
        foreach ($candidates as $candidate) {
148 9
            if (true === file_exists(implode('', $candidate))) {
149 9
                return $candidate;
150
            }
151
        }
152
153 3
        return [];
154
    }
155
156 10
    private function getEnvironment(string $env, array $configuredEnvs): array
157
    {
158 10
        $envs = [];
159
160 10
        foreach ($configuredEnvs as $name => $data) {
161 8
            $envs[] = [
162
                'env' => $name,
163 8
                KeyLoaderInterface::TYPE_PUBLIC => $data[KeyLoaderInterface::TYPE_PUBLIC],
164 8
                KeyLoaderInterface::TYPE_PRIVATE => $data[KeyLoaderInterface::TYPE_PRIVATE],
165
                'failsafe' => [
166 8
                    KeyLoaderInterface::TYPE_PUBLIC => $data['failsafe'][KeyLoaderInterface::TYPE_PUBLIC] ?? $data[KeyLoaderInterface::TYPE_PUBLIC],
167 8
                    KeyLoaderInterface::TYPE_PRIVATE => $data['failsafe'][KeyLoaderInterface::TYPE_PRIVATE] ?? $data[KeyLoaderInterface::TYPE_PRIVATE],
168
                ],
169
            ];
170
        }
171
172 10
        foreach (array_merge(self::$mapping, $envs) as $mapping) {
173 10
            if ($mapping['env'] === $env) {
174 10
                return $mapping;
175
            }
176
        }
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...
177
    }
178
179 1
    private function getFailsafePrivateKey(): string
180
    {
181 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PRIVATE];
182
    }
183
184 5
    private function getFailsafePublicKey(): string
185
    {
186 5
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PUBLIC];
187
    }
188
189 6
    private function loadFailsafeKey(string $type): string
190
    {
191 6
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ?
192 5
            $this->getFailsafePublicKey() :
193 6
            $this->getFailsafePrivateKey();
194
195 6
        $keyPathCandidateParts = $this->findFirstFileExist($key);
196
197
        // Todo: Remove duplicated code in here and JWKSKeyLoader.
198 6
        $jwksArray = json_decode(file_get_contents(implode('', $keyPathCandidateParts)), true);
199
200 6
        if (false === array_key_exists('keys', $jwksArray)) {
201 1
            throw new ApiGwAuthenticationException(
202 1
                sprintf('Invalid JWKS format of %s key at %s.', $type, $key)
203
            );
204
        }
205
206 5
        if ([] === $jwksArray['keys']) {
207 1
            throw new ApiGwAuthenticationException(
208 1
                sprintf('Invalid JWKS format of %s key at %s, keys array is empty.', $type, $key)
209
            );
210
        }
211
212 4
        return current($this->keyConverter->fromJWKStoPEMS($jwksArray['keys']));
213
    }
214
}
215