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
This commit is contained in:
Jeremy May
2017-09-09 13:26:18 -04:00
committed by Florent Vilmart
parent 52c4dd3704
commit 121d151af9
3 changed files with 103 additions and 5 deletions

View File

@@ -12,7 +12,7 @@ describe('ParseLiveQueryServer', function() {
var mockParseWebSocketServer = jasmine.createSpy('ParseWebSocketServer'); var mockParseWebSocketServer = jasmine.createSpy('ParseWebSocketServer');
jasmine.mockLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer', mockParseWebSocketServer); jasmine.mockLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer', mockParseWebSocketServer);
// Mock Client // Mock Client
var mockClient = function() { var mockClient = function(id, socket, hasMasterKey) {
this.pushConnect = jasmine.createSpy('pushConnect'); this.pushConnect = jasmine.createSpy('pushConnect');
this.pushSubscribe = jasmine.createSpy('pushSubscribe'); this.pushSubscribe = jasmine.createSpy('pushSubscribe');
this.pushUnsubscribe = jasmine.createSpy('pushUnsubscribe'); this.pushUnsubscribe = jasmine.createSpy('pushUnsubscribe');
@@ -24,6 +24,7 @@ describe('ParseLiveQueryServer', function() {
this.addSubscriptionInfo = jasmine.createSpy('addSubscriptionInfo'); this.addSubscriptionInfo = jasmine.createSpy('addSubscriptionInfo');
this.getSubscriptionInfo = jasmine.createSpy('getSubscriptionInfo'); this.getSubscriptionInfo = jasmine.createSpy('getSubscriptionInfo');
this.deleteSubscriptionInfo = jasmine.createSpy('deleteSubscriptionInfo'); this.deleteSubscriptionInfo = jasmine.createSpy('deleteSubscriptionInfo');
this.hasMasterKey = hasMasterKey;
} }
mockClient.pushError = jasmine.createSpy('pushError'); mockClient.pushError = jasmine.createSpy('pushError');
jasmine.mockLibrary('../src/LiveQuery/Client', 'Client', mockClient); jasmine.mockLibrary('../src/LiveQuery/Client', 'Client', mockClient);
@@ -1018,6 +1019,89 @@ describe('ParseLiveQueryServer', function() {
expect(parseLiveQueryServer._validateKeys(request, parseLiveQueryServer.keyPairs)).toBeTruthy(); 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(){ afterEach(function(){
jasmine.restoreLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer'); jasmine.restoreLibrary('../src/LiveQuery/ParseWebSocketServer', 'ParseWebSocketServer');
jasmine.restoreLibrary('../src/LiveQuery/Client', 'Client'); jasmine.restoreLibrary('../src/LiveQuery/Client', 'Client');

View File

@@ -8,6 +8,7 @@ const dafaultFields = ['className', 'objectId', 'updatedAt', 'createdAt', 'ACL']
class Client { class Client {
id: number; id: number;
parseWebSocket: any; parseWebSocket: any;
hasMasterKey: boolean;
userId: string; userId: string;
roles: Array<string>; roles: Array<string>;
subscriptionInfos: Object; subscriptionInfos: Object;
@@ -20,9 +21,10 @@ class Client {
pushDelete: Function; pushDelete: Function;
pushLeave: Function; pushLeave: Function;
constructor(id: number, parseWebSocket: any) { constructor(id: number, parseWebSocket: any, hasMasterKey: boolean) {
this.id = id; this.id = id;
this.parseWebSocket = parseWebSocket; this.parseWebSocket = parseWebSocket;
this.hasMasterKey = hasMasterKey;
this.roles = []; this.roles = [];
this.subscriptionInfos = new Map(); this.subscriptionInfos = new Map();
this.pushConnect = this._pushEvent('connected'); this.pushConnect = this._pushEvent('connected');

View File

@@ -310,8 +310,8 @@ class ParseLiveQueryServer {
} }
_matchesACL(acl: any, client: any, requestId: number): any { _matchesACL(acl: any, client: any, requestId: number): any {
// If ACL is undefined or null, or ACL has public read access, return true directly // Return true directly if ACL isn't present, ACL is public read, or client has master key
if (!acl || acl.getPublicReadAccess()) { if (!acl || acl.getPublicReadAccess() || client.hasMasterKey) {
return Parse.Promise.as(true); return Parse.Promise.as(true);
} }
// Check subscription sessionToken matches ACL first // Check subscription sessionToken matches ACL first
@@ -403,7 +403,8 @@ class ParseLiveQueryServer {
logger.error('Key in request is not valid'); logger.error('Key in request is not valid');
return; 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; parseWebsocket.clientId = this.clientId;
this.clientId += 1; this.clientId += 1;
this.clients.set(parseWebsocket.clientId, client); this.clients.set(parseWebsocket.clientId, client);
@@ -411,6 +412,17 @@ class ParseLiveQueryServer {
client.pushConnect(); 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 { _validateKeys(request: any, validKeyPairs: any): boolean {
if (!validKeyPairs || validKeyPairs.size == 0) { if (!validKeyPairs || validKeyPairs.size == 0) {
return true; return true;