From 00077f13ceb052bf3bca309e17cc0efcb9ae92a7 Mon Sep 17 00:00:00 2001 From: Daniel Rearden Date: Mon, 1 Jun 2020 15:55:52 -0400 Subject: [PATCH] Add NoSchemaIntrospectionCustomRule --- src/index.d.ts | 2 + src/index.js | 2 + .../NoSchemaIntrospectionCustomRule-test.js | 142 ++++++++++++++++++ src/validation/index.d.ts | 3 + src/validation/index.js | 3 + .../NoSchemaIntrospectionCustomRule.d.ts | 16 ++ .../custom/NoSchemaIntrospectionCustomRule.js | 39 +++++ 7 files changed, 207 insertions(+) create mode 100644 src/validation/__tests__/NoSchemaIntrospectionCustomRule-test.js create mode 100644 src/validation/rules/custom/NoSchemaIntrospectionCustomRule.d.ts create mode 100644 src/validation/rules/custom/NoSchemaIntrospectionCustomRule.js diff --git a/src/index.d.ts b/src/index.d.ts index 14a1551b4f..5e16c7ec04 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -335,6 +335,8 @@ export { UniqueFieldDefinitionNamesRule, UniqueDirectiveNamesRule, PossibleTypeExtensionsRule, + // Custom validation rules + NoSchemaIntrospectionCustomRule, ValidationRule, } from './validation/index'; diff --git a/src/index.js b/src/index.js index 8e2d9f4dd4..fc7d16bd69 100644 --- a/src/index.js +++ b/src/index.js @@ -333,6 +333,8 @@ export { UniqueFieldDefinitionNamesRule, UniqueDirectiveNamesRule, PossibleTypeExtensionsRule, + // Custom validation rules + NoSchemaIntrospectionCustomRule, } from './validation/index'; export type { ValidationRule } from './validation/index'; diff --git a/src/validation/__tests__/NoSchemaIntrospectionCustomRule-test.js b/src/validation/__tests__/NoSchemaIntrospectionCustomRule-test.js new file mode 100644 index 0000000000..bbb671b0ef --- /dev/null +++ b/src/validation/__tests__/NoSchemaIntrospectionCustomRule-test.js @@ -0,0 +1,142 @@ +// @flow strict + +import { describe, it } from 'mocha'; + +import { buildSchema } from '../../utilities/buildASTSchema'; + +import { NoSchemaIntrospectionCustomRule } from '../rules/custom/NoSchemaIntrospectionCustomRule'; + +import { expectValidationErrorsWithSchema } from './harness'; + +function expectErrors(queryStr) { + return expectValidationErrorsWithSchema( + schema, + NoSchemaIntrospectionCustomRule, + queryStr, + ); +} + +function expectValid(queryStr) { + expectErrors(queryStr).to.deep.equal([]); +} + +const schema = buildSchema(` + type Query { + someQuery: SomeType + } + + type SomeType { + someField: String + introspectionField: __EnumValue + } +`); + +describe('Validate: Prohibit introspection queries', () => { + it('ignores valid fields including __typename', () => { + expectValid(` + { + someQuery { + __typename + someField + } + } + `); + }); + + it('ignores fields not in the schema', () => { + expectValid(` + { + __introspect + } + `); + }); + + it('reports error when a field with an introspection type is requested', () => { + expectErrors(` + { + __schema { + queryType { + name + } + } + } + `).to.deep.equal([ + { + message: + 'GraphQL introspection has been disabled, but the requested query contained the field "__schema".', + locations: [{ line: 3, column: 9 }], + }, + { + message: + 'GraphQL introspection has been disabled, but the requested query contained the field "queryType".', + locations: [{ line: 4, column: 11 }], + }, + ]); + }); + + it('reports error when a field with an introspection type is requested and aliased', () => { + expectErrors(` + { + s: __schema { + queryType { + name + } + } + } + `).to.deep.equal([ + { + message: + 'GraphQL introspection has been disabled, but the requested query contained the field "__schema".', + locations: [{ line: 3, column: 9 }], + }, + { + message: + 'GraphQL introspection has been disabled, but the requested query contained the field "queryType".', + locations: [{ line: 4, column: 11 }], + }, + ]); + }); + + it('reports error when using a fragment with a field with an introspection type', () => { + expectErrors(` + { + ...QueryFragment + } + + fragment QueryFragment on Query { + __schema { + queryType { + name + } + } + } + `).to.deep.equal([ + { + message: + 'GraphQL introspection has been disabled, but the requested query contained the field "__schema".', + locations: [{ line: 7, column: 9 }], + }, + { + message: + 'GraphQL introspection has been disabled, but the requested query contained the field "queryType".', + locations: [{ line: 8, column: 11 }], + }, + ]); + }); + + it('reports error for non-standard introspection fields', () => { + expectErrors(` + { + someQuery { + introspectionField + } + } + `).to.deep.equal([ + { + message: + 'GraphQL introspection has been disabled, but the requested query contained the field "introspectionField".', + locations: [{ line: 4, column: 11 }], + }, + ]); + }); +}); diff --git a/src/validation/index.d.ts b/src/validation/index.d.ts index 79317bff03..191d1cc58c 100644 --- a/src/validation/index.d.ts +++ b/src/validation/index.d.ts @@ -90,3 +90,6 @@ export { UniqueEnumValueNamesRule } from './rules/UniqueEnumValueNamesRule'; export { UniqueFieldDefinitionNamesRule } from './rules/UniqueFieldDefinitionNamesRule'; export { UniqueDirectiveNamesRule } from './rules/UniqueDirectiveNamesRule'; export { PossibleTypeExtensionsRule } from './rules/PossibleTypeExtensionsRule'; + +// Optional rules not defined by the GraphQL Specification +export { NoSchemaIntrospectionCustomRule } from './rules/custom/NoSchemaIntrospectionCustomRule'; diff --git a/src/validation/index.js b/src/validation/index.js index 906b31e43c..ff4968581e 100644 --- a/src/validation/index.js +++ b/src/validation/index.js @@ -94,3 +94,6 @@ export { UniqueEnumValueNamesRule } from './rules/UniqueEnumValueNamesRule'; export { UniqueFieldDefinitionNamesRule } from './rules/UniqueFieldDefinitionNamesRule'; export { UniqueDirectiveNamesRule } from './rules/UniqueDirectiveNamesRule'; export { PossibleTypeExtensionsRule } from './rules/PossibleTypeExtensionsRule'; + +// Optional rules not defined by the GraphQL Specification +export { NoSchemaIntrospectionCustomRule } from './rules/custom/NoSchemaIntrospectionCustomRule'; diff --git a/src/validation/rules/custom/NoSchemaIntrospectionCustomRule.d.ts b/src/validation/rules/custom/NoSchemaIntrospectionCustomRule.d.ts new file mode 100644 index 0000000000..3677fa1c73 --- /dev/null +++ b/src/validation/rules/custom/NoSchemaIntrospectionCustomRule.d.ts @@ -0,0 +1,16 @@ +import { ASTVisitor } from '../../../language/visitor'; +import { ValidationContext } from '../../ValidationContext'; + +/** + * Prohibit introspection queries + * + * A GraphQL document is only valid if all fields selected are not fields that + * return an introspection type. + * + * Note: This rule is optional and is not part of the Validation section of the + * GraphQL Specification. This rule effectively disables introspection, which + * does not reflect best practices and should only be done if absolutely necessary. + */ +export function NoSchemaIntrospectionCustomRule( + context: ValidationContext, +): ASTVisitor; diff --git a/src/validation/rules/custom/NoSchemaIntrospectionCustomRule.js b/src/validation/rules/custom/NoSchemaIntrospectionCustomRule.js new file mode 100644 index 0000000000..2f4d857697 --- /dev/null +++ b/src/validation/rules/custom/NoSchemaIntrospectionCustomRule.js @@ -0,0 +1,39 @@ +// @flow strict + +import { GraphQLError } from '../../../error/GraphQLError'; + +import type { FieldNode } from '../../../language/ast'; +import type { ASTVisitor } from '../../../language/visitor'; + +import { getNamedType } from '../../../type/definition'; +import { isIntrospectionType } from '../../../type/introspection'; + +import type { ValidationContext } from '../../ValidationContext'; + +/** + * Prohibit introspection queries + * + * A GraphQL document is only valid if all fields selected are not fields that + * return an introspection type. + * + * Note: This rule is optional and is not part of the Validation section of the + * GraphQL Specification. This rule effectively disables introspection, which + * does not reflect best practices and should only be done if absolutely necessary. + */ +export function NoSchemaIntrospectionCustomRule( + context: ValidationContext, +): ASTVisitor { + return { + Field(node: FieldNode) { + const type = getNamedType(context.getType()); + if (type && isIntrospectionType(type)) { + context.reportError( + new GraphQLError( + `GraphQL introspection has been disabled, but the requested query contained the field "${node.name.value}".`, + node, + ), + ); + } + }, + }; +}