fix: Parse Pointer allows to access internal Parse Server classes and circumvent beforeFind query trigger; fixes security vulnerability [GHSA-fcv6-fg5r-jm9q](https://github.com/parse-community/parse-server/security/advisories/GHSA-fcv6-fg5r-jm9q)
This commit is contained in:
@@ -2342,6 +2342,35 @@ describe('beforeFind hooks', () => {
|
|||||||
})
|
})
|
||||||
.then(() => done());
|
.then(() => done());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should run beforeFind on pointers and array of pointers from an object', async () => {
|
||||||
|
const obj1 = new Parse.Object('TestObject');
|
||||||
|
const obj2 = new Parse.Object('TestObject2');
|
||||||
|
const obj3 = new Parse.Object('TestObject');
|
||||||
|
obj2.set('aField', 'aFieldValue');
|
||||||
|
await obj2.save();
|
||||||
|
obj1.set('pointerField', obj2);
|
||||||
|
obj3.set('pointerFieldArray', [obj2]);
|
||||||
|
await obj1.save();
|
||||||
|
await obj3.save();
|
||||||
|
const spy = jasmine.createSpy('beforeFindSpy');
|
||||||
|
Parse.Cloud.beforeFind('TestObject2', spy);
|
||||||
|
const query = new Parse.Query('TestObject');
|
||||||
|
await query.get(obj1.id);
|
||||||
|
// Pointer not included in query so we don't expect beforeFind to be called
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
const query2 = new Parse.Query('TestObject');
|
||||||
|
query2.include('pointerField');
|
||||||
|
const res = await query2.get(obj1.id);
|
||||||
|
expect(res.get('pointerField').get('aField')).toBe('aFieldValue');
|
||||||
|
// Pointer included in query so we expect beforeFind to be called
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
const query3 = new Parse.Query('TestObject');
|
||||||
|
query3.include('pointerFieldArray');
|
||||||
|
const res2 = await query3.get(obj3.id);
|
||||||
|
expect(res2.get('pointerFieldArray')[0].get('aField')).toBe('aFieldValue');
|
||||||
|
expect(spy).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('afterFind hooks', () => {
|
describe('afterFind hooks', () => {
|
||||||
|
|||||||
@@ -5269,7 +5269,6 @@ describe('ParseGraphQLServer', () => {
|
|||||||
|
|
||||||
it('should only count', async () => {
|
it('should only count', async () => {
|
||||||
await prepareData();
|
await prepareData();
|
||||||
|
|
||||||
await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear();
|
await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear();
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ describe('Parse Role testing', () => {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
const restExecute = spyOn(RestQuery.prototype, 'execute').and.callThrough();
|
const restExecute = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
|
||||||
|
|
||||||
let user, auth, getAllRolesSpy;
|
let user, auth, getAllRolesSpy;
|
||||||
createTestUser()
|
createTestUser()
|
||||||
|
|||||||
@@ -398,15 +398,16 @@ describe('RestQuery.each', () => {
|
|||||||
}
|
}
|
||||||
const config = Config.get('test');
|
const config = Config.get('test');
|
||||||
await Parse.Object.saveAll(objects);
|
await Parse.Object.saveAll(objects);
|
||||||
const query = new RestQuery(
|
const query = await RestQuery({
|
||||||
|
method: RestQuery.Method.find,
|
||||||
config,
|
config,
|
||||||
auth.master(config),
|
auth: auth.master(config),
|
||||||
'Object',
|
className: 'Object',
|
||||||
{ value: { $gt: 2 } },
|
restWhere: { value: { $gt: 2 } },
|
||||||
{ limit: 2 }
|
restOptions: { limit: 2 },
|
||||||
);
|
});
|
||||||
const spy = spyOn(query, 'execute').and.callThrough();
|
const spy = spyOn(query, 'execute').and.callThrough();
|
||||||
const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough();
|
const classSpy = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
|
||||||
const results = [];
|
const results = [];
|
||||||
await query.each(result => {
|
await query.each(result => {
|
||||||
expect(result.value).toBeGreaterThan(2);
|
expect(result.value).toBeGreaterThan(2);
|
||||||
@@ -437,34 +438,37 @@ describe('RestQuery.each', () => {
|
|||||||
* Two queries needed since objectId are sorted and we can't know which one
|
* Two queries needed since objectId are sorted and we can't know which one
|
||||||
* going to be the first and then skip by the $gt added by each
|
* going to be the first and then skip by the $gt added by each
|
||||||
*/
|
*/
|
||||||
const queryOne = new RestQuery(
|
const queryOne = await RestQuery({
|
||||||
|
method: RestQuery.Method.get,
|
||||||
config,
|
config,
|
||||||
auth.master(config),
|
auth: auth.master(config),
|
||||||
'Letter',
|
className: 'Letter',
|
||||||
{
|
restWhere: {
|
||||||
numbers: {
|
numbers: {
|
||||||
__type: 'Pointer',
|
__type: 'Pointer',
|
||||||
className: 'Number',
|
className: 'Number',
|
||||||
objectId: object1.id,
|
objectId: object1.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ limit: 1 }
|
restOptions: { limit: 1 },
|
||||||
);
|
});
|
||||||
const queryTwo = new RestQuery(
|
|
||||||
|
const queryTwo = await RestQuery({
|
||||||
|
method: RestQuery.Method.get,
|
||||||
config,
|
config,
|
||||||
auth.master(config),
|
auth: auth.master(config),
|
||||||
'Letter',
|
className: 'Letter',
|
||||||
{
|
restWhere: {
|
||||||
numbers: {
|
numbers: {
|
||||||
__type: 'Pointer',
|
__type: 'Pointer',
|
||||||
className: 'Number',
|
className: 'Number',
|
||||||
objectId: object2.id,
|
objectId: object2.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ limit: 1 }
|
restOptions: { limit: 1 },
|
||||||
);
|
});
|
||||||
|
|
||||||
const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough();
|
const classSpy = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
|
||||||
const resultsOne = [];
|
const resultsOne = [];
|
||||||
const resultsTwo = [];
|
const resultsTwo = [];
|
||||||
await queryOne.each(result => {
|
await queryOne.each(result => {
|
||||||
|
|||||||
@@ -660,6 +660,38 @@ describe('rest create', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('cannot get object in volatileClasses if not masterKey through pointer', async () => {
|
||||||
|
const masterKeyOnlyClassObject = new Parse.Object('_PushStatus');
|
||||||
|
await masterKeyOnlyClassObject.save(null, { useMasterKey: true });
|
||||||
|
const obj2 = new Parse.Object('TestObject');
|
||||||
|
// Anyone is can basically create a pointer to any object
|
||||||
|
// or some developers can use master key in some hook to link
|
||||||
|
// private objects to standard objects
|
||||||
|
obj2.set('pointer', masterKeyOnlyClassObject);
|
||||||
|
await obj2.save();
|
||||||
|
const query = new Parse.Query('TestObject');
|
||||||
|
query.include('pointer');
|
||||||
|
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
|
||||||
|
"Clients aren't allowed to perform the get operation on the _PushStatus collection."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot get object in _GlobalConfig if not masterKey through pointer', async () => {
|
||||||
|
await Parse.Config.save({ privateData: 'secret' }, { privateData: true });
|
||||||
|
const obj2 = new Parse.Object('TestObject');
|
||||||
|
obj2.set('globalConfigPointer', {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: '_GlobalConfig',
|
||||||
|
objectId: 1,
|
||||||
|
});
|
||||||
|
await obj2.save();
|
||||||
|
const query = new Parse.Query('TestObject');
|
||||||
|
query.include('globalConfigPointer');
|
||||||
|
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
|
||||||
|
"Clients aren't allowed to perform the get operation on the _GlobalConfig collection."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('locks down session', done => {
|
it('locks down session', done => {
|
||||||
let currentUser;
|
let currentUser;
|
||||||
Parse.User.signUp('foo', 'bar')
|
Parse.User.signUp('foo', 'bar')
|
||||||
|
|||||||
48
src/Auth.js
48
src/Auth.js
@@ -84,7 +84,15 @@ const getAuthForSessionToken = async function ({
|
|||||||
include: 'user',
|
include: 'user',
|
||||||
};
|
};
|
||||||
|
|
||||||
const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions);
|
const query = await RestQuery({
|
||||||
|
method: RestQuery.Method.get,
|
||||||
|
runBeforeFind: false,
|
||||||
|
config,
|
||||||
|
auth: master(config),
|
||||||
|
className: '_Session',
|
||||||
|
restWhere: { sessionToken },
|
||||||
|
restOptions,
|
||||||
|
});
|
||||||
results = (await query.execute()).results;
|
results = (await query.execute()).results;
|
||||||
} else {
|
} else {
|
||||||
results = (
|
results = (
|
||||||
@@ -121,11 +129,19 @@ const getAuthForSessionToken = async function ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var getAuthForLegacySessionToken = function ({ config, sessionToken, installationId }) {
|
var getAuthForLegacySessionToken = async function ({ config, sessionToken, installationId }) {
|
||||||
var restOptions = {
|
var restOptions = {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
};
|
};
|
||||||
var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions);
|
var query = await RestQuery({
|
||||||
|
method: RestQuery.Method.get,
|
||||||
|
runBeforeFind: false,
|
||||||
|
config,
|
||||||
|
auth: master(config),
|
||||||
|
className: '_User',
|
||||||
|
restWhere: { sessionToken },
|
||||||
|
restOptions,
|
||||||
|
});
|
||||||
return query.execute().then(response => {
|
return query.execute().then(response => {
|
||||||
var results = response.results;
|
var results = response.results;
|
||||||
if (results.length !== 1) {
|
if (results.length !== 1) {
|
||||||
@@ -169,9 +185,16 @@ Auth.prototype.getRolesForUser = async function () {
|
|||||||
objectId: this.user.id,
|
objectId: this.user.id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result =>
|
const query = await RestQuery({
|
||||||
results.push(result)
|
method: RestQuery.Method.find,
|
||||||
);
|
config: this.config,
|
||||||
|
auth: master(this.config),
|
||||||
|
runBeforeFind: false,
|
||||||
|
className: '_Role',
|
||||||
|
restWhere,
|
||||||
|
restOptions: {},
|
||||||
|
});
|
||||||
|
await query.each(result => results.push(result));
|
||||||
} else {
|
} else {
|
||||||
await new Parse.Query(Parse.Role)
|
await new Parse.Query(Parse.Role)
|
||||||
.equalTo('users', this.user)
|
.equalTo('users', this.user)
|
||||||
@@ -262,9 +285,16 @@ Auth.prototype.getRolesByIds = async function (ins) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
const restWhere = { roles: { $in: roles } };
|
const restWhere = { roles: { $in: roles } };
|
||||||
await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result =>
|
const query = await RestQuery({
|
||||||
results.push(result)
|
method: RestQuery.Method.find,
|
||||||
);
|
runBeforeFind: false,
|
||||||
|
config: this.config,
|
||||||
|
auth: master(this.config),
|
||||||
|
className: '_Role',
|
||||||
|
restWhere,
|
||||||
|
restOptions: {},
|
||||||
|
});
|
||||||
|
await query.each(result => results.push(result));
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,9 +58,16 @@ export class PushController {
|
|||||||
|
|
||||||
// Force filtering on only valid device tokens
|
// Force filtering on only valid device tokens
|
||||||
const updateWhere = applyDeviceTokenExists(where);
|
const updateWhere = applyDeviceTokenExists(where);
|
||||||
badgeUpdate = () => {
|
badgeUpdate = async () => {
|
||||||
// Build a real RestQuery so we can use it in RestWrite
|
// Build a real RestQuery so we can use it in RestWrite
|
||||||
const restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);
|
const restQuery = await RestQuery({
|
||||||
|
method: RestQuery.Method.find,
|
||||||
|
config,
|
||||||
|
runBeforeFind: false,
|
||||||
|
auth: master(config),
|
||||||
|
className: '_Installation',
|
||||||
|
restWhere: updateWhere,
|
||||||
|
});
|
||||||
return restQuery.buildRestWhere().then(() => {
|
return restQuery.buildRestWhere().then(() => {
|
||||||
const write = new RestWrite(
|
const write = new RestWrite(
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export class UserController extends AdaptableController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyEmail(username, token) {
|
async verifyEmail(username, token) {
|
||||||
if (!this.shouldVerifyEmails) {
|
if (!this.shouldVerifyEmails) {
|
||||||
// Trying to verify email when not enabled
|
// Trying to verify email when not enabled
|
||||||
// TODO: Better error here.
|
// TODO: Better error here.
|
||||||
@@ -70,12 +70,14 @@ export class UserController extends AdaptableController {
|
|||||||
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
|
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
|
||||||
}
|
}
|
||||||
const masterAuth = Auth.master(this.config);
|
const masterAuth = Auth.master(this.config);
|
||||||
var findUserForEmailVerification = new RestQuery(
|
var findUserForEmailVerification = await RestQuery({
|
||||||
this.config,
|
method: RestQuery.Method.get,
|
||||||
Auth.master(this.config),
|
config: this.config,
|
||||||
'_User',
|
runBeforeFind: false,
|
||||||
{ username: username }
|
auth: Auth.master(this.config),
|
||||||
);
|
className: '_User',
|
||||||
|
restWhere: { username },
|
||||||
|
});
|
||||||
return findUserForEmailVerification.execute().then(result => {
|
return findUserForEmailVerification.execute().then(result => {
|
||||||
if (result.results.length && result.results[0].emailVerified) {
|
if (result.results.length && result.results[0].emailVerified) {
|
||||||
return Promise.resolve(result.results.length[0]);
|
return Promise.resolve(result.results.length[0]);
|
||||||
@@ -112,7 +114,7 @@ export class UserController extends AdaptableController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserIfNeeded(user) {
|
async getUserIfNeeded(user) {
|
||||||
if (user.username && user.email) {
|
if (user.username && user.email) {
|
||||||
return Promise.resolve(user);
|
return Promise.resolve(user);
|
||||||
}
|
}
|
||||||
@@ -124,7 +126,14 @@ export class UserController extends AdaptableController {
|
|||||||
where.email = user.email;
|
where.email = user.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
var query = await RestQuery({
|
||||||
|
method: RestQuery.Method.get,
|
||||||
|
config: this.config,
|
||||||
|
runBeforeFind: false,
|
||||||
|
auth: Auth.master(this.config),
|
||||||
|
className: '_User',
|
||||||
|
restWhere: where,
|
||||||
|
});
|
||||||
return query.execute().then(function (result) {
|
return query.execute().then(function (result) {
|
||||||
if (result.results.length != 1) {
|
if (result.results.length != 1) {
|
||||||
throw undefined;
|
throw undefined;
|
||||||
|
|||||||
197
src/RestQuery.js
197
src/RestQuery.js
@@ -6,6 +6,8 @@ var Parse = require('parse/node').Parse;
|
|||||||
const triggers = require('./triggers');
|
const triggers = require('./triggers');
|
||||||
const { continueWhile } = require('parse/lib/node/promiseUtils');
|
const { continueWhile } = require('parse/lib/node/promiseUtils');
|
||||||
const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
|
const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
|
||||||
|
const { enforceRoleSecurity } = require('./SharedRest');
|
||||||
|
|
||||||
// restOptions can include:
|
// restOptions can include:
|
||||||
// skip
|
// skip
|
||||||
// limit
|
// limit
|
||||||
@@ -18,7 +20,80 @@ const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
|
|||||||
// readPreference
|
// readPreference
|
||||||
// includeReadPreference
|
// includeReadPreference
|
||||||
// subqueryReadPreference
|
// subqueryReadPreference
|
||||||
function RestQuery(
|
/**
|
||||||
|
* Use to perform a query on a class. It will run security checks and triggers.
|
||||||
|
* @param options
|
||||||
|
* @param options.method {RestQuery.Method} The type of query to perform
|
||||||
|
* @param options.config {ParseServerConfiguration} The server configuration
|
||||||
|
* @param options.auth {Auth} The auth object for the request
|
||||||
|
* @param options.className {string} The name of the class to query
|
||||||
|
* @param options.restWhere {object} The where object for the query
|
||||||
|
* @param options.restOptions {object} The options object for the query
|
||||||
|
* @param options.clientSDK {string} The client SDK that is performing the query
|
||||||
|
* @param options.runAfterFind {boolean} Whether to run the afterFind trigger
|
||||||
|
* @param options.runBeforeFind {boolean} Whether to run the beforeFind trigger
|
||||||
|
* @param options.context {object} The context object for the query
|
||||||
|
* @returns {Promise<_UnsafeRestQuery>} A promise that is resolved with the _UnsafeRestQuery object
|
||||||
|
*/
|
||||||
|
async function RestQuery({
|
||||||
|
method,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
restWhere = {},
|
||||||
|
restOptions = {},
|
||||||
|
clientSDK,
|
||||||
|
runAfterFind = true,
|
||||||
|
runBeforeFind = true,
|
||||||
|
context,
|
||||||
|
}) {
|
||||||
|
if (![RestQuery.Method.find, RestQuery.Method.get].includes(method)) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad query type');
|
||||||
|
}
|
||||||
|
enforceRoleSecurity(method, className, auth);
|
||||||
|
const result = runBeforeFind
|
||||||
|
? await triggers.maybeRunQueryTrigger(
|
||||||
|
triggers.Types.beforeFind,
|
||||||
|
className,
|
||||||
|
restWhere,
|
||||||
|
restOptions,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
context,
|
||||||
|
method === RestQuery.Method.get
|
||||||
|
)
|
||||||
|
: Promise.resolve({ restWhere, restOptions });
|
||||||
|
|
||||||
|
return new _UnsafeRestQuery(
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
result.restWhere || restWhere,
|
||||||
|
result.restOptions || restOptions,
|
||||||
|
clientSDK,
|
||||||
|
runAfterFind,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RestQuery.Method = Object.freeze({
|
||||||
|
get: 'get',
|
||||||
|
find: 'find',
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _UnsafeRestQuery is meant for specific internal usage only. When you need to skip security checks or some triggers.
|
||||||
|
* Don't use it if you don't know what you are doing.
|
||||||
|
* @param config
|
||||||
|
* @param auth
|
||||||
|
* @param className
|
||||||
|
* @param restWhere
|
||||||
|
* @param restOptions
|
||||||
|
* @param clientSDK
|
||||||
|
* @param runAfterFind
|
||||||
|
* @param context
|
||||||
|
*/
|
||||||
|
function _UnsafeRestQuery(
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
className,
|
className,
|
||||||
@@ -197,7 +272,7 @@ function RestQuery(
|
|||||||
// Returns a promise for the response - an object with optional keys
|
// Returns a promise for the response - an object with optional keys
|
||||||
// 'results' and 'count'.
|
// 'results' and 'count'.
|
||||||
// TODO: consolidate the replaceX functions
|
// TODO: consolidate the replaceX functions
|
||||||
RestQuery.prototype.execute = function (executeOptions) {
|
_UnsafeRestQuery.prototype.execute = function (executeOptions) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.buildRestWhere();
|
return this.buildRestWhere();
|
||||||
@@ -228,7 +303,7 @@ RestQuery.prototype.execute = function (executeOptions) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
RestQuery.prototype.each = function (callback) {
|
_UnsafeRestQuery.prototype.each = function (callback) {
|
||||||
const { config, auth, className, restWhere, restOptions, clientSDK } = this;
|
const { config, auth, className, restWhere, restOptions, clientSDK } = this;
|
||||||
// if the limit is set, use it
|
// if the limit is set, use it
|
||||||
restOptions.limit = restOptions.limit || 100;
|
restOptions.limit = restOptions.limit || 100;
|
||||||
@@ -240,7 +315,9 @@ RestQuery.prototype.each = function (callback) {
|
|||||||
return !finished;
|
return !finished;
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const query = new RestQuery(
|
// Safe here to use _UnsafeRestQuery because the security was already
|
||||||
|
// checked during "await RestQuery()"
|
||||||
|
const query = new _UnsafeRestQuery(
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
className,
|
className,
|
||||||
@@ -262,7 +339,7 @@ RestQuery.prototype.each = function (callback) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
RestQuery.prototype.buildRestWhere = function () {
|
_UnsafeRestQuery.prototype.buildRestWhere = function () {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.getUserAndRoleACL();
|
return this.getUserAndRoleACL();
|
||||||
@@ -291,7 +368,7 @@ RestQuery.prototype.buildRestWhere = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Uses the Auth object to get the list of roles, adds the user id
|
// Uses the Auth object to get the list of roles, adds the user id
|
||||||
RestQuery.prototype.getUserAndRoleACL = function () {
|
_UnsafeRestQuery.prototype.getUserAndRoleACL = function () {
|
||||||
if (this.auth.isMaster) {
|
if (this.auth.isMaster) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -310,7 +387,7 @@ RestQuery.prototype.getUserAndRoleACL = function () {
|
|||||||
|
|
||||||
// Changes the className if redirectClassNameForKey is set.
|
// Changes the className if redirectClassNameForKey is set.
|
||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
RestQuery.prototype.redirectClassNameForKey = function () {
|
_UnsafeRestQuery.prototype.redirectClassNameForKey = function () {
|
||||||
if (!this.redirectKey) {
|
if (!this.redirectKey) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -325,7 +402,7 @@ RestQuery.prototype.redirectClassNameForKey = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Validates this operation against the allowClientClassCreation config.
|
// Validates this operation against the allowClientClassCreation config.
|
||||||
RestQuery.prototype.validateClientClassCreation = function () {
|
_UnsafeRestQuery.prototype.validateClientClassCreation = function () {
|
||||||
if (
|
if (
|
||||||
this.config.allowClientClassCreation === false &&
|
this.config.allowClientClassCreation === false &&
|
||||||
!this.auth.isMaster &&
|
!this.auth.isMaster &&
|
||||||
@@ -368,7 +445,7 @@ function transformInQuery(inQueryObject, className, results) {
|
|||||||
// $inQuery clause.
|
// $inQuery clause.
|
||||||
// The $inQuery clause turns into an $in with values that are just
|
// The $inQuery clause turns into an $in with values that are just
|
||||||
// pointers to the objects returned in the subquery.
|
// pointers to the objects returned in the subquery.
|
||||||
RestQuery.prototype.replaceInQuery = function () {
|
_UnsafeRestQuery.prototype.replaceInQuery = async function () {
|
||||||
var inQueryObject = findObjectWithKey(this.restWhere, '$inQuery');
|
var inQueryObject = findObjectWithKey(this.restWhere, '$inQuery');
|
||||||
if (!inQueryObject) {
|
if (!inQueryObject) {
|
||||||
return;
|
return;
|
||||||
@@ -391,13 +468,14 @@ RestQuery.prototype.replaceInQuery = function () {
|
|||||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
var subquery = new RestQuery(
|
const subquery = await RestQuery({
|
||||||
this.config,
|
method: RestQuery.Method.find,
|
||||||
this.auth,
|
config: this.config,
|
||||||
inQueryValue.className,
|
auth: this.auth,
|
||||||
inQueryValue.where,
|
className: inQueryValue.className,
|
||||||
additionalOptions
|
restWhere: inQueryValue.where,
|
||||||
);
|
restOptions: additionalOptions,
|
||||||
|
});
|
||||||
return subquery.execute().then(response => {
|
return subquery.execute().then(response => {
|
||||||
transformInQuery(inQueryObject, subquery.className, response.results);
|
transformInQuery(inQueryObject, subquery.className, response.results);
|
||||||
// Recurse to repeat
|
// Recurse to repeat
|
||||||
@@ -426,7 +504,7 @@ function transformNotInQuery(notInQueryObject, className, results) {
|
|||||||
// $notInQuery clause.
|
// $notInQuery clause.
|
||||||
// The $notInQuery clause turns into a $nin with values that are just
|
// The $notInQuery clause turns into a $nin with values that are just
|
||||||
// pointers to the objects returned in the subquery.
|
// pointers to the objects returned in the subquery.
|
||||||
RestQuery.prototype.replaceNotInQuery = function () {
|
_UnsafeRestQuery.prototype.replaceNotInQuery = async function () {
|
||||||
var notInQueryObject = findObjectWithKey(this.restWhere, '$notInQuery');
|
var notInQueryObject = findObjectWithKey(this.restWhere, '$notInQuery');
|
||||||
if (!notInQueryObject) {
|
if (!notInQueryObject) {
|
||||||
return;
|
return;
|
||||||
@@ -449,13 +527,15 @@ RestQuery.prototype.replaceNotInQuery = function () {
|
|||||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
var subquery = new RestQuery(
|
const subquery = await RestQuery({
|
||||||
this.config,
|
method: RestQuery.Method.find,
|
||||||
this.auth,
|
config: this.config,
|
||||||
notInQueryValue.className,
|
auth: this.auth,
|
||||||
notInQueryValue.where,
|
className: notInQueryValue.className,
|
||||||
additionalOptions
|
restWhere: notInQueryValue.where,
|
||||||
);
|
restOptions: additionalOptions,
|
||||||
|
});
|
||||||
|
|
||||||
return subquery.execute().then(response => {
|
return subquery.execute().then(response => {
|
||||||
transformNotInQuery(notInQueryObject, subquery.className, response.results);
|
transformNotInQuery(notInQueryObject, subquery.className, response.results);
|
||||||
// Recurse to repeat
|
// Recurse to repeat
|
||||||
@@ -489,7 +569,7 @@ const transformSelect = (selectObject, key, objects) => {
|
|||||||
// The $select clause turns into an $in with values selected out of
|
// The $select clause turns into an $in with values selected out of
|
||||||
// the subquery.
|
// the subquery.
|
||||||
// Returns a possible-promise.
|
// Returns a possible-promise.
|
||||||
RestQuery.prototype.replaceSelect = function () {
|
_UnsafeRestQuery.prototype.replaceSelect = async function () {
|
||||||
var selectObject = findObjectWithKey(this.restWhere, '$select');
|
var selectObject = findObjectWithKey(this.restWhere, '$select');
|
||||||
if (!selectObject) {
|
if (!selectObject) {
|
||||||
return;
|
return;
|
||||||
@@ -519,13 +599,15 @@ RestQuery.prototype.replaceSelect = function () {
|
|||||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
var subquery = new RestQuery(
|
const subquery = await RestQuery({
|
||||||
this.config,
|
method: RestQuery.Method.find,
|
||||||
this.auth,
|
config: this.config,
|
||||||
selectValue.query.className,
|
auth: this.auth,
|
||||||
selectValue.query.where,
|
className: selectValue.query.className,
|
||||||
additionalOptions
|
restWhere: selectValue.query.where,
|
||||||
);
|
restOptions: additionalOptions,
|
||||||
|
});
|
||||||
|
|
||||||
return subquery.execute().then(response => {
|
return subquery.execute().then(response => {
|
||||||
transformSelect(selectObject, selectValue.key, response.results);
|
transformSelect(selectObject, selectValue.key, response.results);
|
||||||
// Keep replacing $select clauses
|
// Keep replacing $select clauses
|
||||||
@@ -551,7 +633,7 @@ const transformDontSelect = (dontSelectObject, key, objects) => {
|
|||||||
// The $dontSelect clause turns into an $nin with values selected out of
|
// The $dontSelect clause turns into an $nin with values selected out of
|
||||||
// the subquery.
|
// the subquery.
|
||||||
// Returns a possible-promise.
|
// Returns a possible-promise.
|
||||||
RestQuery.prototype.replaceDontSelect = function () {
|
_UnsafeRestQuery.prototype.replaceDontSelect = async function () {
|
||||||
var dontSelectObject = findObjectWithKey(this.restWhere, '$dontSelect');
|
var dontSelectObject = findObjectWithKey(this.restWhere, '$dontSelect');
|
||||||
if (!dontSelectObject) {
|
if (!dontSelectObject) {
|
||||||
return;
|
return;
|
||||||
@@ -579,13 +661,15 @@ RestQuery.prototype.replaceDontSelect = function () {
|
|||||||
additionalOptions.readPreference = this.restOptions.readPreference;
|
additionalOptions.readPreference = this.restOptions.readPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
var subquery = new RestQuery(
|
const subquery = await RestQuery({
|
||||||
this.config,
|
method: RestQuery.Method.find,
|
||||||
this.auth,
|
config: this.config,
|
||||||
dontSelectValue.query.className,
|
auth: this.auth,
|
||||||
dontSelectValue.query.where,
|
className: dontSelectValue.query.className,
|
||||||
additionalOptions
|
restWhere: dontSelectValue.query.where,
|
||||||
);
|
restOptions: additionalOptions,
|
||||||
|
});
|
||||||
|
|
||||||
return subquery.execute().then(response => {
|
return subquery.execute().then(response => {
|
||||||
transformDontSelect(dontSelectObject, dontSelectValue.key, response.results);
|
transformDontSelect(dontSelectObject, dontSelectValue.key, response.results);
|
||||||
// Keep replacing $dontSelect clauses
|
// Keep replacing $dontSelect clauses
|
||||||
@@ -593,7 +677,7 @@ RestQuery.prototype.replaceDontSelect = function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanResultAuthData = function (result) {
|
_UnsafeRestQuery.prototype.cleanResultAuthData = function (result) {
|
||||||
delete result.password;
|
delete result.password;
|
||||||
if (result.authData) {
|
if (result.authData) {
|
||||||
Object.keys(result.authData).forEach(provider => {
|
Object.keys(result.authData).forEach(provider => {
|
||||||
@@ -632,7 +716,7 @@ const replaceEqualityConstraint = constraint => {
|
|||||||
return constraint;
|
return constraint;
|
||||||
};
|
};
|
||||||
|
|
||||||
RestQuery.prototype.replaceEquality = function () {
|
_UnsafeRestQuery.prototype.replaceEquality = function () {
|
||||||
if (typeof this.restWhere !== 'object') {
|
if (typeof this.restWhere !== 'object') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -643,7 +727,7 @@ RestQuery.prototype.replaceEquality = function () {
|
|||||||
|
|
||||||
// Returns a promise for whether it was successful.
|
// Returns a promise for whether it was successful.
|
||||||
// Populates this.response with an object that only has 'results'.
|
// Populates this.response with an object that only has 'results'.
|
||||||
RestQuery.prototype.runFind = function (options = {}) {
|
_UnsafeRestQuery.prototype.runFind = function (options = {}) {
|
||||||
if (this.findOptions.limit === 0) {
|
if (this.findOptions.limit === 0) {
|
||||||
this.response = { results: [] };
|
this.response = { results: [] };
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -662,7 +746,7 @@ RestQuery.prototype.runFind = function (options = {}) {
|
|||||||
.then(results => {
|
.then(results => {
|
||||||
if (this.className === '_User' && !findOptions.explain) {
|
if (this.className === '_User' && !findOptions.explain) {
|
||||||
for (var result of results) {
|
for (var result of results) {
|
||||||
cleanResultAuthData(result);
|
this.cleanResultAuthData(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,7 +763,7 @@ RestQuery.prototype.runFind = function (options = {}) {
|
|||||||
|
|
||||||
// Returns a promise for whether it was successful.
|
// Returns a promise for whether it was successful.
|
||||||
// Populates this.response.count with the count
|
// Populates this.response.count with the count
|
||||||
RestQuery.prototype.runCount = function () {
|
_UnsafeRestQuery.prototype.runCount = function () {
|
||||||
if (!this.doCount) {
|
if (!this.doCount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -691,7 +775,7 @@ RestQuery.prototype.runCount = function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
RestQuery.prototype.denyProtectedFields = async function () {
|
_UnsafeRestQuery.prototype.denyProtectedFields = async function () {
|
||||||
if (this.auth.isMaster) {
|
if (this.auth.isMaster) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -716,7 +800,7 @@ RestQuery.prototype.denyProtectedFields = async function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Augments this.response with all pointers on an object
|
// Augments this.response with all pointers on an object
|
||||||
RestQuery.prototype.handleIncludeAll = function () {
|
_UnsafeRestQuery.prototype.handleIncludeAll = function () {
|
||||||
if (!this.includeAll) {
|
if (!this.includeAll) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -745,7 +829,7 @@ RestQuery.prototype.handleIncludeAll = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Updates property `this.keys` to contain all keys but the ones unselected.
|
// Updates property `this.keys` to contain all keys but the ones unselected.
|
||||||
RestQuery.prototype.handleExcludeKeys = function () {
|
_UnsafeRestQuery.prototype.handleExcludeKeys = function () {
|
||||||
if (!this.excludeKeys) {
|
if (!this.excludeKeys) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -763,7 +847,7 @@ RestQuery.prototype.handleExcludeKeys = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Augments this.response with data at the paths provided in this.include.
|
// Augments this.response with data at the paths provided in this.include.
|
||||||
RestQuery.prototype.handleInclude = function () {
|
_UnsafeRestQuery.prototype.handleInclude = function () {
|
||||||
if (this.include.length == 0) {
|
if (this.include.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -790,7 +874,7 @@ RestQuery.prototype.handleInclude = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Returns a promise of a processed set of results
|
//Returns a promise of a processed set of results
|
||||||
RestQuery.prototype.runAfterFindTrigger = function () {
|
_UnsafeRestQuery.prototype.runAfterFindTrigger = function () {
|
||||||
if (!this.response) {
|
if (!this.response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -910,7 +994,7 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
|||||||
includeRestOptions.readPreference = restOptions.readPreference;
|
includeRestOptions.readPreference = restOptions.readPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryPromises = Object.keys(pointersHash).map(className => {
|
const queryPromises = Object.keys(pointersHash).map(async className => {
|
||||||
const objectIds = Array.from(pointersHash[className]);
|
const objectIds = Array.from(pointersHash[className]);
|
||||||
let where;
|
let where;
|
||||||
if (objectIds.length === 1) {
|
if (objectIds.length === 1) {
|
||||||
@@ -918,7 +1002,14 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
|||||||
} else {
|
} else {
|
||||||
where = { objectId: { $in: objectIds } };
|
where = { objectId: { $in: objectIds } };
|
||||||
}
|
}
|
||||||
var query = new RestQuery(config, auth, className, where, includeRestOptions);
|
const query = await RestQuery({
|
||||||
|
method: objectIds.length === 1 ? RestQuery.Method.get : RestQuery.Method.find,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
restWhere: where,
|
||||||
|
restOptions: includeRestOptions,
|
||||||
|
});
|
||||||
return query.execute({ op: 'get' }).then(results => {
|
return query.execute({ op: 'get' }).then(results => {
|
||||||
results.className = className;
|
results.className = className;
|
||||||
return Promise.resolve(results);
|
return Promise.resolve(results);
|
||||||
@@ -1049,3 +1140,5 @@ function findObjectWithKey(root, key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RestQuery;
|
module.exports = RestQuery;
|
||||||
|
// For tests
|
||||||
|
module.exports._UnsafeRestQuery = _UnsafeRestQuery;
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ RestWrite.prototype.handleAuthData = function (authData) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The non-third-party parts of User transformation
|
// The non-third-party parts of User transformation
|
||||||
RestWrite.prototype.transformUser = function () {
|
RestWrite.prototype.transformUser = async function () {
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
|
|
||||||
if (this.className !== '_User') {
|
if (this.className !== '_User') {
|
||||||
@@ -599,15 +599,21 @@ RestWrite.prototype.transformUser = function () {
|
|||||||
if (this.query && this.objectId()) {
|
if (this.query && this.objectId()) {
|
||||||
// If we're updating a _User object, we need to clear out the cache for that user. Find all their
|
// If we're updating a _User object, we need to clear out the cache for that user. Find all their
|
||||||
// session tokens, and remove them from the cache.
|
// session tokens, and remove them from the cache.
|
||||||
promise = new RestQuery(this.config, Auth.master(this.config), '_Session', {
|
const query = await RestQuery({
|
||||||
|
method: RestQuery.Method.find,
|
||||||
|
config: this.config,
|
||||||
|
auth: Auth.master(this.config),
|
||||||
|
className: '_Session',
|
||||||
|
runBeforeFind: false,
|
||||||
|
restWhere: {
|
||||||
user: {
|
user: {
|
||||||
__type: 'Pointer',
|
__type: 'Pointer',
|
||||||
className: '_User',
|
className: '_User',
|
||||||
objectId: this.objectId(),
|
objectId: this.objectId(),
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
.execute()
|
});
|
||||||
.then(results => {
|
promise = query.execute().then(results => {
|
||||||
results.results.forEach(session =>
|
results.results.forEach(session =>
|
||||||
this.config.cacheController.user.del(session.sessionToken)
|
this.config.cacheController.user.del(session.sessionToken)
|
||||||
);
|
);
|
||||||
|
|||||||
33
src/SharedRest.js
Normal file
33
src/SharedRest.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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 = {
|
||||||
|
enforceRoleSecurity,
|
||||||
|
};
|
||||||
112
src/rest.js
112
src/rest.js
@@ -12,6 +12,7 @@ var Parse = require('parse/node').Parse;
|
|||||||
var RestQuery = require('./RestQuery');
|
var RestQuery = require('./RestQuery');
|
||||||
var RestWrite = require('./RestWrite');
|
var RestWrite = require('./RestWrite');
|
||||||
var triggers = require('./triggers');
|
var triggers = require('./triggers');
|
||||||
|
const { enforceRoleSecurity } = require('./SharedRest');
|
||||||
|
|
||||||
function checkTriggers(className, config, types) {
|
function checkTriggers(className, config, types) {
|
||||||
return types.some(triggerType => {
|
return types.some(triggerType => {
|
||||||
@@ -24,65 +25,34 @@ function checkLiveQuery(className, config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||||
function find(config, auth, className, restWhere, restOptions, clientSDK, context) {
|
const find = async (config, auth, className, restWhere, restOptions, clientSDK, context) => {
|
||||||
enforceRoleSecurity('find', className, auth);
|
const query = await RestQuery({
|
||||||
return triggers
|
method: RestQuery.Method.find,
|
||||||
.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,
|
config,
|
||||||
auth,
|
auth,
|
||||||
className,
|
className,
|
||||||
restWhere,
|
restWhere,
|
||||||
restOptions,
|
restOptions,
|
||||||
clientSDK,
|
clientSDK,
|
||||||
true,
|
context,
|
||||||
context
|
|
||||||
);
|
|
||||||
return query.execute();
|
|
||||||
});
|
});
|
||||||
}
|
return query.execute();
|
||||||
|
};
|
||||||
|
|
||||||
// get is just like find but only queries an objectId.
|
// 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 };
|
var restWhere = { objectId };
|
||||||
enforceRoleSecurity('get', className, auth);
|
const query = await RestQuery({
|
||||||
return triggers
|
method: RestQuery.Method.get,
|
||||||
.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,
|
config,
|
||||||
auth,
|
auth,
|
||||||
className,
|
className,
|
||||||
restWhere,
|
restWhere,
|
||||||
restOptions,
|
restOptions,
|
||||||
clientSDK,
|
clientSDK,
|
||||||
true,
|
context,
|
||||||
context
|
|
||||||
);
|
|
||||||
return query.execute();
|
|
||||||
});
|
});
|
||||||
|
return query.execute();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a promise that doesn't resolve to any useful value.
|
// Returns a promise that doesn't resolve to any useful value.
|
||||||
@@ -101,13 +71,18 @@ function del(config, auth, className, objectId, context) {
|
|||||||
let schemaController;
|
let schemaController;
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
const hasTriggers = checkTriggers(className, config, ['beforeDelete', 'afterDelete']);
|
const hasTriggers = checkTriggers(className, config, ['beforeDelete', 'afterDelete']);
|
||||||
const hasLiveQuery = checkLiveQuery(className, config);
|
const hasLiveQuery = checkLiveQuery(className, config);
|
||||||
if (hasTriggers || hasLiveQuery || className == '_Session') {
|
if (hasTriggers || hasLiveQuery || className == '_Session') {
|
||||||
return new RestQuery(config, auth, className, { objectId })
|
const query = await RestQuery({
|
||||||
.execute({ op: 'delete' })
|
method: RestQuery.Method.get,
|
||||||
.then(response => {
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
restWhere: { objectId },
|
||||||
|
});
|
||||||
|
return query.execute({ op: 'delete' }).then(response => {
|
||||||
if (response && response.results && response.results.length) {
|
if (response && response.results && response.results.length) {
|
||||||
const firstResult = response.results[0];
|
const firstResult = response.results[0];
|
||||||
firstResult.className = className;
|
firstResult.className = className;
|
||||||
@@ -193,21 +168,22 @@ function update(config, auth, className, restWhere, restObject, clientSDK, conte
|
|||||||
enforceRoleSecurity('update', className, auth);
|
enforceRoleSecurity('update', className, auth);
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
const hasTriggers = checkTriggers(className, config, ['beforeSave', 'afterSave']);
|
const hasTriggers = checkTriggers(className, config, ['beforeSave', 'afterSave']);
|
||||||
const hasLiveQuery = checkLiveQuery(className, config);
|
const hasLiveQuery = checkLiveQuery(className, config);
|
||||||
if (hasTriggers || hasLiveQuery) {
|
if (hasTriggers || hasLiveQuery) {
|
||||||
// Do not use find, as it runs the before finds
|
// Do not use find, as it runs the before finds
|
||||||
return new RestQuery(
|
const query = await RestQuery({
|
||||||
|
method: RestQuery.Method.get,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
className,
|
className,
|
||||||
restWhere,
|
restWhere,
|
||||||
undefined,
|
runAfterFind: false,
|
||||||
undefined,
|
runBeforeFind: false,
|
||||||
false,
|
context,
|
||||||
context
|
});
|
||||||
).execute({
|
return query.execute({
|
||||||
op: 'update',
|
op: 'update',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -243,36 +219,6 @@ function handleSessionMissingError(error, className, auth) {
|
|||||||
throw error;
|
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) {
|
|
||||||
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) {
|
|
||||||
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 = {
|
module.exports = {
|
||||||
create,
|
create,
|
||||||
del,
|
del,
|
||||||
|
|||||||
Reference in New Issue
Block a user