fix: live query role cache does not clear when a user is added to a role (#8026)
This commit is contained in:
@@ -836,6 +836,50 @@ describe('ParseLiveQuery', function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('LiveQuery should work with changing role', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
liveQuery: {
|
||||||
|
classNames: ['Chat'],
|
||||||
|
},
|
||||||
|
startLiveQueryServer: true,
|
||||||
|
});
|
||||||
|
const user = new Parse.User();
|
||||||
|
user.setUsername('username');
|
||||||
|
user.setPassword('password');
|
||||||
|
await user.signUp();
|
||||||
|
|
||||||
|
const role = new Parse.Role('Test', new Parse.ACL(user));
|
||||||
|
await role.save();
|
||||||
|
|
||||||
|
const chatQuery = new Parse.Query('Chat');
|
||||||
|
const subscription = await chatQuery.subscribe();
|
||||||
|
subscription.on('create', () => {
|
||||||
|
fail('should not call create as user is not part of role.');
|
||||||
|
});
|
||||||
|
|
||||||
|
const object = new Parse.Object('Chat');
|
||||||
|
const acl = new Parse.ACL();
|
||||||
|
acl.setRoleReadAccess(role, true);
|
||||||
|
object.setACL(acl);
|
||||||
|
object.set({ foo: 'bar' });
|
||||||
|
await object.save(null, { useMasterKey: true });
|
||||||
|
role.getUsers().add(user);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
await role.save();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
object.set('foo', 'yolo');
|
||||||
|
await Promise.all([
|
||||||
|
new Promise(resolve => {
|
||||||
|
subscription.on('update', obj => {
|
||||||
|
expect(obj.get('foo')).toBe('yolo');
|
||||||
|
expect(obj.getACL().toJSON()).toEqual({ 'role:Test': { read: true } });
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
object.save(null, { useMasterKey: true }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('liveQuery on Session class', async done => {
|
it('liveQuery on Session class', async done => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
liveQuery: { classNames: [Parse.Session] },
|
liveQuery: { classNames: [Parse.Session] },
|
||||||
|
|||||||
@@ -230,6 +230,15 @@ Auth.prototype.cacheRoles = function () {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Auth.prototype.clearRoleCache = function (sessionToken) {
|
||||||
|
if (!this.cacheController) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.cacheController.role.del(this.user.id);
|
||||||
|
this.cacheController.user.del(sessionToken);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
Auth.prototype.getRolesByIds = async function (ins) {
|
Auth.prototype.getRolesByIds = async function (ins) {
|
||||||
const results = [];
|
const results = [];
|
||||||
// Build an OR query across all parentRoles
|
// Build an OR query across all parentRoles
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ export class LiveQueryController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCachedRoles(user: any) {
|
||||||
|
if (!user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.liveQueryPublisher.onClearCachedRoles(user);
|
||||||
|
}
|
||||||
|
|
||||||
_makePublisherRequest(currentObject: any, originalObject: any, classLevelPermissions: ?any): any {
|
_makePublisherRequest(currentObject: any, originalObject: any, classLevelPermissions: ?any): any {
|
||||||
const req = {
|
const req = {
|
||||||
object: currentObject,
|
object: currentObject,
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ class ParseCloudCodePublisher {
|
|||||||
this._onCloudCodeMessage(Parse.applicationId + 'afterDelete', request);
|
this._onCloudCodeMessage(Parse.applicationId + 'afterDelete', request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClearCachedRoles(user: Parse.Object) {
|
||||||
|
this.parsePublisher.publish(
|
||||||
|
Parse.applicationId + 'clearCache',
|
||||||
|
JSON.stringify({ userId: user.id })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Request is the request object from cloud code functions. request.object is a ParseObject.
|
// Request is the request object from cloud code functions. request.object is a ParseObject.
|
||||||
_onCloudCodeMessage(type: string, request: any): void {
|
_onCloudCodeMessage(type: string, request: any): void {
|
||||||
logger.verbose(
|
logger.verbose(
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ import { ParsePubSub } from './ParsePubSub';
|
|||||||
import SchemaController from '../Controllers/SchemaController';
|
import SchemaController from '../Controllers/SchemaController';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { runLiveQueryEventHandlers, getTrigger, runTrigger, resolveError, toJSONwithObjects } from '../triggers';
|
import {
|
||||||
|
runLiveQueryEventHandlers,
|
||||||
|
getTrigger,
|
||||||
|
runTrigger,
|
||||||
|
resolveError,
|
||||||
|
toJSONwithObjects,
|
||||||
|
} from '../triggers';
|
||||||
import { getAuthForSessionToken, Auth } from '../Auth';
|
import { getAuthForSessionToken, Auth } from '../Auth';
|
||||||
import { getCacheController } from '../Controllers';
|
import { getCacheController } from '../Controllers';
|
||||||
import LRU from 'lru-cache';
|
import LRU from 'lru-cache';
|
||||||
@@ -71,6 +77,7 @@ class ParseLiveQueryServer {
|
|||||||
this.subscriber = ParsePubSub.createSubscriber(config);
|
this.subscriber = ParsePubSub.createSubscriber(config);
|
||||||
this.subscriber.subscribe(Parse.applicationId + 'afterSave');
|
this.subscriber.subscribe(Parse.applicationId + 'afterSave');
|
||||||
this.subscriber.subscribe(Parse.applicationId + 'afterDelete');
|
this.subscriber.subscribe(Parse.applicationId + 'afterDelete');
|
||||||
|
this.subscriber.subscribe(Parse.applicationId + 'clearCache');
|
||||||
// Register message handler for subscriber. When publisher get messages, it will publish message
|
// Register message handler for subscriber. When publisher get messages, it will publish message
|
||||||
// to the subscribers and the handler will be called.
|
// to the subscribers and the handler will be called.
|
||||||
this.subscriber.on('message', (channel, messageStr) => {
|
this.subscriber.on('message', (channel, messageStr) => {
|
||||||
@@ -82,6 +89,10 @@ class ParseLiveQueryServer {
|
|||||||
logger.error('unable to parse message', messageStr, e);
|
logger.error('unable to parse message', messageStr, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (channel === Parse.applicationId + 'clearCache') {
|
||||||
|
this._clearCachedRoles(message.userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._inflateParseObject(message);
|
this._inflateParseObject(message);
|
||||||
if (channel === Parse.applicationId + 'afterSave') {
|
if (channel === Parse.applicationId + 'afterSave') {
|
||||||
this._onAfterSave(message);
|
this._onAfterSave(message);
|
||||||
@@ -468,6 +479,32 @@ class ParseLiveQueryServer {
|
|||||||
return matchesQuery(parseObject, subscription.query);
|
return matchesQuery(parseObject, subscription.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _clearCachedRoles(userId: string) {
|
||||||
|
try {
|
||||||
|
const validTokens = await new Parse.Query(Parse.Session)
|
||||||
|
.equalTo('user', Parse.User.createWithoutData(userId))
|
||||||
|
.find({ useMasterKey: true });
|
||||||
|
await Promise.all(
|
||||||
|
validTokens.map(async token => {
|
||||||
|
const sessionToken = token.get('sessionToken');
|
||||||
|
const authPromise = this.authCache.get(sessionToken);
|
||||||
|
if (!authPromise) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [auth1, auth2] = await Promise.all([
|
||||||
|
authPromise,
|
||||||
|
getAuthForSessionToken({ cacheController: this.cacheController, sessionToken }),
|
||||||
|
]);
|
||||||
|
auth1.auth?.clearRoleCache(sessionToken);
|
||||||
|
auth2.auth?.clearRoleCache(sessionToken);
|
||||||
|
this.authCache.del(sessionToken);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.verbose(`Could not clear role cache. ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getAuthForSessionToken(sessionToken: ?string): Promise<{ auth: ?Auth, userId: ?string }> {
|
getAuthForSessionToken(sessionToken: ?string): Promise<{ auth: ?Auth, userId: ?string }> {
|
||||||
if (!sessionToken) {
|
if (!sessionToken) {
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
@@ -574,7 +611,6 @@ class ParseLiveQueryServer {
|
|||||||
if (!acl_has_roles) {
|
if (!acl_has_roles) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleNames = await auth.getUserRoles();
|
const roleNames = await auth.getUserRoles();
|
||||||
// Finally, see if any of the user's roles allow them read access
|
// Finally, see if any of the user's roles allow them read access
|
||||||
for (const role of roleNames) {
|
for (const role of roleNames) {
|
||||||
|
|||||||
@@ -1326,6 +1326,9 @@ RestWrite.prototype.runDatabaseOperation = function () {
|
|||||||
|
|
||||||
if (this.className === '_Role') {
|
if (this.className === '_Role') {
|
||||||
this.config.cacheController.role.clear();
|
this.config.cacheController.role.clear();
|
||||||
|
if (this.config.liveQueryController) {
|
||||||
|
this.config.liveQueryController.clearCachedRoles(this.auth.user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) {
|
if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user