Skip to content

Commit

Permalink
Add NoSchemaIntrospectionCustomRule
Browse files Browse the repository at this point in the history
  • Loading branch information
danielrearden committed Jun 20, 2020
1 parent 1597f5d commit 13086bb
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 0 deletions.
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,
),
);
}
},
};
}

0 comments on commit 13086bb

Please sign in to comment.