Failed Conditions
Push — master ( e31947...cc39b3 )
by Šimon
11s
created

QuerySecurityRule::getFragments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Validator\Rules;
6
7
use Closure;
8
use GraphQL\Language\AST\FieldNode;
9
use GraphQL\Language\AST\FragmentDefinitionNode;
10
use GraphQL\Language\AST\FragmentSpreadNode;
11
use GraphQL\Language\AST\NodeKind;
12
use GraphQL\Language\AST\SelectionSetNode;
13
use GraphQL\Type\Definition\Type;
14
use GraphQL\Type\Introspection;
15
use GraphQL\Utils\TypeInfo;
16
use GraphQL\Validator\ValidationContext;
17
use function class_alias;
18
use function method_exists;
19
use function sprintf;
20
21
abstract class QuerySecurityRule extends ValidationRule
22
{
23
    public const DISABLED = 0;
24
25
    /** @var FragmentDefinitionNode[] */
26
    private $fragments = [];
27
28
    /**
29
     * check if equal to 0 no check is done. Must be greater or equal to 0.
30
     *
31
     * @param string $name
32
     * @param int    $value
33
     */
34 55
    protected function checkIfGreaterOrEqualToZero($name, $value)
35
    {
36 55
        if ($value < 0) {
37 2
            throw new \InvalidArgumentException(sprintf('$%s argument must be greater or equal to 0.', $name));
38
        }
39 53
    }
40
41 13
    protected function getFragment(FragmentSpreadNode $fragmentSpread)
42
    {
43 13
        $spreadName = $fragmentSpread->name->value;
44 13
        $fragments  = $this->getFragments();
45
46 13
        return $fragments[$spreadName] ?? null;
47
    }
48
49
    /**
50
     * @return FragmentDefinitionNode[]
51
     */
52 13
    protected function getFragments()
53
    {
54 13
        return $this->fragments;
55
    }
56
57
    /**
58
     * @param Closure[] $validators
59
     * @return Closure[]
60
     */
61 160
    protected function invokeIfNeeded(ValidationContext $context, array $validators)
62
    {
63
        // is disabled?
64 160
        if (! $this->isEnabled()) {
65 119
            return [];
66
        }
67
68 53
        $this->gatherFragmentDefinition($context);
69
70 53
        return $validators;
71
    }
72
73
    abstract protected function isEnabled();
74
75 53
    protected function gatherFragmentDefinition(ValidationContext $context)
76
    {
77
        // Gather all the fragment definition.
78
        // Importantly this does not include inline fragments.
79 53
        $definitions = $context->getDocument()->definitions;
80 53
        foreach ($definitions as $node) {
81 53
            if (! ($node instanceof FragmentDefinitionNode)) {
82 53
                continue;
83
            }
84
85 13
            $this->fragments[$node->name->value] = $node;
86
        }
87 53
    }
88
89
    /**
90
     * Given a selectionSet, adds all of the fields in that selection to
91
     * the passed in map of fields, and returns it at the end.
92
     *
93
     * Note: This is not the same as execution's collectFields because at static
94
     * time we do not know what object type will be used, so we unconditionally
95
     * spread in all fragments.
96
     *
97
     * @see \GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged
98
     *
99
     * @param Type|null $parentType
100
     *
101
     * @return \ArrayObject
102
     */
103 16
    protected function collectFieldASTsAndDefs(
104
        ValidationContext $context,
105
        $parentType,
106
        SelectionSetNode $selectionSet,
107
        ?\ArrayObject $visitedFragmentNames = null,
108
        ?\ArrayObject $astAndDefs = null
109
    ) {
110 16
        $_visitedFragmentNames = $visitedFragmentNames ?: new \ArrayObject();
111 16
        $_astAndDefs           = $astAndDefs ?: new \ArrayObject();
112
113 16
        foreach ($selectionSet->selections as $selection) {
114 16
            switch ($selection->getKind()) {
115 16
                case NodeKind::FIELD:
116
                    /** @var FieldNode $selection */
117 16
                    $fieldName = $selection->name->value;
118 16
                    $fieldDef  = null;
119 16
                    if ($parentType && method_exists($parentType, 'getFields')) {
120 16
                        $tmp                  = $parentType->getFields();
121 16
                        $schemaMetaFieldDef   = Introspection::schemaMetaFieldDef();
122 16
                        $typeMetaFieldDef     = Introspection::typeMetaFieldDef();
123 16
                        $typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef();
124
125 16
                        if ($fieldName === $schemaMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) {
126 1
                            $fieldDef = $schemaMetaFieldDef;
127 16
                        } elseif ($fieldName === $typeMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) {
128 1
                            $fieldDef = $typeMetaFieldDef;
129 16
                        } elseif ($fieldName === $typeNameMetaFieldDef->name) {
130 1
                            $fieldDef = $typeNameMetaFieldDef;
131 16
                        } elseif (isset($tmp[$fieldName])) {
132 16
                            $fieldDef = $tmp[$fieldName];
133
                        }
134
                    }
135 16
                    $responseName = $this->getFieldName($selection);
136 16
                    if (! isset($_astAndDefs[$responseName])) {
137 16
                        $_astAndDefs[$responseName] = new \ArrayObject();
138
                    }
139
                    // create field context
140 16
                    $_astAndDefs[$responseName][] = [$selection, $fieldDef];
141 16
                    break;
142 3
                case NodeKind::INLINE_FRAGMENT:
143
                    /** @var InlineFragmentNode $selection */
144 1
                    $_astAndDefs = $this->collectFieldASTsAndDefs(
145 1
                        $context,
146 1
                        TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition),
147 1
                        $selection->selectionSet,
148 1
                        $_visitedFragmentNames,
149 1
                        $_astAndDefs
150
                    );
151 1
                    break;
152 2
                case NodeKind::FRAGMENT_SPREAD:
153
                    /** @var FragmentSpreadNode $selection */
154 2
                    $fragName = $selection->name->value;
155
156 2
                    if (empty($_visitedFragmentNames[$fragName])) {
157 2
                        $_visitedFragmentNames[$fragName] = true;
158 2
                        $fragment                         = $context->getFragment($fragName);
159
160 2
                        if ($fragment) {
161 2
                            $_astAndDefs = $this->collectFieldASTsAndDefs(
162 2
                                $context,
163 2
                                TypeInfo::typeFromAST($context->getSchema(), $fragment->typeCondition),
164 2
                                $fragment->selectionSet,
165 2
                                $_visitedFragmentNames,
166 2
                                $_astAndDefs
167
                            );
168
                        }
169
                    }
170 16
                    break;
171
            }
172
        }
173
174 16
        return $_astAndDefs;
175
    }
176
177 16
    protected function getFieldName(FieldNode $node)
178
    {
179 16
        $fieldName = $node->name->value;
180
181 16
        return $node->alias ? $node->alias->value : $fieldName;
182
    }
183
}
184
185
class_alias(QuerySecurityRule::class, 'GraphQL\Validator\Rules\AbstractQuerySecurity');
186