diff --git a/CHANGELOG.md b/CHANGELOG.md index 1115ec1bd0..4651eb5894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Upcoming - `apollo` - - + - Support disabling literal stripping when extracting queries. [1703](https://github.com/apollographql/apollo-tooling/pull/1703) - `apollo-codegen-flow` - - `apollo-codegen-scala` diff --git a/packages/apollo-graphql/src/__tests__/__snapshots__/operationId.test.ts.snap b/packages/apollo-graphql/src/__tests__/__snapshots__/operationId.test.ts.snap index 77cfd7884b..1b35cd5850 100644 --- a/packages/apollo-graphql/src/__tests__/__snapshots__/operationId.test.ts.snap +++ b/packages/apollo-graphql/src/__tests__/__snapshots__/operationId.test.ts.snap @@ -16,18 +16,20 @@ exports[`defaultEngineReportingSignature with various argument types 1`] = `"que exports[`defaultEngineReportingSignature with various inline types 1`] = `"query OpName{user{name(apple:[],bag:{},cat:ENUM_VALUE)}}"`; -exports[`defaultOperationRegistrySignature basic test 1`] = `"{user{name}}"`; +exports[`operationRegistrySignature basic test 1`] = `"{user{name}}"`; -exports[`defaultOperationRegistrySignature basic test with query 1`] = `"{user{name}}"`; +exports[`operationRegistrySignature basic test with query 1`] = `"{user{name}}"`; -exports[`defaultOperationRegistrySignature basic with operation name 1`] = `"query OpName{user{name}}"`; +exports[`operationRegistrySignature basic with operation name 1`] = `"query OpName{user{name}}"`; -exports[`defaultOperationRegistrySignature fragment 1`] = `"fragment Bar on User{asd}{user{name...Bar}}"`; +exports[`operationRegistrySignature fragment 1`] = `"fragment Bar on User{asd}{user{name...Bar}}"`; -exports[`defaultOperationRegistrySignature fragments in various order 1`] = `"fragment Bar on User{asd}{user{name...Bar}}"`; +exports[`operationRegistrySignature fragments in various order 1`] = `"fragment Bar on User{asd}{user{name...Bar}}"`; -exports[`defaultOperationRegistrySignature full test 1`] = `"fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}query Foo($a:Boolean,$b:Int){user(age:0,name:\\"\\"){aliased:name tz...Bar...on User{bee hello}}}"`; +exports[`operationRegistrySignature full test 1`] = `"fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}query Foo($a:Boolean,$b:Int){user(age:0,name:\\"\\"){aliased:name tz...Bar...on User{bee hello}}}"`; -exports[`defaultOperationRegistrySignature with various argument types 1`] = `"query OpName($a:[[Boolean!]!],$b:EnumType,$c:Int!){user{name(apple:$a,bag:$b,cat:$c)}}"`; +exports[`operationRegistrySignature test with preserveStringAndNumericLiterals=true 1`] = `"query Foo($b:Int){user(age:5,name:\\"hello\\"){a@skip(if:true)b@include(if:false)c(value:4){d}...Bar...on User{hello@directive(arg:\\"Value!\\")}}}"`; -exports[`defaultOperationRegistrySignature with various inline types 1`] = `"query OpName{user{name(apple:[[0]],bag:{input:\\"\\"},cat:ENUM_VALUE)}}"`; +exports[`operationRegistrySignature with various argument types 1`] = `"query OpName($a:[[Boolean!]!],$b:EnumType,$c:Int!){user{name(apple:$a,bag:$b,cat:$c)}}"`; + +exports[`operationRegistrySignature with various inline types 1`] = `"query OpName{user{name(apple:[[0]],bag:{input:\\"\\"},cat:ENUM_VALUE)}}"`; diff --git a/packages/apollo-graphql/src/__tests__/operationId.test.ts b/packages/apollo-graphql/src/__tests__/operationId.test.ts index d905afeab2..dc1dc93f79 100644 --- a/packages/apollo-graphql/src/__tests__/operationId.test.ts +++ b/packages/apollo-graphql/src/__tests__/operationId.test.ts @@ -1,7 +1,7 @@ import { default as gql, disableFragmentWarnings } from "graphql-tag"; import { defaultEngineReportingSignature, - defaultOperationRegistrySignature + operationRegistrySignature } from "../operationId"; // The gql duplicate fragment warning feature really is just warnings; nothing @@ -146,7 +146,7 @@ describe("defaultEngineReportingSignature", () => { }); }); -describe("defaultOperationRegistrySignature", () => { +describe("operationRegistrySignature", () => { const cases = [ // Test cases borrowed from optics-agent-js. { @@ -273,12 +273,32 @@ describe("defaultOperationRegistrySignature", () => { blah } ` + }, + { + name: "test with preserveStringAndNumericLiterals=true", + operationName: "Foo", + input: gql` + query Foo($b: Int) { + user(name: "hello", age: 5) { + ...Bar + a @skip(if: true) + b @include(if: false) + c(value: 4) { + d + } + ... on User { + hello @directive(arg: "Value!") + } + } + } + `, + options: { preserveStringAndNumericLiterals: true } } ]; - cases.forEach(({ name, operationName, input }) => { + cases.forEach(({ name, operationName, input, options }) => { test(name, () => { expect( - defaultOperationRegistrySignature(input, operationName) + operationRegistrySignature(input, operationName, options) ).toMatchSnapshot(); }); }); diff --git a/packages/apollo-graphql/src/index.ts b/packages/apollo-graphql/src/index.ts index c5876e172c..8c605defc5 100644 --- a/packages/apollo-graphql/src/index.ts +++ b/packages/apollo-graphql/src/index.ts @@ -1,6 +1,7 @@ export { defaultEngineReportingSignature, defaultOperationRegistrySignature, + operationRegistrySignature, operationHash } from "./operationId"; export * from "./schema"; diff --git a/packages/apollo-graphql/src/operationId.ts b/packages/apollo-graphql/src/operationId.ts index fadc0a9395..cf1a791dd5 100644 --- a/packages/apollo-graphql/src/operationId.ts +++ b/packages/apollo-graphql/src/operationId.ts @@ -69,25 +69,30 @@ export function defaultEngineReportingSignature( } // The operation registry signature function consists of removing extra whitespace, -// sorting the AST in a deterministic manner, hiding string and numeric literals, -// and removing unused definitions. This is a less aggressive transform than its -// engine reporting signature counterpart. -// -// XXX -// The hiding of literals is currently being discussed. This behavior -// should not be depended on in the future, as it's likely we will choose not -// to hide them at all. The rationale being, if queries are being shipped to -// a client bundle, exposing PII via this signature is a very small concern, -// relatively speaking. +// sorting the AST in a deterministic manner, potentially hiding string and numeric +// literals, and removing unused definitions. This is a less aggressive transform +// than its engine reporting signature counterpart. +export function operationRegistrySignature( + ast: DocumentNode, + operationName: string, + options: { preserveStringAndNumericLiterals: boolean } = { + preserveStringAndNumericLiterals: false + } +): string { + const withoutUnusedDefs = dropUnusedDefinitions(ast, operationName); + const maybeWithLiterals = options.preserveStringAndNumericLiterals + ? withoutUnusedDefs + : hideStringAndNumericLiterals(withoutUnusedDefs); + return printWithReducedWhitespace(sortAST(maybeWithLiterals)); +} + export function defaultOperationRegistrySignature( ast: DocumentNode, operationName: string ): string { - return printWithReducedWhitespace( - sortAST( - hideStringAndNumericLiterals(dropUnusedDefinitions(ast, operationName)) - ) - ); + return operationRegistrySignature(ast, operationName, { + preserveStringAndNumericLiterals: false + }); } export function operationHash(operation: string): string { diff --git a/packages/apollo/README.md b/packages/apollo/README.md index afae58b1f0..757ad74b99 100644 --- a/packages/apollo/README.md +++ b/packages/apollo/README.md @@ -278,6 +278,12 @@ OPTIONS --queries=queries Deprecated in favor of the includes flag + --preserveStringAndNumericLiterals Disable redaction of string and numerical literals. Without this flag, these + values will be replaced with empty strings (`''`) and zeroes (`0`) + respectively. This redaction is intended to avoid inadvertently outputting + potentially personally identifiable information (e.g. embedded passwords or + API keys) into operation manifests. DEFAULT: false + --tagName=tagName Name of the template literal tag used to identify template literals containing GraphQL queries in Javascript/Typescript code ``` diff --git a/packages/apollo/src/commands/client/extract.ts b/packages/apollo/src/commands/client/extract.ts index f6936cb3d4..b2c9816041 100644 --- a/packages/apollo/src/commands/client/extract.ts +++ b/packages/apollo/src/commands/client/extract.ts @@ -1,3 +1,4 @@ +import { flags } from "@oclif/command"; import { writeFileSync } from "fs"; import { ClientCommand } from "../../Command"; import { @@ -9,7 +10,15 @@ import { ClientIdentity } from "apollo-language-server"; export default class ClientExtract extends ClientCommand { static description = "Extract queries from a client"; static flags = { - ...ClientCommand.flags + ...ClientCommand.flags, + preserveStringAndNumericLiterals: flags.boolean({ + description: + "Disable redaction of string and numerical literals. Without this flag, these values will be replaced" + + " with empty strings (`''`) and zeroes (`0`) respectively. This redaction is intended to avoid " + + " inadvertently outputting potentially personally identifiable information (e.g. embedded passwords " + + " or API keys) into operation manifests", + default: false + }) }; static args = [ @@ -30,7 +39,10 @@ export default class ClientExtract extends ClientCommand { { title: "Extracting operations from project", task: async ctx => { - ctx.operations = getOperationManifestFromProject(this.project); + ctx.operations = getOperationManifestFromProject(this.project, { + preserveStringAndNumericLiterals: + flags.preserveStringAndNumericLiterals + }); ctx.clientIdentity = config.client; } }, diff --git a/packages/apollo/src/utils/getOperationManifestFromProject.ts b/packages/apollo/src/utils/getOperationManifestFromProject.ts index b7724029ef..af18fa25e3 100644 --- a/packages/apollo/src/utils/getOperationManifestFromProject.ts +++ b/packages/apollo/src/utils/getOperationManifestFromProject.ts @@ -1,8 +1,5 @@ import { GraphQLClientProject } from "apollo-language-server"; -import { - defaultOperationRegistrySignature, - operationHash -} from "apollo-graphql"; +import { operationHash, operationRegistrySignature } from "apollo-graphql"; export interface ManifestEntry { signature: string; @@ -13,15 +10,17 @@ export interface ManifestEntry { } export function getOperationManifestFromProject( - project: GraphQLClientProject + project: GraphQLClientProject, + options: { preserveStringAndNumericLiterals: boolean } = { + preserveStringAndNumericLiterals: false + } ): ManifestEntry[] { const manifest = Object.entries( project.mergedOperationsAndFragmentsForService ).map(([operationName, operationAST]) => { - const printed = defaultOperationRegistrySignature( - operationAST, - operationName - ); + const printed = operationRegistrySignature(operationAST, operationName, { + preserveStringAndNumericLiterals: options.preserveStringAndNumericLiterals + }); return { signature: operationHash(printed),