Issues (281)

src/DependencyInjection/Configuration.php (3 issues)

Labels
Severity
1
<?php
2
3
/*******************************************************************************
4
 *  This file is part of the GraphQL Bundle package.
5
 *
6
 *  (c) YnloUltratech <[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
namespace Ynlo\GraphQLBundle\DependencyInjection;
13
14
use GraphQL\Validator\Rules\QueryComplexity;
15
use GraphQL\Validator\Rules\QueryDepth;
16
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
17
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
18
use Symfony\Component\Config\Definition\ConfigurationInterface;
19
use Ynlo\GraphQLBundle\Encoder\SecureIDEncoder;
20
use Ynlo\GraphQLBundle\Error\DefaultErrorFormatter;
21
use Ynlo\GraphQLBundle\Error\DefaultErrorHandler;
22
use Ynlo\GraphQLBundle\Subscription\PubSub\RedisPubSubHandler;
23
use Ynlo\GraphQLBundle\Subscription\Subscriber;
24
25
/**
26
 * Class Configuration
27
 */
28
class Configuration implements ConfigurationInterface
29
{
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public function getConfigTreeBuilder()
34
    {
35
        $treeBuilder = new TreeBuilder('graphql');
36
        $rootNode = $treeBuilder->getRootNode()->addDefaultsIfNotSet()->children();
0 ignored issues
show
The method addDefaultsIfNotSet() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. It seems like you code against a sub-type of Symfony\Component\Config...\Builder\NodeDefinition such as Symfony\Component\Config...der\ArrayNodeDefinition. ( Ignorable by Annotation )

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

36
        $rootNode = $treeBuilder->getRootNode()->/** @scrutinizer ignore-call */ addDefaultsIfNotSet()->children();
Loading history...
37
        $this->configureEndpoints($rootNode);
38
        $this->configureSubscriptions($rootNode);
39
        $this->configureErrorHandling($rootNode);
40
        $this->configureCORS($rootNode);
41
        $this->configureGraphiQL($rootNode);
42
        $this->configurePlugins($rootNode);
43
        $this->configureSecurity($rootNode);
44
        $this->configureOthers($rootNode);
45
        $this->configureBCCompatibility($rootNode);
46
47
        return $treeBuilder;
48
    }
49
50
    protected function configureSubscriptions(NodeBuilder $root)
51
    {
52
        $subscriptions = $root->arrayNode('subscriptions')
53
                              ->info('Manage subscriptions settings')
54
                              ->canBeDisabled()
55
                              ->addDefaultsIfNotSet()
56
                              ->children();
57
58
        $subscriptions->scalarNode('mercure_hub')->defaultValue('default');
59
        $subscriptions->scalarNode('pubsub_handler')->defaultValue(RedisPubSubHandler::class);
60
        $redis = $subscriptions->arrayNode('redis')->info('Configure redis server to use as subscription handler')
61
                               ->addDefaultsIfNotSet()
62
                               ->children();
63
64
        $redis->scalarNode('host')->defaultValue('localhost');
65
        $redis->integerNode('port')->defaultValue(6379);
66
        $redis->scalarNode('prefix')->defaultValue('GraphQLSubscription:')
67
              ->info('Define custom prefix to avoid collisions between applications');
68
    }
69
70
    protected function configureErrorHandling(NodeBuilder $root)
71
    {
72
        $errorHandling = $root->arrayNode('error_handling')
73
                              ->info('It is important to handle errors and when possible, report these errors back to your users for information. ')
74
                              ->addDefaultsIfNotSet()
75
                              ->children();
76
77
        $errorHandling->enumNode('validation_messages')
78
                      ->values(['error', 'payload', 'both'])
79
                      ->info('Where should be displayed validation messages.')
80
                      ->defaultValue('error');
81
82
        //@deprecated since v1.1 snd should be deleted in 2.0, this is a compatibility flag
83
        $errorHandling->booleanNode('jwt_auth_failure_compatibility')
84
                      ->info('Keep BC with oldest version of JWT Authentication errors')
85
                      ->setDeprecated('Since v1.1 and will will be removed in the next mayor release')
86
                      ->defaultFalse();
87
88
        $errorHandling->booleanNode('show_trace')->info('Show error trace in debug mode')->defaultFalse();
89
90
        $errorHandling->scalarNode('formatter')
91
                      ->info('Formatter is responsible for converting instances of Error to an array')
92
                      ->defaultValue(DefaultErrorFormatter::class);
93
94
        $errorHandling->scalarNode('handler')
95
                      ->info('Handler is useful for error filtering and logging.')
96
                      ->defaultValue(DefaultErrorHandler::class);
97
98
        $controlledErrors = $errorHandling
99
            ->arrayNode('controlled_errors')
100
            ->info('List of controlled errors')
101
            ->addDefaultsIfNotSet()
102
            ->children();
103
104
        $map = $controlledErrors->arrayNode('map')->useAttributeAsKey('code')->arrayPrototype()->children();
105
        $map->scalarNode('message')->isRequired();
106
        $map->scalarNode('description')->isRequired();
107
        $map->scalarNode('category')->defaultValue('user');
108
109
        $autoload = $controlledErrors
110
            ->arrayNode('autoload')
111
            ->info('Autoload exceptions implementing ControlledErrorInterface')
112
            ->addDefaultsIfNotSet()
113
            ->canBeDisabled()
114
            ->children();
115
116
        $autoload
117
            ->variableNode('locations')
118
            ->defaultValue(['Exception', 'Error'])
119
            ->info('Default folder to find exceptions and errors implementing controlled interface.')
120
            ->beforeNormalization()
121
            ->ifString()
122
            ->then(
123
                function ($v) {
124
                    return [$v];
125
                }
126
            )
127
            ->end();
128
129
        $autoload
130
            ->variableNode('whitelist')
131
            ->info('White listed classes')
132
            ->defaultValue(['/App\\\\[Exception|Error]/', '/\w+Bundle\\\\[Exception|Error]/'])
133
            ->beforeNormalization()
134
            ->ifString()
135
            ->then(
136
                function ($v) {
137
                    return [$v];
138
                }
139
            )
140
            ->end()
141
            ->validate()
142
            ->ifTrue(
143
                function (array $value) {
144
                    foreach ($value as $val) {
145
                        try {
146
                            preg_match($val, null);
147
                        } catch (\Exception $exception) {
148
                            return true;
149
                        }
150
                    }
151
                }
152
            )->thenInvalid('Invalid regular expression');
153
154
        $autoload
155
            ->variableNode('blacklist')
156
            ->info('Black listed classes')
157
            ->beforeNormalization()
158
            ->ifString()
159
            ->then(
160
                function ($v) {
161
                    return [$v];
162
                }
163
            )
164
            ->end()
165
            ->validate()
166
            ->ifTrue(
167
                function (array $value) {
168
                    foreach ($value as $val) {
169
                        try {
170
                            preg_match($val, null);
171
                        } catch (\Exception $exception) {
172
                            return true;
173
                        }
174
                    }
175
                }
176
            )->thenInvalid('Invalid regular expression');
177
    }
178
179
    protected function configureEndpoints(NodeBuilder $root)
180
    {
181
        $endpoints = $root->arrayNode('endpoints')
182
                          ->useAttributeAsKey('name')
183
                          ->validate()
184
                          ->ifTrue(
185
                              function ($v) {
186
                                  return array_key_exists('default', $v);
187
                              }
188
                          )->thenInvalid('"default" can\'t be used as endpoint name, the system internally use this endpoint name to store the entire schema.')
189
                          ->end()
190
                          ->arrayPrototype()
0 ignored issues
show
The method arrayPrototype() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. It seems like you code against a sub-type of Symfony\Component\Config...\Builder\NodeDefinition such as Symfony\Component\Config...der\ArrayNodeDefinition. ( Ignorable by Annotation )

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

190
                          ->/** @scrutinizer ignore-call */ arrayPrototype()
Loading history...
191
                          ->children();
192
193
        $endpoints->arrayNode('roles')
194
                  ->beforeNormalization()
195
                  ->ifString()
196
                  ->then(
197
                      function ($v) {
198
                          return preg_split('/\s*,\s*/', $v);
199
                      }
200
                  )
201
                  ->end()
202
                  ->prototype('scalar')
203
                  ->end();
204
205
        $endpoints->scalarNode('host')->example('^api\.backend\.');
206
        $endpoints->scalarNode('path')->example('/backend');
207
208
        $root->arrayNode('endpoint_alias')
209
             ->info('Use alias to refer to multiple endpoints using only one name')
210
             ->useAttributeAsKey('name')
211
             ->beforeNormalization()
212
             ->ifString()
213
             ->then(
214
                 function ($v) {
215
                     return preg_split('/\s*,\s*/', $v);
216
                 }
217
             )
218
             ->end()
219
             ->variablePrototype();
0 ignored issues
show
The method variablePrototype() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. It seems like you code against a sub-type of Symfony\Component\Config...\Builder\NodeDefinition such as Symfony\Component\Config...der\ArrayNodeDefinition. ( Ignorable by Annotation )

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

219
             ->/** @scrutinizer ignore-call */ variablePrototype();
Loading history...
220
221
        $root->scalarNode('endpoint_default')->info('Endpoint to apply to all definitions without explicit endpoint.');
222
223
    }
224
225
    protected function configureGraphiQL(NodeBuilder $root)
226
    {
227
        $graphiql = $root->arrayNode('graphiql')->addDefaultsIfNotSet()->children();
228
229
        $graphiql->scalarNode('title')
230
                 ->defaultValue('GraphQL API Explorer');
231
232
        $graphiql
233
            ->scalarNode('data_warning_message')
234
            ->defaultValue('Heads up! GraphQL Explorer makes use of your <strong>real</strong>, <strong>live</strong>, <strong>production</strong> data.');
235
        $graphiql->booleanNode('data_warning_dismissible')->defaultTrue();
236
        $graphiql->enumNode('data_warning_style')->values(['info', 'warning', 'danger'])->defaultValue('danger');
237
238
        $graphiql->scalarNode('template')->defaultValue('@YnloGraphQL/explorer.html.twig');
239
        $graphiql->scalarNode('default_query')->defaultNull()->info('An optional GraphQL string to use when no query exists from a previous session. If none is provided, GraphiQL will use its own default query.');
240
241
        $graphiql->scalarNode('favicon')->info('Url or path to favicon');
242
243
        $docs = $graphiql->arrayNode('documentation')->info('Display external API documentation link')->addDefaultsIfNotSet()->children();
244
        $docs->scalarNode('link')->info('Url, route or path.');
245
        $docs->scalarNode('btn_label')->defaultValue('Documentation');
246
        $docs->scalarNode('btn_class')->defaultValue('btn btn-outline-success');
247
248
        $authentication = $graphiql->arrayNode('authentication')->addDefaultsIfNotSet()->children();
249
        $authentication
250
            ->booleanNode('required')
251
            ->info(
252
                'The API require credentials to make any requests, 
253
if this value is FALSE and a provider is specified the authentication is optional.'
254
            )
255
            ->defaultFalse();
256
257
        $authentication->scalarNode('login_message')
258
                       ->defaultValue('Start exploring GraphQL API queries using your account’s data now.');
259
260
        $authenticationProvider = $authentication->arrayNode('provider')->children();
261
262
        //the updated version of `jwt` to use lexik authentication bundle
263
        $lexikJwt = $authenticationProvider->arrayNode('lexik_jwt')
264
                                           ->canBeEnabled()
265
                                           ->children();
266
267
        $lexikJwt->scalarNode('user_provider')
268
                 ->isRequired()
269
                 ->info('Name of the user provider to use');
270
271
        $lexikJwt->scalarNode('username_label')
272
                 ->defaultValue('Username');
273
274
        $lexikJwt->scalarNode('password_label')
275
                 ->defaultValue('Password');
276
277
        $authenticationProvider->scalarNode('custom')
278
                               ->defaultNull()
279
                               ->info('Configure custom service to use as authentication provider');
280
281
        //deprecated since v1.1 and should be deleted in v2.0
282
        $jwt = $authenticationProvider->arrayNode('jwt')
283
                                      ->setDeprecated('Use lexik_jwt instead, this provider will be removed in the next mayor release.')
284
                                      ->canBeEnabled()
285
                                      ->children();
286
287
        $jwtLogin = $jwt->arrayNode('login')->children();
288
289
        $jwtLogin->scalarNode('url')
290
                 ->info('Route name or URI to make the login process to retrieve the token.')
291
                 ->isRequired();
292
293
        $jwtLogin->scalarNode('username_parameter')
294
                 ->defaultValue('username');
295
296
        $jwtLogin->scalarNode('username_label')
297
                 ->defaultValue('Username');
298
299
        $jwtLogin->scalarNode('password_parameter')
300
                 ->defaultValue('password');
301
302
        $jwtLogin->scalarNode('password_label')
303
                 ->defaultValue('Password');
304
305
        $jwtLogin->enumNode('parameters_in')
306
                 ->values(['form', 'query', 'header'])
307
                 ->info('How pass parameters to request the token')
308
                 ->defaultValue('form');
309
310
        $jwtLogin->scalarNode('response_token_path')
311
                 ->defaultValue('token')
312
                 ->info('Where the token should be located in the response in case of JSON, set null if the response is the token.');
313
314
        $jwtRequests = $jwt->arrayNode('requests')->addDefaultsIfNotSet()->children();
315
316
        $jwtRequests->enumNode('token_in')
317
                    ->values(['query', 'header'])
318
                    ->info('Where should be located the token on every request')
319
                    ->defaultValue('header');
320
321
        $jwtRequests->scalarNode('token_name')
322
                    ->defaultValue('Authorization')
323
                    ->info('Name of the token in query or header name');
324
325
        $jwtRequests->scalarNode('token_template')
326
                    ->defaultValue('Bearer {token}')
327
                    ->info('Customize how the token should be send,  use the place holder {token} to replace for current token');
328
329
    }
330
331
    protected function configureCORS(NodeBuilder $root)
332
    {
333
        $cors = $root->arrayNode('cors')->canBeEnabled()->children();
334
        $cors->booleanNode('allow_credentials')->defaultTrue();
335
        $cors->variableNode('allow_headers')->defaultValue(['Origin', 'Content-Type', 'Accept', 'Authorization']);
336
        $cors->integerNode('max_age')->defaultValue(3600);
337
        $cors->variableNode('allow_methods')->defaultValue(['POST', 'GET', 'OPTIONS']);
338
        $cors->variableNode('allow_origins')->defaultValue(['*']);
339
    }
340
341
    protected function configurePlugins(NodeBuilder $root)
342
    {
343
        $this->configurePluginPaginationGlobalConfig($root);
344
        $this->configurePluginNamespaceGlobalConfig($root);
345
    }
346
347
    protected function configurePluginPaginationGlobalConfig(NodeBuilder $root)
348
    {
349
        $pagination = $root->arrayNode('pagination')->addDefaultsIfNotSet()->children();
350
        $pagination->integerNode('limit')
351
                   ->defaultValue(100)->info('Maximum limit allowed for all paginations');
352
    }
353
354
    protected function configurePluginNamespaceGlobalConfig(NodeBuilder $root)
355
    {
356
        $namespaces = $root->arrayNode('namespaces')
357
                           ->info(
358
                               'Group GraphQL schema using namespaced schemas. 
359
On large schemas is  helpful to keep schemas grouped by bundle and node'
360
                           )
361
                           ->canBeEnabled()
362
                           ->addDefaultsIfNotSet()
363
                           ->children();
364
365
        $bundles = $namespaces->arrayNode('bundles')
366
                              ->info('Group each bundle into a separate schema definition')
367
                              ->canBeDisabled()
368
                              ->addDefaultsIfNotSet()
369
                              ->children();
370
371
        $bundles->scalarNode('query_suffix')
372
                ->info('The following suffix will be used for bundle query groups')
373
                ->defaultValue('BundleQuery');
374
375
        $bundles->scalarNode('mutation_suffix')
376
                ->info('The following suffix will be used for bundle mutation groups')
377
                ->defaultValue('BundleMutation');
378
379
        $bundles->scalarNode('subscription_suffix')
380
                ->info('The following suffix will be used for bundle subscription groups')
381
                ->defaultValue('BundleSubscription');
382
383
        $bundles->variableNode('ignore')
384
                ->info('The following bundles will be ignore for grouping, all definitions will be placed in the root query or mutation')
385
                ->defaultValue(['AppBundle']);
386
387
        $bundles->arrayNode('aliases')
388
                ->info(
389
                    'Define aliases for bundles to set definitions inside other desired bundle name. 
390
Can be used to group multiple bundles or publish a bundle with a different name'
391
                )
392
                ->example('SecurityBundle: AppBundle')
393
                ->useAttributeAsKey('name')
394
                ->prototype('scalar');
395
396
397
        $nodes = $namespaces->arrayNode('nodes')
398
                            ->info('Group queries and mutations of the same node into a node specific schema definition.')
399
                            ->addDefaultsIfNotSet()
400
                            ->canBeDisabled()
401
                            ->children();
402
403
        $nodes->scalarNode('query_suffix')
404
              ->info('The following suffix will be used to create the name for queries to the same node')
405
              ->defaultValue('Query');
406
407
        $nodes->scalarNode('mutation_suffix')
408
              ->info('The following suffix will be used to create the name for mutations to the same node')
409
              ->defaultValue('Mutation');
410
411
        $nodes->scalarNode('subscription_suffix')
412
              ->info('The following suffix will be used to create the name for subscriptions to the same node')
413
              ->defaultValue('Subscriptions');
414
415
        $nodes->variableNode('ignore')
416
              ->info('The following nodes will be ignore for grouping, all definitions will be placed in the root query or mutation')
417
              ->defaultValue(['Node']);
418
419
        $nodes->arrayNode('aliases')
420
              ->info(
421
                  'Define aliases for nodes to set definitions inside other desired node name. 
422
Can be used to group multiple nodes or publish a node with a different group name'
423
              )
424
              ->example('InvoiceItem: Invoice')
425
              ->useAttributeAsKey('name')
426
              ->prototype('scalar');
427
    }
428
429
    private function configureSecurity(NodeBuilder $rootNode)
430
    {
431
        $securityNode = $rootNode
432
            ->arrayNode('security')
433
            ->canBeEnabled()
434
            ->children();
435
436
        $validationRulesNode = $securityNode
437
            ->arrayNode('validation_rules')
438
            ->addDefaultsIfNotSet()
439
            ->children();
440
        $validationRulesNode
441
            ->integerNode('query_complexity')
442
            ->info('Query complexity score before execution. (Recommended >= 200)')
443
            ->min(0)
444
            ->defaultValue(QueryComplexity::DISABLED);
445
        $validationRulesNode
446
            ->integerNode('query_depth')
447
            ->info('Max depth of the query. (Recommended >= 11)')
448
            ->min(0)
449
            ->defaultValue(QueryDepth::DISABLED);
450
        $validationRulesNode
451
            ->booleanNode('disable_introspection')
452
            ->defaultFalse();
453
    }
454
455
    private function configureOthers(NodeBuilder $rootNode)
456
    {
457
        $rootNode
458
            ->scalarNode('id_encoder')
459
            ->defaultValue(SecureIDEncoder::class)
460
            ->info('Service used to encode nodes identifiers, must implements IDEncoderInterface');
461
    }
462
463
    private function configureBCCompatibility(NodeBuilder $rootNode)
464
    {
465
        $bcNode = $rootNode
466
            ->arrayNode('bc')
467
            ->info('Backward compatibility layer to keep deprecated features during some time to upgrade API consumers progressively.')
468
            ->addDefaultsIfNotSet()
469
            ->children();
470
471
        $bcNode->variableNode('filters')
472
               ->info('Keep deprecated "filters" argument in collections')
473
               ->setDeprecated('v1.2')
474
               ->defaultFalse();
475
476
        $bcNode->variableNode('orderBy')
477
               ->info('Keep deprecated "orderBy" argument in collections')
478
               ->setDeprecated('v1.2')
479
               ->defaultFalse();
480
    }
481
}
482