Skip to content

Commit 199dfc1

Browse files
authored
fix: live query role cache does not clear when a user is added to a role (#8026)
1 parent 0cd902b commit 199dfc1

File tree

6 files changed

+108
-2
lines changed

6 files changed

+108
-2
lines changed

Diff for: spec/ParseLiveQuery.spec.js

+44
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,50 @@ describe('ParseLiveQuery', function () {
836836
}
837837
});
838838

839+
it('LiveQuery should work with changing role', async () => {
840+
await reconfigureServer({
841+
liveQuery: {
842+
classNames: ['Chat'],
843+
},
844+
startLiveQueryServer: true,
845+
});
846+
const user = new Parse.User();
847+
user.setUsername('username');
848+
user.setPassword('password');
849+
await user.signUp();
850+
851+
const role = new Parse.Role('Test', new Parse.ACL(user));
852+
await role.save();
853+
854+
const chatQuery = new Parse.Query('Chat');
855+
const subscription = await chatQuery.subscribe();
856+
subscription.on('create', () => {
857+
fail('should not call create as user is not part of role.');
858+
});
859+
860+
const object = new Parse.Object('Chat');
861+
const acl = new Parse.ACL();
862+
acl.setRoleReadAccess(role, true);
863+
object.setACL(acl);
864+
object.set({ foo: 'bar' });
865+
await object.save(null, { useMasterKey: true });
866+
role.getUsers().add(user);
867+
await new Promise(resolve => setTimeout(resolve, 1000));
868+
await role.save();
869+
await new Promise(resolve => setTimeout(resolve, 1000));
870+
object.set('foo', 'yolo');
871+
await Promise.all([
872+
new Promise(resolve => {
873+
subscription.on('update', obj => {
874+
expect(obj.get('foo')).toBe('yolo');
875+
expect(obj.getACL().toJSON()).toEqual({ 'role:Test': { read: true } });
876+
resolve();
877+
});
878+
}),
879+
object.save(null, { useMasterKey: true }),
880+
]);
881+
});
882+
839883
it('liveQuery on Session class', async done => {
840884
await reconfigureServer({
841885
liveQuery: { classNames: [Parse.Session] },

Diff for: src/Auth.js

+9
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,15 @@ Auth.prototype.cacheRoles = function () {
230230
return true;
231231
};
232232

233+
Auth.prototype.clearRoleCache = function (sessionToken) {
234+
if (!this.cacheController) {
235+
return false;
236+
}
237+
this.cacheController.role.del(this.user.id);
238+
this.cacheController.user.del(sessionToken);
239+
return true;
240+
};
241+
233242
Auth.prototype.getRolesByIds = async function (ins) {
234243
const results = [];
235244
// Build an OR query across all parentRoles

Diff for: src/Controllers/LiveQueryController.js

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ export class LiveQueryController {
5656
return false;
5757
}
5858

59+
clearCachedRoles(user: any) {
60+
if (!user) {
61+
return;
62+
}
63+
return this.liveQueryPublisher.onClearCachedRoles(user);
64+
}
65+
5966
_makePublisherRequest(currentObject: any, originalObject: any, classLevelPermissions: ?any): any {
6067
const req = {
6168
object: currentObject,

Diff for: src/LiveQuery/ParseCloudCodePublisher.js

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ class ParseCloudCodePublisher {
1919
this._onCloudCodeMessage(Parse.applicationId + 'afterDelete', request);
2020
}
2121

22+
onClearCachedRoles(user: Parse.Object) {
23+
this.parsePublisher.publish(
24+
Parse.applicationId + 'clearCache',
25+
JSON.stringify({ userId: user.id })
26+
);
27+
}
28+
2229
// Request is the request object from cloud code functions. request.object is a ParseObject.
2330
_onCloudCodeMessage(type: string, request: any): void {
2431
logger.verbose(

Diff for: src/LiveQuery/ParseLiveQueryServer.js

+38-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ import { ParsePubSub } from './ParsePubSub';
1010
import SchemaController from '../Controllers/SchemaController';
1111
import _ from 'lodash';
1212
import { v4 as uuidv4 } from 'uuid';
13-
import { runLiveQueryEventHandlers, getTrigger, runTrigger, resolveError, toJSONwithObjects } from '../triggers';
13+
import {
14+
runLiveQueryEventHandlers,
15+
getTrigger,
16+
runTrigger,
17+
resolveError,
18+
toJSONwithObjects,
19+
} from '../triggers';
1420
import { getAuthForSessionToken, Auth } from '../Auth';
1521
import { getCacheController } from '../Controllers';
1622
import LRU from 'lru-cache';
@@ -71,6 +77,7 @@ class ParseLiveQueryServer {
7177
this.subscriber = ParsePubSub.createSubscriber(config);
7278
this.subscriber.subscribe(Parse.applicationId + 'afterSave');
7379
this.subscriber.subscribe(Parse.applicationId + 'afterDelete');
80+
this.subscriber.subscribe(Parse.applicationId + 'clearCache');
7481
// Register message handler for subscriber. When publisher get messages, it will publish message
7582
// to the subscribers and the handler will be called.
7683
this.subscriber.on('message', (channel, messageStr) => {
@@ -82,6 +89,10 @@ class ParseLiveQueryServer {
8289
logger.error('unable to parse message', messageStr, e);
8390
return;
8491
}
92+
if (channel === Parse.applicationId + 'clearCache') {
93+
this._clearCachedRoles(message.userId);
94+
return;
95+
}
8596
this._inflateParseObject(message);
8697
if (channel === Parse.applicationId + 'afterSave') {
8798
this._onAfterSave(message);
@@ -468,6 +479,32 @@ class ParseLiveQueryServer {
468479
return matchesQuery(parseObject, subscription.query);
469480
}
470481

482+
async _clearCachedRoles(userId: string) {
483+
try {
484+
const validTokens = await new Parse.Query(Parse.Session)
485+
.equalTo('user', Parse.User.createWithoutData(userId))
486+
.find({ useMasterKey: true });
487+
await Promise.all(
488+
validTokens.map(async token => {
489+
const sessionToken = token.get('sessionToken');
490+
const authPromise = this.authCache.get(sessionToken);
491+
if (!authPromise) {
492+
return;
493+
}
494+
const [auth1, auth2] = await Promise.all([
495+
authPromise,
496+
getAuthForSessionToken({ cacheController: this.cacheController, sessionToken }),
497+
]);
498+
auth1.auth?.clearRoleCache(sessionToken);
499+
auth2.auth?.clearRoleCache(sessionToken);
500+
this.authCache.del(sessionToken);
501+
})
502+
);
503+
} catch (e) {
504+
logger.verbose(`Could not clear role cache. ${e}`);
505+
}
506+
}
507+
471508
getAuthForSessionToken(sessionToken: ?string): Promise<{ auth: ?Auth, userId: ?string }> {
472509
if (!sessionToken) {
473510
return Promise.resolve({});
@@ -574,7 +611,6 @@ class ParseLiveQueryServer {
574611
if (!acl_has_roles) {
575612
return false;
576613
}
577-
578614
const roleNames = await auth.getUserRoles();
579615
// Finally, see if any of the user's roles allow them read access
580616
for (const role of roleNames) {

Diff for: src/RestWrite.js

+3
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,9 @@ RestWrite.prototype.runDatabaseOperation = function () {
13261326

13271327
if (this.className === '_Role') {
13281328
this.config.cacheController.role.clear();
1329+
if (this.config.liveQueryController) {
1330+
this.config.liveQueryController.clearCachedRoles(this.auth.user);
1331+
}
13291332
}
13301333

13311334
if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) {

0 commit comments

Comments
 (0)