DamaxApiAuthExtension::configureJwtClaims()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 17
nc 1
nop 3
dl 0
loc 25
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Damax\Bundle\ApiAuthBundle\DependencyInjection;
6
7
use Damax\Bundle\ApiAuthBundle\Command\Storage\AddKeyCommand;
8
use Damax\Bundle\ApiAuthBundle\Command\Storage\LookupKeyCommand;
9
use Damax\Bundle\ApiAuthBundle\Command\Storage\RemoveKeyCommand;
10
use Damax\Bundle\ApiAuthBundle\Controller\TokenController;
11
use Damax\Bundle\ApiAuthBundle\Extractor\ChainExtractor;
12
use Damax\Bundle\ApiAuthBundle\Jwt\Claims;
13
use Damax\Bundle\ApiAuthBundle\Jwt\Claims\ClaimsCollector;
14
use Damax\Bundle\ApiAuthBundle\Jwt\Claims\OrganizationClaims;
15
use Damax\Bundle\ApiAuthBundle\Jwt\Claims\SecurityClaims;
16
use Damax\Bundle\ApiAuthBundle\Jwt\Claims\TimestampClaims;
17
use Damax\Bundle\ApiAuthBundle\Jwt\Lcobucci\Builder;
18
use Damax\Bundle\ApiAuthBundle\Jwt\Lcobucci\Parser;
19
use Damax\Bundle\ApiAuthBundle\Jwt\TokenBuilder;
20
use Damax\Bundle\ApiAuthBundle\Key\Factory;
21
use Damax\Bundle\ApiAuthBundle\Key\Generator\Generator;
22
use Damax\Bundle\ApiAuthBundle\Key\Generator\RandomGenerator;
23
use Damax\Bundle\ApiAuthBundle\Key\Storage\ChainStorage;
24
use Damax\Bundle\ApiAuthBundle\Key\Storage\DoctrineStorage;
25
use Damax\Bundle\ApiAuthBundle\Key\Storage\DummyStorage;
26
use Damax\Bundle\ApiAuthBundle\Key\Storage\InMemoryStorage;
27
use Damax\Bundle\ApiAuthBundle\Key\Storage\Reader;
28
use Damax\Bundle\ApiAuthBundle\Key\Storage\RedisStorage;
29
use Damax\Bundle\ApiAuthBundle\Key\Storage\Writer;
30
use Damax\Bundle\ApiAuthBundle\Security\ApiKey\Authenticator as ApiKeyAuthenticator;
31
use Damax\Bundle\ApiAuthBundle\Security\ApiKey\StorageUserProvider;
32
use Damax\Bundle\ApiAuthBundle\Security\JsonResponseFactory;
33
use Damax\Bundle\ApiAuthBundle\Security\Jwt\AuthenticationHandler;
34
use Damax\Bundle\ApiAuthBundle\Security\Jwt\Authenticator as JwtAuthenticator;
35
use Damax\Bundle\ApiAuthBundle\Security\ResponseFactory;
36
use Lcobucci\Clock\SystemClock;
37
use Lcobucci\JWT\Configuration as JwtConfiguration;
38
use Lcobucci\JWT\Signer\Key;
39
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
40
use Symfony\Component\DependencyInjection\ContainerBuilder;
41
use Symfony\Component\DependencyInjection\Definition;
42
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
43
use Symfony\Component\DependencyInjection\Reference;
44
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
45
46
final class DamaxApiAuthExtension extends ConfigurableExtension implements PrependExtensionInterface
47
{
48
    public function prepend(ContainerBuilder $container): void
49
    {
50
        $bundles = $container->getParameter('kernel.bundles');
51
52
        if (isset($bundles['NelmioApiDocBundle'])) {
53
            $container->prependExtensionConfig('nelmio_api_doc', [
54
                'documentation' => ['definitions' => require_once __DIR__ . '/../Resources/api-doc-definitions.php'],
55
            ]);
56
        }
57
    }
58
59
    protected function loadInternal(array $config, ContainerBuilder $container)
60
    {
61
        $container->register(JsonResponseFactory::class);
62
        $container->setAlias(ResponseFactory::class, $config['response_factory_service_id']);
63
64
        if ($config['api_key']['enabled']) {
65
            $this->configureApiKey($config['api_key'], $container);
66
        }
67
68
        if ($config['jwt']['enabled']) {
69
            $this->configureJwt($config['jwt'], $container);
70
        }
71
    }
72
73
    private function configureApiKey(array $config, ContainerBuilder $container): self
74
    {
75
        $extractors = $this->configureExtractors($config['extractors']);
76
77
        // User provider.
78
        $container->autowire('damax.api_auth.api_key.user_provider', StorageUserProvider::class);
79
80
        // Authenticator.
81
        $container
82
            ->register('damax.api_auth.api_key.authenticator', ApiKeyAuthenticator::class)
83
            ->addArgument($extractors)
84
            ->addArgument(new Reference(ResponseFactory::class))
85
        ;
86
87
        $container
88
            ->register(Generator::class, RandomGenerator::class)
89
            ->addArgument($config['generator']['key_size'])
90
        ;
91
92
        // Key factory.
93
        $container->autowire(Factory::class);
94
95
        return $this->configureKeyStorage($config['storage'], $container);
96
    }
97
98
    private function configureJwt(array $config, ContainerBuilder $container): self
99
    {
100
        $signer = $this->configureJwtSigner($config['signer']);
101
102
        $clock = new Definition(SystemClock::class);
103
104
        $configuration = (new Definition(JwtConfiguration::class))
105
            ->setFactory(JwtConfiguration::class . '::forSymmetricSigner')
106
            ->addArgument($signer)
107
            ->addArgument(new Definition(Key::class, [
108
                $config['signer']['signing_key'],
109
                $config['signer']['passphrase'],
110
            ]))
111
        ;
112
113
        if (Configuration::SIGNER_ASYMMETRIC === $config['signer']['type']) {
114
            $configuration
115
                ->setFactory(JwtConfiguration::class . '::forAsymmetricSigner')
116
                ->addArgument(new Definition(Key::class, [
117
                    $config['signer']['verification_key'],
118
                ]))
119
            ;
120
        }
121
122
        $parser = (new Definition(Parser::class))
123
            ->addArgument($configuration)
124
            ->addArgument($clock)
125
            ->addArgument($config['parser']['issuers'] ?? null)
126
            ->addArgument($config['parser']['audience'] ?? null)
127
        ;
128
129
        $claims = $this->configureJwtClaims($config['builder'], $clock, $container);
130
131
        $container
132
            ->register(TokenBuilder::class, Builder::class)
133
            ->addArgument($configuration)
134
            ->addArgument($claims)
135
        ;
136
137
        $extractors = $this->configureExtractors($config['extractors']);
138
139
        // Authenticator.
140
        $container
141
            ->register('damax.api_auth.jwt.authenticator', JwtAuthenticator::class)
142
            ->addArgument($extractors)
143
            ->addArgument(new Reference(ResponseFactory::class))
144
            ->addArgument($parser)
145
            ->addArgument($config['identity_claim'] ?? null)
146
        ;
147
148
        // Handler.
149
        $container->autowire('damax.api_auth.jwt.handler', AuthenticationHandler::class);
150
151
        // Controller
152
        $container
153
            ->autowire(TokenController::class)
154
            ->addTag('controller.service_arguments')
155
        ;
156
157
        return $this;
158
    }
159
160
    private function configureJwtClaims(array $config, Definition $clock, ContainerBuilder $container): Definition
161
    {
162
        // Default claims.
163
        $container
164
            ->register(TimestampClaims::class)
165
            ->addArgument($clock)
166
            ->addArgument($config['ttl'])
167
            ->addTag('damax.api_auth.jwt_claims')
168
        ;
169
        $container
170
            ->register(OrganizationClaims::class)
171
            ->addArgument($config['issuer'] ?? null)
172
            ->addArgument($config['audience'] ?? null)
173
            ->addTag('damax.api_auth.jwt_claims')
174
        ;
175
        $container
176
            ->register(SecurityClaims::class)
177
            ->addTag('damax.api_auth.jwt_claims')
178
        ;
179
180
        $container->setAlias(Claims::class, ClaimsCollector::class);
181
182
        return $container
183
            ->register(ClaimsCollector::class)
184
            ->addArgument(new TaggedIteratorArgument('damax.api_auth.jwt_claims'))
185
        ;
186
    }
187
188
    private function configureJwtSigner(array $config): Definition
189
    {
190
        $dirs = ['HS' => 'Hmac', 'RS' => 'Rsa', 'ES' => 'Ecdsa'];
191
        $algo = $config['algorithm'];
192
193
        return new Definition('Lcobucci\\JWT\\Signer\\' . $dirs[substr($algo, 0, 2)] . '\\Sha' . substr($algo, 2));
194
    }
195
196
    private function configureExtractors(array $config): Definition
197
    {
198
        $extractors = [];
199
200
        foreach ($config as $item) {
201
            $className = sprintf('Damax\\Bundle\\ApiAuthBundle\\Extractor\\%sExtractor', ucfirst($item['type']));
202
203
            $extractors[] = (new Definition($className))
204
                ->setArgument(0, $item['name'])
205
                ->setArgument(1, $item['prefix'] ?? null)
206
            ;
207
        }
208
209
        return new Definition(ChainExtractor::class, [$extractors]);
210
    }
211
212
    private function configureKeyStorage(array $config, ContainerBuilder $container): self
213
    {
214
        $drivers = [];
215
216
        // Default writable storage.
217
        $container->register(Writer::class, DummyStorage::class);
218
219
        foreach ($config as $item) {
220
            $drivers[] = $driver = new Definition();
221
222
            if (Configuration::STORAGE_REDIS === $item['type']) {
223
                $driver
224
                    ->setClass(RedisStorage::class)
225
                    ->addArgument(new Reference($item['redis_client_id']))
226
                    ->addArgument($item['key_prefix'] ?? '')
227
                ;
228
            } elseif (Configuration::STORAGE_DOCTRINE === $item['type']) {
229
                $driver
230
                    ->setClass(DoctrineStorage::class)
231
                    ->addArgument(new Reference($item['doctrine_connection_id']))
232
                    ->addArgument($item['table_name'])
233
                    ->addArgument($item['fields'] ?? [])
234
                ;
235
            } else {
236
                $driver
237
                    ->setClass(InMemoryStorage::class)
238
                    ->addArgument($item['tokens'])
239
                ;
240
            }
241
242
            if ($item['writable']) {
243
                $container->setDefinition(Writer::class, $driver);
244
            }
245
        }
246
247
        $container
248
            ->register(Reader::class, ChainStorage::class)
249
            ->addArgument($drivers)
250
        ;
251
252
        $container
253
            ->autowire(AddKeyCommand::class)
254
            ->addTag('console.command')
255
        ;
256
        $container
257
            ->autowire(LookupKeyCommand::class)
258
            ->addTag('console.command')
259
        ;
260
        $container
261
            ->autowire(RemoveKeyCommand::class)
262
            ->addTag('console.command')
263
        ;
264
265
        return $this;
266
    }
267
}
268