Passed
Push — master ( 11687d...119443 )
by Pol
07:09 queued 04:54
created

ApiGwKeyLoader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 4
crap 1
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 4
                ['', $key], // Look whereever you want.
99
            ];
100
101 4
            foreach ($keyPathCandidates as $keyPathCandidateParts) {
102 4
                if (true === file_exists(implode('', $keyPathCandidateParts))) {
103 3
                    $prefix = current($keyPathCandidateParts);
104
105 3
                    return (new RawKeyLoader($prefix . $signingKey, $prefix . $publicKey, $passPhrase))
106 3
                        ->loadKey($type);
107
                }
108
            }
109
        }
110
111
        try {
112 4
            $key = (new JWKSKeyLoader($this, $this->httpClient, $this->keyConverter))
113 4
                ->loadKey($type);
114 2
        } catch (Throwable $e) {
115 2
            $key = $this->loadFailsafeKey($type);
116
        }
117
118 4
        return $key;
119
    }
120
121 8
    private function getEnvironment(string $env): array
122
    {
123 8
        $envs = [];
124
125 8
        foreach ($this->configuration['envs'] as $name => $data) {
126 6
            $envs[] = [
127 6
                'env' => $name,
128 6
                KeyLoaderInterface::TYPE_PUBLIC => $data[KeyLoaderInterface::TYPE_PUBLIC],
129 6
                KeyLoaderInterface::TYPE_PRIVATE => $data[KeyLoaderInterface::TYPE_PRIVATE],
130
                'failsafe' => [
131 6
                    KeyLoaderInterface::TYPE_PUBLIC => $data['failsafe'][KeyLoaderInterface::TYPE_PUBLIC] ?? $data[KeyLoaderInterface::TYPE_PUBLIC],
132 6
                    KeyLoaderInterface::TYPE_PRIVATE => $data['failsafe'][KeyLoaderInterface::TYPE_PRIVATE] ?? $data[KeyLoaderInterface::TYPE_PRIVATE],
133
                ],
134
            ];
135
        }
136
137 8
        foreach (array_merge(self::$mapping, $envs) as $mapping) {
138 8
            if ($mapping['env'] === $env) {
139 8
                return $mapping;
140
            }
141
        }
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...
142
    }
143
144 1
    private function getFailsafePrivateKey(): string
145
    {
146 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PRIVATE];
147
    }
148
149 1
    private function getFailsafePublicKey(): string
150
    {
151 1
        return $this->environment['failsafe'][KeyLoaderInterface::TYPE_PUBLIC];
152
    }
153
154 2
    private function loadFailsafeKey(string $type): string
155
    {
156 2
        $key = KeyLoaderInterface::TYPE_PUBLIC === $type ?
157 1
            $this->getFailsafePublicKey() :
158 2
            $this->getFailsafePrivateKey();
159
160
        // Todo: Remove duplicated code in here and JWKSKeyLoader.
161 2
        $jwksArray = json_decode(file_get_contents($key), true);
162
163 2
        if (false === array_key_exists('keys', $jwksArray)) {
164
            throw new ApiGwAuthenticationException(
165
                sprintf('Invalid JWKS format of %s key at %s.', $type, $key)
166
            );
167
        }
168
169 2
        if ([] === $jwksArray['keys']) {
170
            throw new ApiGwAuthenticationException(
171
                sprintf('Invalid JWKS format of %s key at %s, keys array is empty.', $type, $key)
172
            );
173
        }
174
175 2
        return current($this->keyConverter->fromJWKStoPEMS($jwksArray['keys']));
176
    }
177
}
178