Skip to content

Commit af55cd1

Browse files
aaron-blondeau-doseflovilmart
authored andcommitted
Add role based ACL checks to LiveQuery (#2893)
* Add acl role check to _matchesACL, start adding tests. * Add tests for ACL role checks in LiveQueryServer. * Switch to arrow functions, add immutabalized code from @acinader, swap for loop style.
1 parent 0faaec3 commit af55cd1

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed

spec/ParseLiveQueryServer.spec.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,116 @@ describe('ParseLiveQueryServer', function() {
834834
});
835835
});
836836

837+
it('won\'t match ACL that doesn\'t have public read or any roles', function(done){
838+
839+
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
840+
var acl = new Parse.ACL();
841+
acl.setPublicReadAccess(false);
842+
var client = {
843+
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
844+
sessionToken: 'sessionToken'
845+
})
846+
};
847+
var requestId = 0;
848+
849+
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
850+
expect(isMatched).toBe(false);
851+
done();
852+
});
853+
854+
});
855+
856+
it('won\'t match non-public ACL with role when there is no user', function(done){
857+
858+
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
859+
var acl = new Parse.ACL();
860+
acl.setPublicReadAccess(false);
861+
acl.setRoleReadAccess("livequery", true);
862+
var client = {
863+
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
864+
})
865+
};
866+
var requestId = 0;
867+
868+
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
869+
expect(isMatched).toBe(false);
870+
done();
871+
});
872+
873+
});
874+
875+
it('won\'t match ACL with role based read access set to false', function(done){
876+
877+
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
878+
var acl = new Parse.ACL();
879+
acl.setPublicReadAccess(false);
880+
acl.setRoleReadAccess("liveQueryRead", false);
881+
var client = {
882+
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
883+
sessionToken: 'sessionToken'
884+
})
885+
};
886+
var requestId = 0;
887+
888+
spyOn(Parse, "Query").and.callFake(function(){
889+
return {
890+
equalTo(relation, value) {
891+
// Nothing to do here
892+
},
893+
find() {
894+
//Return a role with the name "liveQueryRead" as that is what was set on the ACL
895+
var liveQueryRole = new Parse.Role();
896+
liveQueryRole.set('name', 'liveQueryRead');
897+
return [
898+
liveQueryRole
899+
];
900+
}
901+
}
902+
});
903+
904+
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
905+
expect(isMatched).toBe(false);
906+
done();
907+
});
908+
909+
});
910+
911+
it('will match ACL with role based read access set to true', function(done){
912+
913+
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
914+
var acl = new Parse.ACL();
915+
acl.setPublicReadAccess(false);
916+
acl.setRoleReadAccess("liveQueryRead", true);
917+
var client = {
918+
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
919+
sessionToken: 'sessionToken'
920+
})
921+
};
922+
var requestId = 0;
923+
924+
spyOn(Parse, "Query").and.callFake(function(){
925+
return {
926+
equalTo(relation, value) {
927+
// Nothing to do here
928+
},
929+
find() {
930+
//Return a role with the name "liveQueryRead" as that is what was set on the ACL
931+
var liveQueryRole = new Parse.Role();
932+
liveQueryRole.set('name', 'liveQueryRead');
933+
return [
934+
liveQueryRole
935+
];
936+
}
937+
}
938+
});
939+
940+
parseLiveQueryServer._matchesACL(acl, client, requestId).then(function(isMatched) {
941+
expect(isMatched).toBe(true);
942+
done();
943+
});
944+
945+
});
946+
837947
it('can validate key when valid key is provided', function() {
838948
var parseLiveQueryServer = new ParseLiveQueryServer({}, {
839949
keyPairs: {

src/LiveQuery/ParseLiveQueryServer.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,64 @@ class ParseLiveQueryServer {
327327
if (isSubscriptionSessionTokenMatched) {
328328
return Parse.Promise.as(true);
329329
}
330+
331+
// Check if the user has any roles that match the ACL
332+
return new Parse.Promise((resolve, reject) => {
333+
334+
// Resolve false right away if the acl doesn't have any roles
335+
const acl_has_roles = Object.keys(acl.permissionsById).some(key => key.startsWith("role:"));
336+
if (!acl_has_roles) {
337+
return resolve(false);
338+
}
339+
340+
this.sessionTokenCache.getUserId(subscriptionSessionToken)
341+
.then((userId) => {
342+
343+
// Pass along a null if there is no user id
344+
if (!userId) {
345+
return Parse.Promise.as(null);
346+
}
347+
348+
// Prepare a user object to query for roles
349+
// To eliminate a query for the user, create one locally with the id
350+
var user = new Parse.User();
351+
user.id = userId;
352+
return user;
353+
354+
})
355+
.then((user) => {
356+
357+
// Pass along an empty array (of roles) if no user
358+
if (!user) {
359+
return Parse.Promise.as([]);
360+
}
361+
362+
// Then get the user's roles
363+
var rolesQuery = new Parse.Query(Parse.Role);
364+
rolesQuery.equalTo("users", user);
365+
return rolesQuery.find();
366+
}).
367+
then((roles) => {
368+
369+
// Finally, see if any of the user's roles allow them read access
370+
for (let role of roles) {
371+
if (acl.getRoleReadAccess(role)) {
372+
return resolve(true);
373+
}
374+
}
375+
resolve(false);
376+
})
377+
.catch((error) => {
378+
reject(error);
379+
});
380+
381+
});
382+
}).then((isRoleMatched) => {
383+
384+
if(isRoleMatched) {
385+
return Parse.Promise.as(true);
386+
}
387+
330388
// Check client sessionToken matches ACL
331389
let clientSessionToken = client.sessionToken;
332390
return this.sessionTokenCache.getUserId(clientSessionToken).then((userId) => {

0 commit comments

Comments
 (0)