Completed
Pull Request — master (#13)
by Rafael
06:46
created

EndpointsDefinitionPlugin::secureOperations()   F

Complexity

Conditions 19
Paths 648

Size

Total Lines 46
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 23.4381

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 46
ccs 20
cts 26
cp 0.7692
rs 0.8388
c 0
b 0
f 0
cc 19
nc 648
nop 3
crap 23.4381

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Definition\Plugin;
13
14
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
15
use Ynlo\GraphQLBundle\Definition\ClassAwareDefinitionInterface;
16
use Ynlo\GraphQLBundle\Definition\DefinitionInterface;
17
use Ynlo\GraphQLBundle\Definition\ExecutableDefinitionInterface;
18
use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface;
19
use Ynlo\GraphQLBundle\Definition\ImplementorInterface;
20
use Ynlo\GraphQLBundle\Definition\InterfaceDefinition;
21
use Ynlo\GraphQLBundle\Definition\MutationDefinition;
22
use Ynlo\GraphQLBundle\Definition\NodeAwareDefinitionInterface;
23
use Ynlo\GraphQLBundle\Definition\QueryDefinition;
24
use Ynlo\GraphQLBundle\Definition\Registry\DefinitionRegistry;
25
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
26
use Ynlo\GraphQLBundle\Definition\SubscriptionDefinition;
27
use Ynlo\GraphQLBundle\Definition\UnionDefinition;
28
use Ynlo\GraphQLBundle\Model\NodeInterface;
29
30
class EndpointsDefinitionPlugin extends AbstractDefinitionPlugin
31
{
32
    /**
33
     * @var array
34
     */
35
    private $endpointAlias = [];
36
37
    /**
38
     * @var string
39
     */
40
    private $endpointDefault;
41
42
43
    /**
44
     * EndpointsDefinitionPlugin constructor.
45
     *
46
     * @param array $endpointsConfig
47
     */
48 3
    public function __construct(array $endpointsConfig)
49
    {
50 3
        $this->endpointAlias = $endpointsConfig['alias'] ?? [];
51 3
        $this->endpointDefault = $endpointsConfig['default'] ?? null;
52 3
    }
53
54
    /**
55
     * {@inheritDoc}
56
     */
57
    public function buildConfig(ArrayNodeDefinition $root): void
58
    {
59
        $root
60
            ->info('List of endpoints for queries and mutations')
61
            ->scalarPrototype();
62
    }
63
64
    /**
65
     * {@inheritDoc}
66
     */
67 3
    public function normalizeConfig(DefinitionInterface $definition, $config): array
68
    {
69 3
        $endpoints = $config['endpoints'] ?? [];
70
71
        //allow set only one endpoint in a simple string
72 3
        if (\is_string($endpoints)) {
73 2
            $endpoints = [$endpoints];
74
        }
75
76 3
        return $endpoints;
77
    }
78
79
    /**
80
     * {@inheritDoc}
81
     */
82 1
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void
83
    {
84 1
        if ($endpoint->getName() === DefinitionRegistry::DEFAULT_ENDPOINT) {
85
            return;
86
        }
87
88
        //ignore safe operations
89 1
        if (\in_array($definition->getName(), ['node', 'nodes'])) {
90
            return;
91
        }
92
93
        //apply default endpoint to all operations
94 1
        $endpoints = $this->normalizeConfig($definition, $definition->getMeta('endpoints'));
95 1
        if (!$endpoints && $this->endpointDefault) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $endpoints of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
96 1
            if ($definition instanceof QueryDefinition
97 1
                || ($definition instanceof ClassAwareDefinitionInterface
98 1
                    && is_subclass_of($definition->getClass(), NodeInterface::class, true))
99
            ) {
100 1
                $definition->setMeta('endpoints', ['endpoints' => [$this->endpointDefault]]);
101
            }
102
        }
103 1
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 1
    public function configureEndpoint(Endpoint $endpoint): void
109
    {
110 1
        if ($endpoint->getName() === DefinitionRegistry::DEFAULT_ENDPOINT) {
111
            return;
112
        }
113
114 1
        $forbiddenTypes = $this->getForbiddenTypes($endpoint);
115 1
        $this->processForbiddenTypes($endpoint, $forbiddenTypes);
116 1
    }
117
118 1
    protected function processForbiddenTypes(Endpoint $endpoint, $forbiddenTypes)
119
    {
120 1
        foreach ($endpoint->allQueries() as $queries) {
121 1
            $this->secureOperations($endpoint, $queries, $forbiddenTypes);
122
        }
123
124 1
        foreach ($endpoint->allMutations() as $mutations) {
125 1
            $this->secureOperations($endpoint, $mutations, $forbiddenTypes);
126
        }
127
128 1
        foreach ($endpoint->allSubscriptions() as $subscriptions) {
129
            $this->secureOperations($endpoint, $subscriptions, $forbiddenTypes);
130
        }
131
132 1
        foreach ($endpoint->allTypes() as $type) {
133
            //remove implementations of forbidden interfaces
134 1
            if ($type instanceof ImplementorInterface) {
135 1
                foreach ($type->getInterfaces() as $interface) {
136 1
                    if (in_array($interface, $forbiddenTypes)) {
137 1
                        $type->removeInterface($interface);
138
                    }
139
                }
140
            }
141
142 1
            if ($type instanceof UnionDefinition) {
143
                foreach ($type->getTypes() as $subType) {
144
                    //remove union sub-type related to forbidden type
145
                    $fieldType = $endpoint->hasType($subType->getType()) ? $endpoint->getType($subType->getType()) : null;
146
                    if (($fieldType && in_array($fieldType->getName(), $forbiddenTypes))) {
147
                        $type->removeType($subType->getType());
148
                    }
149
                }
150
151
                //after delete sub-types related to forbidden objects,
152
                //verify if the union has at least one sub-type
153
                //otherwise mark this type as forbidden
154
                if (!$type->getTypes()) {
155
                    $forbiddenTypes[] = $type->getName();
156
                    $this->processForbiddenTypes($endpoint, $forbiddenTypes);
157
                }
158
            }
159
160
            //remove fields related to forbidden interfaces
161 1
            if ($type instanceof FieldsAwareDefinitionInterface) {
162 1
                if ($type->getFields()) {
163 1
                    foreach ($type->getFields() as $field) {
164
                        //remove forbidden field
165 1
                        if (!$this->isGranted($endpoint, $field)) {
166 1
                            $type->removeField($field->getName());
167
                        }
168
169
                        //remove field related to forbidden type
170 1
                        $fieldType = $endpoint->hasType($field->getType()) ? $endpoint->getType($field->getType()) : null;
171 1
                        $fieldNodeType = $endpoint->hasType($field->getMeta('node')) ? $endpoint->getType($field->getMeta('node')) : null;
172 1
                        if (($fieldType && in_array($fieldType->getName(), $forbiddenTypes))
173 1
                            || ($fieldNodeType && in_array($fieldNodeType->getName(), $forbiddenTypes))) {
174 1
                            $type->removeField($field->getName());
175
                        }
176
177
                        // remove field inherited from forbidden interface
178 1
                        if ($field->getInheritedFrom()) {
179 1
                            $inheritedFromArray = $field->getInheritedFrom();
180 1
                            foreach ($field->getInheritedFrom() as $index => $inheritedFrom) {
181 1
                                if (in_array($inheritedFrom, $forbiddenTypes, true)) {
182
                                    // remove inheritance
183 1
                                    unset($inheritedFromArray[$index]);
184
                                }
185
                            }
186 1
                            $field->setInheritedFrom($inheritedFromArray);
187
                            // if all parents are removed, remove the field
188 1
                            if (empty($inheritedFromArray)) {
189 1
                                $type->removeField($field->getName());
190
                            }
191
                        }
192
193 1
                        foreach ($field->getArguments() as $argument) {
194
                            //remove forbidden argument
195 1
                            if (!$this->isGranted($endpoint, $argument)) {
196
                                $field->removeArgument($argument->getName());
197
                            }
198
199
                            //remove argument related to forbidden type
200 1
                            $argumentType = $endpoint->hasType($argument->getType()) ? $endpoint->getType($argument->getType()) : null;
201 1
                            if ($argumentType && \in_array($argumentType->getName(), $forbiddenTypes, true)) {
202
                                $field->removeArgument($argument->getName());
203
                            }
204
                        }
205
                    }
206
207
                    //after delete fields related to forbidden objects,
208
                    //verify if the object has at least one field
209
                    //otherwise mark this type as forbidden
210 1
                    if (!$type->getFields()) {
211 1
                        $forbiddenTypes[] = $type->getName();
212 1
                        $this->processForbiddenTypes($endpoint, $forbiddenTypes);
213
                    }
214
                }
215
            }
216
        }
217
218
        /** @var InterfaceDefinition $type */
219 1
        foreach ($endpoint->allInterfaces() as $type) {
220 1
            if ($type->getImplementors()) {
221 1
                foreach ($type->getImplementors() as $implementor) {
222 1
                    if (in_array($implementor, $forbiddenTypes)) {
223 1
                        $type->removeImplementor($implementor);
224
                    }
225
                }
226
227
                //after delete forbidden implementors
228
                //verify if the interface has at least one implementor
229
                //otherwise mark this interface as forbidden
230 1
                if (!$type->getImplementors()) {
231 1
                    $forbiddenTypes[] = $type->getName();
232 1
                    $this->processForbiddenTypes($endpoint, $forbiddenTypes);
233
                }
234
            }
235
        }
236
237 1
        foreach ($forbiddenTypes as $type) {
238 1
            $endpoint->removeType($type);
239
        }
240 1
    }
241
242
    /**
243
     * Remove
244
     *
245
     * @param Endpoint                      $endpoint
246
     * @param ExecutableDefinitionInterface $executableDefinition
247
     * @param array|string[]                $forbiddenTypes
248
     */
249 1
    protected function secureOperations(Endpoint $endpoint, ExecutableDefinitionInterface $executableDefinition, $forbiddenTypes)
250
    {
251 1
        $type = $endpoint->hasType($executableDefinition->getType()) ? $endpoint->getType($executableDefinition->getType()) : null;
252
253 1
        $node = null;
254
        //resolve the related node using interface
255 1
        if ($executableDefinition instanceof NodeAwareDefinitionInterface) {
0 ignored issues
show
introduced by
$executableDefinition is always a sub-type of Ynlo\GraphQLBundle\Defin...wareDefinitionInterface.
Loading history...
256 1
            $node = $endpoint->hasType($executableDefinition->getNode()) ? $endpoint->getType($executableDefinition->getNode()) : null;
257
        }
258
259
        //resolve related node using metadata
260 1
        if (!$node) {
261 1
            $node = $endpoint->hasType($executableDefinition->getMeta('node')) ? $endpoint->getType($executableDefinition->getMeta('node')) : null;
262
        }
263
264 1
        $granted = true;
265 1
        $endpoints = $this->normalizeConfig($executableDefinition, $executableDefinition->getMeta('endpoints', []));
266
267
        //if the operation has endpoints defined use that,
268
        //otherwise check by related type and node
269 1
        if ($endpoints) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $endpoints of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
270 1
            $granted = $this->isGranted($endpoint, $executableDefinition);
271 1
        } elseif (($type && \in_array($type->getName(), $forbiddenTypes, true))
272 1
                  || ($node && \in_array($node->getName(), $forbiddenTypes, true))) {
273 1
            $granted = false;
274
        }
275
276 1
        if (!$granted) {
277 1
            if ($executableDefinition instanceof MutationDefinition) {
278 1
                $endpoint->removeMutation($executableDefinition->getName());
279 1
            } elseif ($executableDefinition instanceof SubscriptionDefinition) {
280
                $endpoint->removeSubscription($executableDefinition->getName());
281
            } else {
282 1
                $endpoint->removeQuery($executableDefinition->getName());
283
            }
284
        } else {
285 1
            foreach ($executableDefinition->getArguments() as $argument) {
286
                //remove forbidden argument
287
                if (!$this->isGranted($endpoint, $argument)) {
288
                    $executableDefinition->removeArgument($argument->getName());
289
                }
290
291
                //remove argument related to forbidden type
292
                $argumentType = $endpoint->hasType($argument->getType()) ? $endpoint->getType($argument->getType()) : null;
293
                if ($argumentType && \in_array($argumentType->getName(), $forbiddenTypes, true)) {
294
                    $executableDefinition->removeArgument($argument->getName());
295
                }
296
            }
297
        }
298 1
    }
299
300 1
    protected function getForbiddenTypes(Endpoint $endpoint)
301
    {
302 1
        $forbiddenTypes = [];
303 1
        foreach ($endpoint->allTypes() as $type) {
304 1
            if (!$this->isGranted($endpoint, $type)) {
305 1
                $forbiddenTypes[] = $type->getName();
306
            }
307
        }
308
309 1
        return $forbiddenTypes;
310
    }
311
312 1
    protected function isGranted(Endpoint $endpoint, DefinitionInterface $definition)
313
    {
314 1
        $endpoints = $this->normalizeConfig($definition, $definition->getMeta('endpoints', []));
315 1
        if ($endpoints) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $endpoints of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
316 1
            $endpoints = $this->endpointsAliasToRealNames($endpoints);
317
        }
318
319 1
        return empty($endpoints) || \in_array($endpoint->getName(), $endpoints);
320
    }
321
322
    /**
323
     * Given array of endpoints (containing alias) return the array of specific endpoints (without aliases)
324
     *
325
     * ["all"] => ["admin", "frontend"]
326
     *
327
     * @param array $endpoints
328
     *
329
     * @return array
330
     */
331 1
    protected function endpointsAliasToRealNames($endpoints)
332
    {
333 1
        foreach ($endpoints as $index => $endpointName) {
334 1
            foreach ($this->endpointAlias as $alias => $targets) {
335 1
                if ($alias === $endpointName) {
336 1
                    $targets = $this->endpointsAliasToRealNames($targets);
337 1
                    unset($endpoints[$index]);
338 1
                    $endpoints = array_merge($endpoints, $targets);
339
                }
340
            }
341
        }
342
343 1
        return $endpoints;
344
    }
345
}
346