fix: Server internal error details leaking in error messages returned to clients (#9937)
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -82,7 +82,7 @@
|
||||
"deep-diff": "1.0.2",
|
||||
"eslint": "9.27.0",
|
||||
"eslint-plugin-expect-type": "0.6.2",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"eslint-plugin-unused-imports": "4.3.0",
|
||||
"flow-bin": "0.271.0",
|
||||
"form-data": "4.0.4",
|
||||
"globals": "16.2.0",
|
||||
|
||||
@@ -5,6 +5,13 @@ const request = require('../lib/request');
|
||||
const AudiencesRouter = require('../lib/Routers/AudiencesRouter').AudiencesRouter;
|
||||
|
||||
describe('AudiencesRouter', () => {
|
||||
let loggerErrorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('uses find condition from request.body', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
@@ -263,55 +270,65 @@ describe('AudiencesRouter', () => {
|
||||
});
|
||||
|
||||
it('should only create with master key', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
Parse._request('POST', 'push_audiences', {
|
||||
name: 'My Audience',
|
||||
query: JSON.stringify({ deviceType: 'ios' }),
|
||||
}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
expect(error.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only find with master key', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
Parse._request('GET', 'push_audiences', {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
expect(error.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only get with master key', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
Parse._request('GET', `push_audiences/someId`, {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
expect(error.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only update with master key', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
Parse._request('PUT', `push_audiences/someId`, {
|
||||
name: 'My Audience 2',
|
||||
}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
expect(error.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should only delete with master key', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
Parse._request('DELETE', `push_audiences/someId`, {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
expect(error.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -52,6 +52,9 @@ describe_only(() => {
|
||||
});
|
||||
|
||||
it('can check invalid master key of request', done => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: 'http://localhost:8378/1/scriptlog',
|
||||
headers: {
|
||||
@@ -61,7 +64,8 @@ describe_only(() => {
|
||||
}).then(fail, response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(403);
|
||||
expect(body.error).toEqual('unauthorized: master key is required');
|
||||
expect(body.error).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ const request = require('../lib/request');
|
||||
const Parse = require('parse/node');
|
||||
const Config = require('../lib/Config');
|
||||
const SchemaController = require('../lib/Controllers/SchemaController');
|
||||
const TestUtils = require('../lib/TestUtils');
|
||||
const { destroyAllDataPermanently } = require('../lib/TestUtils');
|
||||
|
||||
const userSchema = SchemaController.convertSchemaToAdapterSchema({
|
||||
className: '_User',
|
||||
@@ -169,7 +169,7 @@ describe('miscellaneous', () => {
|
||||
}
|
||||
const config = Config.get('test');
|
||||
// Remove existing data to clear out unique index
|
||||
TestUtils.destroyAllDataPermanently()
|
||||
destroyAllDataPermanently()
|
||||
.then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] }))
|
||||
.then(() => config.database.adapter.createClass('_User', userSchema))
|
||||
.then(() =>
|
||||
@@ -210,7 +210,7 @@ describe('miscellaneous', () => {
|
||||
it_id('d00f907e-41b9-40f6-8168-63e832199a8c')(it)('ensure that if people already have duplicate emails, they can still sign up new users', done => {
|
||||
const config = Config.get('test');
|
||||
// Remove existing data to clear out unique index
|
||||
TestUtils.destroyAllDataPermanently()
|
||||
destroyAllDataPermanently()
|
||||
.then(() => config.database.adapter.performInitialization({ VolatileClassesSchemas: [] }))
|
||||
.then(() => config.database.adapter.createClass('_User', userSchema))
|
||||
.then(() =>
|
||||
@@ -1710,11 +1710,15 @@ describe('miscellaneous', () => {
|
||||
});
|
||||
|
||||
it('fail on purge all objects in class without master key', done => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
method: 'DELETE',
|
||||
headers: headers,
|
||||
@@ -1724,7 +1728,8 @@ describe('miscellaneous', () => {
|
||||
fail('Should not succeed');
|
||||
})
|
||||
.catch(response => {
|
||||
expect(response.data.error).toEqual('unauthorized: master key is required');
|
||||
expect(response.data.error).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,13 @@ for (let i = 0; i < str.length; i++) {
|
||||
}
|
||||
|
||||
describe('Parse.File testing', () => {
|
||||
let loggerErrorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
describe('creating files', () => {
|
||||
it('works with Content-Type', done => {
|
||||
const headers = {
|
||||
@@ -146,6 +153,7 @@ describe('Parse.File testing', () => {
|
||||
const b = response.data;
|
||||
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*thefile.jpg$/);
|
||||
// missing X-Parse-Master-Key header
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
@@ -156,8 +164,10 @@ describe('Parse.File testing', () => {
|
||||
}).then(fail, response => {
|
||||
const del_b = response.data;
|
||||
expect(response.status).toEqual(403);
|
||||
expect(del_b.error).toMatch(/unauthorized/);
|
||||
expect(del_b.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
// incorrect X-Parse-Master-Key header
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
@@ -169,7 +179,8 @@ describe('Parse.File testing', () => {
|
||||
}).then(fail, response => {
|
||||
const del_b2 = response.data;
|
||||
expect(response.status).toEqual(403);
|
||||
expect(del_b2.error).toMatch(/unauthorized/);
|
||||
expect(del_b2.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -756,11 +767,13 @@ describe('Parse.File testing', () => {
|
||||
|
||||
describe('getting files', () => {
|
||||
it('does not crash on file request with invalid app ID', async () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const res1 = await request({
|
||||
url: 'http://localhost:8378/1/files/invalid-id/invalid-file.txt',
|
||||
}).catch(e => e);
|
||||
expect(res1.status).toBe(403);
|
||||
expect(res1.data).toEqual({ code: 119, error: 'Invalid application ID.' });
|
||||
expect(res1.data).toEqual({ code: 119, error: 'Permission denied' });
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Invalid application ID.'));
|
||||
// Ensure server did not crash
|
||||
const res2 = await request({ url: 'http://localhost:8378/1/health' });
|
||||
expect(res2.status).toEqual(200);
|
||||
|
||||
@@ -220,6 +220,9 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
|
||||
it('fail to update if master key is missing', done => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
@@ -233,7 +236,8 @@ describe('a GlobalConfig', () => {
|
||||
}).then(fail, response => {
|
||||
const body = response.data;
|
||||
expect(response.status).toEqual(403);
|
||||
expect(body.error).toEqual('unauthorized: master key is required');
|
||||
expect(body.error).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,8 @@ function handleError(e) {
|
||||
describe('ParseGraphQLServer', () => {
|
||||
let parseServer;
|
||||
let parseGraphQLServer;
|
||||
let loggerErrorSpy;
|
||||
|
||||
|
||||
beforeEach(async () => {
|
||||
parseServer = await global.reconfigureServer({
|
||||
@@ -58,6 +60,9 @@ describe('ParseGraphQLServer', () => {
|
||||
playgroundPath: '/playground',
|
||||
subscriptionsPath: '/subscriptions',
|
||||
});
|
||||
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
@@ -3488,6 +3493,7 @@ describe('ParseGraphQLServer', () => {
|
||||
});
|
||||
|
||||
it('should require master key to create a new class', async () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
try {
|
||||
await apolloClient.mutate({
|
||||
mutation: gql`
|
||||
@@ -3501,7 +3507,8 @@ describe('ParseGraphQLServer', () => {
|
||||
fail('should fail');
|
||||
} catch (e) {
|
||||
expect(e.graphQLErrors[0].extensions.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(e.graphQLErrors[0].message).toEqual('unauthorized: master key is required');
|
||||
expect(e.graphQLErrors[0].message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3858,6 +3865,7 @@ describe('ParseGraphQLServer', () => {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
loggerErrorSpy.calls.reset();
|
||||
try {
|
||||
await apolloClient.mutate({
|
||||
mutation: gql`
|
||||
@@ -3871,7 +3879,8 @@ describe('ParseGraphQLServer', () => {
|
||||
fail('should fail');
|
||||
} catch (e) {
|
||||
expect(e.graphQLErrors[0].extensions.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(e.graphQLErrors[0].message).toEqual('unauthorized: master key is required');
|
||||
expect(e.graphQLErrors[0].message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4083,6 +4092,7 @@ describe('ParseGraphQLServer', () => {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
loggerErrorSpy.calls.reset();
|
||||
try {
|
||||
await apolloClient.mutate({
|
||||
mutation: gql`
|
||||
@@ -4096,7 +4106,8 @@ describe('ParseGraphQLServer', () => {
|
||||
fail('should fail');
|
||||
} catch (e) {
|
||||
expect(e.graphQLErrors[0].extensions.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(e.graphQLErrors[0].message).toEqual('unauthorized: master key is required');
|
||||
expect(e.graphQLErrors[0].message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4124,6 +4135,7 @@ describe('ParseGraphQLServer', () => {
|
||||
});
|
||||
|
||||
it('should require master key to get an existing class', async () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
try {
|
||||
await apolloClient.query({
|
||||
query: gql`
|
||||
@@ -4137,11 +4149,13 @@ describe('ParseGraphQLServer', () => {
|
||||
fail('should fail');
|
||||
} catch (e) {
|
||||
expect(e.graphQLErrors[0].extensions.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(e.graphQLErrors[0].message).toEqual('unauthorized: master key is required');
|
||||
expect(e.graphQLErrors[0].message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
}
|
||||
});
|
||||
|
||||
it('should require master key to find the existing classes', async () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
try {
|
||||
await apolloClient.query({
|
||||
query: gql`
|
||||
@@ -4155,7 +4169,8 @@ describe('ParseGraphQLServer', () => {
|
||||
fail('should fail');
|
||||
} catch (e) {
|
||||
expect(e.graphQLErrors[0].extensions.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(e.graphQLErrors[0].message).toEqual('unauthorized: master key is required');
|
||||
expect(e.graphQLErrors[0].message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -6081,7 +6096,7 @@ describe('ParseGraphQLServer', () => {
|
||||
}
|
||||
|
||||
await expectAsync(createObject('GraphQLClass')).toBeRejectedWith(
|
||||
jasmine.stringMatching('Permission denied for action create on class GraphQLClass')
|
||||
jasmine.stringMatching('Permission denied')
|
||||
);
|
||||
await expectAsync(createObject('PublicClass')).toBeResolved();
|
||||
await expectAsync(
|
||||
@@ -6115,7 +6130,7 @@ describe('ParseGraphQLServer', () => {
|
||||
'X-Parse-Session-Token': user4.getSessionToken(),
|
||||
})
|
||||
).toBeRejectedWith(
|
||||
jasmine.stringMatching('Permission denied for action create on class GraphQLClass')
|
||||
jasmine.stringMatching('Permission denied')
|
||||
);
|
||||
await expectAsync(
|
||||
createObject('PublicClass', {
|
||||
@@ -7802,7 +7817,8 @@ describe('ParseGraphQLServer', () => {
|
||||
} catch (err) {
|
||||
const { graphQLErrors } = err;
|
||||
expect(graphQLErrors.length).toBe(1);
|
||||
expect(graphQLErrors[0].message).toBe('Invalid session token');
|
||||
expect(graphQLErrors[0].message).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Invalid session token'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7840,7 +7856,8 @@ describe('ParseGraphQLServer', () => {
|
||||
} catch (err) {
|
||||
const { graphQLErrors } = err;
|
||||
expect(graphQLErrors.length).toBe(1);
|
||||
expect(graphQLErrors[0].message).toBe('Invalid session token');
|
||||
expect(graphQLErrors[0].message).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Invalid session token'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,6 +157,9 @@ describe('Installations', () => {
|
||||
});
|
||||
|
||||
it('should properly fail queying installations', done => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
|
||||
const installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
const device = 'android';
|
||||
const input = {
|
||||
@@ -166,6 +169,7 @@ describe('Installations', () => {
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query(Parse.Installation);
|
||||
return query.find();
|
||||
})
|
||||
@@ -174,10 +178,11 @@ describe('Installations', () => {
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toBe(119);
|
||||
expect(error.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(error.message).toBe(
|
||||
"Clients aren't allowed to perform the find operation on the installation collection."
|
||||
'Permission denied'
|
||||
);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("Clients aren't allowed to perform the find operation on the installation collection."));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,10 +74,14 @@ describe('Parse.Query Aggregate testing', () => {
|
||||
});
|
||||
|
||||
it('should only query aggregate with master key', done => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
loggerErrorSpy.calls.reset();
|
||||
Parse._request('GET', `aggregate/someClass`, {}).then(
|
||||
() => {},
|
||||
error => {
|
||||
expect(error.message).toEqual('unauthorized: master key is required');
|
||||
expect(error.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ const passwordCrypto = require('../lib/password');
|
||||
const Config = require('../lib/Config');
|
||||
const cryptoUtils = require('../lib/cryptoUtils');
|
||||
|
||||
|
||||
describe('allowExpiredAuthDataToken option', () => {
|
||||
it('should accept true value', async () => {
|
||||
await reconfigureServer({ allowExpiredAuthDataToken: true });
|
||||
@@ -38,6 +39,12 @@ describe('allowExpiredAuthDataToken option', () => {
|
||||
});
|
||||
|
||||
describe('Parse.User testing', () => {
|
||||
let loggerErrorSpy;
|
||||
beforeEach(() => {
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('user sign up class method', async done => {
|
||||
const user = await Parse.User.signUp('asdf', 'zxcv');
|
||||
ok(user.getSessionToken());
|
||||
@@ -2651,6 +2658,7 @@ describe('Parse.User testing', () => {
|
||||
const b = response.data;
|
||||
expect(b.results.length).toEqual(1);
|
||||
const objId = b.results[0].objectId;
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
@@ -2661,7 +2669,9 @@ describe('Parse.User testing', () => {
|
||||
}).then(fail, response => {
|
||||
const b = response.data;
|
||||
expect(b.code).toEqual(209);
|
||||
expect(b.error).toBe('Invalid session token');
|
||||
expect(b.error).toBe('Permission denied');
|
||||
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Invalid session token'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -3355,6 +3365,9 @@ describe('Parse.User testing', () => {
|
||||
sendMail: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
let logger;
|
||||
let loggerErrorSpy;
|
||||
|
||||
const user = new Parse.User();
|
||||
user.set({
|
||||
username: 'hello',
|
||||
@@ -3369,9 +3382,12 @@ describe('Parse.User testing', () => {
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
})
|
||||
.then(() => {
|
||||
logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
return Parse.User.current().set('emailVerified', true).save();
|
||||
})
|
||||
.then(() => {
|
||||
@@ -3379,7 +3395,9 @@ describe('Parse.User testing', () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
expect(err.message).toBe("Clients aren't allowed to manually update email verification.");
|
||||
expect(err.message).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("Clients aren't allowed to manually update email verification."));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -4277,6 +4295,12 @@ describe('Security Advisory GHSA-8w3j-g983-8jh5', function () {
|
||||
});
|
||||
|
||||
describe('login as other user', () => {
|
||||
let loggerErrorSpy;
|
||||
beforeEach(() => {
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('allows creating a session for another user with the master key', async done => {
|
||||
await Parse.User.signUp('some_user', 'some_password');
|
||||
const userId = Parse.User.current().id;
|
||||
@@ -4376,6 +4400,7 @@ describe('login as other user', () => {
|
||||
const userId = Parse.User.current().id;
|
||||
await Parse.User.logOut();
|
||||
|
||||
loggerErrorSpy.calls.reset();
|
||||
try {
|
||||
await request({
|
||||
method: 'POST',
|
||||
@@ -4393,7 +4418,8 @@ describe('login as other user', () => {
|
||||
done();
|
||||
} catch (err) {
|
||||
expect(err.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(err.data.error).toBe('master key is required');
|
||||
expect(err.data.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('master key is required'));
|
||||
}
|
||||
|
||||
const sessionsQuery = new Parse.Query(Parse.Session);
|
||||
|
||||
@@ -5,7 +5,6 @@ const Config = require('../lib/Config');
|
||||
const rest = require('../lib/rest');
|
||||
const RestQuery = require('../lib/RestQuery');
|
||||
const request = require('../lib/request');
|
||||
|
||||
const querystring = require('querystring');
|
||||
|
||||
let config;
|
||||
@@ -155,9 +154,13 @@ describe('rest query', () => {
|
||||
});
|
||||
|
||||
it('query non-existent class when disabled client class creation', done => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
|
||||
const customConfig = Object.assign({}, config, {
|
||||
allowClientClassCreation: false,
|
||||
});
|
||||
loggerErrorSpy.calls.reset();
|
||||
rest.find(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {}).then(
|
||||
() => {
|
||||
fail('Should throw an error');
|
||||
@@ -165,9 +168,8 @@ describe('rest query', () => {
|
||||
},
|
||||
err => {
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(err.message).toEqual(
|
||||
'This user is not allowed to access ' + 'non-existent class: ClientClassCreation'
|
||||
);
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('This user is not allowed to access ' + 'non-existent class: ClientClassCreation'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
@@ -243,7 +245,7 @@ describe('rest query', () => {
|
||||
expectAsync(new Parse.Query('Test').exists('zip').find()).toBeRejectedWith(
|
||||
new Parse.Error(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to query zip on class Test'
|
||||
'Permission denied'
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -20,8 +20,12 @@ const hasAllPODobject = () => {
|
||||
};
|
||||
|
||||
describe('SchemaController', () => {
|
||||
let loggerErrorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
config = Config.get('test');
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('can validate one object', done => {
|
||||
@@ -275,6 +279,7 @@ describe('SchemaController', () => {
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toBe(1);
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.count();
|
||||
})
|
||||
@@ -283,7 +288,9 @@ describe('SchemaController', () => {
|
||||
fail('Class permissions should have rejected this query.');
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action count on class Stuff.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action count on class Stuff'));
|
||||
done();
|
||||
}
|
||||
)
|
||||
@@ -1427,8 +1434,12 @@ describe('SchemaController', () => {
|
||||
});
|
||||
|
||||
describe('Class Level Permissions for requiredAuth', () => {
|
||||
let loggerErrorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
config = Config.get('test');
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
function createUser() {
|
||||
@@ -1453,6 +1464,7 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
})
|
||||
@@ -1462,7 +1474,8 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
done();
|
||||
},
|
||||
e => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
expect(e.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied, user needs to be authenticated.'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
@@ -1551,6 +1564,7 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const stuff = new Parse.Object('Stuff');
|
||||
stuff.set('foo', 'bar');
|
||||
return stuff.save();
|
||||
@@ -1561,7 +1575,8 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
done();
|
||||
},
|
||||
e => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
expect(e.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied, user needs to be authenticated.'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
@@ -1639,6 +1654,7 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
const stuff = new Parse.Object('Stuff');
|
||||
stuff.set('foo', 'bar');
|
||||
return stuff.save().then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.get(stuff.id);
|
||||
});
|
||||
@@ -1649,7 +1665,8 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
done();
|
||||
},
|
||||
e => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
expect(e.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied, user needs to be authenticated.'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
@@ -1685,6 +1702,7 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
})
|
||||
.then(result => {
|
||||
expect(result.get('foo')).toEqual('bar');
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('Stuff');
|
||||
return query.find();
|
||||
})
|
||||
@@ -1694,7 +1712,8 @@ describe('Class Level Permissions for requiredAuth', () => {
|
||||
done();
|
||||
},
|
||||
e => {
|
||||
expect(e.message).toEqual('Permission denied, user needs to be authenticated.');
|
||||
expect(e.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied, user needs to be authenticated.'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -20,6 +20,9 @@ describe('features', () => {
|
||||
});
|
||||
|
||||
it('requires the master key to get features', async done => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
loggerErrorSpy.calls.reset();
|
||||
try {
|
||||
await request({
|
||||
url: 'http://localhost:8378/1/serverInfo',
|
||||
@@ -32,7 +35,8 @@ describe('features', () => {
|
||||
done.fail('The serverInfo request should be rejected without the master key');
|
||||
} catch (error) {
|
||||
expect(error.status).toEqual(403);
|
||||
expect(error.data.error).toEqual('unauthorized: master key is required');
|
||||
expect(error.data.error).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('unauthorized: master key is required'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,9 +11,14 @@ let config;
|
||||
let database;
|
||||
|
||||
describe('rest create', () => {
|
||||
let loggerErrorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
config = Config.get('test');
|
||||
database = config.database;
|
||||
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('handles _id', done => {
|
||||
@@ -317,6 +322,7 @@ describe('rest create', () => {
|
||||
const customConfig = Object.assign({}, config, {
|
||||
allowClientClassCreation: false,
|
||||
});
|
||||
loggerErrorSpy.calls.reset();
|
||||
rest.create(customConfig, auth.nobody(customConfig), 'ClientClassCreation', {}).then(
|
||||
() => {
|
||||
fail('Should throw an error');
|
||||
@@ -324,9 +330,8 @@ describe('rest create', () => {
|
||||
},
|
||||
err => {
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(err.message).toEqual(
|
||||
'This user is not allowed to access ' + 'non-existent class: ClientClassCreation'
|
||||
);
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('This user is not allowed to access ' + 'non-existent class: ClientClassCreation'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
@@ -772,6 +777,7 @@ describe('rest create', () => {
|
||||
});
|
||||
|
||||
it('cannot get object in volatileClasses if not masterKey through pointer', async () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const masterKeyOnlyClassObject = new Parse.Object('_PushStatus');
|
||||
await masterKeyOnlyClassObject.save(null, { useMasterKey: true });
|
||||
const obj2 = new Parse.Object('TestObject');
|
||||
@@ -783,11 +789,13 @@ describe('rest create', () => {
|
||||
const query = new Parse.Query('TestObject');
|
||||
query.include('pointer');
|
||||
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
|
||||
"Clients aren't allowed to perform the get operation on the _PushStatus collection."
|
||||
'Permission denied'
|
||||
);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("Clients aren't allowed to perform the get operation on the _PushStatus collection."));
|
||||
});
|
||||
|
||||
it_id('3ce563bf-93aa-4d0b-9af9-c5fb246ac9fc')(it)('cannot get object in _GlobalConfig if not masterKey through pointer', async () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
await Parse.Config.save({ privateData: 'secret' }, { privateData: true });
|
||||
const obj2 = new Parse.Object('TestObject');
|
||||
obj2.set('globalConfigPointer', {
|
||||
@@ -799,8 +807,9 @@ describe('rest create', () => {
|
||||
const query = new Parse.Query('TestObject');
|
||||
query.include('globalConfigPointer');
|
||||
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
|
||||
"Clients aren't allowed to perform the get operation on the _GlobalConfig collection."
|
||||
'Permission denied'
|
||||
);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("Clients aren't allowed to perform the get operation on the _GlobalConfig collection."));
|
||||
});
|
||||
|
||||
it('locks down session', done => {
|
||||
@@ -945,7 +954,16 @@ describe('rest update', () => {
|
||||
});
|
||||
|
||||
describe('read-only masterKey', () => {
|
||||
let loggerErrorSpy;
|
||||
let logger;
|
||||
|
||||
beforeEach(() => {
|
||||
logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('properly throws on rest.create, rest.update and rest.del', () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const config = Config.get('test');
|
||||
const readOnly = auth.readOnly(config);
|
||||
expect(() => {
|
||||
@@ -953,9 +971,10 @@ describe('read-only masterKey', () => {
|
||||
}).toThrow(
|
||||
new Parse.Error(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`read-only masterKey isn't allowed to perform the create operation.`
|
||||
'Permission denied'
|
||||
)
|
||||
);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to perform the create operation."));
|
||||
expect(() => {
|
||||
rest.update(config, readOnly, 'AnObject', {});
|
||||
}).toThrow();
|
||||
@@ -968,6 +987,9 @@ describe('read-only masterKey', () => {
|
||||
await reconfigureServer({
|
||||
readOnlyMasterKey: 'yolo-read-only',
|
||||
});
|
||||
// Need to be re required because reconfigureServer resets the logger
|
||||
const logger2 = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger2, 'error').and.callThrough();
|
||||
try {
|
||||
await request({
|
||||
url: `${Parse.serverURL}/classes/MyYolo`,
|
||||
@@ -983,8 +1005,9 @@ describe('read-only masterKey', () => {
|
||||
} catch (res) {
|
||||
expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(res.data.error).toBe(
|
||||
"read-only masterKey isn't allowed to perform the create operation."
|
||||
'Permission denied'
|
||||
);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to perform the create operation."));
|
||||
}
|
||||
await reconfigureServer();
|
||||
});
|
||||
@@ -1012,18 +1035,18 @@ describe('read-only masterKey', () => {
|
||||
});
|
||||
|
||||
it('should throw when trying to create RestWrite', () => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const config = Config.get('test');
|
||||
expect(() => {
|
||||
new RestWrite(config, auth.readOnly(config));
|
||||
}).toThrow(
|
||||
new Parse.Error(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'Cannot perform a write operation when using readOnlyMasterKey'
|
||||
)
|
||||
new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied')
|
||||
);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("Cannot perform a write operation when using readOnlyMasterKey"));
|
||||
});
|
||||
|
||||
it('should throw when trying to create schema', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
method: 'POST',
|
||||
url: `${Parse.serverURL}/schemas`,
|
||||
@@ -1037,12 +1060,14 @@ describe('read-only masterKey', () => {
|
||||
.then(done.fail)
|
||||
.catch(res => {
|
||||
expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(res.data.error).toBe("read-only masterKey isn't allowed to create a schema.");
|
||||
expect(res.data.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to create a schema."));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when trying to create schema with a name', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: `${Parse.serverURL}/schemas/MyClass`,
|
||||
method: 'POST',
|
||||
@@ -1056,12 +1081,14 @@ describe('read-only masterKey', () => {
|
||||
.then(done.fail)
|
||||
.catch(res => {
|
||||
expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(res.data.error).toBe("read-only masterKey isn't allowed to create a schema.");
|
||||
expect(res.data.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to create a schema."));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when trying to update schema', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: `${Parse.serverURL}/schemas/MyClass`,
|
||||
method: 'PUT',
|
||||
@@ -1075,12 +1102,14 @@ describe('read-only masterKey', () => {
|
||||
.then(done.fail)
|
||||
.catch(res => {
|
||||
expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(res.data.error).toBe("read-only masterKey isn't allowed to update a schema.");
|
||||
expect(res.data.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to update a schema."));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when trying to delete schema', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: `${Parse.serverURL}/schemas/MyClass`,
|
||||
method: 'DELETE',
|
||||
@@ -1094,12 +1123,14 @@ describe('read-only masterKey', () => {
|
||||
.then(done.fail)
|
||||
.catch(res => {
|
||||
expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(res.data.error).toBe("read-only masterKey isn't allowed to delete a schema.");
|
||||
expect(res.data.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to delete a schema."));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when trying to update the global config', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: `${Parse.serverURL}/config`,
|
||||
method: 'PUT',
|
||||
@@ -1113,12 +1144,14 @@ describe('read-only masterKey', () => {
|
||||
.then(done.fail)
|
||||
.catch(res => {
|
||||
expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(res.data.error).toBe("read-only masterKey isn't allowed to update the config.");
|
||||
expect(res.data.error).toBe('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to update the config."));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when trying to send push', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: `${Parse.serverURL}/push`,
|
||||
method: 'POST',
|
||||
@@ -1133,8 +1166,9 @@ describe('read-only masterKey', () => {
|
||||
.catch(res => {
|
||||
expect(res.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(res.data.error).toBe(
|
||||
"read-only masterKey isn't allowed to send push notifications."
|
||||
'Permission denied'
|
||||
);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("read-only masterKey isn't allowed to send push notifications."));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,9 +147,14 @@ const masterKeyHeaders = {
|
||||
};
|
||||
|
||||
describe('schemas', () => {
|
||||
let loggerErrorSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
await reconfigureServer();
|
||||
config = Config.get('test');
|
||||
|
||||
const logger = require('../lib/logger').default;
|
||||
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
});
|
||||
|
||||
it('requires the master key to get all schemas', done => {
|
||||
@@ -167,25 +172,29 @@ describe('schemas', () => {
|
||||
});
|
||||
|
||||
it('requires the master key to get one schema', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: 'http://localhost:8378/1/schemas/SomeSchema',
|
||||
json: true,
|
||||
headers: restKeyHeaders,
|
||||
}).then(fail, response => {
|
||||
expect(response.status).toEqual(403);
|
||||
expect(response.data.error).toEqual('unauthorized: master key is required');
|
||||
expect(response.data.error).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("unauthorized: master key is required"));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('asks for the master key if you use the rest key', done => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
request({
|
||||
url: 'http://localhost:8378/1/schemas',
|
||||
json: true,
|
||||
headers: restKeyHeaders,
|
||||
}).then(fail, response => {
|
||||
expect(response.status).toEqual(403);
|
||||
expect(response.data.error).toEqual('unauthorized: master key is required');
|
||||
expect(response.data.error).toEqual('Permission denied');
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("unauthorized: master key is required"));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -1826,6 +1835,7 @@ describe('schemas', () => {
|
||||
},
|
||||
},
|
||||
}).then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const object = new Parse.Object('AClass');
|
||||
object.set('hello', 'world');
|
||||
return object.save().then(
|
||||
@@ -1834,7 +1844,9 @@ describe('schemas', () => {
|
||||
done();
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action addField on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action addField on class AClass'));
|
||||
done();
|
||||
}
|
||||
);
|
||||
@@ -2198,13 +2210,16 @@ describe('schemas', () => {
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('AClass');
|
||||
return query.find().then(
|
||||
() => {
|
||||
fail('Use should hot be able to find!');
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action find on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action find on class AClass'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
);
|
||||
@@ -2258,13 +2273,16 @@ describe('schemas', () => {
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('AClass');
|
||||
return query.find().then(
|
||||
() => {
|
||||
fail('User should not be able to find!');
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action find on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action find on class AClass'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
);
|
||||
@@ -2343,13 +2361,16 @@ describe('schemas', () => {
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('AClass');
|
||||
return query.find().then(
|
||||
() => {
|
||||
fail('User should not be able to find!');
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action find on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action find on class AClass'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
);
|
||||
@@ -2419,13 +2440,16 @@ describe('schemas', () => {
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('AClass');
|
||||
return query.find().then(
|
||||
() => {
|
||||
fail('User should not be able to find!');
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action find on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action find on class AClass'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
);
|
||||
@@ -2450,13 +2474,16 @@ describe('schemas', () => {
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('AClass');
|
||||
return query.find().then(
|
||||
() => {
|
||||
fail('User should not be able to find!');
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action find on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action find on class AClass'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
);
|
||||
@@ -2531,6 +2558,7 @@ describe('schemas', () => {
|
||||
return Parse.User.logIn('admin', 'admin');
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('AClass');
|
||||
return query.find();
|
||||
})
|
||||
@@ -2540,7 +2568,9 @@ describe('schemas', () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action create on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action create on class AClass'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
)
|
||||
@@ -2548,6 +2578,7 @@ describe('schemas', () => {
|
||||
return Parse.User.logIn('user2', 'user2');
|
||||
})
|
||||
.then(() => {
|
||||
loggerErrorSpy.calls.reset();
|
||||
const query = new Parse.Query('AClass');
|
||||
return query.find();
|
||||
})
|
||||
@@ -2557,7 +2588,9 @@ describe('schemas', () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
err => {
|
||||
expect(err.message).toEqual('Permission denied for action find on class AClass.');
|
||||
expect(err.message).toEqual('Permission denied');
|
||||
expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining('Permission denied for action find on class AClass'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
)
|
||||
|
||||
@@ -13,9 +13,13 @@ describe('Vulnerabilities', () => {
|
||||
});
|
||||
|
||||
it('denies user creation with poisoned object ID', async () => {
|
||||
const logger = require('../lib/logger').default;
|
||||
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
|
||||
loggerErrorSpy.calls.reset();
|
||||
await expectAsync(
|
||||
new Parse.User({ id: 'role:a', username: 'a', password: '123' }).save()
|
||||
).toBeRejectedWith(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.'));
|
||||
).toBeRejectedWith(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied'));
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('Sanitized error:', jasmine.stringContaining("Invalid object ID."));
|
||||
});
|
||||
|
||||
describe('existing sessions for users with poisoned object ID', () => {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { StorageAdapter } from '../Adapters/Storage/StorageAdapter';
|
||||
import SchemaCache from '../Adapters/Cache/SchemaCache';
|
||||
import DatabaseController from './DatabaseController';
|
||||
import Config from '../Config';
|
||||
import { createSanitizedError } from '../Error';
|
||||
// @flow-disable-next
|
||||
import deepcopy from 'deepcopy';
|
||||
import type {
|
||||
@@ -1403,12 +1404,12 @@ export default class SchemaController {
|
||||
if (perms['requiresAuthentication']) {
|
||||
// If aclGroup has * (public)
|
||||
if (!aclGroup || aclGroup.length == 0) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Permission denied, user needs to be authenticated.'
|
||||
);
|
||||
} else if (aclGroup.indexOf('*') > -1 && aclGroup.length == 1) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Permission denied, user needs to be authenticated.'
|
||||
);
|
||||
@@ -1425,7 +1426,7 @@ export default class SchemaController {
|
||||
|
||||
// Reject create when write lockdown
|
||||
if (permissionField == 'writeUserFields' && operation == 'create') {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Permission denied for action ${operation} on class ${className}.`
|
||||
);
|
||||
@@ -1448,7 +1449,7 @@ export default class SchemaController {
|
||||
}
|
||||
}
|
||||
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Permission denied for action ${operation} on class ${className}.`
|
||||
);
|
||||
|
||||
44
src/Error.js
Normal file
44
src/Error.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import defaultLogger from './logger';
|
||||
|
||||
/**
|
||||
* Creates a sanitized error that hides detailed information from clients
|
||||
* while logging the detailed message server-side.
|
||||
*
|
||||
* @param {number} errorCode - The Parse.Error code (e.g., Parse.Error.OPERATION_FORBIDDEN)
|
||||
* @param {string} detailedMessage - The detailed error message to log server-side
|
||||
* @returns {Parse.Error} A Parse.Error with sanitized message
|
||||
*/
|
||||
function createSanitizedError(errorCode, detailedMessage) {
|
||||
// On testing we need to add a prefix to the message to allow to find the correct call in the TestUtils.js file
|
||||
if (process.env.TESTING) {
|
||||
defaultLogger.error('Sanitized error:', detailedMessage);
|
||||
} else {
|
||||
defaultLogger.error(detailedMessage);
|
||||
}
|
||||
|
||||
return new Parse.Error(errorCode, 'Permission denied');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sanitized error from a regular Error object
|
||||
* Used for non-Parse.Error errors (e.g., Express errors)
|
||||
*
|
||||
* @param {number} statusCode - HTTP status code (e.g., 403)
|
||||
* @param {string} detailedMessage - The detailed error message to log server-side
|
||||
* @returns {Error} An Error with sanitized message
|
||||
*/
|
||||
function createSanitizedHttpError(statusCode, detailedMessage) {
|
||||
// On testing we need to add a prefix to the message to allow to find the correct call in the TestUtils.js file
|
||||
if (process.env.TESTING) {
|
||||
defaultLogger.error('Sanitized error:', detailedMessage);
|
||||
} else {
|
||||
defaultLogger.error(detailedMessage);
|
||||
}
|
||||
|
||||
const error = new Error();
|
||||
error.status = statusCode;
|
||||
error.message = 'Permission denied';
|
||||
return error;
|
||||
}
|
||||
|
||||
export { createSanitizedError, createSanitizedHttpError };
|
||||
@@ -6,6 +6,7 @@ import * as schemaTypes from './schemaTypes';
|
||||
import { transformToParse, transformToGraphQL } from '../transformers/schemaFields';
|
||||
import { enforceMasterKeyAccess } from '../parseGraphQLUtils';
|
||||
import { getClass } from './schemaQueries';
|
||||
import { createSanitizedError } from '../../Error';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
const createClassMutation = mutationWithClientMutationId({
|
||||
@@ -33,9 +34,9 @@ const load = parseGraphQLSchema => {
|
||||
enforceMasterKeyAccess(auth);
|
||||
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to create a schema."
|
||||
"read-only masterKey isn't allowed to create a schema.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ const load = parseGraphQLSchema => {
|
||||
enforceMasterKeyAccess(auth);
|
||||
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update a schema."
|
||||
);
|
||||
@@ -133,9 +134,9 @@ const load = parseGraphQLSchema => {
|
||||
enforceMasterKeyAccess(auth);
|
||||
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to delete a schema."
|
||||
"read-only masterKey isn't allowed to delete a schema.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ import Parse from 'parse/node';
|
||||
import rest from '../../rest';
|
||||
import { extractKeysAndInclude } from './parseClassTypes';
|
||||
import { Auth } from '../../Auth';
|
||||
import { createSanitizedError } from '../../Error';
|
||||
|
||||
const getUserFromSessionToken = async (context, queryInfo, keysPrefix, userId) => {
|
||||
const { info, config } = context;
|
||||
if (!info || !info.sessionToken) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const sessionToken = info.sessionToken;
|
||||
const selectedFields = getFieldNames(queryInfo)
|
||||
@@ -62,7 +63,7 @@ const getUserFromSessionToken = async (context, queryInfo, keysPrefix, userId) =
|
||||
info.context
|
||||
);
|
||||
if (!response.results || response.results.length == 0) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
} else {
|
||||
const user = response.results[0];
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import Parse from 'parse/node';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export function enforceMasterKeyAccess(auth) {
|
||||
if (!auth.isMaster) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'unauthorized: master key is required');
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'unauthorized: master key is required',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const triggers = require('./triggers');
|
||||
const { continueWhile } = require('parse/lib/node/promiseUtils');
|
||||
const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
|
||||
const { enforceRoleSecurity } = require('./SharedRest');
|
||||
const { createSanitizedError } = require('./Error');
|
||||
|
||||
// restOptions can include:
|
||||
// skip
|
||||
@@ -120,7 +121,7 @@ function _UnsafeRestQuery(
|
||||
if (!this.auth.isMaster) {
|
||||
if (this.className == '_Session') {
|
||||
if (!this.auth.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
this.restWhere = {
|
||||
$and: [
|
||||
@@ -421,7 +422,7 @@ _UnsafeRestQuery.prototype.validateClientClassCreation = function () {
|
||||
.then(schemaController => schemaController.hasClass(this.className))
|
||||
.then(hasClass => {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' + 'non-existent class: ' + this.className
|
||||
);
|
||||
@@ -800,7 +801,7 @@ _UnsafeRestQuery.prototype.denyProtectedFields = async function () {
|
||||
) || [];
|
||||
for (const key of protectedFields) {
|
||||
if (this.restWhere[key]) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`This user is not allowed to query ${key} on class ${this.className}`
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import RestQuery from './RestQuery';
|
||||
import _ from 'lodash';
|
||||
import logger from './logger';
|
||||
import { requiredColumns } from './Controllers/SchemaController';
|
||||
import { createSanitizedError } from './Error';
|
||||
|
||||
// query and data are both provided in REST API format. So data
|
||||
// types are encoded by plain old objects.
|
||||
@@ -29,9 +30,9 @@ import { requiredColumns } from './Controllers/SchemaController';
|
||||
// for the _User class.
|
||||
function RestWrite(config, auth, className, query, data, originalData, clientSDK, context, action) {
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'Cannot perform a write operation when using readOnlyMasterKey'
|
||||
'Cannot perform a write operation when using readOnlyMasterKey',
|
||||
);
|
||||
}
|
||||
this.config = config;
|
||||
@@ -199,9 +200,9 @@ RestWrite.prototype.validateClientClassCreation = function () {
|
||||
.then(schemaController => schemaController.hasClass(this.className))
|
||||
.then(hasClass => {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' + 'non-existent class: ' + this.className
|
||||
'This user is not allowed to access non-existent class: ' + this.className,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -566,7 +567,6 @@ RestWrite.prototype.handleAuthData = async function (authData) {
|
||||
|
||||
// User found with provided authData
|
||||
if (results.length === 1) {
|
||||
|
||||
this.storage.authProvider = Object.keys(authData).join(',');
|
||||
|
||||
const { hasMutatedAuthData, mutatedAuthData } = Auth.hasMutatedAuthData(
|
||||
@@ -660,8 +660,10 @@ RestWrite.prototype.checkRestrictedFields = async function () {
|
||||
}
|
||||
|
||||
if (!this.auth.isMaintenance && !this.auth.isMaster && 'emailVerified' in this.data) {
|
||||
const error = `Clients aren't allowed to manually update email verification.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"Clients aren't allowed to manually update email verification."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1450,7 +1452,7 @@ RestWrite.prototype.runDatabaseOperation = function () {
|
||||
}
|
||||
|
||||
if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.SESSION_MISSING,
|
||||
`Cannot modify user ${this.query.objectId}.`
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import rest from '../rest';
|
||||
import _ from 'lodash';
|
||||
import Parse from 'parse/node';
|
||||
import { promiseEnsureIdempotency } from '../middlewares';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
const ALLOWED_GET_QUERY_KEYS = [
|
||||
'keys',
|
||||
@@ -111,7 +112,7 @@ export class ClassesRouter extends PromiseRouter {
|
||||
typeof req.body?.objectId === 'string' &&
|
||||
req.body.objectId.startsWith('role:')
|
||||
) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.');
|
||||
throw createSanitizedError(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.');
|
||||
}
|
||||
return rest.create(
|
||||
req.config,
|
||||
|
||||
@@ -5,6 +5,7 @@ import Config from '../Config';
|
||||
import logger from '../logger';
|
||||
const triggers = require('../triggers');
|
||||
const Utils = require('../Utils');
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class FilesRouter {
|
||||
expressRouter({ maxUploadSize = '20Mb' } = {}) {
|
||||
@@ -43,7 +44,7 @@ export class FilesRouter {
|
||||
const config = Config.get(req.params.appId);
|
||||
if (!config) {
|
||||
res.status(403);
|
||||
const err = new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.');
|
||||
const err = createSanitizedError(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.');
|
||||
res.json({ code: err.code, error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Parse from 'parse/node';
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import * as triggers from '../triggers';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
const getConfigFromParams = params => {
|
||||
const config = new Parse.Config();
|
||||
@@ -41,9 +42,9 @@ export class GlobalConfigRouter extends PromiseRouter {
|
||||
|
||||
async updateGlobalConfig(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update the config."
|
||||
"read-only masterKey isn't allowed to update the config.",
|
||||
);
|
||||
}
|
||||
const params = req.body.params || {};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Parse from 'parse/node';
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
const GraphQLConfigPath = '/graphql-config';
|
||||
|
||||
@@ -14,9 +15,9 @@ export class GraphQLRouter extends PromiseRouter {
|
||||
|
||||
async updateGraphQLConfig(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update the GraphQL config."
|
||||
"read-only masterKey isn't allowed to update the GraphQL config.",
|
||||
);
|
||||
}
|
||||
const data = await req.config.parseGraphQLController.updateGraphQLConfig(req.body?.params || {});
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import Parse from 'parse/node';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class PurgeRouter extends PromiseRouter {
|
||||
handlePurge(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to purge a schema."
|
||||
"read-only masterKey isn't allowed to purge a schema.",
|
||||
);
|
||||
}
|
||||
return req.config.database
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import { Parse } from 'parse/node';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class PushRouter extends PromiseRouter {
|
||||
mountRoutes() {
|
||||
@@ -9,9 +10,9 @@ export class PushRouter extends PromiseRouter {
|
||||
|
||||
static handlePOST(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to send push notifications."
|
||||
"read-only masterKey isn't allowed to send push notifications.",
|
||||
);
|
||||
}
|
||||
const pushController = req.config.pushController;
|
||||
|
||||
@@ -5,6 +5,7 @@ var Parse = require('parse/node').Parse,
|
||||
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
function classNameMismatchResponse(bodyClass, pathClass) {
|
||||
throw new Parse.Error(
|
||||
@@ -72,9 +73,9 @@ export const internalUpdateSchema = async (className, body, config) => {
|
||||
async function createSchema(req) {
|
||||
checkIfDefinedSchemasIsUsed(req);
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to create a schema."
|
||||
"read-only masterKey isn't allowed to create a schema.",
|
||||
);
|
||||
}
|
||||
if (req.params.className && req.body?.className) {
|
||||
@@ -94,9 +95,9 @@ async function createSchema(req) {
|
||||
function modifySchema(req) {
|
||||
checkIfDefinedSchemasIsUsed(req);
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update a schema."
|
||||
"read-only masterKey isn't allowed to update a schema.",
|
||||
);
|
||||
}
|
||||
if (req.body?.className && req.body.className != req.params.className) {
|
||||
@@ -109,9 +110,9 @@ function modifySchema(req) {
|
||||
|
||||
const deleteSchema = req => {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to delete a schema."
|
||||
"read-only masterKey isn't allowed to delete a schema.",
|
||||
);
|
||||
}
|
||||
if (!SchemaController.classNameIsValid(req.params.className)) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { promiseEnsureIdempotency } from '../middlewares';
|
||||
import RestWrite from '../RestWrite';
|
||||
import { logger } from '../logger';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class UsersRouter extends ClassesRouter {
|
||||
className() {
|
||||
@@ -171,7 +172,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
handleMe(req) {
|
||||
if (!req.info || !req.info.sessionToken) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const sessionToken = req.info.sessionToken;
|
||||
return rest
|
||||
@@ -186,7 +187,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
)
|
||||
.then(response => {
|
||||
if (!response.results || response.results.length == 0 || !response.results[0].user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
} else {
|
||||
const user = response.results[0].user;
|
||||
// Send token back on the login, because SDKs expect that.
|
||||
@@ -334,7 +335,10 @@ export class UsersRouter extends ClassesRouter {
|
||||
*/
|
||||
async handleLogInAs(req) {
|
||||
if (!req.auth.isMaster) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'master key is required');
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'master key is required',
|
||||
);
|
||||
}
|
||||
|
||||
const userId = req.body?.userId || req.query.userId;
|
||||
|
||||
@@ -6,12 +6,16 @@ const classesWithMasterOnlyAccess = [
|
||||
'_JobSchedule',
|
||||
'_Idempotency',
|
||||
];
|
||||
const { createSanitizedError } = require('./Error');
|
||||
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
function enforceRoleSecurity(method, className, auth) {
|
||||
if (className === '_Installation' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (method === 'delete' || method === 'find') {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Clients aren't allowed to perform the ${method} operation on the installation collection.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +25,18 @@ function enforceRoleSecurity(method, className, auth) {
|
||||
!auth.isMaster &&
|
||||
!auth.isMaintenance
|
||||
) {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the ${className} collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Clients aren't allowed to perform the ${method} operation on the ${className} collection.`
|
||||
);
|
||||
}
|
||||
|
||||
// readOnly masterKey is not allowed
|
||||
if (auth.isReadOnly && (method === 'delete' || method === 'create' || method === 'update')) {
|
||||
const error = `read-only masterKey isn't allowed to perform the ${method} operation.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`read-only masterKey isn't allowed to perform the ${method} operation.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,3 +81,4 @@ export class Connections {
|
||||
return this.sockets.size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { pathToRegexp } from 'path-to-regexp';
|
||||
import RedisStore from 'rate-limit-redis';
|
||||
import { createClient } from 'redis';
|
||||
import { BlockList, isIPv4 } from 'net';
|
||||
import { createSanitizedHttpError } from './Error';
|
||||
|
||||
export const DEFAULT_ALLOWED_HEADERS =
|
||||
'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control';
|
||||
@@ -501,8 +502,9 @@ export function handleParseErrors(err, req, res, next) {
|
||||
|
||||
export function enforceMasterKeyAccess(req, res, next) {
|
||||
if (!req.auth.isMaster) {
|
||||
res.status(403);
|
||||
res.end('{"error":"unauthorized: master key is required"}');
|
||||
const error = createSanitizedHttpError(403, 'unauthorized: master key is required');
|
||||
res.status(error.status);
|
||||
res.end(`{"error":"${error.message}"}`);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
@@ -510,10 +512,7 @@ export function enforceMasterKeyAccess(req, res, next) {
|
||||
|
||||
export function promiseEnforceMasterKeyAccess(request) {
|
||||
if (!request.auth.isMaster) {
|
||||
const error = new Error();
|
||||
error.status = 403;
|
||||
error.message = 'unauthorized: master key is required';
|
||||
throw error;
|
||||
throw createSanitizedHttpError(403, 'unauthorized: master key is required');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ var RestQuery = require('./RestQuery');
|
||||
var RestWrite = require('./RestWrite');
|
||||
var triggers = require('./triggers');
|
||||
const { enforceRoleSecurity } = require('./SharedRest');
|
||||
const { createSanitizedError } = require('./Error');
|
||||
|
||||
function checkTriggers(className, config, types) {
|
||||
return types.some(triggerType => {
|
||||
@@ -195,7 +196,7 @@ function del(config, auth, className, objectId, context) {
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
}
|
||||
var cacheAdapter = config.cacheController;
|
||||
@@ -326,7 +327,7 @@ function handleSessionMissingError(error, className, auth) {
|
||||
!auth.isMaster &&
|
||||
!auth.isMaintenance
|
||||
) {
|
||||
throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Insufficient auth.');
|
||||
throw createSanitizedError(Parse.Error.SESSION_MISSING, 'Insufficient auth.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user