Completed
Push — master ( 7a107a...5cd32c )
by Rafael
04:36
created

EndpointsDefinitionPlugin::secureOperations()   D

Complexity

Conditions 13
Paths 162

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 13.9292

Importance

Changes 0
Metric Value
dl 0
loc 28
c 0
b 0
f 0
ccs 14
cts 17
cp 0.8235
rs 4.8178
cc 13
eloc 17
nc 162
nop 3
crap 13.9292

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\Registry\DefinitionRegistry;
24
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
25
use Ynlo\GraphQLBundle\Model\NodeInterface;
26
27
class EndpointsDefinitionPlugin extends AbstractDefinitionPlugin
28
{
29
    /**
30
     * @var array
31
     */
32
    private $endpointAlias = [];
33
34
    /**
35
     * @var string
36
     */
37
    private $endpointDefault;
38
39
40
    /**
41
     * EndpointsDefinitionPlugin constructor.
42
     *
43
     * @param array $endpointsConfig
44
     */
45 1
    public function __construct(array $endpointsConfig)
46
    {
47 1
        $this->endpointAlias = $endpointsConfig['alias'] ?? [];
48 1
        $this->endpointDefault = $endpointsConfig['default'] ?? null;
49 1
    }
50
51
    /**
52
     * {@inheritDoc}
53
     */
54 1
    public function buildConfig(ArrayNodeDefinition $root): void
55
    {
56
        $root
57 1
            ->info('List of endpoints for queries and mutations')
58 1
            ->scalarPrototype();
59 1
    }
60
61
    /**
62
     * {@inheritDoc}
63
     */
64 1
    public function normalizeConfig(DefinitionInterface $definition, $config): array
65
    {
66 1
        $endpoints = $config['endpoints'] ?? [];
67
68
        //allow set only one endpoint in a simple string
69 1
        if (\is_string($endpoints)) {
70 1
            $endpoints = [$endpoints];
71
        }
72
73 1
        return $endpoints;
74
    }
75
76
    /**
77
     * {@inheritDoc}
78
     */
79 1
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void
80
    {
81 1
        if ($endpoint->getName() === DefinitionRegistry::DEFAULT_ENDPOINT) {
82 1
            return;
83
        }
84
85
        //ignore safe operations
86 1
        if (in_array($definition->getName(), ['node', 'nodes'])) {
87 1
            return;
88
        }
89
90
        //apply default endpoint to all operations
91 1
        $endpoints = $this->normalizeConfig($definition, $definition->getMeta('endpoints'));
92 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...
93 1
            if ($definition instanceof ExecutableDefinitionInterface
94 1
                || ($definition instanceof ClassAwareDefinitionInterface
95 1
                    && is_subclass_of($definition->getClass(), NodeInterface::class, true))
96
            ) {
97 1
                $definition->setMeta('endpoints', ['endpoints' => [$this->endpointDefault]]);
98
            }
99
        }
100 1
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105 1
    public function configureEndpoint(Endpoint $endpoint): void
106
    {
107 1
        if ($endpoint->getName() === DefinitionRegistry::DEFAULT_ENDPOINT) {
108 1
            return;
109
        }
110
111 1
        $forbiddenTypes = $this->getForbiddenTypes($endpoint);
112 1
        $this->processForbiddenTypes($endpoint, $forbiddenTypes);
113 1
    }
114
115 1
    protected function processForbiddenTypes(Endpoint $endpoint, $forbiddenTypes)
116
    {
117 1
        foreach ($endpoint->allQueries() as $queries) {
118 1
            $this->secureOperations($endpoint, $queries, $forbiddenTypes);
119
        }
120
121 1
        foreach ($endpoint->allMutations() as $mutations) {
122 1
            $this->secureOperations($endpoint, $mutations, $forbiddenTypes);
123
        }
124
125 1
        foreach ($endpoint->allTypes() as $type) {
126
            //remove implementations of forbidden interfaces
127 1
            if ($type instanceof ImplementorInterface) {
128 1
                foreach ($type->getInterfaces() as $interface) {
129 1
                    if (in_array($interface, $forbiddenTypes)) {
130 1
                        $type->removeInterface($interface);
131
                    }
132
                }
133
            }
134
            //remove fields related to forbidden interfaces
135 1
            if ($type instanceof FieldsAwareDefinitionInterface) {
136 1
                if ($type->getFields()) {
137 1
                    foreach ($type->getFields() as $field) {
138 1
                        $fieldType = $endpoint->hasType($field->getType()) ? $endpoint->getType($field->getType()) : null;
139 1
                        $fieldNodeType = $endpoint->hasType($field->getMeta('node')) ? $endpoint->getType($field->getMeta('node')) : null;
140 1
                        if (($fieldType && in_array($fieldType->getName(), $forbiddenTypes))
141 1
                            || ($fieldNodeType && in_array($fieldNodeType->getName(), $forbiddenTypes))) {
142 1
                            $type->removeField($field->getName());
143
                        }
144
                    }
145
146
                    //after delete fields related to forbidden objects,
147
                    //verify if the object has at least one field
148
                    //otherwise mark this type as forbidden
149 1
                    if (!$type->getFields()) {
150
                        $forbiddenTypes[] = $type->getName();
151 1
                        $this->processForbiddenTypes($endpoint, $forbiddenTypes);
152
                    }
153
                }
154
            }
155
        }
156
157
        /** @var InterfaceDefinition $type */
158 1
        foreach ($endpoint->allInterfaces() as $type) {
159 1
            if ($type->getImplementors()) {
160 1
                foreach ($type->getImplementors() as $implementor) {
161 1
                    if (in_array($implementor, $forbiddenTypes)) {
162 1
                        $type->removeImplementor($implementor);
163
                    }
164
                }
165
166
                //after delete forbidden implementors
167
                //verify if the interface has at least one implementor
168
                //otherwise mark this interface as forbidden
169 1
                if (!$type->getImplementors()) {
170
                    $forbiddenTypes[] = $type->getName();
171 1
                    $this->processForbiddenTypes($endpoint, $forbiddenTypes);
172
                }
173
            }
174
        }
175
176 1
        foreach ($forbiddenTypes as $type) {
177
            $endpoint->removeType($type);
178
        }
179 1
    }
180
181
    /**
182
     * Remove
183
     *
184
     * @param Endpoint                      $endpoint
185
     * @param ExecutableDefinitionInterface $executableDefinition
186
     * @param array|string[]                $forbiddenTypes
187
     */
188 1
    protected function secureOperations(Endpoint $endpoint, ExecutableDefinitionInterface $executableDefinition, $forbiddenTypes)
189
    {
190 1
        $type = $endpoint->hasType($executableDefinition->getType()) ? $endpoint->getType($executableDefinition->getType()) : null;
191
192 1
        $node = null;
193
        //resolve the related node using interface
194 1
        if ($executableDefinition instanceof NodeAwareDefinitionInterface) {
0 ignored issues
show
introduced by
$executableDefinition is always a sub-type of Ynlo\GraphQLBundle\Defin...wareDefinitionInterface.
Loading history...
195 1
            $node = $endpoint->hasType($executableDefinition->getNode()) ? $endpoint->getType($executableDefinition->getNode()) : null;
196
        }
197
198
        //resolve related node using metadata
199 1
        if (!$node) {
200
            $node = $endpoint->hasType($executableDefinition->getMeta('node')) ? $endpoint->getType($executableDefinition->getMeta('node')) : null;
201
        }
202
203 1
        $granted = true;
204 1
        if (($type && in_array($type->getName(), $forbiddenTypes))
205 1
            || ($node && in_array($node->getName(), $forbiddenTypes))) {
206
            $granted = false;
207 1
        } elseif (!$this->isGranted($endpoint, $executableDefinition)) {
208 1
            $granted = false;
209
        }
210
211 1
        if (!$granted) {
212 1
            if ($executableDefinition instanceof MutationDefinition) {
213
                $endpoint->removeMutation($executableDefinition->getName());
214
            } else {
215 1
                $endpoint->removeQuery($executableDefinition->getName());
216
            }
217
        }
218 1
    }
219
220 1
    protected function getForbiddenTypes(Endpoint $endpoint)
221
    {
222 1
        $forbiddenTypes = [];
223 1
        foreach ($endpoint->allTypes() as $type) {
224 1
            if (!$this->isGranted($endpoint, $type)) {
225 1
                $forbiddenTypes[] = $type->getName();
226
            }
227
        }
228
229 1
        return $forbiddenTypes;
230
    }
231
232 1
    protected function isGranted(Endpoint $endpoint, DefinitionInterface $definition)
233
    {
234 1
        $endpoints = $this->normalizeConfig($definition, $definition->getMeta('endpoints', []));
235 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...
236 1
            foreach ($endpoints as $index => $endpointName) {
237 1
                foreach ($this->endpointAlias as $alias => $targets) {
238
                    if ($alias === $endpointName) {
239
                        unset($endpoints[$index]);
240 1
                        $endpoints = array_merge($endpoints, $targets);
241
                    }
242
                }
243
            }
244
        }
245
246 1
        return empty($endpoints) || \in_array($endpoint->getName(), $endpoints);
247
    }
248
}
249