From e66852177e2c7bc310efcaf795c171f7637f4fe6 Mon Sep 17 00:00:00 2001 From: Dobbias Nan Date: Mon, 5 Aug 2019 16:39:21 +0200 Subject: [PATCH] fixed protectedFields not working based on users for custom classes and added tests --- spec/ParseACL.spec.js | 139 ++++++++++++++++++++++++++ src/Controllers/DatabaseController.js | 24 ++++- 2 files changed, 160 insertions(+), 3 deletions(-) diff --git a/spec/ParseACL.spec.js b/spec/ParseACL.spec.js index 80e5a40081..a7a88e3f0b 100644 --- a/spec/ParseACL.spec.js +++ b/spec/ParseACL.spec.js @@ -931,4 +931,143 @@ describe('Parse.ACL', () => { rest.create(config, auth.nobody(config), '_User', anonUser); }); + + it('acl an object with read access by one user allows reading protectedFields by the same user', async done => { + // Add a protected field to the TestObject class + await reconfigureServer({ + protectedFields: { + TestObject: { + '*': ['protectedInfo'], + }, + }, + }); + + // Create an object readable by Alice. + const user = new Parse.User(); + user.set('username', 'alice'); + user.set('password', 'wonderland'); + await user.signUp(); + + const object = new TestObject(); + object.set({ + protectedInfo: 'IAmASecret', + }); + const acl = new Parse.ACL(); + acl.setReadAccess(user.id, true); + object.setACL(acl); + await object.save(); + + // Check if the logged in user can read the protected fields + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const objectRefetched = await query.find(); + equal(objectRefetched.length, 1); + equal(objectRefetched[0].get('protectedInfo'), 'IAmASecret'); + done(); + }); + + it('acl an object with read access by one user disallows reading protectedFields by another user', async done => { + // Add a protected field to the TestObject class + await reconfigureServer({ + protectedFields: { + TestObject: { + '*': ['protectedInfo'], + }, + }, + }); + + // Create an object readable by Alice. + let user = new Parse.User(); + user.set('username', 'alice'); + user.set('password', 'wonderland'); + await user.signUp(); + + const object = new TestObject(); + object.set({ + protectedInfo: 'IAmASecret', + }); + const acl = new Parse.ACL(); + acl.setPublicReadAccess(true); + acl.setReadAccess(user.id, true); + object.setACL(acl); + await object.save(); + await Parse.User.logOut(); + + // Signin as an user without ACL read access + user = new Parse.User(); + user.set('username', 'alice2'); + user.set('password', 'wonderland2'); + await user.signUp(); + + // Check if the logged in user can read the protected fields + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const objectRefetched = await query.find(); + equal(objectRefetched.length, 1); + equal(objectRefetched[0].get('protectedInfo'), undefined); + done(); + }); + + it('acl an object with public access and an singedIn user does not allow reading protectedFields', async done => { + // Add a protected field to the TestObject class + await reconfigureServer({ + protectedFields: { + TestObject: { + '*': ['protectedInfo'], + }, + }, + }); + + // Create a dummy user + const user = new Parse.User(); + user.set('username', 'alice'); + user.set('password', 'wonderland'); + await user.signUp(); + + const object = new TestObject(); + object.set({ + protectedInfo: 'IAmASecret', + }); + const acl = new Parse.ACL(); + acl.setPublicReadAccess(true); + object.setACL(acl); + await object.save(); + + // Check if the dummy user can read the protected fields + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const objectRefetched = await query.find(); + equal(objectRefetched.length, 1); + equal(objectRefetched[0].get('protectedInfo'), undefined); + done(); + }); + + it('acl an object with public access does not allow reading protectedFields', async done => { + // Add a protected field to the TestObject class + await reconfigureServer({ + protectedFields: { + TestObject: { + '*': ['protectedInfo'], + }, + }, + }); + + // Create an object with public read access + const object = new TestObject(); + object.set({ + protectedInfo: 'IAmASecret', + }); + const acl = new Parse.ACL(); + acl.setPublicReadAccess(true); + object.setACL(acl); + await object.save(); + + // Check if the oublic fields can be read publicly + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const objectRefetched = await query.find(); + equal(objectRefetched.length, 1); + equal(objectRefetched[0].get('protectedInfo'), undefined); + done(); + }); }); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 0dc89c412a..51c991a5ad 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -198,7 +198,24 @@ const filterSensitiveData = ( protectedFields, object ) => { - protectedFields && protectedFields.forEach(k => delete object[k]); + // Get all users with read access + if (protectedFields && protectedFields.length > 0) { + const userACLs = aclGroup.filter(acl => { + return acl.indexOf('role:') != 0 && acl != '*'; + }); + + var objectCopyWithClassName = {}; + Object.assign(objectCopyWithClassName, object); + objectCopyWithClassName.className = className; + + if ( + userACLs.length !== 1 || + !Parse.Object.fromJSON(objectCopyWithClassName) + .getACL() + .getReadAccess(userACLs[0]) + ) + protectedFields.forEach(k => delete object[k]); + } if (className !== '_User') { return object; @@ -1315,8 +1332,9 @@ class DatabaseController { query, aclGroup ); - // ProtectedFields is generated before executing the query so we - // can optimize the query using Mongo Projection at a later stage. + // Don't use a Mongo Projection for removing the protectedFields from the query. + // The retrieval of the data could be allowed to the user through whitelisting by + // a read ACL. This is determined after querying. protectedFields = this.addProtectedFields( schemaController, className,