mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 14:26:08 +03:00
Merge pull request #32 from mcg-web/add_query_security_rules
Add query security document validation rules
This commit is contained in:
commit
36a845499c
36
README.md
36
README.md
@ -462,6 +462,42 @@ header('Content-Type: application/json');
|
|||||||
echo json_encode($result);
|
echo json_encode($result);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
#### Query Complexity Analysis
|
||||||
|
|
||||||
|
This is a PHP port of [Query Complexity Analysis](http://sangria-graphql.org/learn/#query-complexity-analysis) in Sangria implementation.
|
||||||
|
Introspection query with description max complexity is **109**.
|
||||||
|
|
||||||
|
This document validator rule is disabled by default. Here an example to enabled it:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
|
||||||
|
/** @var \GraphQL\Validator\Rules\QueryComplexity $queryComplexity */
|
||||||
|
$queryComplexity = DocumentValidator::getRule('QueryComplexity');
|
||||||
|
$queryComplexity->setMaxQueryComplexity($maxQueryComplexity = 110);
|
||||||
|
|
||||||
|
GraphQL::execute(/*...*/);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Limiting Query Depth
|
||||||
|
|
||||||
|
This is a PHP port of [Limiting Query Depth](http://sangria-graphql.org/learn/#limiting-query-depth) in Sangria implementation.
|
||||||
|
Introspection query with description max depth is **7**.
|
||||||
|
|
||||||
|
This document validator rule is disabled by default. Here an example to enabled it:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
|
||||||
|
/** @var \GraphQL\Validator\Rules\QueryDepth $queryDepth */
|
||||||
|
$queryDepth = DocumentValidator::getRule('QueryDepth');
|
||||||
|
$queryDepth->setMaxQueryDepth($maxQueryDepth = 10);
|
||||||
|
|
||||||
|
GraphQL::execute(/*...*/);
|
||||||
|
```
|
||||||
|
|
||||||
### More Examples
|
### More Examples
|
||||||
Make sure to check [tests](https://github.com/webonyx/graphql-php/tree/master/tests) for more usage examples.
|
Make sure to check [tests](https://github.com/webonyx/graphql-php/tree/master/tests) for more usage examples.
|
||||||
|
|
||||||
|
@ -18,15 +18,13 @@
|
|||||||
"bin-dir": "bin"
|
"bin-dir": "bin"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": [
|
"psr-4": {
|
||||||
"src/"
|
"GraphQL\\": "src/"
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"classmap": [
|
"psr-4": {
|
||||||
"tests/"
|
"GraphQL\\Tests\\": "tests/"
|
||||||
],
|
}
|
||||||
"files": [
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
phpunit.xml.dist
Normal file
34
phpunit.xml.dist
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
syntaxCheck="false"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="webonyx/graphql-php Test Suite">
|
||||||
|
<directory>./tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory>./</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory>./tests</directory>
|
||||||
|
<directory>./vendor</directory>
|
||||||
|
</exclude>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<php>
|
||||||
|
<ini name="error_reporting" value="E_ALL"/>
|
||||||
|
</php>
|
||||||
|
|
||||||
|
</phpunit>
|
@ -6,6 +6,7 @@ use GraphQL\Executor\Executor;
|
|||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
|
|
||||||
class GraphQL
|
class GraphQL
|
||||||
{
|
{
|
||||||
@ -35,6 +36,11 @@ class GraphQL
|
|||||||
try {
|
try {
|
||||||
$source = new Source($requestString ?: '', 'GraphQL request');
|
$source = new Source($requestString ?: '', 'GraphQL request');
|
||||||
$documentAST = Parser::parse($source);
|
$documentAST = Parser::parse($source);
|
||||||
|
|
||||||
|
/** @var QueryComplexity $queryComplexity */
|
||||||
|
$queryComplexity = DocumentValidator::getRule('QueryComplexity');
|
||||||
|
$queryComplexity->setRawVariableValues($variableValues);
|
||||||
|
|
||||||
$validationErrors = DocumentValidator::validate($schema, $documentAST);
|
$validationErrors = DocumentValidator::validate($schema, $documentAST);
|
||||||
|
|
||||||
if (!empty($validationErrors)) {
|
if (!empty($validationErrors)) {
|
||||||
|
@ -5,6 +5,8 @@ use GraphQL\Utils;
|
|||||||
|
|
||||||
class FieldDefinition
|
class FieldDefinition
|
||||||
{
|
{
|
||||||
|
const DEFAULT_COMPLEXITY_FN = 'GraphQL\Type\Definition\FieldDefinition::defaultComplexity';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@ -72,6 +74,7 @@ class FieldDefinition
|
|||||||
'map' => Config::CALLBACK,
|
'map' => Config::CALLBACK,
|
||||||
'description' => Config::STRING,
|
'description' => Config::STRING,
|
||||||
'deprecationReason' => Config::STRING,
|
'deprecationReason' => Config::STRING,
|
||||||
|
'complexity' => Config::CALLBACK,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +116,8 @@ class FieldDefinition
|
|||||||
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
|
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
|
||||||
|
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
|
||||||
|
$this->complexityFn = isset($config['complexity']) ? $config['complexity'] : static::DEFAULT_COMPLEXITY_FN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,4 +146,17 @@ class FieldDefinition
|
|||||||
}
|
}
|
||||||
return $this->resolvedType;
|
return $this->resolvedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return callable|\Closure
|
||||||
|
*/
|
||||||
|
public function getComplexityFn()
|
||||||
|
{
|
||||||
|
return $this->complexityFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function defaultComplexity($childrenComplexity)
|
||||||
|
{
|
||||||
|
return $childrenComplexity + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,59 +34,91 @@ use GraphQL\Validator\Rules\NoUnusedVariables;
|
|||||||
use GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged;
|
use GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged;
|
||||||
use GraphQL\Validator\Rules\PossibleFragmentSpreads;
|
use GraphQL\Validator\Rules\PossibleFragmentSpreads;
|
||||||
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
||||||
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
|
use GraphQL\Validator\Rules\QueryDepth;
|
||||||
use GraphQL\Validator\Rules\ScalarLeafs;
|
use GraphQL\Validator\Rules\ScalarLeafs;
|
||||||
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
||||||
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
||||||
|
|
||||||
class DocumentValidator
|
class DocumentValidator
|
||||||
{
|
{
|
||||||
private static $allRules;
|
private static $rules = [];
|
||||||
|
|
||||||
static function allRules()
|
private static $defaultRules;
|
||||||
|
|
||||||
|
private static $initRules = false;
|
||||||
|
|
||||||
|
public static function allRules()
|
||||||
{
|
{
|
||||||
if (null === self::$allRules) {
|
if (!self::$initRules) {
|
||||||
self::$allRules = [
|
self::$rules = array_merge(static::defaultRules(), self::$rules);
|
||||||
|
self::$initRules = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function defaultRules()
|
||||||
|
{
|
||||||
|
if (null === self::$defaultRules) {
|
||||||
|
self::$defaultRules = [
|
||||||
// new UniqueOperationNames,
|
// new UniqueOperationNames,
|
||||||
// new LoneAnonymousOperation,
|
// new LoneAnonymousOperation,
|
||||||
new KnownTypeNames,
|
'KnownTypeNames' => new KnownTypeNames(),
|
||||||
new FragmentsOnCompositeTypes,
|
'FragmentsOnCompositeTypes' => new FragmentsOnCompositeTypes(),
|
||||||
new VariablesAreInputTypes,
|
'VariablesAreInputTypes' => new VariablesAreInputTypes(),
|
||||||
new ScalarLeafs,
|
'ScalarLeafs' => new ScalarLeafs(),
|
||||||
new FieldsOnCorrectType,
|
'FieldsOnCorrectType' => new FieldsOnCorrectType(),
|
||||||
// new UniqueFragmentNames,
|
// new UniqueFragmentNames,
|
||||||
new KnownFragmentNames,
|
'KnownFragmentNames' => new KnownFragmentNames(),
|
||||||
new NoUnusedFragments,
|
'NoUnusedFragments' => new NoUnusedFragments(),
|
||||||
new PossibleFragmentSpreads,
|
'PossibleFragmentSpreads' => new PossibleFragmentSpreads(),
|
||||||
new NoFragmentCycles,
|
'NoFragmentCycles' => new NoFragmentCycles(),
|
||||||
new NoUndefinedVariables,
|
'NoUndefinedVariables' => new NoUndefinedVariables(),
|
||||||
new NoUnusedVariables,
|
'NoUnusedVariables' => new NoUnusedVariables(),
|
||||||
new KnownDirectives,
|
'KnownDirectives' => new KnownDirectives(),
|
||||||
new KnownArgumentNames,
|
'KnownArgumentNames' => new KnownArgumentNames(),
|
||||||
// new UniqueArgumentNames,
|
// new UniqueArgumentNames,
|
||||||
new ArgumentsOfCorrectType,
|
'ArgumentsOfCorrectType' => new ArgumentsOfCorrectType(),
|
||||||
new ProvidedNonNullArguments,
|
'ProvidedNonNullArguments' => new ProvidedNonNullArguments(),
|
||||||
new DefaultValuesOfCorrectType,
|
'DefaultValuesOfCorrectType' => new DefaultValuesOfCorrectType(),
|
||||||
new VariablesInAllowedPosition,
|
'VariablesInAllowedPosition' => new VariablesInAllowedPosition(),
|
||||||
new OverlappingFieldsCanBeMerged,
|
'OverlappingFieldsCanBeMerged' => new OverlappingFieldsCanBeMerged(),
|
||||||
|
// Query Security
|
||||||
|
'QueryDepth' => new QueryDepth(QueryDepth::DISABLED), // default disabled
|
||||||
|
'QueryComplexity' => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return self::$allRules;
|
|
||||||
|
return self::$defaultRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRule($name)
|
||||||
|
{
|
||||||
|
$rules = static::allRules();
|
||||||
|
|
||||||
|
return isset($rules[$name]) ? $rules[$name] : null ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function addRule($name, callable $rule)
|
||||||
|
{
|
||||||
|
self::$rules[$name] = $rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function validate(Schema $schema, Document $ast, array $rules = null)
|
public static function validate(Schema $schema, Document $ast, array $rules = null)
|
||||||
{
|
{
|
||||||
$errors = self::visitUsingRules($schema, $ast, $rules ?: self::allRules());
|
$errors = static::visitUsingRules($schema, $ast, $rules ?: static::allRules());
|
||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function isError($value)
|
public static function isError($value)
|
||||||
{
|
{
|
||||||
return is_array($value)
|
return is_array($value)
|
||||||
? count(array_filter($value, function($item) { return $item instanceof \Exception;})) === count($value)
|
? count(array_filter($value, function($item) { return $item instanceof \Exception;})) === count($value)
|
||||||
: $value instanceof \Exception;
|
: $value instanceof \Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function append(&$arr, $items)
|
public static function append(&$arr, $items)
|
||||||
{
|
{
|
||||||
if (is_array($items)) {
|
if (is_array($items)) {
|
||||||
$arr = array_merge($arr, $items);
|
$arr = array_merge($arr, $items);
|
||||||
@ -96,7 +128,7 @@ class DocumentValidator
|
|||||||
return $arr;
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function isValidLiteralValue($valueAST, Type $type)
|
public static function isValidLiteralValue($valueAST, Type $type)
|
||||||
{
|
{
|
||||||
// A value can only be not provided if the type is nullable.
|
// A value can only be not provided if the type is nullable.
|
||||||
if (!$valueAST) {
|
if (!$valueAST) {
|
||||||
@ -105,7 +137,7 @@ class DocumentValidator
|
|||||||
|
|
||||||
// Unwrap non-null.
|
// Unwrap non-null.
|
||||||
if ($type instanceof NonNull) {
|
if ($type instanceof NonNull) {
|
||||||
return self::isValidLiteralValue($valueAST, $type->getWrappedType());
|
return static::isValidLiteralValue($valueAST, $type->getWrappedType());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function only tests literals, and assumes variables will provide
|
// This function only tests literals, and assumes variables will provide
|
||||||
@ -123,13 +155,13 @@ class DocumentValidator
|
|||||||
$itemType = $type->getWrappedType();
|
$itemType = $type->getWrappedType();
|
||||||
if ($valueAST instanceof ListValue) {
|
if ($valueAST instanceof ListValue) {
|
||||||
foreach($valueAST->values as $itemAST) {
|
foreach($valueAST->values as $itemAST) {
|
||||||
if (!self::isValidLiteralValue($itemAST, $itemType)) {
|
if (!static::isValidLiteralValue($itemAST, $itemType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return self::isValidLiteralValue($valueAST, $itemType);
|
return static::isValidLiteralValue($valueAST, $itemType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +189,7 @@ class DocumentValidator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($fieldASTs as $fieldAST) {
|
foreach ($fieldASTs as $fieldAST) {
|
||||||
if (empty($fields[$fieldAST->name->value]) || !self::isValidLiteralValue($fieldAST->value, $fields[$fieldAST->name->value]->getType())) {
|
if (empty($fields[$fieldAST->name->value]) || !static::isValidLiteralValue($fieldAST->value, $fields[$fieldAST->name->value]->getType())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,8 +263,8 @@ class DocumentValidator
|
|||||||
} else if ($result->doBreak) {
|
} else if ($result->doBreak) {
|
||||||
$instances[$i] = null;
|
$instances[$i] = null;
|
||||||
}
|
}
|
||||||
} else if ($result && self::isError($result)) {
|
} else if ($result && static::isError($result)) {
|
||||||
self::append($errors, $result);
|
static::append($errors, $result);
|
||||||
for ($j = $i - 1; $j >= 0; $j--) {
|
for ($j = $i - 1; $j >= 0; $j--) {
|
||||||
$leaveFn = Visitor::getVisitFn($instances[$j], true, $node->kind);
|
$leaveFn = Visitor::getVisitFn($instances[$j], true, $node->kind);
|
||||||
if ($leaveFn) {
|
if ($leaveFn) {
|
||||||
@ -243,8 +275,8 @@ class DocumentValidator
|
|||||||
if ($result->doBreak) {
|
if ($result->doBreak) {
|
||||||
$instances[$j] = null;
|
$instances[$j] = null;
|
||||||
}
|
}
|
||||||
} else if (self::isError($result)) {
|
} else if (static::isError($result)) {
|
||||||
self::append($errors, $result);
|
static::append($errors, $result);
|
||||||
} else if ($result !== null) {
|
} else if ($result !== null) {
|
||||||
throw new \Exception("Config cannot edit document.");
|
throw new \Exception("Config cannot edit document.");
|
||||||
}
|
}
|
||||||
@ -294,8 +326,8 @@ class DocumentValidator
|
|||||||
if ($result->doBreak) {
|
if ($result->doBreak) {
|
||||||
$instances[$i] = null;
|
$instances[$i] = null;
|
||||||
}
|
}
|
||||||
} else if (self::isError($result)) {
|
} else if (static::isError($result)) {
|
||||||
self::append($errors, $result);
|
static::append($errors, $result);
|
||||||
} else if ($result !== null) {
|
} else if ($result !== null) {
|
||||||
throw new \Exception("Config cannot edit document.");
|
throw new \Exception("Config cannot edit document.");
|
||||||
}
|
}
|
||||||
@ -309,7 +341,7 @@ class DocumentValidator
|
|||||||
// Visit the whole document with instances of all provided rules.
|
// Visit the whole document with instances of all provided rules.
|
||||||
$allRuleInstances = [];
|
$allRuleInstances = [];
|
||||||
foreach ($rules as $rule) {
|
foreach ($rules as $rule) {
|
||||||
$allRuleInstances[] = $rule($context);
|
$allRuleInstances[] = call_user_func_array($rule, [$context]);
|
||||||
}
|
}
|
||||||
$visitInstances($documentAST, $allRuleInstances);
|
$visitInstances($documentAST, $allRuleInstances);
|
||||||
|
|
||||||
|
171
src/Validator/Rules/AbstractQuerySecurity.php
Normal file
171
src/Validator/Rules/AbstractQuerySecurity.php
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
|
use GraphQL\Language\AST\Field;
|
||||||
|
use GraphQL\Language\AST\FragmentDefinition;
|
||||||
|
use GraphQL\Language\AST\FragmentSpread;
|
||||||
|
use GraphQL\Language\AST\InlineFragment;
|
||||||
|
use GraphQL\Language\AST\Node;
|
||||||
|
use GraphQL\Language\AST\SelectionSet;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Introspection;
|
||||||
|
use GraphQL\Utils\TypeInfo;
|
||||||
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
|
||||||
|
abstract class AbstractQuerySecurity
|
||||||
|
{
|
||||||
|
const DISABLED = 0;
|
||||||
|
|
||||||
|
/** @var FragmentDefinition[] */
|
||||||
|
private $fragments = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \GraphQL\Language\AST\FragmentDefinition[]
|
||||||
|
*/
|
||||||
|
protected function getFragments()
|
||||||
|
{
|
||||||
|
return $this->fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if equal to 0 no check is done. Must be greater or equal to 0.
|
||||||
|
*
|
||||||
|
* @param $value
|
||||||
|
*/
|
||||||
|
protected function checkIfGreaterOrEqualToZero($name, $value)
|
||||||
|
{
|
||||||
|
if ($value < 0) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('$%s argument must be greater or equal to 0.', $name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function gatherFragmentDefinition(ValidationContext $context)
|
||||||
|
{
|
||||||
|
// Gather all the fragment definition.
|
||||||
|
// Importantly this does not include inline fragments.
|
||||||
|
$definitions = $context->getDocument()->definitions;
|
||||||
|
foreach ($definitions as $node) {
|
||||||
|
if ($node instanceof FragmentDefinition) {
|
||||||
|
$this->fragments[$node->name->value] = $node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFragment(FragmentSpread $fragmentSpread)
|
||||||
|
{
|
||||||
|
$spreadName = $fragmentSpread->name->value;
|
||||||
|
$fragments = $this->getFragments();
|
||||||
|
|
||||||
|
return isset($fragments[$spreadName]) ? $fragments[$spreadName] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function invokeIfNeeded(ValidationContext $context, array $validators)
|
||||||
|
{
|
||||||
|
// is disabled?
|
||||||
|
if (!$this->isEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->gatherFragmentDefinition($context);
|
||||||
|
|
||||||
|
return $validators;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a selectionSet, adds all of the fields in that selection to
|
||||||
|
* the passed in map of fields, and returns it at the end.
|
||||||
|
*
|
||||||
|
* Note: This is not the same as execution's collectFields because at static
|
||||||
|
* time we do not know what object type will be used, so we unconditionally
|
||||||
|
* spread in all fragments.
|
||||||
|
*
|
||||||
|
* @see GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged
|
||||||
|
*
|
||||||
|
* @param ValidationContext $context
|
||||||
|
* @param Type|null $parentType
|
||||||
|
* @param SelectionSet $selectionSet
|
||||||
|
* @param \ArrayObject $visitedFragmentNames
|
||||||
|
* @param \ArrayObject $astAndDefs
|
||||||
|
*
|
||||||
|
* @return \ArrayObject
|
||||||
|
*/
|
||||||
|
protected function collectFieldASTsAndDefs(ValidationContext $context, $parentType, SelectionSet $selectionSet, \ArrayObject $visitedFragmentNames = null, \ArrayObject $astAndDefs = null)
|
||||||
|
{
|
||||||
|
$_visitedFragmentNames = $visitedFragmentNames ?: new \ArrayObject();
|
||||||
|
$_astAndDefs = $astAndDefs ?: new \ArrayObject();
|
||||||
|
|
||||||
|
foreach ($selectionSet->selections as $selection) {
|
||||||
|
switch ($selection->kind) {
|
||||||
|
case Node::FIELD:
|
||||||
|
/* @var Field $selection */
|
||||||
|
$fieldName = $selection->name->value;
|
||||||
|
$fieldDef = null;
|
||||||
|
if ($parentType && method_exists($parentType, 'getFields')) {
|
||||||
|
$tmp = $parentType->getFields();
|
||||||
|
$schemaMetaFieldDef = Introspection::schemaMetaFieldDef();
|
||||||
|
$typeMetaFieldDef = Introspection::typeMetaFieldDef();
|
||||||
|
$typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef();
|
||||||
|
|
||||||
|
if ($fieldName === $schemaMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) {
|
||||||
|
$fieldDef = $schemaMetaFieldDef;
|
||||||
|
} elseif ($fieldName === $typeMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) {
|
||||||
|
$fieldDef = $typeMetaFieldDef;
|
||||||
|
} elseif ($fieldName === $typeNameMetaFieldDef->name) {
|
||||||
|
$fieldDef = $typeNameMetaFieldDef;
|
||||||
|
} elseif (isset($tmp[$fieldName])) {
|
||||||
|
$fieldDef = $tmp[$fieldName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$responseName = $this->getFieldName($selection);
|
||||||
|
if (!isset($_astAndDefs[$responseName])) {
|
||||||
|
$_astAndDefs[$responseName] = new \ArrayObject();
|
||||||
|
}
|
||||||
|
// create field context
|
||||||
|
$_astAndDefs[$responseName][] = [$selection, $fieldDef];
|
||||||
|
break;
|
||||||
|
case Node::INLINE_FRAGMENT:
|
||||||
|
/* @var InlineFragment $selection */
|
||||||
|
$_astAndDefs = $this->collectFieldASTsAndDefs(
|
||||||
|
$context,
|
||||||
|
TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition),
|
||||||
|
$selection->selectionSet,
|
||||||
|
$_visitedFragmentNames,
|
||||||
|
$_astAndDefs
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Node::FRAGMENT_SPREAD:
|
||||||
|
/* @var FragmentSpread $selection */
|
||||||
|
$fragName = $selection->name->value;
|
||||||
|
|
||||||
|
if (empty($_visitedFragmentNames[$fragName])) {
|
||||||
|
$_visitedFragmentNames[$fragName] = true;
|
||||||
|
$fragment = $context->getFragment($fragName);
|
||||||
|
|
||||||
|
if ($fragment) {
|
||||||
|
$_astAndDefs = $this->collectFieldASTsAndDefs(
|
||||||
|
$context,
|
||||||
|
TypeInfo::typeFromAST($context->getSchema(), $fragment->typeCondition),
|
||||||
|
$fragment->selectionSet,
|
||||||
|
$_visitedFragmentNames,
|
||||||
|
$_astAndDefs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $_astAndDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFieldName(Field $node)
|
||||||
|
{
|
||||||
|
$fieldName = $node->name->value;
|
||||||
|
$responseName = $node->alias ? $node->alias->value : $fieldName;
|
||||||
|
|
||||||
|
return $responseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function isEnabled();
|
||||||
|
}
|
212
src/Validator/Rules/QueryComplexity.php
Normal file
212
src/Validator/Rules/QueryComplexity.php
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
|
use GraphQL\Error;
|
||||||
|
use GraphQL\Executor\Values;
|
||||||
|
use GraphQL\Language\AST\Field;
|
||||||
|
use GraphQL\Language\AST\FragmentSpread;
|
||||||
|
use GraphQL\Language\AST\InlineFragment;
|
||||||
|
use GraphQL\Language\AST\Node;
|
||||||
|
use GraphQL\Language\AST\OperationDefinition;
|
||||||
|
use GraphQL\Language\AST\SelectionSet;
|
||||||
|
use GraphQL\Language\Visitor;
|
||||||
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
|
||||||
|
class QueryComplexity extends AbstractQuerySecurity
|
||||||
|
{
|
||||||
|
private $maxQueryComplexity;
|
||||||
|
|
||||||
|
private $rawVariableValues = [];
|
||||||
|
|
||||||
|
private $variableDefs;
|
||||||
|
|
||||||
|
private $fieldAstAndDefs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ValidationContext
|
||||||
|
*/
|
||||||
|
private $context;
|
||||||
|
|
||||||
|
public function __construct($maxQueryDepth)
|
||||||
|
{
|
||||||
|
$this->setMaxQueryComplexity($maxQueryDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function maxQueryComplexityErrorMessage($max, $count)
|
||||||
|
{
|
||||||
|
return sprintf('Max query complexity should be %d but got %d.', $max, $count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set max query complexity. If equal to 0 no check is done. Must be greater or equal to 0.
|
||||||
|
*
|
||||||
|
* @param $maxQueryComplexity
|
||||||
|
*/
|
||||||
|
public function setMaxQueryComplexity($maxQueryComplexity)
|
||||||
|
{
|
||||||
|
$this->checkIfGreaterOrEqualToZero('maxQueryComplexity', $maxQueryComplexity);
|
||||||
|
|
||||||
|
$this->maxQueryComplexity = (int) $maxQueryComplexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaxQueryComplexity()
|
||||||
|
{
|
||||||
|
return $this->maxQueryComplexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRawVariableValues(array $rawVariableValues = null)
|
||||||
|
{
|
||||||
|
$this->rawVariableValues = $rawVariableValues ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRawVariableValues()
|
||||||
|
{
|
||||||
|
return $this->rawVariableValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(ValidationContext $context)
|
||||||
|
{
|
||||||
|
$this->context = $context;
|
||||||
|
|
||||||
|
$this->variableDefs = new \ArrayObject();
|
||||||
|
$this->fieldAstAndDefs = new \ArrayObject();
|
||||||
|
$complexity = 0;
|
||||||
|
|
||||||
|
return $this->invokeIfNeeded(
|
||||||
|
$context,
|
||||||
|
[
|
||||||
|
// Visit FragmentDefinition after visiting FragmentSpread
|
||||||
|
'visitSpreadFragments' => true,
|
||||||
|
Node::SELECTION_SET => function (SelectionSet $selectionSet) use ($context) {
|
||||||
|
$this->fieldAstAndDefs = $this->collectFieldASTsAndDefs(
|
||||||
|
$context,
|
||||||
|
$context->getParentType(),
|
||||||
|
$selectionSet,
|
||||||
|
null,
|
||||||
|
$this->fieldAstAndDefs
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Node::VARIABLE_DEFINITION => function ($def) {
|
||||||
|
$this->variableDefs[] = $def;
|
||||||
|
|
||||||
|
return Visitor::skipNode();
|
||||||
|
},
|
||||||
|
Node::OPERATION_DEFINITION => [
|
||||||
|
'leave' => function (OperationDefinition $operationDefinition) use ($context, &$complexity) {
|
||||||
|
$complexity = $this->fieldComplexity($operationDefinition, $complexity);
|
||||||
|
|
||||||
|
if ($complexity > $this->getMaxQueryComplexity()) {
|
||||||
|
return new Error($this->maxQueryComplexityErrorMessage($this->getMaxQueryComplexity(), $complexity));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fieldComplexity($node, $complexity = 0)
|
||||||
|
{
|
||||||
|
if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSet) {
|
||||||
|
foreach ($node->selectionSet->selections as $childNode) {
|
||||||
|
$complexity = $this->nodeComplexity($childNode, $complexity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $complexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function nodeComplexity(Node $node, $complexity = 0)
|
||||||
|
{
|
||||||
|
switch ($node->kind) {
|
||||||
|
case Node::FIELD:
|
||||||
|
/* @var Field $node */
|
||||||
|
// default values
|
||||||
|
$args = [];
|
||||||
|
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
|
||||||
|
|
||||||
|
// calculate children complexity if needed
|
||||||
|
$childrenComplexity = 0;
|
||||||
|
|
||||||
|
// node has children?
|
||||||
|
if (isset($node->selectionSet)) {
|
||||||
|
$childrenComplexity = $this->fieldComplexity($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
$astFieldInfo = $this->astFieldInfo($node);
|
||||||
|
$fieldDef = $astFieldInfo[1];
|
||||||
|
|
||||||
|
if ($fieldDef instanceof FieldDefinition) {
|
||||||
|
$args = $this->buildFieldArguments($node);
|
||||||
|
//get complexity fn using fieldDef complexity
|
||||||
|
if (method_exists($fieldDef, 'getComplexityFn')) {
|
||||||
|
$complexityFn = $fieldDef->getComplexityFn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$complexity += call_user_func_array($complexityFn, [$childrenComplexity, $args]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Node::INLINE_FRAGMENT:
|
||||||
|
/* @var InlineFragment $node */
|
||||||
|
// node has children?
|
||||||
|
if (isset($node->selectionSet)) {
|
||||||
|
$complexity = $this->fieldComplexity($node, $complexity);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Node::FRAGMENT_SPREAD:
|
||||||
|
/* @var FragmentSpread $node */
|
||||||
|
$fragment = $this->getFragment($node);
|
||||||
|
|
||||||
|
if (null !== $fragment) {
|
||||||
|
$complexity = $this->fieldComplexity($fragment, $complexity);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $complexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function astFieldInfo(Field $field)
|
||||||
|
{
|
||||||
|
$fieldName = $this->getFieldName($field);
|
||||||
|
$astFieldInfo = [null, null];
|
||||||
|
if (isset($this->fieldAstAndDefs[$fieldName])) {
|
||||||
|
foreach ($this->fieldAstAndDefs[$fieldName] as $astAndDef) {
|
||||||
|
if ($astAndDef[0] == $field) {
|
||||||
|
$astFieldInfo = $astAndDef;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $astFieldInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFieldArguments(Field $node)
|
||||||
|
{
|
||||||
|
$rawVariableValues = $this->getRawVariableValues();
|
||||||
|
$astFieldInfo = $this->astFieldInfo($node);
|
||||||
|
$fieldDef = $astFieldInfo[1];
|
||||||
|
|
||||||
|
$args = [];
|
||||||
|
|
||||||
|
if ($fieldDef instanceof FieldDefinition) {
|
||||||
|
$variableValues = Values::getVariableValues(
|
||||||
|
$this->context->getSchema(),
|
||||||
|
$this->variableDefs,
|
||||||
|
$rawVariableValues
|
||||||
|
);
|
||||||
|
$args = Values::getArgumentValues($fieldDef->args, $node->arguments, $variableValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isEnabled()
|
||||||
|
{
|
||||||
|
return $this->getMaxQueryComplexity() !== static::DISABLED;
|
||||||
|
}
|
||||||
|
}
|
116
src/Validator/Rules/QueryDepth.php
Normal file
116
src/Validator/Rules/QueryDepth.php
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
|
use GraphQL\Error;
|
||||||
|
use GraphQL\Language\AST\Field;
|
||||||
|
use GraphQL\Language\AST\FragmentSpread;
|
||||||
|
use GraphQL\Language\AST\InlineFragment;
|
||||||
|
use GraphQL\Language\AST\Node;
|
||||||
|
use GraphQL\Language\AST\OperationDefinition;
|
||||||
|
use GraphQL\Language\AST\SelectionSet;
|
||||||
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
|
||||||
|
class QueryDepth extends AbstractQuerySecurity
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $maxQueryDepth;
|
||||||
|
|
||||||
|
public function __construct($maxQueryDepth)
|
||||||
|
{
|
||||||
|
$this->setMaxQueryDepth($maxQueryDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
|
||||||
|
*
|
||||||
|
* @param $maxQueryDepth
|
||||||
|
*/
|
||||||
|
public function setMaxQueryDepth($maxQueryDepth)
|
||||||
|
{
|
||||||
|
$this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth);
|
||||||
|
|
||||||
|
$this->maxQueryDepth = (int) $maxQueryDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaxQueryDepth()
|
||||||
|
{
|
||||||
|
return $this->maxQueryDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function maxQueryDepthErrorMessage($max, $count)
|
||||||
|
{
|
||||||
|
return sprintf('Max query depth should be %d but got %d.', $max, $count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(ValidationContext $context)
|
||||||
|
{
|
||||||
|
return $this->invokeIfNeeded(
|
||||||
|
$context,
|
||||||
|
[
|
||||||
|
Node::OPERATION_DEFINITION => [
|
||||||
|
'leave' => function (OperationDefinition $operationDefinition) use ($context) {
|
||||||
|
$maxDepth = $this->fieldDepth($operationDefinition);
|
||||||
|
|
||||||
|
if ($maxDepth > $this->getMaxQueryDepth()) {
|
||||||
|
return new Error($this->maxQueryDepthErrorMessage($this->getMaxQueryDepth(), $maxDepth));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isEnabled()
|
||||||
|
{
|
||||||
|
return $this->getMaxQueryDepth() !== static::DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fieldDepth($node, $depth = 0, $maxDepth = 0)
|
||||||
|
{
|
||||||
|
if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSet) {
|
||||||
|
foreach ($node->selectionSet->selections as $childNode) {
|
||||||
|
$maxDepth = $this->nodeDepth($childNode, $depth, $maxDepth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $maxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0)
|
||||||
|
{
|
||||||
|
switch ($node->kind) {
|
||||||
|
case Node::FIELD:
|
||||||
|
/* @var Field $node */
|
||||||
|
// node has children?
|
||||||
|
if (null !== $node->selectionSet) {
|
||||||
|
// update maxDepth if needed
|
||||||
|
if ($depth > $maxDepth) {
|
||||||
|
$maxDepth = $depth;
|
||||||
|
}
|
||||||
|
$maxDepth = $this->fieldDepth($node, $depth + 1, $maxDepth);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Node::INLINE_FRAGMENT:
|
||||||
|
/* @var InlineFragment $node */
|
||||||
|
// node has children?
|
||||||
|
if (null !== $node->selectionSet) {
|
||||||
|
$maxDepth = $this->fieldDepth($node, $depth, $maxDepth);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Node::FRAGMENT_SPREAD:
|
||||||
|
/* @var FragmentSpread $node */
|
||||||
|
$fragment = $this->getFragment($node);
|
||||||
|
|
||||||
|
if (null !== $fragment) {
|
||||||
|
$maxDepth = $this->fieldDepth($fragment, $depth, $maxDepth);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $maxDepth;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
require_once __DIR__ . '/TestClasses.php';
|
|
||||||
|
|
||||||
|
use GraphQL\Executor\ExecutionResult;
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\Config;
|
use GraphQL\Type\Definition\Config;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
use GraphQL\Error;
|
use GraphQL\Error;
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
use GraphQL\Error;
|
use GraphQL\Error;
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
use GraphQL\Error;
|
use GraphQL\Error;
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
use GraphQL\Type\Definition\ScalarType;
|
use GraphQL\Type\Definition\ScalarType;
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
require_once __DIR__ . '/TestClasses.php';
|
require_once __DIR__ . '/TestClasses.php';
|
||||||
|
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\Config;
|
use GraphQL\Type\Definition\Config;
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
require_once __DIR__ . '/TestClasses.php';
|
require_once __DIR__ . '/TestClasses.php';
|
||||||
|
|
||||||
use GraphQL\Error;
|
use GraphQL\Error;
|
||||||
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Language;
|
namespace GraphQL\Tests\Language;
|
||||||
|
|
||||||
|
use GraphQL\Language\Lexer;
|
||||||
|
use GraphQL\Language\Source;
|
||||||
|
use GraphQL\Language\Token;
|
||||||
use GraphQL\SyntaxError;
|
use GraphQL\SyntaxError;
|
||||||
|
|
||||||
class LexerTest extends \PHPUnit_Framework_TestCase
|
class LexerTest extends \PHPUnit_Framework_TestCase
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Language;
|
namespace GraphQL\Tests\Language;
|
||||||
|
|
||||||
use GraphQL\Error;
|
use GraphQL\Error;
|
||||||
use GraphQL\Language\AST\Argument;
|
use GraphQL\Language\AST\Argument;
|
||||||
@ -10,6 +10,9 @@ use GraphQL\Language\AST\Location;
|
|||||||
use GraphQL\Language\AST\Name;
|
use GraphQL\Language\AST\Name;
|
||||||
use GraphQL\Language\AST\OperationDefinition;
|
use GraphQL\Language\AST\OperationDefinition;
|
||||||
use GraphQL\Language\AST\SelectionSet;
|
use GraphQL\Language\AST\SelectionSet;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Language\Source;
|
||||||
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\SyntaxError;
|
use GraphQL\SyntaxError;
|
||||||
|
|
||||||
class ParserTest extends \PHPUnit_Framework_TestCase
|
class ParserTest extends \PHPUnit_Framework_TestCase
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Language;
|
namespace GraphQL\Tests\Language;
|
||||||
|
|
||||||
use GraphQL\Language\AST\Document;
|
use GraphQL\Language\AST\Document;
|
||||||
use GraphQL\Language\AST\EnumValue;
|
use GraphQL\Language\AST\EnumValue;
|
||||||
@ -10,6 +10,8 @@ use GraphQL\Language\AST\SelectionSet;
|
|||||||
use GraphQL\Language\AST\StringValue;
|
use GraphQL\Language\AST\StringValue;
|
||||||
use GraphQL\Language\AST\Variable;
|
use GraphQL\Language\AST\Variable;
|
||||||
use GraphQL\Language\AST\VariableDefinition;
|
use GraphQL\Language\AST\VariableDefinition;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Language\Printer;
|
||||||
|
|
||||||
class PrinterTest extends \PHPUnit_Framework_TestCase
|
class PrinterTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Language;
|
namespace GraphQL\Tests\Language;
|
||||||
|
|
||||||
use GraphQL\Language\AST\Field;
|
use GraphQL\Language\AST\Field;
|
||||||
use GraphQL\Language\AST\Name;
|
use GraphQL\Language\AST\Name;
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
use GraphQL\Language\AST\SelectionSet;
|
use GraphQL\Language\AST\SelectionSet;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Language\Visitor;
|
||||||
|
|
||||||
class VisitorTest extends \PHPUnit_Framework_TestCase
|
class VisitorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL;
|
namespace GraphQL\Tests;
|
||||||
|
|
||||||
class StarWarsData
|
class StarWarsData
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL;
|
namespace GraphQL\Tests;
|
||||||
|
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
|
||||||
class StarWarsIntrospectionTest extends \PHPUnit_Framework_TestCase
|
class StarWarsIntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
// Star Wars Introspection Tests
|
// Star Wars Introspection Tests
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL;
|
namespace GraphQL\Tests;
|
||||||
|
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
|
||||||
class StarWarsQueryTest extends \PHPUnit_Framework_TestCase
|
class StarWarsQueryTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
// Star Wars Query Tests
|
// Star Wars Query Tests
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL;
|
namespace GraphQL\Tests;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is designed to be an end-to-end test, demonstrating
|
* This is designed to be an end-to-end test, demonstrating
|
||||||
@ -11,6 +11,7 @@ namespace GraphQL;
|
|||||||
* NOTE: This may contain spoilers for the original Star
|
* NOTE: This may contain spoilers for the original Star
|
||||||
* Wars trilogy.
|
* Wars trilogy.
|
||||||
*/
|
*/
|
||||||
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\EnumType;
|
use GraphQL\Type\Definition\EnumType;
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
use GraphQL\Type\Definition\NonNull;
|
use GraphQL\Type\Definition\NonNull;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL;
|
namespace GraphQL\Tests;
|
||||||
|
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Type;
|
namespace GraphQL\Tests\Type;
|
||||||
|
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\Config;
|
use GraphQL\Type\Definition\Config;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Type;
|
namespace GraphQL\Tests\Type;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
@ -9,6 +9,7 @@ use GraphQL\Type\Definition\EnumType;
|
|||||||
use GraphQL\Type\Definition\InputObjectType;
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Introspection;
|
||||||
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
||||||
|
|
||||||
class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Type;
|
namespace GraphQL\Tests\Type;
|
||||||
|
|
||||||
use GraphQL\GraphQL;
|
use GraphQL\GraphQL;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Type;
|
namespace GraphQL\Tests\Type;
|
||||||
|
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Type;
|
namespace GraphQL\Tests\Type;
|
||||||
|
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\InputObjectType;
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
@ -9,6 +9,8 @@ use GraphQL\Type\Definition\NonNull;
|
|||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\UnionType;
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Type\Introspection;
|
||||||
|
use GraphQL\Type\SchemaValidator;
|
||||||
|
|
||||||
class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
95
tests/Validator/AbstractQuerySecurityTest.php
Normal file
95
tests/Validator/AbstractQuerySecurityTest.php
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
|
use GraphQL\FormattedError;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Type\Introspection;
|
||||||
|
use GraphQL\Validator\DocumentValidator;
|
||||||
|
use GraphQL\Validator\Rules\AbstractQuerySecurity;
|
||||||
|
|
||||||
|
abstract class AbstractQuerySecurityTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param $max
|
||||||
|
*
|
||||||
|
* @return AbstractQuerySecurity
|
||||||
|
*/
|
||||||
|
abstract protected function getRule($max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $max
|
||||||
|
* @param $count
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract protected function getErrorMessage($max, $count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage argument must be greater or equal to 0.
|
||||||
|
*/
|
||||||
|
public function testMaxQueryDepthMustBeGreaterOrEqualTo0()
|
||||||
|
{
|
||||||
|
$this->getRule(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createFormattedError($max, $count, $locations = [])
|
||||||
|
{
|
||||||
|
return FormattedError::create($this->getErrorMessage($max, $count), $locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertDocumentValidator($queryString, $max, array $expectedErrors = [])
|
||||||
|
{
|
||||||
|
$errors = DocumentValidator::validate(
|
||||||
|
QuerySecuritySchema::buildSchema(),
|
||||||
|
Parser::parse($queryString),
|
||||||
|
[$this->getRule($max)]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($expectedErrors, array_map(['GraphQL\Error', 'formatError'], $errors), $queryString);
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertIntrospectionQuery($maxExpected)
|
||||||
|
{
|
||||||
|
$query = Introspection::getIntrospectionQuery(true);
|
||||||
|
|
||||||
|
$this->assertMaxValue($query, $maxExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertIntrospectionTypeMetaFieldQuery($maxExpected)
|
||||||
|
{
|
||||||
|
$query = '
|
||||||
|
{
|
||||||
|
__type(name: "Human") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$this->assertMaxValue($query, $maxExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertTypeNameMetaFieldQuery($maxExpected)
|
||||||
|
{
|
||||||
|
$query = '
|
||||||
|
{
|
||||||
|
human {
|
||||||
|
__typename
|
||||||
|
firstName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$this->assertMaxValue($query, $maxExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertMaxValue($query, $maxExpected)
|
||||||
|
{
|
||||||
|
$this->assertDocumentValidator($query, $maxExpected);
|
||||||
|
$newMax = $maxExpected - 1;
|
||||||
|
if ($newMax !== AbstractQuerySecurity::DISABLED) {
|
||||||
|
$this->assertDocumentValidator($query, $newMax, [$this->createFormattedError($newMax, $maxExpected)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
use GraphQL\Validator\Messages;
|
||||||
use GraphQL\Validator\Rules\DefaultValuesOfCorrectType;
|
use GraphQL\Validator\Rules\DefaultValuesOfCorrectType;
|
||||||
|
|
||||||
class DefaultValuesOfCorrectTypeTest extends TestCase
|
class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
use GraphQL\Validator\Messages;
|
||||||
use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
||||||
|
|
||||||
class FieldsOnCorrectTypeTest extends TestCase
|
class FieldsOnCorrectTypeTest extends TestCase
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\Validator\Rules\KnownFragmentNames;
|
use GraphQL\Validator\Rules\KnownFragmentNames;
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
116
tests/Validator/QueryComplexityTest.php
Normal file
116
tests/Validator/QueryComplexityTest.php
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
|
|
||||||
|
class QueryComplexityTest extends AbstractQuerySecurityTest
|
||||||
|
{
|
||||||
|
/** @var QueryComplexity */
|
||||||
|
private static $rule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $max
|
||||||
|
* @param $count
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getErrorMessage($max, $count)
|
||||||
|
{
|
||||||
|
return QueryComplexity::maxQueryComplexityErrorMessage($max, $count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $maxDepth
|
||||||
|
*
|
||||||
|
* @return QueryComplexity
|
||||||
|
*/
|
||||||
|
protected function getRule($maxDepth = null)
|
||||||
|
{
|
||||||
|
if (null === self::$rule) {
|
||||||
|
self::$rule = new QueryComplexity($maxDepth);
|
||||||
|
} elseif (null !== $maxDepth) {
|
||||||
|
self::$rule->setMaxQueryComplexity($maxDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSimpleQueries()
|
||||||
|
{
|
||||||
|
$query = 'query MyQuery { human { firstName } }';
|
||||||
|
|
||||||
|
$this->assertDocumentValidators($query, 2, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInlineFragmentQueries()
|
||||||
|
{
|
||||||
|
$query = 'query MyQuery { human { ... on Human { firstName } } }';
|
||||||
|
|
||||||
|
$this->assertDocumentValidators($query, 2, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFragmentQueries()
|
||||||
|
{
|
||||||
|
$query = 'query MyQuery { human { ...F1 } } fragment F1 on Human { firstName}';
|
||||||
|
|
||||||
|
$this->assertDocumentValidators($query, 2, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAliasesQueries()
|
||||||
|
{
|
||||||
|
$query = 'query MyQuery { thomas: human(name: "Thomas") { firstName } jeremy: human(name: "Jeremy") { firstName } }';
|
||||||
|
|
||||||
|
$this->assertDocumentValidators($query, 4, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomComplexityQueries()
|
||||||
|
{
|
||||||
|
$query = 'query MyQuery { human { dogs { name } } }';
|
||||||
|
|
||||||
|
$this->assertDocumentValidators($query, 12, 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomComplexityWithArgsQueries()
|
||||||
|
{
|
||||||
|
$query = 'query MyQuery { human { dogs(name: "Root") { name } } }';
|
||||||
|
|
||||||
|
$this->assertDocumentValidators($query, 3, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomComplexityWithVariablesQueries()
|
||||||
|
{
|
||||||
|
$query = 'query MyQuery($dog: String!) { human { dogs(name: $dog) { name } } }';
|
||||||
|
|
||||||
|
$this->getRule()->setRawVariableValues(['dog' => 'Roots']);
|
||||||
|
|
||||||
|
$this->assertDocumentValidators($query, 3, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testComplexityIntrospectionQuery()
|
||||||
|
{
|
||||||
|
$this->assertIntrospectionQuery(109);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIntrospectionTypeMetaFieldQuery()
|
||||||
|
{
|
||||||
|
$this->assertIntrospectionTypeMetaFieldQuery(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTypeNameMetaFieldQuery()
|
||||||
|
{
|
||||||
|
$this->assertTypeNameMetaFieldQuery(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertDocumentValidators($query, $queryComplexity, $startComplexity)
|
||||||
|
{
|
||||||
|
for ($maxComplexity = $startComplexity; $maxComplexity >= 0; --$maxComplexity) {
|
||||||
|
$positions = [];
|
||||||
|
|
||||||
|
if ($maxComplexity < $queryComplexity && $maxComplexity !== QueryComplexity::DISABLED) {
|
||||||
|
$positions = [$this->createFormattedError($maxComplexity, $queryComplexity)];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertDocumentValidator($query, $maxComplexity, $positions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
tests/Validator/QueryDepthTest.php
Normal file
148
tests/Validator/QueryDepthTest.php
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
|
use GraphQL\Validator\Rules\QueryDepth;
|
||||||
|
|
||||||
|
class QueryDepthTest extends AbstractQuerySecurityTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param $max
|
||||||
|
* @param $count
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getErrorMessage($max, $count)
|
||||||
|
{
|
||||||
|
return QueryDepth::maxQueryDepthErrorMessage($max, $count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $maxDepth
|
||||||
|
*
|
||||||
|
* @return QueryDepth
|
||||||
|
*/
|
||||||
|
protected function getRule($maxDepth)
|
||||||
|
{
|
||||||
|
return new QueryDepth($maxDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $queryDepth
|
||||||
|
* @param int $maxQueryDepth
|
||||||
|
* @param array $expectedErrors
|
||||||
|
* @dataProvider queryDataProvider
|
||||||
|
*/
|
||||||
|
public function testSimpleQueries($queryDepth, $maxQueryDepth = 7, $expectedErrors = [])
|
||||||
|
{
|
||||||
|
$this->assertDocumentValidator($this->buildRecursiveQuery($queryDepth), $maxQueryDepth, $expectedErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $queryDepth
|
||||||
|
* @param int $maxQueryDepth
|
||||||
|
* @param array $expectedErrors
|
||||||
|
* @dataProvider queryDataProvider
|
||||||
|
*/
|
||||||
|
public function testFragmentQueries($queryDepth, $maxQueryDepth = 7, $expectedErrors = [])
|
||||||
|
{
|
||||||
|
$this->assertDocumentValidator($this->buildRecursiveUsingFragmentQuery($queryDepth), $maxQueryDepth, $expectedErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $queryDepth
|
||||||
|
* @param int $maxQueryDepth
|
||||||
|
* @param array $expectedErrors
|
||||||
|
* @dataProvider queryDataProvider
|
||||||
|
*/
|
||||||
|
public function testInlineFragmentQueries($queryDepth, $maxQueryDepth = 7, $expectedErrors = [])
|
||||||
|
{
|
||||||
|
$this->assertDocumentValidator($this->buildRecursiveUsingInlineFragmentQuery($queryDepth), $maxQueryDepth, $expectedErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testComplexityIntrospectionQuery()
|
||||||
|
{
|
||||||
|
$this->assertIntrospectionQuery(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIntrospectionTypeMetaFieldQuery()
|
||||||
|
{
|
||||||
|
$this->assertIntrospectionTypeMetaFieldQuery(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTypeNameMetaFieldQuery()
|
||||||
|
{
|
||||||
|
$this->assertTypeNameMetaFieldQuery(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function queryDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[1], // Valid because depth under default limit (7)
|
||||||
|
[2],
|
||||||
|
[3],
|
||||||
|
[4],
|
||||||
|
[5],
|
||||||
|
[6],
|
||||||
|
[7],
|
||||||
|
[8, 9], // Valid because depth under new limit (9)
|
||||||
|
[10, 0], // Valid because 0 depth disable limit
|
||||||
|
[
|
||||||
|
10,
|
||||||
|
8,
|
||||||
|
[$this->createFormattedError(8, 10)],
|
||||||
|
], // failed because depth over limit (8)
|
||||||
|
[
|
||||||
|
20,
|
||||||
|
15,
|
||||||
|
[$this->createFormattedError(15, 20)],
|
||||||
|
], // failed because depth over limit (15)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRecursiveQuery($depth)
|
||||||
|
{
|
||||||
|
$query = sprintf('query MyQuery { human%s }', $this->buildRecursiveQueryPart($depth));
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRecursiveUsingFragmentQuery($depth)
|
||||||
|
{
|
||||||
|
$query = sprintf(
|
||||||
|
'query MyQuery { human { ...F1 } } fragment F1 on Human %s',
|
||||||
|
$this->buildRecursiveQueryPart($depth)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRecursiveUsingInlineFragmentQuery($depth)
|
||||||
|
{
|
||||||
|
$query = sprintf(
|
||||||
|
'query MyQuery { human { ...on Human %s } }',
|
||||||
|
$this->buildRecursiveQueryPart($depth)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRecursiveQueryPart($depth)
|
||||||
|
{
|
||||||
|
$templates = [
|
||||||
|
'human' => ' { firstName%s } ',
|
||||||
|
'dog' => ' dogs { name%s } ',
|
||||||
|
];
|
||||||
|
|
||||||
|
$part = $templates['human'];
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $depth; ++$i) {
|
||||||
|
$key = ($i % 2 == 1) ? 'human' : 'dog';
|
||||||
|
$template = $templates[$key];
|
||||||
|
|
||||||
|
$part = sprintf($part, ('human' == $key ? ' owner ' : '').$template);
|
||||||
|
}
|
||||||
|
$part = str_replace('%s', '', $part);
|
||||||
|
|
||||||
|
return $part;
|
||||||
|
}
|
||||||
|
}
|
104
tests/Validator/QuerySecuritySchema.php
Normal file
104
tests/Validator/QuerySecuritySchema.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
|
||||||
|
class QuerySecuritySchema
|
||||||
|
{
|
||||||
|
private static $schema;
|
||||||
|
|
||||||
|
private static $dogType;
|
||||||
|
|
||||||
|
private static $humanType;
|
||||||
|
|
||||||
|
private static $queryRootType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Schema
|
||||||
|
*/
|
||||||
|
public static function buildSchema()
|
||||||
|
{
|
||||||
|
if (null !== self::$schema) {
|
||||||
|
return self::$schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$schema = new Schema(static::buildQueryRootType());
|
||||||
|
|
||||||
|
return self::$schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function buildQueryRootType()
|
||||||
|
{
|
||||||
|
if (null !== self::$queryRootType) {
|
||||||
|
return self::$queryRootType;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$queryRootType = new ObjectType([
|
||||||
|
'name' => 'QueryRoot',
|
||||||
|
'fields' => [
|
||||||
|
'human' => [
|
||||||
|
'type' => self::buildHumanType(),
|
||||||
|
'args' => ['name' => ['type' => Type::string()]],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return self::$queryRootType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function buildHumanType()
|
||||||
|
{
|
||||||
|
if (null !== self::$humanType) {
|
||||||
|
return self::$humanType;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$humanType = new ObjectType(
|
||||||
|
[
|
||||||
|
'name' => 'Human',
|
||||||
|
'fields' => [
|
||||||
|
'firstName' => ['type' => Type::nonNull(Type::string())],
|
||||||
|
'dogs' => [
|
||||||
|
'type' => function () {
|
||||||
|
return Type::nonNull(
|
||||||
|
Type::listOf(
|
||||||
|
Type::nonNull(self::buildDogType())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'complexity' => function ($childrenComplexity, $args) {
|
||||||
|
$complexity = isset($args['name']) ? 1 : 10;
|
||||||
|
|
||||||
|
return $childrenComplexity + $complexity;
|
||||||
|
},
|
||||||
|
'args' => ['name' => ['type' => Type::string()]],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return self::$humanType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function buildDogType()
|
||||||
|
{
|
||||||
|
if (null !== self::$dogType) {
|
||||||
|
return self::$dogType;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$dogType = new ObjectType(
|
||||||
|
[
|
||||||
|
'name' => 'Dog',
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::nonNull(Type::string())],
|
||||||
|
'master' => [
|
||||||
|
'type' => self::buildHumanType(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return self::$dogType;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
@ -10,6 +10,7 @@ use GraphQL\Type\Definition\ListOfType;
|
|||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\UnionType;
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Validator\DocumentValidator;
|
||||||
|
|
||||||
abstract class TestCase extends \PHPUnit_Framework_TestCase
|
abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\FormattedError;
|
use GraphQL\FormattedError;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
use GraphQL\Validator\Messages;
|
||||||
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
||||||
|
|
||||||
class VariablesInAllowedPositionTest extends TestCase
|
class VariablesInAllowedPositionTest extends TestCase
|
||||||
|
Loading…
Reference in New Issue
Block a user