@@ -280,6 +280,71 @@ describe('Cloud Code', () => {
280
280
expect ( findSpy ) . not . toHaveBeenCalled ( ) ;
281
281
} ) ;
282
282
} ) ;
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
+ } ) ;
283
348
const { maybeRunAfterFindTrigger } = require ( '../lib/triggers' ) ;
284
349
285
350
describe ( 'maybeRunAfterFindTrigger - direct function tests' , ( ) => {
0 commit comments