Passed
Push — master ( 75b7c0...25469b )
by Pol
02:40
created

ApiGwKeyLoader::findFirstFileExist()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 17
ccs 8
cts 8
cp 1
rs 10
cc 3
nc 3
nop 1
crap 3
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 const API_GW_ACCEPTANCE = 'https://api.acceptance.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json';
23
24
    private const API_GW_INTRA = 'https://intrapi.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json';
25
26
    private const API_GW_PRODUCTION = 'https://api.tech.ec.europa.eu/federation/oauth/token/.well-known/jwks.json';
27
28
    private const LOCAL_FAILSAFE_PATH = '/../../Resources/keys';
29
30
    private array $environment;
31
32
    private HttpClientInterface $httpClient;
33
34
    private KeyConverterInterface $keyConverter;
35
36
    private static array $mapping = [
37
        [
38
            'env' => 'production',
39
            KeyLoaderInterface::TYPE_PUBLIC => self::API_GW_PRODUCTION,
40
            KeyLoaderInterface::TYPE_PRIVATE => '',
41
            'failsafe' => [
42
                KeyLoaderInterface::TYPE_PUBLIC => self::LOCAL_FAILSAFE_PATH . '/production/jwks.json',
43
                KeyLoaderInterface::TYPE_PRIVATE => '',
44
            ],
45
        ],
46
        [
47
            'env' => 'intra',
48
            KeyLoaderInterface::TYPE_PUBLIC => self::API_GW_INTRA,
49
            KeyLoaderInterface::TYPE_PRIVATE => '',
50
            'failsafe' => [
51
                KeyLoaderInterface::TYPE_PUBLIC => self::LOCAL_FAILSAFE_PATH . '/intra/jwks.json',
52
                KeyLoaderInterface::TYPE_PRIVATE => '',
53
            ],
54
        ],
55
        [
56
            'env' => 'acceptance',
57
            KeyLoaderInterface::TYPE_PUBLIC => self::API_GW_ACCEPTANCE,
58
            KeyLoaderInterface::TYPE_PRIVATE => '',
59
            'failsafe' => [
60
                KeyLoaderInterface::TYPE_PUBLIC => self::LOCAL_FAILSAFE_PATH . '/acceptance/jwks.json',
61
                KeyLoaderInterface::TYPE_PRIVATE => '',
62
            ],
63
        ],
64
    ];
65
66
    private string $projectDir;
67
68 10
    public function __construct(
69
        HttpClientInterface $httpClient,
70
        KeyConverterInterface $keyConverter,
71
        string $projectDir,
72
        array $configuration
73
    ) {
74 10
        $this->httpClient = $httpClient;
75 10
        $this->keyConverter = $keyConverter;
76 10
        $this->projectDir = $projectDir;
77 10
        $this->environment = $this->getEnvironment($configuration['defaults']['env'], $configuration['envs']);
78 10
    }
79
80 9
    public function getPassphrase(): string
81
    {
82
        // Todo: Not supported yet.
83 9
        return '';
84
    }
85
86 9
    public function getPublicKey(): string
87
    {
88 9
        return $this->environment[KeyLoaderInterface::TYPE_PUBLIC] ?? '';
89
    }
90
91 9
    public function getSigningKey(): string
92
    {
93 9
        return $this->environment[KeyLoaderInterface::TYPE_PRIVATE] ?? '';
94
    }
95
96 9
    public function loadKey($type): string
97
    {
98 9
        $publicKey = $this->getPublicKey();
99 9
        $signingKey = $this->getSigningKey();
100 9
        $passPhrase = $this->getPassphrase();
101
102 9
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ? $publicKey : $signingKey;
103
104 9
        if ('user' === $this->environment['env']) {
105 6
            $keyPathCandidateParts = $this->findFirstFileExist($key);
106
107 6
            if ([] !== $keyPathCandidateParts) {
108 3
                $prefix = current($keyPathCandidateParts);
109
110 3
                return (new RawKeyLoader($prefix . $signingKey, $prefix . $publicKey, $passPhrase))
111 3
                    ->loadKey($type);
112
            }
113
        }
114
115
        try {
116 6
            $key = (new JWKSKeyLoader($this, $this->httpClient, $this->keyConverter))
117 6
                ->loadKey($type);
118 4
        } catch (Throwable $e) {
119 4
            $key = $this->loadFailsafeKey($type);
120
        }
121
122 4
        return $key;
123
    }
124
125 7
    private function findFirstFileExist(string $key): array
126
    {
127 7
        $candidates = array_map(
128 7
            static fn (string $directory): array => [$directory, $key],
129
            [
130 7
                $this->projectDir,
131
                __DIR__,
132
            ]
133
        );
134
135 7
        foreach ($candidates as $candidate) {
136 7
            if (true === file_exists(implode('', $candidate))) {
137 7
                return $candidate;
138
            }
139
        }
140
141 3
        return [];
142
    }
143
144 10
    private function getEnvironment(string $env, array $configuredEnvs): array
145
    {
146 10
        $envs = [];
147
148 10
        foreach ($configuredEnvs as $name => $data) {
149 8
            $envs[] = [
150 8
                'env' => $name,
151 8
                KeyLoaderInterface::TYPE_PUBLIC => $data[KeyLoaderInterface::TYPE_PUBLIC],
152 8
                KeyLoaderInterface::TYPE_PRIVATE => $data[KeyLoaderInterface::TYPE_PRIVATE],
153
                'failsafe' => [
154 8
                    KeyLoaderInterface::TYPE_PUBLIC => $data['failsafe'][KeyLoaderInterface::TYPE_PUBLIC] ?? $data[KeyLoaderInterface::TYPE_PUBLIC],
155 8
                    KeyLoaderInterface::TYPE_PRIVATE => $data['failsafe'][KeyLoaderInterface::TYPE_PRIVATE] ?? $data[KeyLoaderInterface::TYPE_PRIVATE],
156
                ],
157
            ];
158
        }
159
160 10
        foreach (array_merge(self::$mapping, $envs) as $mapping) {
161 10
            if ($mapping['env'] === $env) {
162 10
                return $mapping;
163
            }
164
        }
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...
165
    }
166
167 1
    private function getFailsafePrivateKey(): string
168
    {
169 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PRIVATE];
170
    }
171
172 3
    private function getFailsafePublicKey(): string
173
    {
174 3
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PUBLIC];
175
    }
176
177 4
    private function loadFailsafeKey(string $type): string
178
    {
179 4
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ?
180 3
            $this->getFailsafePublicKey() :
181 4
            $this->getFailsafePrivateKey();
182
183 4
        $keyPathCandidateParts = $this->findFirstFileExist($key);
184
185
        // Todo: Remove duplicated code in here and JWKSKeyLoader.
186 4
        $jwksArray = json_decode(file_get_contents(implode('', $keyPathCandidateParts)), true);
187
188 4
        if (false === array_key_exists('keys', $jwksArray)) {
189 1
            throw new ApiGwAuthenticationException(
190 1
                sprintf('Invalid JWKS format of %s key at %s.', $type, $key)
191
            );
192
        }
193
194 3
        if ([] === $jwksArray['keys']) {
195 1
            throw new ApiGwAuthenticationException(
196 1
                sprintf('Invalid JWKS format of %s key at %s, keys array is empty.', $type, $key)
197
            );
198
        }
199
200 2
        return current($this->keyConverter->fromJWKStoPEMS($jwksArray['keys']));
201
    }
202
}
203