Passed
Push — master ( 66c18a...043c10 )
by Pol
22:47 queued 20:11
created

ApiGwKeyLoader::getEnvironment()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

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