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..a402290434 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -123,7 +123,7 @@ const filterSensitiveData = ( aclGroup: any[], auth: any, operation: any, - schema: SchemaController.SchemaController, + schema: SchemaController.SchemaController | any, className: string, protectedFields: null | Array, object: any @@ -132,7 +132,8 @@ const filterSensitiveData = ( 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 +1431,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; @@ -1741,8 +1745,10 @@ class DatabaseController { } static _validateQuery: any => void; + static filterSensitiveData: (boolean, any[], any, any, any, string, any[], any) => void; } 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..0e95476cc7 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -17,9 +17,10 @@ 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'; 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,53 @@ 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; + } + 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, + clientAuth, + op, + classLevelPermissions, + res.object.className, + protectedFields, + obj + ); + }; + res.object = filter(res.object); + res.original = filter(res.original); + } + _getCLPOperation(query: any) { return typeof query === 'object' && Object.keys(query).length == 1 &&