Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NoSchemaIntrospectionCustomRule #2600

Merged
merged 1 commit into from Jun 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.d.ts
Expand Up @@ -335,6 +335,8 @@ export {
UniqueFieldDefinitionNamesRule,
UniqueDirectiveNamesRule,
PossibleTypeExtensionsRule,
// Custom validation rules
NoSchemaIntrospectionCustomRule,
ValidationRule,
} from './validation/index';

Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Expand Up @@ -333,6 +333,8 @@ export {
UniqueFieldDefinitionNamesRule,
UniqueDirectiveNamesRule,
PossibleTypeExtensionsRule,
// Custom validation rules
NoSchemaIntrospectionCustomRule,
} from './validation/index';

export type { ValidationRule } from './validation/index';
Expand Down
142 changes: 142 additions & 0 deletions 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 }],
},
]);
});
});
3 changes: 3 additions & 0 deletions src/validation/index.d.ts
Expand Up @@ -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';
3 changes: 3 additions & 0 deletions src/validation/index.js
Expand Up @@ -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';
16 changes: 16 additions & 0 deletions 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;
39 changes: 39 additions & 0 deletions 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,
),
);
}
},
};
}