Passed
Push — master ( be2cf3...17d44b )
by Pol
02:37
created

ApiGwKeyLoader::loadFailsafeKey()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.5923

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 22
ccs 8
cts 12
cp 0.6667
rs 9.9
cc 4
nc 6
nop 1
crap 4.5923
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 = __DIR__ . '/../../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 8
    public function __construct(
69
        HttpClientInterface $httpClient,
70
        KeyConverterInterface $keyConverter,
71
        string $projectDir,
72
        array $configuration
73
    ) {
74 8
        $this->httpClient = $httpClient;
75 8
        $this->keyConverter = $keyConverter;
76 8
        $this->projectDir = $projectDir;
77 8
        $this->environment = $this->getEnvironment($configuration['defaults']['env'], $configuration['envs']);
78 8
    }
79
80 7
    public function getPassphrase(): string
81
    {
82
        // Todo: Not supported yet.
83 7
        return '';
84
    }
85
86 7
    public function getPublicKey(): string
87
    {
88 7
        return $this->environment[KeyLoaderInterface::TYPE_PUBLIC] ?? '';
89
    }
90
91 7
    public function getSigningKey(): string
92
    {
93 7
        return $this->environment[KeyLoaderInterface::TYPE_PRIVATE] ?? '';
94
    }
95
96 7
    public function loadKey($type): string
97
    {
98 7
        $publicKey = $this->getPublicKey();
99 7
        $signingKey = $this->getSigningKey();
100 7
        $passPhrase = $this->getPassphrase();
101
102 7
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ? $publicKey : $signingKey;
103
104 7
        if ('user' === $this->environment['env']) {
105
            $keyPathCandidates = [
106 4
                [$this->projectDir, $key], // Look in the App dir,
107 4
                [__DIR__, $key], // Look in this bundle dir,
108
            ];
109
110 4
            foreach ($keyPathCandidates as $keyPathCandidateParts) {
111 4
                if (true === file_exists(implode('', $keyPathCandidateParts))) {
112 3
                    $prefix = current($keyPathCandidateParts);
113
114 3
                    return (new RawKeyLoader($prefix . $signingKey, $prefix . $publicKey, $passPhrase))
115 3
                        ->loadKey($type);
116
                }
117
            }
118
        }
119
120
        try {
121 4
            $key = (new JWKSKeyLoader($this, $this->httpClient, $this->keyConverter))
122 4
                ->loadKey($type);
123 2
        } catch (Throwable $e) {
124 2
            $key = $this->loadFailsafeKey($type);
125
        }
126
127 4
        return $key;
128
    }
129
130 8
    private function getEnvironment(string $env, array $configuredEnvs): array
131
    {
132 8
        $envs = [];
133
134 8
        foreach ($configuredEnvs as $name => $data) {
135 6
            $envs[] = [
136 6
                'env' => $name,
137 6
                KeyLoaderInterface::TYPE_PUBLIC => $data[KeyLoaderInterface::TYPE_PUBLIC],
138 6
                KeyLoaderInterface::TYPE_PRIVATE => $data[KeyLoaderInterface::TYPE_PRIVATE],
139
                'failsafe' => [
140 6
                    KeyLoaderInterface::TYPE_PUBLIC => $data['failsafe'][KeyLoaderInterface::TYPE_PUBLIC] ?? $data[KeyLoaderInterface::TYPE_PUBLIC],
141 6
                    KeyLoaderInterface::TYPE_PRIVATE => $data['failsafe'][KeyLoaderInterface::TYPE_PRIVATE] ?? $data[KeyLoaderInterface::TYPE_PRIVATE],
142
                ],
143
            ];
144
        }
145
146 8
        foreach (array_merge(self::$mapping, $envs) as $mapping) {
147 8
            if ($mapping['env'] === $env) {
148 8
                return $mapping;
149
            }
150
        }
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...
151
    }
152
153 1
    private function getFailsafePrivateKey(): string
154
    {
155 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PRIVATE];
156
    }
157
158 1
    private function getFailsafePublicKey(): string
159
    {
160 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PUBLIC];
161
    }
162
163 2
    private function loadFailsafeKey(string $type): string
164
    {
165 2
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ?
166 1
            $this->getFailsafePublicKey() :
167 2
            $this->getFailsafePrivateKey();
168
169
        // Todo: Remove duplicated code in here and JWKSKeyLoader.
170 2
        $jwksArray = json_decode(file_get_contents($key), true);
171
172 2
        if (false === array_key_exists('keys', $jwksArray)) {
173
            throw new ApiGwAuthenticationException(
174
                sprintf('Invalid JWKS format of %s key at %s.', $type, $key)
175
            );
176
        }
177
178 2
        if ([] === $jwksArray['keys']) {
179
            throw new ApiGwAuthenticationException(
180
                sprintf('Invalid JWKS format of %s key at %s, keys array is empty.', $type, $key)
181
            );
182
        }
183
184 2
        return current($this->keyConverter->fromJWKStoPEMS($jwksArray['keys']));
185
    }
186
}
187