From 8584f36395abd8d4307e3391d5337e25a7819a97 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 26 Jun 2022 21:30:09 +0200 Subject: [PATCH 1/2] fix --- spec/ParseLiveQuery.spec.js | 46 ++++++++++++++ src/Controllers/DatabaseController.js | 26 ++++++-- src/LiveQuery/ParseCloudCodePublisher.js | 3 + src/LiveQuery/ParseLiveQueryServer.js | 77 ++++++++++++++++++------ 4 files changed, 127 insertions(+), 25 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 906d5daf02..66a7360cb8 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -974,6 +974,52 @@ describe('ParseLiveQuery', function () { } }); + it('should strip out protected fields', async () => { + await reconfigureServer({ + liveQuery: { classNames: ['Test'] }, + startLiveQueryServer: true, + }); + const obj1 = new Parse.Object('Test'); + obj1.set('foo', 'foo'); + obj1.set('bar', 'bar'); + obj1.set('qux', 'qux'); + await obj1.save(); + const config = Config.get(Parse.applicationId); + const schemaController = await config.database.loadSchema(); + await schemaController.updateClass( + 'Test', + {}, + { + get: { '*': true }, + find: { '*': true }, + update: { '*': true }, + protectedFields: { + '*': ['foo'], + }, + } + ); + const object = await obj1.fetch(); + expect(object.get('foo')).toBe(undefined); + expect(object.get('bar')).toBeDefined(); + expect(object.get('qux')).toBeDefined(); + + const subscription = await new Parse.Query('Test').subscribe(); + await Promise.all([ + new Promise(resolve => { + subscription.on('update', (obj, original) => { + expect(obj.get('foo')).toBe(undefined); + expect(obj.get('bar')).toBeDefined(); + expect(obj.get('qux')).toBeDefined(); + expect(original.get('foo')).toBe(undefined); + expect(original.get('bar')).toBeDefined(); + expect(original.get('qux')).toBeDefined(); + resolve(); + }); + }), + obj1.save({ foo: 'abc' }), + ]); + }); + afterEach(async function (done) { const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); client.close(); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 93933fd9a3..40b34df87a 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -123,16 +123,28 @@ const filterSensitiveData = ( aclGroup: any[], auth: any, operation: any, - schema: SchemaController.SchemaController, + schema: SchemaController.SchemaController | any, className: string, protectedFields: null | Array, - object: any + object: any, + query: any = {} ) => { + if (!isMaster && !Array.isArray(protectedFields)) { + protectedFields = new DatabaseController().addProtectedFields( + schema, + className, + query, + aclGroup, + auth + ); + } + let userId = null; if (auth && auth.user) userId = auth.user.id; // replace protectedFields when using pointer-permissions - const perms = schema.getClassLevelPermissions(className); + const perms = + schema && schema.getClassLevelPermissions ? schema.getClassLevelPermissions(className) : {}; if (perms) { const isReadOperation = ['get', 'find'].indexOf(operation) > -1; @@ -1430,14 +1442,17 @@ class DatabaseController { } addProtectedFields( - schema: SchemaController.SchemaController, + schema: SchemaController.SchemaController | any, className: string, query: any = {}, aclGroup: any[] = [], auth: any = {}, queryOptions: FullQueryOptions = {} ): null | string[] { - const perms = schema.getClassLevelPermissions(className); + const perms = + schema && schema.getClassLevelPermissions + ? schema.getClassLevelPermissions(className) + : schema; if (!perms) return null; const protectedFields = perms.protectedFields; @@ -1746,3 +1761,4 @@ class DatabaseController { module.exports = DatabaseController; // Expose validateQuery for tests module.exports._validateQuery = validateQuery; +module.exports.filterSensitiveData = filterSensitiveData; diff --git a/src/LiveQuery/ParseCloudCodePublisher.js b/src/LiveQuery/ParseCloudCodePublisher.js index 85e95121fb..7d306d1b57 100644 --- a/src/LiveQuery/ParseCloudCodePublisher.js +++ b/src/LiveQuery/ParseCloudCodePublisher.js @@ -33,6 +33,9 @@ class ParseCloudCodePublisher { if (request.original) { message.originalParseObject = request.original._toFullJSON(); } + if (request.classLevelPermissions) { + message.classLevelPermissions = request.classLevelPermissions; + } this.parsePublisher.publish(type, JSON.stringify(message)); } } diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 7fb6418d25..658c5a35eb 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -20,6 +20,7 @@ import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; import LRU from 'lru-cache'; import UserRouter from '../Routers/UsersRouter'; +import DatabaseController from '../Controllers/DatabaseController'; class ParseLiveQueryServer { clients: Map; @@ -171,7 +172,7 @@ class ParseLiveQueryServer { }; return maybeRunAfterEventTrigger('afterEvent', className, res); }) - .then(() => { + .then(async () => { if (!res.sendEvent) { return; } @@ -179,14 +180,14 @@ class ParseLiveQueryServer { deletedParseObject = res.object.toJSON(); deletedParseObject.className = className; } - if ( - (deletedParseObject.className === '_User' || - deletedParseObject.className === '_Session') && - !client.hasMasterKey - ) { - delete deletedParseObject.sessionToken; - delete deletedParseObject.authData; - } + await this._filterSensitiveData( + classLevelPermissions, + res, + client, + requestId, + op, + subscription.query + ); client.pushDelete(requestId, deletedParseObject); }) .catch(error => { @@ -310,7 +311,7 @@ class ParseLiveQueryServer { return maybeRunAfterEventTrigger('afterEvent', className, res); }) .then( - () => { + async () => { if (!res.sendEvent) { return; } @@ -323,16 +324,14 @@ class ParseLiveQueryServer { originalParseObject = res.original.toJSON(); originalParseObject.className = res.original.className || className; } - if ( - (currentParseObject.className === '_User' || - currentParseObject.className === '_Session') && - !client.hasMasterKey - ) { - delete currentParseObject.sessionToken; - delete originalParseObject?.sessionToken; - delete currentParseObject.authData; - delete originalParseObject?.authData; - } + await this._filterSensitiveData( + classLevelPermissions, + res, + client, + requestId, + op, + subscription.query + ); const functionName = 'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1); if (client[functionName]) { @@ -532,6 +531,44 @@ class ParseLiveQueryServer { // return rolesQuery.find({useMasterKey:true}); } + async _filterSensitiveData( + classLevelPermissions: ?any, + res: any, + client: any, + requestId: number, + op: string, + query: any + ) { + const subscriptionInfo = client.getSubscriptionInfo(requestId); + const aclGroup = ['*']; + let clientAuth; + if (typeof subscriptionInfo !== 'undefined') { + const { userId, auth } = await this.getAuthForSessionToken(subscriptionInfo.sessionToken); + if (userId) { + aclGroup.push(userId); + } + clientAuth = auth; + } + const filter = obj => { + if (!obj) { + return; + } + return DatabaseController.filterSensitiveData( + client.hasMasterKey, + aclGroup, + clientAuth, + op, + classLevelPermissions, + res.object.className, + classLevelPermissions?.protectedFields, + obj, + query + ); + }; + res.object = filter(res.object); + res.original = filter(res.original); + } + _getCLPOperation(query: any) { return typeof query === 'object' && Object.keys(query).length == 1 && From deb517202f1c45d0aa582532644df88e455000f5 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Thu, 30 Jun 2022 11:35:10 +0200 Subject: [PATCH 2/2] fix2 --- src/Controllers/DatabaseController.js | 14 ++------------ src/LiveQuery/ParseLiveQueryServer.js | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 40b34df87a..a402290434 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -126,19 +126,8 @@ const filterSensitiveData = ( schema: SchemaController.SchemaController | any, className: string, protectedFields: null | Array, - object: any, - query: any = {} + object: any ) => { - if (!isMaster && !Array.isArray(protectedFields)) { - protectedFields = new DatabaseController().addProtectedFields( - schema, - className, - query, - aclGroup, - auth - ); - } - let userId = null; if (auth && auth.user) userId = auth.user.id; @@ -1756,6 +1745,7 @@ class DatabaseController { } static _validateQuery: any => void; + static filterSensitiveData: (boolean, any[], any, any, any, string, any[], any) => void; } module.exports = DatabaseController; diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 658c5a35eb..0e95476cc7 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -17,7 +17,7 @@ import { maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; -import { getCacheController } from '../Controllers'; +import { getCacheController, getDatabaseController } from '../Controllers'; import LRU from 'lru-cache'; import UserRouter from '../Routers/UsersRouter'; import DatabaseController from '../Controllers/DatabaseController'; @@ -553,6 +553,16 @@ class ParseLiveQueryServer { if (!obj) { return; } + let protectedFields = classLevelPermissions?.protectedFields || []; + if (!client.hasMasterKey && !Array.isArray(protectedFields)) { + protectedFields = getDatabaseController(this.config).addProtectedFields( + classLevelPermissions, + res.object.className, + query, + aclGroup, + clientAuth + ); + } return DatabaseController.filterSensitiveData( client.hasMasterKey, aclGroup, @@ -560,9 +570,8 @@ class ParseLiveQueryServer { op, classLevelPermissions, res.object.className, - classLevelPermissions?.protectedFields, - obj, - query + protectedFields, + obj ); }; res.object = filter(res.object);