refactor: Parse Pointer allows to access internal Parse Server classes and circumvent beforeFind query trigger (#8735)
This commit is contained in:
184
src/rest.js
184
src/rest.js
@@ -12,6 +12,7 @@ var Parse = require('parse/node').Parse;
|
||||
var RestQuery = require('./RestQuery');
|
||||
var RestWrite = require('./RestWrite');
|
||||
var triggers = require('./triggers');
|
||||
const { enforceRoleSecurity } = require('./SharedRest');
|
||||
|
||||
function checkTriggers(className, config, types) {
|
||||
return types.some(triggerType => {
|
||||
@@ -24,65 +25,34 @@ function checkLiveQuery(className, config) {
|
||||
}
|
||||
|
||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||
function find(config, auth, className, restWhere, restOptions, clientSDK, context) {
|
||||
enforceRoleSecurity('find', className, auth);
|
||||
return triggers
|
||||
.maybeRunQueryTrigger(
|
||||
triggers.Types.beforeFind,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
config,
|
||||
auth,
|
||||
context
|
||||
)
|
||||
.then(result => {
|
||||
restWhere = result.restWhere || restWhere;
|
||||
restOptions = result.restOptions || restOptions;
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
true,
|
||||
context
|
||||
);
|
||||
return query.execute();
|
||||
});
|
||||
}
|
||||
const find = async (config, auth, className, restWhere, restOptions, clientSDK, context) => {
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.find,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
context,
|
||||
});
|
||||
return query.execute();
|
||||
};
|
||||
|
||||
// get is just like find but only queries an objectId.
|
||||
const get = (config, auth, className, objectId, restOptions, clientSDK, context) => {
|
||||
const get = async (config, auth, className, objectId, restOptions, clientSDK, context) => {
|
||||
var restWhere = { objectId };
|
||||
enforceRoleSecurity('get', className, auth);
|
||||
return triggers
|
||||
.maybeRunQueryTrigger(
|
||||
triggers.Types.beforeFind,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
config,
|
||||
auth,
|
||||
context,
|
||||
true
|
||||
)
|
||||
.then(result => {
|
||||
restWhere = result.restWhere || restWhere;
|
||||
restOptions = result.restOptions || restOptions;
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
true,
|
||||
context
|
||||
);
|
||||
return query.execute();
|
||||
});
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
context,
|
||||
});
|
||||
return query.execute();
|
||||
};
|
||||
|
||||
// Returns a promise that doesn't resolve to any useful value.
|
||||
@@ -101,35 +71,40 @@ function del(config, auth, className, objectId, context) {
|
||||
let schemaController;
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeDelete', 'afterDelete']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery || className == '_Session') {
|
||||
return new RestQuery(config, auth, className, { objectId })
|
||||
.execute({ op: 'delete' })
|
||||
.then(response => {
|
||||
if (response && response.results && response.results.length) {
|
||||
const firstResult = response.results[0];
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere: { objectId },
|
||||
});
|
||||
return query.execute({ op: 'delete' }).then(response => {
|
||||
if (response && response.results && response.results.length) {
|
||||
const firstResult = response.results[0];
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
var cacheAdapter = config.cacheController;
|
||||
cacheAdapter.user.del(firstResult.sessionToken);
|
||||
inflatedObject = Parse.Object.fromJSON(firstResult);
|
||||
return triggers.maybeRunTrigger(
|
||||
triggers.Types.beforeDelete,
|
||||
auth,
|
||||
inflatedObject,
|
||||
null,
|
||||
config,
|
||||
context
|
||||
);
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.');
|
||||
});
|
||||
var cacheAdapter = config.cacheController;
|
||||
cacheAdapter.user.del(firstResult.sessionToken);
|
||||
inflatedObject = Parse.Object.fromJSON(firstResult);
|
||||
return triggers.maybeRunTrigger(
|
||||
triggers.Types.beforeDelete,
|
||||
auth,
|
||||
inflatedObject,
|
||||
null,
|
||||
config,
|
||||
context
|
||||
);
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.');
|
||||
});
|
||||
}
|
||||
return Promise.resolve({});
|
||||
})
|
||||
@@ -193,21 +168,22 @@ function update(config, auth, className, restWhere, restObject, clientSDK, conte
|
||||
enforceRoleSecurity('update', className, auth);
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeSave', 'afterSave']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery) {
|
||||
// Do not use find, as it runs the before finds
|
||||
return new RestQuery(
|
||||
const query = await RestQuery({
|
||||
method: RestQuery.Method.get,
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
context
|
||||
).execute({
|
||||
runAfterFind: false,
|
||||
runBeforeFind: false,
|
||||
context,
|
||||
});
|
||||
return query.execute({
|
||||
op: 'update',
|
||||
});
|
||||
}
|
||||
@@ -248,40 +224,6 @@ function handleSessionMissingError(error, className, auth) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const classesWithMasterOnlyAccess = [
|
||||
'_JobStatus',
|
||||
'_PushStatus',
|
||||
'_Hooks',
|
||||
'_GlobalConfig',
|
||||
'_JobSchedule',
|
||||
'_Idempotency',
|
||||
];
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
function enforceRoleSecurity(method, className, auth) {
|
||||
if (className === '_Installation' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (method === 'delete' || method === 'find') {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
}
|
||||
|
||||
//all volatileClasses are masterKey only
|
||||
if (
|
||||
classesWithMasterOnlyAccess.indexOf(className) >= 0 &&
|
||||
!auth.isMaster &&
|
||||
!auth.isMaintenance
|
||||
) {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the ${className} collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
|
||||
// readOnly masterKey is not allowed
|
||||
if (auth.isReadOnly && (method === 'delete' || method === 'create' || method === 'update')) {
|
||||
const error = `read-only masterKey isn't allowed to perform the ${method} operation.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
del,
|
||||
|
||||
Reference in New Issue
Block a user