fix: Server internal error details leaking in error messages returned to clients (#9937)

This commit is contained in:
Lucas Coratger
2025-11-23 13:51:42 +01:00
committed by GitHub
parent 38c9d2e359
commit 50edb5ab4b
35 changed files with 390 additions and 125 deletions

View File

@@ -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();
}
);

View File

@@ -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();
});
});

View File

@@ -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();
});
});

View File

@@ -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);

View File

@@ -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();
});
});

View File

@@ -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'));
}
});
});

View File

@@ -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();
});
});

View File

@@ -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();
}
);

View File

@@ -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);

View File

@@ -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'
)
),
]);

View File

@@ -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();
}
);

View File

@@ -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();
}
});

View File

@@ -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();
});
});

View File

@@ -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();
}
)

View File

@@ -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', () => {