New: requireAnyUserRoles and requireAllUserRoles for Parse Cloud Validator (#7097)
* new: requireUserRole for Parse Cloud Validator * change to requireUserRoles * Update CHANGELOG.md * revoke triggers * Update triggers.js * Update ParseLiveQueryServer.js * Update ParseLiveQueryServer.js * create requireUserRoles * rename to requireAny and requireAll * allow for a function
This commit is contained in:
@@ -10,12 +10,7 @@ import { ParsePubSub } from './ParsePubSub';
|
||||
import SchemaController from '../Controllers/SchemaController';
|
||||
import _ from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
runLiveQueryEventHandlers,
|
||||
maybeRunConnectTrigger,
|
||||
maybeRunSubscribeTrigger,
|
||||
maybeRunAfterEventTrigger,
|
||||
} from '../triggers';
|
||||
import { runLiveQueryEventHandlers, getTrigger, runTrigger } from '../triggers';
|
||||
import { getAuthForSessionToken, Auth } from '../Auth';
|
||||
import { getCacheController } from '../Controllers';
|
||||
import LRU from 'lru-cache';
|
||||
@@ -121,7 +116,7 @@ class ParseLiveQueryServer {
|
||||
|
||||
// Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes.
|
||||
// Message.originalParseObject is the original ParseObject.
|
||||
_onAfterDelete(message: any): void {
|
||||
async _onAfterDelete(message: any): void {
|
||||
logger.verbose(Parse.applicationId + 'afterDelete is triggered');
|
||||
|
||||
let deletedParseObject = message.currentParseObject.toJSON();
|
||||
@@ -135,6 +130,7 @@ class ParseLiveQueryServer {
|
||||
logger.debug('Can not find subscriptions under this class ' + className);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const subscription of classSubscriptions.values()) {
|
||||
const isSubscriptionMatched = this._matchesSubscription(deletedParseObject, subscription);
|
||||
if (!isSubscriptionMatched) {
|
||||
@@ -145,63 +141,71 @@ class ParseLiveQueryServer {
|
||||
if (typeof client === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
for (const requestId of requestIds) {
|
||||
requestIds.forEach(async requestId => {
|
||||
const acl = message.currentParseObject.getACL();
|
||||
// Check CLP
|
||||
const op = this._getCLPOperation(subscription.query);
|
||||
let res = {};
|
||||
this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
|
||||
.then(() => {
|
||||
// Check ACL
|
||||
return this._matchesACL(acl, client, requestId);
|
||||
})
|
||||
.then(isMatched => {
|
||||
if (!isMatched) {
|
||||
return null;
|
||||
try {
|
||||
await this._matchesCLP(
|
||||
classLevelPermissions,
|
||||
message.currentParseObject,
|
||||
client,
|
||||
requestId,
|
||||
op
|
||||
);
|
||||
const isMatched = await this._matchesACL(acl, client, requestId);
|
||||
if (!isMatched) {
|
||||
return null;
|
||||
}
|
||||
res = {
|
||||
event: 'delete',
|
||||
sessionToken: client.sessionToken,
|
||||
object: deletedParseObject,
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size,
|
||||
useMasterKey: client.hasMasterKey,
|
||||
installationId: client.installationId,
|
||||
sendEvent: true,
|
||||
};
|
||||
const trigger = getTrigger(className, 'afterEvent', Parse.applicationId);
|
||||
if (trigger) {
|
||||
const auth = await this.getAuthForSessionToken(res.sessionToken);
|
||||
res.user = auth.user;
|
||||
if (res.object) {
|
||||
res.object = Parse.Object.fromJSON(res.object);
|
||||
}
|
||||
res = {
|
||||
event: 'delete',
|
||||
sessionToken: client.sessionToken,
|
||||
object: deletedParseObject,
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size,
|
||||
useMasterKey: client.hasMasterKey,
|
||||
installationId: client.installationId,
|
||||
sendEvent: true,
|
||||
};
|
||||
return maybeRunAfterEventTrigger('afterEvent', className, res);
|
||||
})
|
||||
.then(() => {
|
||||
if (!res.sendEvent) {
|
||||
return;
|
||||
}
|
||||
if (res.object && typeof res.object.toJSON === 'function') {
|
||||
deletedParseObject = res.object.toJSON();
|
||||
deletedParseObject.className = className;
|
||||
}
|
||||
client.pushDelete(requestId, deletedParseObject);
|
||||
})
|
||||
.catch(error => {
|
||||
Client.pushError(
|
||||
client.parseWebSocket,
|
||||
error.code || 141,
|
||||
error.message || error,
|
||||
false,
|
||||
requestId
|
||||
);
|
||||
logger.error(
|
||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
await runTrigger(trigger, `afterEvent.${className}`, res, auth);
|
||||
}
|
||||
if (!res.sendEvent) {
|
||||
return;
|
||||
}
|
||||
if (res.object && typeof res.object.toJSON === 'function') {
|
||||
deletedParseObject = res.object.toJSON();
|
||||
deletedParseObject.className = className;
|
||||
}
|
||||
client.pushDelete(requestId, deletedParseObject);
|
||||
} catch (error) {
|
||||
Client.pushError(
|
||||
client.parseWebSocket,
|
||||
error.code || 141,
|
||||
error.message || error,
|
||||
false,
|
||||
requestId
|
||||
);
|
||||
logger.error(
|
||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes.
|
||||
// Message.originalParseObject is the original ParseObject.
|
||||
_onAfterSave(message: any): void {
|
||||
async _onAfterSave(message: any): void {
|
||||
logger.verbose(Parse.applicationId + 'afterSave is triggered');
|
||||
|
||||
let originalParseObject = null;
|
||||
@@ -233,7 +237,7 @@ class ParseLiveQueryServer {
|
||||
if (typeof client === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
for (const requestId of requestIds) {
|
||||
requestIds.forEach(async requestId => {
|
||||
// Set orignal ParseObject ACL checking promise, if the object does not match
|
||||
// subscription, we do not need to check ACL
|
||||
let originalACLCheckingPromise;
|
||||
@@ -256,86 +260,99 @@ class ParseLiveQueryServer {
|
||||
const currentACL = message.currentParseObject.getACL();
|
||||
currentACLCheckingPromise = this._matchesACL(currentACL, client, requestId);
|
||||
}
|
||||
const op = this._getCLPOperation(subscription.query);
|
||||
this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
|
||||
.then(() => {
|
||||
return Promise.all([originalACLCheckingPromise, currentACLCheckingPromise]);
|
||||
})
|
||||
.then(([isOriginalMatched, isCurrentMatched]) => {
|
||||
logger.verbose(
|
||||
'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s',
|
||||
originalParseObject,
|
||||
currentParseObject,
|
||||
isOriginalSubscriptionMatched,
|
||||
isCurrentSubscriptionMatched,
|
||||
isOriginalMatched,
|
||||
isCurrentMatched,
|
||||
subscription.hash
|
||||
);
|
||||
// Decide event type
|
||||
let type;
|
||||
if (isOriginalMatched && isCurrentMatched) {
|
||||
type = 'update';
|
||||
} else if (isOriginalMatched && !isCurrentMatched) {
|
||||
type = 'leave';
|
||||
} else if (!isOriginalMatched && isCurrentMatched) {
|
||||
if (originalParseObject) {
|
||||
type = 'enter';
|
||||
} else {
|
||||
type = 'create';
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
message.event = type;
|
||||
res = {
|
||||
event: type,
|
||||
sessionToken: client.sessionToken,
|
||||
object: currentParseObject,
|
||||
original: originalParseObject,
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size,
|
||||
useMasterKey: client.hasMasterKey,
|
||||
installationId: client.installationId,
|
||||
sendEvent: true,
|
||||
};
|
||||
return maybeRunAfterEventTrigger('afterEvent', className, res);
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
if (!res.sendEvent) {
|
||||
return;
|
||||
}
|
||||
if (res.object && typeof res.object.toJSON === 'function') {
|
||||
currentParseObject = res.object.toJSON();
|
||||
currentParseObject.className = res.object.className || className;
|
||||
}
|
||||
|
||||
if (res.original && typeof res.original.toJSON === 'function') {
|
||||
originalParseObject = res.original.toJSON();
|
||||
originalParseObject.className = res.original.className || className;
|
||||
}
|
||||
const functionName =
|
||||
'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
|
||||
if (client[functionName]) {
|
||||
client[functionName](requestId, currentParseObject, originalParseObject);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
Client.pushError(
|
||||
client.parseWebSocket,
|
||||
error.code || 141,
|
||||
error.message || error,
|
||||
false,
|
||||
requestId
|
||||
);
|
||||
logger.error(
|
||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
);
|
||||
}
|
||||
try {
|
||||
const op = this._getCLPOperation(subscription.query);
|
||||
await this._matchesCLP(
|
||||
classLevelPermissions,
|
||||
message.currentParseObject,
|
||||
client,
|
||||
requestId,
|
||||
op
|
||||
);
|
||||
}
|
||||
const [isOriginalMatched, isCurrentMatched] = await Promise.all([
|
||||
originalACLCheckingPromise,
|
||||
currentACLCheckingPromise,
|
||||
]);
|
||||
logger.verbose(
|
||||
'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s',
|
||||
originalParseObject,
|
||||
currentParseObject,
|
||||
isOriginalSubscriptionMatched,
|
||||
isCurrentSubscriptionMatched,
|
||||
isOriginalMatched,
|
||||
isCurrentMatched,
|
||||
subscription.hash
|
||||
);
|
||||
// Decide event type
|
||||
let type;
|
||||
if (isOriginalMatched && isCurrentMatched) {
|
||||
type = 'update';
|
||||
} else if (isOriginalMatched && !isCurrentMatched) {
|
||||
type = 'leave';
|
||||
} else if (!isOriginalMatched && isCurrentMatched) {
|
||||
if (originalParseObject) {
|
||||
type = 'enter';
|
||||
} else {
|
||||
type = 'create';
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
message.event = type;
|
||||
res = {
|
||||
event: type,
|
||||
sessionToken: client.sessionToken,
|
||||
object: currentParseObject,
|
||||
original: originalParseObject,
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size,
|
||||
useMasterKey: client.hasMasterKey,
|
||||
installationId: client.installationId,
|
||||
sendEvent: true,
|
||||
};
|
||||
const trigger = getTrigger(className, 'afterEvent', Parse.applicationId);
|
||||
if (trigger) {
|
||||
if (res.object) {
|
||||
res.object = Parse.Object.fromJSON(res.object);
|
||||
}
|
||||
if (res.original) {
|
||||
res.original = Parse.Object.fromJSON(res.original);
|
||||
}
|
||||
const auth = await this.getAuthForSessionToken(res.sessionToken);
|
||||
res.user = auth.user;
|
||||
await runTrigger(trigger, `afterEvent.${className}`, res, auth);
|
||||
}
|
||||
if (!res.sendEvent) {
|
||||
return;
|
||||
}
|
||||
if (res.object && typeof res.object.toJSON === 'function') {
|
||||
currentParseObject = res.object.toJSON();
|
||||
currentParseObject.className = res.object.className || className;
|
||||
}
|
||||
|
||||
if (res.original && typeof res.original.toJSON === 'function') {
|
||||
originalParseObject = res.original.toJSON();
|
||||
originalParseObject.className = res.original.className || className;
|
||||
}
|
||||
const functionName =
|
||||
'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
|
||||
if (client[functionName]) {
|
||||
client[functionName](requestId, currentParseObject, originalParseObject);
|
||||
}
|
||||
} catch (error) {
|
||||
Client.pushError(
|
||||
client.parseWebSocket,
|
||||
error.code || 141,
|
||||
error.message || error,
|
||||
false,
|
||||
requestId
|
||||
);
|
||||
logger.error(
|
||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,7 +631,12 @@ class ParseLiveQueryServer {
|
||||
useMasterKey: client.hasMasterKey,
|
||||
installationId: request.installationId,
|
||||
};
|
||||
await maybeRunConnectTrigger('beforeConnect', req);
|
||||
const trigger = getTrigger('@Connect', 'beforeConnect', Parse.applicationId);
|
||||
if (trigger) {
|
||||
const auth = await this.getAuthForSessionToken(req.sessionToken);
|
||||
req.user = auth.user;
|
||||
await runTrigger(trigger, `beforeConnect.@Connect`, req, auth);
|
||||
}
|
||||
parseWebsocket.clientId = clientId;
|
||||
this.clients.set(parseWebsocket.clientId, client);
|
||||
logger.info(`Create new client: ${parseWebsocket.clientId}`);
|
||||
@@ -668,7 +690,22 @@ class ParseLiveQueryServer {
|
||||
const client = this.clients.get(parseWebsocket.clientId);
|
||||
const className = request.query.className;
|
||||
try {
|
||||
await maybeRunSubscribeTrigger('beforeSubscribe', className, request);
|
||||
const trigger = getTrigger(className, 'beforeSubscribe', Parse.applicationId);
|
||||
if (trigger) {
|
||||
const auth = await this.getAuthForSessionToken(request.sessionToken);
|
||||
request.user = auth.user;
|
||||
|
||||
const parseQuery = new Parse.Query(className);
|
||||
parseQuery.withJSON(request.query);
|
||||
request.query = parseQuery;
|
||||
await runTrigger(trigger, `beforeSubscribe.${className}`, request, auth);
|
||||
|
||||
const query = request.query.toJSON();
|
||||
if (query.keys) {
|
||||
query.fields = query.keys.split(',');
|
||||
}
|
||||
request.query = query;
|
||||
}
|
||||
|
||||
// Get subscription from subscriptions, create one if necessary
|
||||
const subscriptionHash = queryHash(request.query);
|
||||
|
||||
Reference in New Issue
Block a user