Test Failed
Push — master ( a25a4c...498f08 )
by Divine Niiquaye
13:17
created

HttpGalaxyExtension   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 196
c 0
b 0
f 0
dl 0
loc 246
rs 10
wmc 15

3 Methods

Rating   Name   Duplication   Size   Complexity  
B getConfigTreeBuilder() 0 160 1
A getAlias() 0 3 1
D register() 0 68 13
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\DI\Extensions;
19
20
use Biurad\Http\Cookie;
21
use Biurad\Http\Factory\CookieFactory;
22
use Biurad\Http\Factory\NyholmPsr7Factory;
23
use Biurad\Http\Interfaces\CookieFactoryInterface;
24
use Biurad\Http\Middlewares\CacheControlMiddleware;
25
use Biurad\Http\Middlewares\CookiesMiddleware;
26
use Biurad\Http\Middlewares\HttpCorsMiddleware;
27
use Biurad\Http\Middlewares\HttpHeadersMiddleware;
28
use Biurad\Http\Middlewares\HttpPolicyMiddleware;
29
use Biurad\Http\Session;
30
use Biurad\Http\Sessions\HandlerFactory;
31
use Biurad\Http\Sessions\Handlers\AbstractSessionHandler;
32
use Biurad\Http\Sessions\Handlers\NativeFileSessionHandler;
33
use Biurad\Http\Sessions\Handlers\NullSessionHandler;
34
use Biurad\Http\Sessions\Handlers\StrictSessionHandler;
35
use Biurad\Http\Sessions\MetadataBag;
36
use Biurad\Http\Sessions\Storage\NativeSessionStorage;
37
use Rade\DI\AbstractContainer;
38
use Rade\DI\Definitions\Statement;
39
use Rade\DI\Services\AliasedInterface;
40
use Rade\Provider\HttpGalaxy\CorsConfiguration;
41
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
42
use Symfony\Component\Config\Definition\ConfigurationInterface;
43
44
use function Rade\DI\Loader\{referenced, service, wrap};
0 ignored issues
show
Bug introduced by
The type Rade\DI\Loader\wrap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type Rade\DI\Loader\referenced was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type Rade\DI\Loader\service was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
45
46
/**
47
 * Biurad Http Galaxy Provider.
48
 *
49
 * @author Divine Niiquaye Ibok <[email protected]>
50
 */
51
class HttpGalaxyExtension implements AliasedInterface, ConfigurationInterface, ExtensionInterface
52
{
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function getAlias(): string
57
    {
58
        return 'http_galaxy';
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function getConfigTreeBuilder(): TreeBuilder
65
    {
66
        $treeBuilder = new TreeBuilder($this->getAlias());
67
        $rootNode = $treeBuilder->getRootNode();
68
69
        $rootNode
70
            ->children()
71
                ->scalarNode('psr17_factory')->end()
72
                ->arrayNode('caching')
73
                    ->canBeEnabled()
74
                    ->addDefaultsIfNotSet()
75
                    ->children()
76
                        ->integerNode('cache_lifetime')->defaultValue(86400 * 30)->end()
77
                        ->integerNode('default_ttl')->end()
78
                        ->enumNode('hash_algo')->values(\hash_algos())->end()
79
                        ->arrayNode('methods')
80
                            ->defaultValue(['GET', 'HEAD'])
81
                            ->prototype('scalar')->end()
82
                        ->end()
83
                        ->arrayNode('respect_response_cache_directives')
84
                            ->performNoDeepMerging()
85
                            ->defaultValue(['no-cache', 'private', 'max-age', 'no-store'])
86
                            ->prototype('scalar')->end()
87
                        ->end()
88
                        ->scalarNode('cache_key_generator')->end()
89
                        ->arrayNode('cache_listeners')
90
                            ->defaultValue([])
91
                            ->prototype('scalar')->end()
92
                        ->end()
93
                        ->arrayNode('blacklisted_paths')
94
                            ->defaultValue([])
95
                            ->prototype('scalar')->end()
96
                        ->end()
97
                    ->end()
98
                ->end()
99
                ->arrayNode('policies')
100
                    ->canBeEnabled()
101
                    ->addDefaultsIfNotSet()
102
                    ->children()
103
                        ->arrayNode('content_security_policy')
104
                            ->defaultValue([])
105
                            ->prototype('scalar')->end()
106
                        ->end()
107
                        ->arrayNode('csp_report_only')
108
                            ->defaultValue([])
109
                            ->prototype('scalar')->end()
110
                        ->end()
111
                        ->arrayNode('feature_policy')
112
                            ->defaultValue([])
113
                            ->prototype('scalar')->end()
114
                        ->end()
115
                        ->arrayNode('referrer_policy')
116
                            ->defaultValue([])
117
                            ->prototype('scalar')->end()
118
                        ->end()
119
                        ->scalarNode('frame_policy')
120
                            ->beforeNormalization()
121
                                ->ifTrue(fn ($v) => false === $v)
122
                                ->then(fn ($v) => 'DENY')
123
                            ->end()
124
                            ->defaultValue('SAMEORIGIN')
125
                        ->end()
126
                        ->booleanNode('expose_csp_nonce')->defaultValue(true)->end()
127
                    ->end()
128
                ->end()
129
                ->arrayNode('headers')
130
                    ->addDefaultsIfNotSet()
131
                    ->children()
132
                        ->append(CorsConfiguration::getConfigNode())
133
                        ->arrayNode('request')
134
                            ->normalizeKeys(false)
135
                            ->useAttributeAsKey('name')
136
                            ->prototype('scalar')->end()
137
                        ->end()
138
                        ->arrayNode('response')
139
                            ->normalizeKeys(false)
140
                            ->useAttributeAsKey('name')
141
                            ->prototype('scalar')->end()
142
                        ->end()
143
                    ->end()
144
                ->end()
145
                ->arrayNode('cookie')
146
                    ->info('cookie configuration')
147
                    ->addDefaultsIfNotSet()
148
                    ->canBeEnabled()
149
                    ->children()
150
                        ->scalarNode('prefix_name')->defaultValue('rade_')->end()
151
                        ->scalarNode('encrypter')->defaultValue(null)->end()
152
                        ->arrayNode('excludes_encryption')
153
                            ->prototype('scalar')->defaultValue([])->end()
154
                        ->end()
155
                        ->arrayNode('cookies')
156
                            ->arrayPrototype()
157
                                ->addDefaultsIfNotSet()
158
                                ->children()
159
                                    ->scalarNode('name')->end()
160
                                    ->scalarNode('value')->end()
161
                                    ->variableNode('expires')
162
                                        ->beforeNormalization()
163
                                            ->always()
164
                                            ->then(fn ($v) => wrap('Nette\Utils\DateTime::from', [$v]))
165
                                        ->end()
166
                                    ->end()
167
                                    ->scalarNode('domain')->end()
168
                                    ->scalarNode('path')->end()
169
                                ->end()
170
                            ->end()
171
                        ->end()
172
                    ->end()
173
                ->end()
174
                ->arrayNode('session')
175
                    ->info('session configuration')
176
                    ->addDefaultsIfNotSet()
177
                    ->canBeEnabled()
178
                    ->children()
179
                        ->scalarNode('storage_id')->defaultValue('session.storage.native')->end()
180
                        ->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end()
181
                        ->scalarNode('name')
182
                            ->validate()
183
                                ->ifTrue(function ($v): bool {
184
                                    \parse_str($v, $parsed);
185
186
                                    return \implode('&', \array_keys($parsed)) !== (string) $v;
187
                                })
188
                                ->thenInvalid('Session name %s contains illegal character(s)')
189
                            ->end()
190
                        ->end()
191
                        ->scalarNode('cookie_lifetime')->defaultValue(\ini_get('session.gc_maxlifetime'))->end()
192
                        ->scalarNode('cookie_path')->end()
193
                        ->scalarNode('cookie_domain')->end()
194
                        ->enumNode('cookie_secure')->values([true, false, 'auto'])->end()
195
                        ->booleanNode('cookie_httponly')->defaultTrue()->end()
196
                        ->enumNode('cookie_samesite')
197
                            ->values([null, Cookie::SAME_SITE_LAX, Cookie::SAME_SITE_NONE, Cookie::SAME_SITE_STRICT])
198
                            ->defaultValue(Cookie::SAME_SITE_LAX)
199
                        ->end()
200
                        ->booleanNode('use_cookies')->end()
201
                        ->scalarNode('gc_divisor')->end()
202
                        ->scalarNode('gc_probability')->defaultValue(1)->end()
203
                        ->scalarNode('gc_maxlifetime')->end()
204
                        ->scalarNode('save_path')->defaultValue('sessions')->end()
205
                        ->scalarNode('meta_storage_key')->defaultValue('_rade_meta')->end()
206
                        ->integerNode('metadata_update_threshold')
207
                            ->defaultValue(0)
208
                            ->info('seconds to wait between 2 session metadata updates')
209
                        ->end()
210
                        ->integerNode('sid_length')
211
                            ->min(22)
212
                            ->max(256)
213
                        ->end()
214
                        ->integerNode('sid_bits_per_character')
215
                            ->min(4)
216
                            ->max(6)
217
                        ->end()
218
                    ->end()
219
                ->end()
220
            ->end()
221
        ;
222
223
        return $treeBuilder;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function register(AbstractContainer $container, array $configs = []): void
230
    {
231
        if (!$container->has('psr17.factory')) {
232
            $container->autowire('psr17.factory', service($configs['psr17_factory'] ?? NyholmPsr7Factory::class));
233
        }
234
235
        if ($configs['cookie']['enabled']) {
236
            unset($configs['cookie']['enabled']);
237
            $cookie = $container->set('http.cookie', service(CookieFactory::class))->typed([CookieFactory::class, CookieFactoryInterface::class]);
238
            $cookieMiddleware = $container->set('http.middleware.cookie', service(CookiesMiddleware::class));
239
            $cookies = [];
240
241
            foreach ($configs['cookie']['cookies'] as $cookieData) {
242
                $cookies[] = new Statement(Cookie::class, $cookieData);
243
            }
244
245
            if (!empty($cookies)) {
246
                $cookie->bind('addCookie', [$cookies]);
247
            }
248
249
            if (!empty($excludeCookies = $configs['cookie']['excludes_encryption'])) {
250
                $cookieMiddleware->bind('excludeEncodingFor', $excludeCookies);
251
            }
252
        }
253
254
        $container->set('http.middleware.headers', service(HttpHeadersMiddleware::class, [\array_diff_key($configs['headers'], ['cors' => []])]));
255
256
        if ($configs['policies']['enabled']) {
257
            unset($configs['policies']['enabled']);
258
            $container->set('http.middleware.policies', service(HttpPolicyMiddleware::class, [$configs['policies']]));
259
        }
260
261
        if ($configs['headers']['cors']['enabled']) {
262
            unset($configs['headers']['cors']['enabled']);
263
            $container->set('http.middleware.cors', service(HttpCorsMiddleware::class, [$configs['headers']['cors']]));
264
        }
265
266
        if ($configs['caching']['enabled']) {
267
            unset($configs['caching']['enabled']);
268
            $container->set('http.middleware.cache', service(CacheControlMiddleware::class, [3 => $configs['caching']]));
269
        }
270
271
        if (($session = $configs['session'])['enabled']) {
272
            unset($session['enabled']);
273
274
            if (!\extension_loaded('session')) {
275
                throw new \LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.');
276
            }
277
278
            $container->autowire('session.storage.native', service(NativeSessionStorage::class, [
279
                \array_intersect_key($session, ['storage_id' => null, 'handler_id' => null, 'meta_storage_key' => null, 'metadata_update_threshold' => null]),
280
                referenced('session.handler'),
281
            ]))
282
                ->arg(2, wrap(MetadataBag::class, [$session['meta_storage_key'], $session['metadata_update_threshold']]));
283
284
            $container->set('session.handler.native_file', service(StrictSessionHandler::class, [
285
                $container->isRunningInConsole() ? wrap(NullSessionHandler::class) : wrap(NativeFileSessionHandler::class, [$container->parameters['project_dir'] . '/' . $session['save_path']]),
0 ignored issues
show
Bug introduced by
The method isRunningInConsole() does not exist on Rade\DI\AbstractContainer. It seems like you code against a sub-type of Rade\DI\AbstractContainer such as Rade\Application or Rade\AppBuilder. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

285
                $container->/** @scrutinizer ignore-call */ 
286
                            isRunningInConsole() ? wrap(NullSessionHandler::class) : wrap(NativeFileSessionHandler::class, [$container->parameters['project_dir'] . '/' . $session['save_path']]),
Loading history...
286
            ]))->autowire([AbstractSessionHandler::class]);
287
288
            if ($container->has($session['handler_id'])) {
289
                $container->alias('session.handler', $session['handler_id']);
290
            } else {
291
                $container->set('session.handler', service([wrap(HandlerFactory::class, [2 => $session['cookie_lifetime']]), 'createHandler']))
292
                    ->args([$session['handler_id']])
293
                    ->autowire([AbstractSessionHandler::class]);
294
            }
295
296
            $container->autowire('http.session', service(Session::class, [referenced($session['storage_id'])]));
297
        }
298
    }
299
}
300