Completed
Push — master ( 383102...f77b2b )
by Rafael
08:15
created

Configuration::configureSecurity()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 17
cts 17
cp 1
rs 8.9713
c 0
b 0
f 0
cc 1
eloc 21
nc 1
nop 1
crap 1
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
23
/**
24
 * Class Configuration
25
 */
26
class Configuration implements ConfigurationInterface
27
{
28
    /**
29
     * {@inheritdoc}
30
     */
31 1
    public function getConfigTreeBuilder()
32
    {
33 1
        $treeBuilder = new TreeBuilder();
34
        /** @var NodeBuilder $rootNode */
35 1
        $rootNode = $treeBuilder->root('graphql')->addDefaultsIfNotSet()->children();
0 ignored issues
show
Bug introduced by
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

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

167
                          ->/** @scrutinizer ignore-call */ arrayPrototype()
Loading history...
168 1
                          ->children();
169
170 1
        $endpoints->arrayNode('roles')
171 1
                  ->beforeNormalization()
172 1
                  ->ifString()
173 1
                  ->then(
174 1
                      function ($v) {
175 1
                          return preg_split('/\s*,\s*/', $v);
176 1
                      }
177
                  )
178 1
                  ->end()
179 1
                  ->prototype('scalar')
180 1
                  ->end();
181
182 1
        $endpoints->scalarNode('host')->example('^api\.backend\.');
183 1
        $endpoints->scalarNode('path')->example('/backend');
184
185 1
        $root->arrayNode('endpoint_alias')
186 1
             ->info('Use alias to refer to multiple endpoints using only one name')
187 1
             ->useAttributeAsKey('name')
188 1
             ->beforeNormalization()
189 1
             ->ifString()
190 1
             ->then(
191 1
                 function ($v) {
192
                     return preg_split('/\s*,\s*/', $v);
193 1
                 }
194
             )
195 1
             ->end()
196 1
             ->variablePrototype();
0 ignored issues
show
Bug introduced by
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

196
             ->/** @scrutinizer ignore-call */ variablePrototype();
Loading history...
197
198 1
        $root->scalarNode('endpoint_default')->info('Endpoint to apply to all definitions without explicit endpoint.');
199
200 1
    }
201
202 1
    protected function configureGraphiQL(NodeBuilder $root)
203
    {
204 1
        $graphiql = $root->arrayNode('graphiql')->addDefaultsIfNotSet()->children();
205
206 1
        $graphiql->scalarNode('title')
207 1
                 ->defaultValue('GraphQL API Explorer');
208
209
        $graphiql
210 1
            ->scalarNode('data_warning_message')
211 1
            ->defaultValue('Heads up! GraphQL Explorer makes use of your <strong>real</strong>, <strong>live</strong>, <strong>production</strong> data.');
212 1
        $graphiql->booleanNode('data_warning_dismissible')->defaultTrue();
213 1
        $graphiql->enumNode('data_warning_style')->values(['info', 'warning', 'danger'])->defaultValue('danger');
214
215 1
        $graphiql->scalarNode('template')->defaultValue('@YnloGraphQL/explorer.html.twig');
216 1
        $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.');
217
218 1
        $graphiql->scalarNode('favicon')->info('Url or path to favicon');
219
220 1
        $docs = $graphiql->arrayNode('documentation')->info('Display external API documentation link')->addDefaultsIfNotSet()->children();
221 1
        $docs->scalarNode('link')->info('Url, route or path.');
222 1
        $docs->scalarNode('btn_label')->defaultValue('Documentation');
223 1
        $docs->scalarNode('btn_class')->defaultValue('btn btn-outline-success');
224
225 1
        $authentication = $graphiql->arrayNode('authentication')->addDefaultsIfNotSet()->children();
226
        $authentication
227 1
            ->booleanNode('required')
228 1
            ->info(
229 1
                'The API require credentials to make any requests, 
230
if this value is FALSE and a provider is specified the authentication is optional.'
231
            )
232 1
            ->defaultFalse();
233
234 1
        $authentication->scalarNode('login_message')
235 1
                       ->defaultValue('Start exploring GraphQL API queries using your account’s data now.');
236
237 1
        $authenticationProvider = $authentication->arrayNode('provider')->children();
238
239 1
        $jwt = $authenticationProvider->arrayNode('jwt')->canBeEnabled()->children();
240
241 1
        $jwtLogin = $jwt->arrayNode('login')->children();
242
243 1
        $jwtLogin->scalarNode('url')
244 1
                 ->info('Route name or URI to make the login process to retrieve the token.')
245 1
                 ->isRequired();
246
247 1
        $jwtLogin->scalarNode('username_parameter')
248 1
                 ->defaultValue('username');
249
250 1
        $jwtLogin->scalarNode('username_label')
251 1
                 ->defaultValue('Username');
252
253 1
        $jwtLogin->scalarNode('password_parameter')
254 1
                 ->defaultValue('password');
255
256 1
        $jwtLogin->scalarNode('password_label')
257 1
                 ->defaultValue('Password');
258
259 1
        $jwtLogin->enumNode('parameters_in')
260 1
                 ->values(['form', 'query', 'header'])
261 1
                 ->info('How pass parameters to request the token')
262 1
                 ->defaultValue('form');
263
264 1
        $jwtLogin->scalarNode('response_token_path')
265 1
                 ->defaultValue('token')
266 1
                 ->info('Where the token should be located in the response in case of JSON, set null if the response is the token.');
267
268 1
        $jwtRequests = $jwt->arrayNode('requests')->addDefaultsIfNotSet()->children();
269
270 1
        $jwtRequests->enumNode('token_in')
271 1
                    ->values(['query', 'header'])
272 1
                    ->info('Where should be located the token on every request')
273 1
                    ->defaultValue('header');
274
275 1
        $jwtRequests->scalarNode('token_name')
276 1
                    ->defaultValue('Authorization')
277 1
                    ->info('Name of the token in query or header name');
278
279 1
        $jwtRequests->scalarNode('token_template')
280 1
                    ->defaultValue('Bearer {token}')
281 1
                    ->info('Customize how the token should be send,  use the place holder {token} to replace for current token');
282
283 1
        $authenticationProvider->scalarNode('custom')
284 1
                               ->defaultNull()
285 1
                               ->info('Configure custom service to use as authentication provider');
286 1
    }
287
288 1
    protected function configureCORS(NodeBuilder $root)
289
    {
290 1
        $cors = $root->arrayNode('cors')->canBeEnabled()->children();
291 1
        $cors->booleanNode('allow_credentials')->defaultTrue();
292 1
        $cors->variableNode('allow_headers')->defaultValue(['Origin', 'Content-Type', 'Accept', 'Authorization']);
293 1
        $cors->integerNode('max_age')->defaultValue(3600);
294 1
        $cors->variableNode('allow_methods')->defaultValue(['POST', 'GET', 'OPTIONS']);
295 1
        $cors->variableNode('allow_origins')->defaultValue(['*']);
296 1
    }
297
298 1
    protected function configurePlugins(NodeBuilder $root)
299
    {
300 1
        $this->configurePluginPaginationGlobalConfig($root);
301 1
        $this->configurePluginNamespaceGlobalConfig($root);
302 1
    }
303
304 1
    protected function configurePluginPaginationGlobalConfig(NodeBuilder $root)
305
    {
306 1
        $pagination = $root->arrayNode('pagination')->addDefaultsIfNotSet()->children();
307 1
        $pagination->integerNode('limit')
308 1
                   ->defaultValue(100)->info('Maximum limit allowed for all paginations');
309 1
    }
310
311 1
    protected function configurePluginNamespaceGlobalConfig(NodeBuilder $root)
312
    {
313 1
        $namespaces = $root->arrayNode('namespaces')
314 1
                           ->info(
315 1
                               'Group GraphQL schema using namespaced schemas. 
316
On large schemas is  helpful to keep schemas grouped by bundle and node'
317
                           )
318 1
                           ->canBeEnabled()
319 1
                           ->addDefaultsIfNotSet()
320 1
                           ->children();
321
322 1
        $bundles = $namespaces->arrayNode('bundles')
323 1
                              ->info('Group each bundle into a separate schema definition')
324 1
                              ->canBeDisabled()
325 1
                              ->addDefaultsIfNotSet()
326 1
                              ->children();
327
328 1
        $bundles->scalarNode('query_suffix')
329 1
                ->info('The following suffix will be used for bundle query groups')
330 1
                ->defaultValue('BundleQuery');
331
332 1
        $bundles->scalarNode('mutation_suffix')
333 1
                ->info('The following suffix will be used for bundle mutation groups')
334 1
                ->defaultValue('BundleMutation');
335
336 1
        $bundles->variableNode('ignore')
337 1
                ->info('The following bundles will be ignore for grouping, all definitions will be placed in the root query or mutation')
338 1
                ->defaultValue(['AppBundle']);
339
340 1
        $bundles->arrayNode('aliases')
341 1
                ->info(
342 1
                    'Define aliases for bundles to set definitions inside other desired bundle name. 
343
Can be used to group multiple bundles or publish a bundle with a different name'
344
                )
345 1
                ->example('SecurityBundle: AppBundle')
346 1
                ->useAttributeAsKey('name')
347 1
                ->prototype('scalar');
348
349
350 1
        $nodes = $namespaces->arrayNode('nodes')
351 1
                            ->info('Group queries and mutations of the same node into a node specific schema definition.')
352 1
                            ->addDefaultsIfNotSet()
353 1
                            ->canBeDisabled()
354 1
                            ->children();
355
356 1
        $nodes->scalarNode('query_suffix')
357 1
              ->info('The following suffix will be used to create the name for queries to the same node')
358 1
              ->defaultValue('Query');
359
360 1
        $nodes->scalarNode('mutation_suffix')
361 1
              ->info('The following suffix will be used to create the name for mutations to the same node')
362 1
              ->defaultValue('Mutation');
363
364 1
        $nodes->variableNode('ignore')
365 1
              ->info('The following nodes will be ignore for grouping, all definitions will be placed in the root query or mutation')
366 1
              ->defaultValue(['Node']);
367
368 1
        $nodes->arrayNode('aliases')
369 1
              ->info(
370 1
                  'Define aliases for nodes to set definitions inside other desired node name. 
371
Can be used to group multiple nodes or publish a node with a different group name'
372
              )
373 1
              ->example('InvoiceItem: Invoice')
374 1
              ->useAttributeAsKey('name')
375 1
              ->prototype('scalar');
376 1
    }
377
378 1
    private function configureSecurity(NodeBuilder $rootNode)
379
    {
380
        $securityNode = $rootNode
381 1
            ->arrayNode('security')
382 1
            ->canBeEnabled()
383 1
            ->children();
384
385
        $validationRulesNode = $securityNode
386 1
            ->arrayNode('validation_rules')
387 1
            ->addDefaultsIfNotSet()
388 1
            ->children();
389
        $validationRulesNode
390 1
            ->integerNode('query_complexity')
391 1
            ->info('Query complexity score before execution. (Recommended >= 200)')
392 1
            ->min(0)
393 1
            ->defaultValue(QueryComplexity::DISABLED);
394
        $validationRulesNode
395 1
            ->integerNode('query_depth')
396 1
            ->info('Max depth of the query. (Recommended >= 11)')
397 1
            ->min(0)
398 1
            ->defaultValue(QueryDepth::DISABLED);
399
        $validationRulesNode
400 1
            ->booleanNode('disable_introspection')
401 1
            ->defaultFalse();
402 1
    }
403
404 1
    private function configureOthers(NodeBuilder $rootNode)
405
    {
406
        $rootNode
407 1
            ->scalarNode('id_encoder')
408 1
            ->defaultValue(SecureIDEncoder::class)
409 1
            ->info('Service used to encode nodes identifiers, must implements IDEncoderInterface');
410 1
    }
411
}
412