From 121d151af9cfa40f70f8fb84c06456077ed07976 Mon Sep 17 00:00:00 2001 From: Jeremy May Date: Sat, 9 Sep 2017 13:26:18 -0400 Subject: [PATCH] Add master key override to live query ACL checks (#4133) * Add master key override to live query ACL checks * Fix mockClient so masterKey tests work correctly --- spec/ParseLiveQueryServer.spec.js | 86 ++++++++++++++++++++++++++- src/LiveQuery/Client.js | 4 +- src/LiveQuery/ParseLiveQueryServer.js | 18 +++++- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 5ad91892..12d3a3f7 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -12,7 +12,7 @@ describe('ParseLiveQueryServer', function() { var mockParseWebSocketServer = jasmine.createSpy('ParseWebSocketServer'); jasmine.mockLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer', mockParseWebSocketServer); // Mock Client - var mockClient = function() { + var mockClient = function(id, socket, hasMasterKey) { this.pushConnect = jasmine.createSpy('pushConnect'); this.pushSubscribe = jasmine.createSpy('pushSubscribe'); this.pushUnsubscribe = jasmine.createSpy('pushUnsubscribe'); @@ -24,6 +24,7 @@ describe('ParseLiveQueryServer', function() { this.addSubscriptionInfo = jasmine.createSpy('addSubscriptionInfo'); this.getSubscriptionInfo = jasmine.createSpy('getSubscriptionInfo'); this.deleteSubscriptionInfo = jasmine.createSpy('deleteSubscriptionInfo'); + this.hasMasterKey = hasMasterKey; } mockClient.pushError = jasmine.createSpy('pushError'); jasmine.mockLibrary('../src/LiveQuery/Client', 'Client', mockClient); @@ -1018,6 +1019,89 @@ describe('ParseLiveQueryServer', function() { expect(parseLiveQueryServer._validateKeys(request, parseLiveQueryServer.keyPairs)).toBeTruthy(); }); + it('can validate client has master key when valid', function() { + var parseLiveQueryServer = new ParseLiveQueryServer({}, { + keyPairs: { + masterKey: 'test' + } + }); + var request = { + masterKey: 'test' + }; + + expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).toBeTruthy(); + }); + + it('can validate client doesn\'t have master key when invalid', function() { + var parseLiveQueryServer = new ParseLiveQueryServer({}, { + keyPairs: { + masterKey: 'test' + } + }); + var request = { + masterKey: 'notValid' + }; + + expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).not.toBeTruthy(); + }); + + it('can validate client doesn\'t have master key when not provided', function() { + var parseLiveQueryServer = new ParseLiveQueryServer({}, { + keyPairs: { + masterKey: 'test' + } + }); + + expect(parseLiveQueryServer._hasMasterKey({}, parseLiveQueryServer.keyPairs)).not.toBeTruthy(); + }); + + it('can validate client doesn\'t have master key when validKeyPairs is empty', function() { + var parseLiveQueryServer = new ParseLiveQueryServer({}, {}); + var request = { + masterKey: 'test' + }; + + expect(parseLiveQueryServer._hasMasterKey(request, parseLiveQueryServer.keyPairs)).not.toBeTruthy(); + }); + + it('will match non-public ACL when client has master key', function(done){ + + var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {}); + var acl = new Parse.ACL(); + acl.setPublicReadAccess(false); + var client = { + getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({ + }), + hasMasterKey: true + }; + var requestId = 0; + + parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) { + expect(isMatched).toBe(true); + done(); + }); + + }); + + it('won\'t match non-public ACL when client has no master key', function(done){ + + var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {}); + var acl = new Parse.ACL(); + acl.setPublicReadAccess(false); + var client = { + getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({ + }), + hasMasterKey: false + }; + var requestId = 0; + + parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) { + expect(isMatched).toBe(false); + done(); + }); + + }); + afterEach(function(){ jasmine.restoreLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer'); jasmine.restoreLibrary('../src/LiveQuery/Client', 'Client'); diff --git a/src/LiveQuery/Client.js b/src/LiveQuery/Client.js index 1b5b49b7..4c88635d 100644 --- a/src/LiveQuery/Client.js +++ b/src/LiveQuery/Client.js @@ -8,6 +8,7 @@ const dafaultFields = ['className', 'objectId', 'updatedAt', 'createdAt', 'ACL'] class Client { id: number; parseWebSocket: any; + hasMasterKey: boolean; userId: string; roles: Array; subscriptionInfos: Object; @@ -20,9 +21,10 @@ class Client { pushDelete: Function; pushLeave: Function; - constructor(id: number, parseWebSocket: any) { + constructor(id: number, parseWebSocket: any, hasMasterKey: boolean) { this.id = id; this.parseWebSocket = parseWebSocket; + this.hasMasterKey = hasMasterKey; this.roles = []; this.subscriptionInfos = new Map(); this.pushConnect = this._pushEvent('connected'); diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index f02e5f85..1bc6e079 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -310,8 +310,8 @@ class ParseLiveQueryServer { } _matchesACL(acl: any, client: any, requestId: number): any { - // If ACL is undefined or null, or ACL has public read access, return true directly - if (!acl || acl.getPublicReadAccess()) { + // Return true directly if ACL isn't present, ACL is public read, or client has master key + if (!acl || acl.getPublicReadAccess() || client.hasMasterKey) { return Parse.Promise.as(true); } // Check subscription sessionToken matches ACL first @@ -403,7 +403,8 @@ class ParseLiveQueryServer { logger.error('Key in request is not valid'); return; } - const client = new Client(this.clientId, parseWebsocket); + const hasMasterKey = this._hasMasterKey(request, this.keyPairs); + const client = new Client(this.clientId, parseWebsocket, hasMasterKey); parseWebsocket.clientId = this.clientId; this.clientId += 1; this.clients.set(parseWebsocket.clientId, client); @@ -411,6 +412,17 @@ class ParseLiveQueryServer { client.pushConnect(); } + _hasMasterKey(request: any, validKeyPairs: any): boolean { + if(!validKeyPairs || validKeyPairs.size == 0 || + !validKeyPairs.has("masterKey")) { + return false; + } + if(!request || !request.hasOwnProperty("masterKey")) { + return false; + } + return request.masterKey === validKeyPairs.get("masterKey"); + } + _validateKeys(request: any, validKeyPairs: any): boolean { if (!validKeyPairs || validKeyPairs.size == 0) { return true;