fix: Data schema exposed via GraphQL API public introspection (GHSA-48q3-prgv-gm4w) (#9819)
This commit is contained in:
@@ -50,6 +50,7 @@ describe('ParseGraphQLServer', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
parseServer = await global.reconfigureServer({
|
||||
maintenanceKey: 'test2',
|
||||
maxUploadSize: '1kb',
|
||||
});
|
||||
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
|
||||
@@ -88,8 +89,8 @@ describe('ParseGraphQLServer', () => {
|
||||
|
||||
it('should initialize parseGraphQLSchema with a log controller', async () => {
|
||||
const loggerAdapter = {
|
||||
log: () => {},
|
||||
error: () => {},
|
||||
log: () => { },
|
||||
error: () => { },
|
||||
};
|
||||
const parseServer = await global.reconfigureServer({
|
||||
loggerAdapter,
|
||||
@@ -124,10 +125,10 @@ describe('ParseGraphQLServer', () => {
|
||||
info: new Object(),
|
||||
config: new Object(),
|
||||
auth: new Object(),
|
||||
get: () => {},
|
||||
get: () => { },
|
||||
};
|
||||
const res = {
|
||||
set: () => {},
|
||||
set: () => { },
|
||||
};
|
||||
|
||||
it_id('0696675e-060f-414f-bc77-9d57f31807f5')(it)('should return schema and context with req\'s info, config and auth', async () => {
|
||||
@@ -431,7 +432,7 @@ describe('ParseGraphQLServer', () => {
|
||||
objects.push(object1, object2, object3, object4);
|
||||
}
|
||||
|
||||
async function createGQLFromParseServer(_parseServer) {
|
||||
async function createGQLFromParseServer(_parseServer, parseGraphQLServerOptions) {
|
||||
if (parseLiveQueryServer) {
|
||||
await parseLiveQueryServer.server.close();
|
||||
}
|
||||
@@ -448,6 +449,7 @@ describe('ParseGraphQLServer', () => {
|
||||
graphQLPath: '/graphql',
|
||||
playgroundPath: '/playground',
|
||||
subscriptionsPath: '/subscriptions',
|
||||
...parseGraphQLServerOptions,
|
||||
});
|
||||
parseGraphQLServer.applyGraphQL(expressApp);
|
||||
parseGraphQLServer.applyPlayground(expressApp);
|
||||
@@ -488,8 +490,8 @@ describe('ParseGraphQLServer', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
spyOn(console, 'warn').and.callFake(() => {});
|
||||
spyOn(console, 'error').and.callFake(() => {});
|
||||
spyOn(console, 'warn').and.callFake(() => { });
|
||||
spyOn(console, 'error').and.callFake(() => { });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -605,6 +607,96 @@ describe('ParseGraphQLServer', () => {
|
||||
]);
|
||||
};
|
||||
|
||||
describe('Introspection', () => {
|
||||
it('should have public introspection disabled by default without master key', async () => {
|
||||
|
||||
try {
|
||||
await apolloClient.query({
|
||||
query: gql`
|
||||
query Introspection {
|
||||
__schema {
|
||||
types {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
fail('should have thrown an error');
|
||||
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('Response not successful: Received status code 403');
|
||||
expect(e.networkError.result.errors[0].message).toEqual('Introspection is not allowed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should always work with master key', async () => {
|
||||
const introspection =
|
||||
await apolloClient.query({
|
||||
query: gql`
|
||||
query Introspection {
|
||||
__schema {
|
||||
types {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
context: {
|
||||
headers: {
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}
|
||||
},)
|
||||
expect(introspection.data).toBeDefined();
|
||||
expect(introspection.errors).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should always work with maintenance key', async () => {
|
||||
const introspection =
|
||||
await apolloClient.query({
|
||||
query: gql`
|
||||
query Introspection {
|
||||
__schema {
|
||||
types {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
context: {
|
||||
headers: {
|
||||
'X-Parse-Maintenance-Key': 'test2',
|
||||
},
|
||||
}
|
||||
},)
|
||||
expect(introspection.data).toBeDefined();
|
||||
expect(introspection.errors).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should have public introspection enabled if enabled', async () => {
|
||||
|
||||
const parseServer = await reconfigureServer();
|
||||
await createGQLFromParseServer(parseServer, { graphQLPublicIntrospection: true });
|
||||
|
||||
const introspection =
|
||||
await apolloClient.query({
|
||||
query: gql`
|
||||
query Introspection {
|
||||
__schema {
|
||||
types {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
expect(introspection.data).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Default Types', () => {
|
||||
it('should have Object scalar type', async () => {
|
||||
const objectType = (
|
||||
@@ -749,6 +841,11 @@ describe('ParseGraphQLServer', () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
context: {
|
||||
headers: {
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}
|
||||
})
|
||||
).data['__schema'].types.map(type => type.name);
|
||||
|
||||
@@ -780,6 +877,11 @@ describe('ParseGraphQLServer', () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
context: {
|
||||
headers: {
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}
|
||||
})
|
||||
).data['__schema'].types.map(type => type.name);
|
||||
|
||||
@@ -864,7 +966,7 @@ describe('ParseGraphQLServer', () => {
|
||||
});
|
||||
|
||||
it('should have clientMutationId in call function input', async () => {
|
||||
Parse.Cloud.define('hello', () => {});
|
||||
Parse.Cloud.define('hello', () => { });
|
||||
|
||||
const callFunctionInputFields = (
|
||||
await apolloClient.query({
|
||||
@@ -886,7 +988,7 @@ describe('ParseGraphQLServer', () => {
|
||||
});
|
||||
|
||||
it('should have clientMutationId in call function payload', async () => {
|
||||
Parse.Cloud.define('hello', () => {});
|
||||
Parse.Cloud.define('hello', () => { });
|
||||
|
||||
const callFunctionPayloadFields = (
|
||||
await apolloClient.query({
|
||||
@@ -1312,6 +1414,11 @@ describe('ParseGraphQLServer', () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
context: {
|
||||
headers: {
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}
|
||||
})
|
||||
).data['__schema'].types.map(type => type.name);
|
||||
|
||||
@@ -7447,9 +7554,9 @@ describe('ParseGraphQLServer', () => {
|
||||
it('should send reset password', async () => {
|
||||
const clientMutationId = uuidv4();
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: () => {},
|
||||
sendVerificationEmail: () => { },
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {},
|
||||
sendMail: () => { },
|
||||
};
|
||||
parseServer = await global.reconfigureServer({
|
||||
appName: 'test',
|
||||
@@ -7488,11 +7595,11 @@ describe('ParseGraphQLServer', () => {
|
||||
const clientMutationId = uuidv4();
|
||||
let resetPasswordToken;
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: () => {},
|
||||
sendVerificationEmail: () => { },
|
||||
sendPasswordResetEmail: ({ link }) => {
|
||||
resetPasswordToken = link.split('token=')[1].split('&')[0];
|
||||
},
|
||||
sendMail: () => {},
|
||||
sendMail: () => { },
|
||||
};
|
||||
parseServer = await global.reconfigureServer({
|
||||
appName: 'test',
|
||||
@@ -7558,9 +7665,9 @@ describe('ParseGraphQLServer', () => {
|
||||
it('should send verification email again', async () => {
|
||||
const clientMutationId = uuidv4();
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: () => {},
|
||||
sendVerificationEmail: () => { },
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {},
|
||||
sendMail: () => { },
|
||||
};
|
||||
parseServer = await global.reconfigureServer({
|
||||
appName: 'test',
|
||||
|
||||
@@ -33,6 +33,7 @@ describe('Security Check Groups', () => {
|
||||
config.security.enableCheckLog = false;
|
||||
config.allowClientClassCreation = false;
|
||||
config.enableInsecureAuthAdapters = false;
|
||||
config.graphQLPublicIntrospection = false;
|
||||
await reconfigureServer(config);
|
||||
|
||||
const group = new CheckGroupServerConfig();
|
||||
@@ -41,12 +42,14 @@ describe('Security Check Groups', () => {
|
||||
expect(group.checks()[1].checkState()).toBe(CheckState.success);
|
||||
expect(group.checks()[2].checkState()).toBe(CheckState.success);
|
||||
expect(group.checks()[4].checkState()).toBe(CheckState.success);
|
||||
expect(group.checks()[5].checkState()).toBe(CheckState.success);
|
||||
});
|
||||
|
||||
it('checks fail correctly', async () => {
|
||||
config.masterKey = 'insecure';
|
||||
config.security.enableCheckLog = true;
|
||||
config.allowClientClassCreation = true;
|
||||
config.graphQLPublicIntrospection = true;
|
||||
await reconfigureServer(config);
|
||||
|
||||
const group = new CheckGroupServerConfig();
|
||||
@@ -55,6 +58,7 @@ describe('Security Check Groups', () => {
|
||||
expect(group.checks()[1].checkState()).toBe(CheckState.fail);
|
||||
expect(group.checks()[2].checkState()).toBe(CheckState.fail);
|
||||
expect(group.checks()[4].checkState()).toBe(CheckState.fail);
|
||||
expect(group.checks()[5].checkState()).toBe(CheckState.fail);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user