Skip to content

Commit

Permalink
Add NoSchemaIntrospectionCustomRule
Browse files Browse the repository at this point in the history
  • Loading branch information
danielrearden committed Jun 6, 2020
1 parent 4f35752 commit b1fbab0
Show file tree
Hide file tree
Showing 7 changed files with 218 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
153 changes: 153 additions & 0 deletions src/validation/__tests__/NoSchemaIntrospectionCustomRule-test.js
@@ -0,0 +1,153 @@
// @flow strict

import { describe, it } from 'mocha';

import {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
__EnumValue,
} from '../../type/index';

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 = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
someQuery: {
type: new GraphQLObjectType({
name: 'SomeType',
fields: {
someField: { type: GraphQLString },
introspectionField: { type: __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 b1fbab0

Please sign in to comment.