Skip to content

Commit 840a3e4

Browse files
committed
feat: Add beforeFind security tests for object visibility and protected fields
1 parent 83a918b commit 840a3e4

File tree

1 file changed

+65
-0
lines changed

1 file changed

+65
-0
lines changed

spec/CloudCode.spec.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,71 @@ describe('Cloud Code', () => {
280280
expect(findSpy).not.toHaveBeenCalled();
281281
});
282282
});
283+
284+
describe('beforeFind security with returned objects', () => {
285+
it('should not expose objects not readable by current user', async () => {
286+
const userA = new Parse.User();
287+
userA.setUsername('userA');
288+
userA.setPassword('passA');
289+
await userA.signUp();
290+
291+
const userB = new Parse.User();
292+
userB.setUsername('userB');
293+
userB.setPassword('passB');
294+
await userB.signUp();
295+
296+
// Create an object readable only by userB
297+
const acl = new Parse.ACL();
298+
acl.setPublicReadAccess(false);
299+
acl.setPublicWriteAccess(false);
300+
acl.setReadAccess(userB.id, true);
301+
acl.setWriteAccess(userB.id, true);
302+
303+
const secret = new Parse.Object('SecretDoc');
304+
secret.set('title', 'top');
305+
secret.set('content', 'classified');
306+
secret.setACL(acl);
307+
await secret.save(null, { sessionToken: userB.getSessionToken() });
308+
309+
Parse.Cloud.beforeFind('SecretDoc', () => {
310+
return [secret];
311+
});
312+
313+
// Query as userA should NOT see the secret
314+
const q = new Parse.Query('SecretDoc');
315+
const results = await q.find({ sessionToken: userA.getSessionToken() });
316+
expect(results.length).toBe(0);
317+
});
318+
319+
it('should apply protectedFields masking after re-filtering', async () => {
320+
// Configure protectedFields for SecretMask: mask `secretField` for everyone
321+
const protectedFields = { SecretMask: { '*': ['secretField'] } };
322+
await reconfigureServer({ protectedFields });
323+
324+
const user = new Parse.User();
325+
user.setUsername('pfUser');
326+
user.setPassword('pfPass');
327+
await user.signUp();
328+
329+
// Object is publicly readable but has a protected field
330+
const doc = new Parse.Object('SecretMask');
331+
doc.set('name', 'visible');
332+
doc.set('secretField', 'hiddenValue');
333+
await doc.save(null, { useMasterKey: true });
334+
335+
Parse.Cloud.beforeFind('SecretMask', () => {
336+
return [doc];
337+
});
338+
339+
// Query as normal user; after re-filtering, secretField should be removed
340+
const res = await new Parse.Query('SecretMask').first({ sessionToken: user.getSessionToken() });
341+
expect(res).toBeDefined();
342+
expect(res.get('name')).toBe('visible');
343+
expect(res.get('secretField')).toBeUndefined();
344+
const json = res.toJSON();
345+
expect(Object.prototype.hasOwnProperty.call(json, 'secretField')).toBeFalse();
346+
});
347+
});
283348
const { maybeRunAfterFindTrigger } = require('../lib/triggers');
284349

285350
describe('maybeRunAfterFindTrigger - direct function tests', () => {

0 commit comments

Comments
 (0)