Passed
Pull Request — main (#140)
by Daniel
07:55 queued 02:24
created

SilverbackApiComponentsExtension   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 157
dl 0
loc 263
ccs 0
cts 132
cp 0
rs 10
c 1
b 0
f 0
wmc 19

9 Methods

Rating   Name   Duplication   Size   Complexity  
A setUserClassArguments() 0 16 1
A loadServiceConfig() 0 13 1
A prependDoctrineConfiguration() 0 8 1
A setMailerServiceArguments() 0 37 4
B load() 0 86 5
A prepend() 0 7 1
A prependApiPlatformConfig() 0 48 3
A setEmailVerificationArguments() 0 10 1
A appendMappingPaths() 0 7 2
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentsBundle\DependencyInjection;
15
16
use Exception;
17
use Ramsey\Uuid\Doctrine\UuidType;
18
use Silverback\ApiComponentsBundle\AttributeReader\UploadableAttributeReader;
19
use Silverback\ApiComponentsBundle\Doctrine\Extension\ORM\RoutableExtension;
20
use Silverback\ApiComponentsBundle\Doctrine\Extension\ORM\RouteExtension;
21
use Silverback\ApiComponentsBundle\Doctrine\Extension\ORM\TablePrefixExtension;
22
use Silverback\ApiComponentsBundle\Exception\ApiPlatformAuthenticationException;
23
use Silverback\ApiComponentsBundle\Exception\UnparseableRequestHeaderException;
24
use Silverback\ApiComponentsBundle\Exception\UserDisabledException;
25
use Silverback\ApiComponentsBundle\Factory\Uploadable\MediaObjectFactory;
26
use Silverback\ApiComponentsBundle\Factory\User\Mailer\ChangeEmailConfirmationEmailFactory;
27
use Silverback\ApiComponentsBundle\Factory\User\Mailer\PasswordChangedEmailFactory;
28
use Silverback\ApiComponentsBundle\Factory\User\Mailer\PasswordResetEmailFactory;
29
use Silverback\ApiComponentsBundle\Factory\User\Mailer\UserEnabledEmailFactory;
30
use Silverback\ApiComponentsBundle\Factory\User\Mailer\UsernameChangedEmailFactory;
31
use Silverback\ApiComponentsBundle\Factory\User\Mailer\VerifyEmailFactory;
32
use Silverback\ApiComponentsBundle\Factory\User\Mailer\WelcomeEmailFactory;
33
use Silverback\ApiComponentsBundle\Factory\User\UserFactory;
34
use Silverback\ApiComponentsBundle\Form\FormTypeInterface;
35
use Silverback\ApiComponentsBundle\Form\Type\User\ChangePasswordType;
36
use Silverback\ApiComponentsBundle\Form\Type\User\NewEmailAddressType;
37
use Silverback\ApiComponentsBundle\Form\Type\User\PasswordUpdateType;
38
use Silverback\ApiComponentsBundle\Form\Type\User\UserRegisterType;
39
use Silverback\ApiComponentsBundle\Helper\Publishable\PublishableStatusChecker;
40
use Silverback\ApiComponentsBundle\Helper\Uploadable\UploadableFileManager;
41
use Silverback\ApiComponentsBundle\Helper\User\UserDataProcessor;
42
use Silverback\ApiComponentsBundle\Helper\User\UserMailer;
43
use Silverback\ApiComponentsBundle\Repository\Core\RefreshTokenRepository;
44
use Silverback\ApiComponentsBundle\Repository\User\UserRepositoryInterface;
45
use Silverback\ApiComponentsBundle\Security\UserChecker;
46
use Silverback\ApiComponentsBundle\Security\Voter\RoutableVoter;
47
use Silverback\ApiComponentsBundle\Security\Voter\RouteVoter;
48
use Silverback\ApiComponentsBundle\Serializer\Normalizer\MetadataNormalizer;
49
use Symfony\Component\Config\FileLocator;
50
use Symfony\Component\DependencyInjection\ContainerBuilder;
51
use Symfony\Component\DependencyInjection\Extension\Extension;
52
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
53
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
54
use Symfony\Component\DependencyInjection\Reference;
55
use Symfony\Component\Security\Http\Event\LogoutEvent;
56
57
/**
58
 * @author Daniel West <[email protected]>
59
 */
60
class SilverbackApiComponentsExtension extends Extension implements PrependExtensionInterface
61
{
62
    /**
63
     * @throws Exception
64
     */
65
    public function load(array $configs, ContainerBuilder $container): void
66
    {
67
        $configuration = new Configuration();
68
        $config = $this->processConfiguration($configuration, $configs);
69
70
        $this->loadServiceConfig($container);
71
72
        $definition = $container->getDefinition(TablePrefixExtension::class);
73
        $definition->setArgument('$prefix', $config['table_prefix']);
74
75
        $definition = $container->getDefinition(UserRepositoryInterface::class);
76
        $definition->setArgument('$entityClass', $config['user']['class_name']);
77
        $definition->setArgument('$passwordRequestTimeout', $config['user']['password_reset']['request_timeout_seconds']);
78
        $definition->setArgument('$newEmailConfirmTimeout', $config['user']['new_email_confirmation']['request_timeout_seconds']);
79
80
        $cookieProvider = new Reference('lexik_jwt_authentication.cookie_provider.' . $config['refresh_token']['cookie_name']);
81
        $definition = $container->getDefinition('silverback.security.jwt_event_listener');
82
        $definition->setArgument('$cookieProvider', $cookieProvider);
83
        $container->setParameter('silverback.api_components.refresh_token.ttl', (int) $config['refresh_token']['ttl']);
84
85
        if (!empty($config['refresh_token']['options'])) {
86
            $definition = $container->getDefinition($config['refresh_token']['handler_id']);
87
            $definition->setArgument('$options', $config['refresh_token']['options']);
88
        }
89
90
        if ('silverback.api_components.refresh_token.storage.doctrine' === $config['refresh_token']['handler_id']) {
91
            $container
92
                ->register(RefreshTokenRepository::class)
93
                ->setArguments([new Reference('doctrine'), $config['refresh_token']['options']['class']])
94
                ->addTag('doctrine.repository_service');
95
        }
96
97
        $definition = $container->getDefinition('silverback.command.refresh_tokens_expire');
98
        $definition->setArgument('$storage', new Reference($config['refresh_token']['handler_id']));
99
100
        if (class_exists(LogoutEvent::class)) {
101
            $definition = $container->getDefinition('silverback.security.logout_listener');
102
            $definition->setArgument('$storage', new Reference($config['refresh_token']['handler_id']));
103
            $definition->setArgument('$cookieProvider', $cookieProvider);
104
        } else {
105
            $definition = $container->getDefinition('silverback.security.logout_handler');
106
            $definition->setArgument('$storage', new Reference($config['refresh_token']['handler_id']));
107
            $definition->setArgument('$cookieProvider', $cookieProvider);
108
        }
109
110
        $definition = $container->getDefinition('silverback.security.jwt_invalid_event_listener');
111
        $definition->setArgument('$cookieProvider', $cookieProvider);
112
113
        $definition = $container->getDefinition('silverback.security.jwt_manager');
114
        $definition->setArgument('$userProvider', new Reference(sprintf('security.user.provider.concrete.%s', $config['refresh_token']['database_user_provider'])));
115
        $definition->setArgument('$storage', new Reference($config['refresh_token']['handler_id']));
116
117
        $definition = $container->getDefinition(PublishableStatusChecker::class);
118
        $definition->setArgument('$permission', $config['publishable']['permission']);
119
120
        $definition = $container->getDefinition(MetadataNormalizer::class);
121
        $definition->setArgument('$metadataKey', $config['metadata_key']);
122
123
        $this->setEmailVerificationArguments($container, $config['user']['email_verification'], $config['user']['password_reset']['repeat_ttl_seconds']);
124
        $this->setUserClassArguments($container, $config['user']['class_name']);
125
        $this->setMailerServiceArguments($container, $config);
126
127
        $imagineEnabled = $container->getParameter('api_components.imagine_enabled');
128
        $definition = $container->getDefinition(UploadableAttributeReader::class);
129
        $definition->setArgument('$imagineBundleEnabled', $imagineEnabled);
130
131
        if ($imagineEnabled) {
132
            $definition = $container->getDefinition(UploadableFileManager::class);
133
            $definition->setArgument('$filterService', new Reference('liip_imagine.service.filter'));
134
            $definition->setArgument('$imagineCacheManager', new Reference('liip_imagine.cache.manager'));
135
136
            $definition = $container->getDefinition(MediaObjectFactory::class);
137
            $definition->setArgument('$filterService', new Reference('liip_imagine.service.filter'));
138
        }
139
140
        $definition = $container->getDefinition(RouteExtension::class);
141
        $definition->setArgument('$config', $config['route_security']);
142
143
        $definition = $container->getDefinition(RouteVoter::class);
144
        $definition->setArgument('$config', $config['route_security']);
145
146
        $definition = $container->getDefinition(RoutableExtension::class);
147
        $definition->setArgument('$securityStr', $config['routable_security']);
148
149
        $definition = $container->getDefinition(RoutableVoter::class);
150
        $definition->setArgument('$securityStr', $config['routable_security']);
151
    }
152
153
    private function setEmailVerificationArguments(ContainerBuilder $container, array $emailVerificationConfig, int $passwordRepeatTtl): void
154
    {
155
        $definition = $container->getDefinition(UserChecker::class);
156
        $definition->setArgument('$denyUnverifiedLogin', $emailVerificationConfig['deny_unverified_login']);
157
158
        $definition = $container->getDefinition(UserDataProcessor::class);
159
        $definition->setArgument('$initialEmailVerifiedState', $emailVerificationConfig['default_value']);
160
        $definition->setArgument('$verifyEmailOnRegister', $emailVerificationConfig['verify_on_register']);
161
        $definition->setArgument('$verifyEmailOnChange', $emailVerificationConfig['verify_on_change']);
162
        $definition->setArgument('$tokenTtl', $passwordRepeatTtl);
163
    }
164
165
    private function setUserClassArguments(ContainerBuilder $container, string $userClass): void
166
    {
167
        $definition = $container->getDefinition(UserFactory::class);
168
        $definition->setArgument('$userClass', $userClass);
169
170
        $definition = $container->getDefinition(ChangePasswordType::class);
171
        $definition->setArgument('$userClass', $userClass);
172
173
        $definition = $container->getDefinition(NewEmailAddressType::class);
174
        $definition->setArgument('$userClass', $userClass);
175
176
        $definition = $container->getDefinition(UserRegisterType::class);
177
        $definition->setArgument('$userClass', $userClass);
178
179
        $definition = $container->getDefinition(PasswordUpdateType::class);
180
        $definition->setArgument('$userClass', $userClass);
181
    }
182
183
    private function setMailerServiceArguments(ContainerBuilder $container, array $config): void
184
    {
185
        $definition = $container->getDefinition(UserMailer::class);
186
        $definition->setArgument(
187
            '$context',
188
            [
189
                'website_name' => $config['website_name'],
190
            ]
191
        );
192
193
        $mapping = [
194
            PasswordChangedEmailFactory::class => 'password_changed',
195
            UserEnabledEmailFactory::class => 'user_enabled',
196
            UsernameChangedEmailFactory::class => 'username_changed',
197
            WelcomeEmailFactory::class => 'welcome',
198
        ];
199
        foreach ($mapping as $class => $key) {
200
            $definition = $container->getDefinition($class);
201
            $definition->setArgument('$subject', $config['user']['emails'][$key]['subject']);
202
            $definition->setArgument('$enabled', $config['user']['emails'][$key]['enabled']);
203
            if (WelcomeEmailFactory::class === $class) {
204
                $definition->setArgument('$defaultRedirectPath', $config['user']['email_verification']['email']['default_redirect_path']);
205
                $definition->setArgument('$redirectPathQueryKey', $config['user']['email_verification']['email']['redirect_path_query']);
206
            }
207
        }
208
209
        $mapping = [
210
            VerifyEmailFactory::class => 'email_verification',
211
            ChangeEmailConfirmationEmailFactory::class => 'new_email_confirmation',
212
            PasswordResetEmailFactory::class => 'password_reset',
213
        ];
214
        foreach ($mapping as $class => $key) {
215
            $definition = $container->getDefinition($class);
216
            $definition->setArgument('$subject', $config['user'][$key]['email']['subject']);
217
            $definition->setArgument('$enabled', true);
218
            $definition->setArgument('$defaultRedirectPath', $config['user'][$key]['email']['default_redirect_path']);
219
            $definition->setArgument('$redirectPathQueryKey', $config['user'][$key]['email']['redirect_path_query']);
220
        }
221
    }
222
223
    /**
224
     * @throws Exception
225
     */
226
    private function loadServiceConfig(ContainerBuilder $container): void
227
    {
228
        $container->registerForAutoconfiguration(FormTypeInterface::class)
229
            ->addTag('silverback_api_components.form_type');
230
231
        $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
232
        $loader->load('services.php');
233
        $loader->load('services_normalizers.php');
234
235
        // Todo: revert to checking for service id api_platform.http_cache.purger when https://github.com/api-platform/core/pull/4695 is merged
236
        // dump($container->hasDefinition('api_platform.http_cache.purger.varnish.xkey'));
237
        // if ($container->hasDefinition('api_platform.http_cache.purger.varnish.xkey')) {
238
        $loader->load('services_doctrine_orm_http_cache_purger.php');
239
        // }
240
    }
241
242
    public function prepend(ContainerBuilder $container): void
243
    {
244
        $configs = $container->getExtensionConfig($this->getAlias());
245
        $configuration = new Configuration();
246
        $config = $this->processConfiguration($configuration, $configs);
247
        $this->prependApiPlatformConfig($container, $config);
248
        $this->prependDoctrineConfiguration($container);
249
    }
250
251
    private function prependDoctrineConfiguration(ContainerBuilder $container): void
252
    {
253
        $container->prependExtensionConfig(
254
            'doctrine',
255
            [
256
                'dbal' => [
257
                    'types' => [
258
                        'uuid' => UuidType::class,
259
                    ],
260
                ],
261
            ]
262
        );
263
    }
264
265
    private function appendMappingPaths(&$mappingPaths, $srcBase, $name): void
266
    {
267
        $configBasePath = $srcBase . '/Resources/config/api_platform';
268
        $mappingPaths[] = sprintf('%s/%s/resource.xml', $configBasePath, $name);
269
        $propertiesPath = sprintf('%s/%s/properties.xml', $configBasePath, $name);
270
        if (file_exists($propertiesPath)) {
271
            $mappingPaths[] = $propertiesPath;
272
        }
273
    }
274
275
    private function prependApiPlatformConfig(ContainerBuilder $container, array $config): void
276
    {
277
        $srcBase = __DIR__ . '/..';
278
        $mappingPaths = [$srcBase . '/Entity/Core'];
279
        $this->appendMappingPaths($mappingPaths, $srcBase, 'uploadable');
280
        $this->appendMappingPaths($mappingPaths, $srcBase, 'page_data_metadata');
281
        foreach ($config['enabled_components'] as $component => $is_enabled) {
282
            if (true === $is_enabled) {
283
                $this->appendMappingPaths($mappingPaths, $srcBase, $component);
284
            }
285
        }
286
287
        $websiteName = $config['website_name'];
288
        $container->prependExtensionConfig(
289
            'api_platform',
290
            [
291
                'title' => $websiteName,
292
                'description' => sprintf('API for %s', $websiteName),
293
                'defaults' => [
294
                    'pagination_client_items_per_page' => true,
295
                    'pagination_maximum_items_per_page' => 100,
296
                ],
297
                'collection' => [
298
                    'pagination' => [
299
                        'items_per_page_parameter_name' => 'perPage',
300
                        // 'client_items_per_page' => true,
301
                        // 'maximum_items_per_page' => 100,
302
                    ],
303
                ],
304
                'mapping' => [
305
                    'paths' => $mappingPaths,
306
                ],
307
                'swagger' => [
308
                    'api_keys' => [
309
                        'API Token' => [
310
                            'name' => 'X-AUTH-TOKEN',
311
                            'type' => 'header',
312
                        ],
313
                        'JWT (use prefix `Bearer`)' => [
314
                            'name' => 'Authorization',
315
                            'type' => 'header',
316
                        ],
317
                    ],
318
                ],
319
                'exception_to_status' => [
320
                    UnparseableRequestHeaderException::class => 400,
321
                    ApiPlatformAuthenticationException::class => 401,
322
                    UserDisabledException::class => 401,
323
                ],
324
            ]
325
        );
326
    }
327
}
328