Skip to content

Commit 739ffbe

Browse files
authored
refactor: Parse Pointer allows to access internal Parse Server classes and circumvent beforeFind query trigger (#8734)
1 parent d6b17ba commit 739ffbe

12 files changed

+423
-230
lines changed

spec/CloudCode.spec.js

+29
Original file line numberDiff line numberDiff line change
@@ -2423,6 +2423,35 @@ describe('beforeFind hooks', () => {
24232423
})
24242424
.then(() => done());
24252425
});
2426+
2427+
it('should run beforeFind on pointers and array of pointers from an object', async () => {
2428+
const obj1 = new Parse.Object('TestObject');
2429+
const obj2 = new Parse.Object('TestObject2');
2430+
const obj3 = new Parse.Object('TestObject');
2431+
obj2.set('aField', 'aFieldValue');
2432+
await obj2.save();
2433+
obj1.set('pointerField', obj2);
2434+
obj3.set('pointerFieldArray', [obj2]);
2435+
await obj1.save();
2436+
await obj3.save();
2437+
const spy = jasmine.createSpy('beforeFindSpy');
2438+
Parse.Cloud.beforeFind('TestObject2', spy);
2439+
const query = new Parse.Query('TestObject');
2440+
await query.get(obj1.id);
2441+
// Pointer not included in query so we don't expect beforeFind to be called
2442+
expect(spy).not.toHaveBeenCalled();
2443+
const query2 = new Parse.Query('TestObject');
2444+
query2.include('pointerField');
2445+
const res = await query2.get(obj1.id);
2446+
expect(res.get('pointerField').get('aField')).toBe('aFieldValue');
2447+
// Pointer included in query so we expect beforeFind to be called
2448+
expect(spy).toHaveBeenCalledTimes(1);
2449+
const query3 = new Parse.Query('TestObject');
2450+
query3.include('pointerFieldArray');
2451+
const res2 = await query3.get(obj3.id);
2452+
expect(res2.get('pointerFieldArray')[0].get('aField')).toBe('aFieldValue');
2453+
expect(spy).toHaveBeenCalledTimes(2);
2454+
});
24262455
});
24272456

24282457
describe('afterFind hooks', () => {

spec/ParseGraphQLServer.spec.js

-1
Original file line numberDiff line numberDiff line change
@@ -5275,7 +5275,6 @@ describe('ParseGraphQLServer', () => {
52755275

52765276
it('should only count', async () => {
52775277
await prepareData();
5278-
52795278
await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear();
52805279

52815280
const where = {

spec/ParseRole.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('Parse Role testing', () => {
142142
return Promise.all(promises);
143143
};
144144

145-
const restExecute = spyOn(RestQuery.prototype, 'execute').and.callThrough();
145+
const restExecute = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
146146

147147
let user, auth, getAllRolesSpy;
148148
createTestUser()

spec/RestQuery.spec.js

+24-20
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,16 @@ describe('RestQuery.each', () => {
399399
}
400400
const config = Config.get('test');
401401
await Parse.Object.saveAll(objects);
402-
const query = new RestQuery(
402+
const query = await RestQuery({
403+
method: RestQuery.Method.find,
403404
config,
404-
auth.master(config),
405-
'Object',
406-
{ value: { $gt: 2 } },
407-
{ limit: 2 }
408-
);
405+
auth: auth.master(config),
406+
className: 'Object',
407+
restWhere: { value: { $gt: 2 } },
408+
restOptions: { limit: 2 },
409+
});
409410
const spy = spyOn(query, 'execute').and.callThrough();
410-
const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough();
411+
const classSpy = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
411412
const results = [];
412413
await query.each(result => {
413414
expect(result.value).toBeGreaterThan(2);
@@ -438,34 +439,37 @@ describe('RestQuery.each', () => {
438439
* Two queries needed since objectId are sorted and we can't know which one
439440
* going to be the first and then skip by the $gt added by each
440441
*/
441-
const queryOne = new RestQuery(
442+
const queryOne = await RestQuery({
443+
method: RestQuery.Method.get,
442444
config,
443-
auth.master(config),
444-
'Letter',
445-
{
445+
auth: auth.master(config),
446+
className: 'Letter',
447+
restWhere: {
446448
numbers: {
447449
__type: 'Pointer',
448450
className: 'Number',
449451
objectId: object1.id,
450452
},
451453
},
452-
{ limit: 1 }
453-
);
454-
const queryTwo = new RestQuery(
454+
restOptions: { limit: 1 },
455+
});
456+
457+
const queryTwo = await RestQuery({
458+
method: RestQuery.Method.get,
455459
config,
456-
auth.master(config),
457-
'Letter',
458-
{
460+
auth: auth.master(config),
461+
className: 'Letter',
462+
restWhere: {
459463
numbers: {
460464
__type: 'Pointer',
461465
className: 'Number',
462466
objectId: object2.id,
463467
},
464468
},
465-
{ limit: 1 }
466-
);
469+
restOptions: { limit: 1 },
470+
});
467471

468-
const classSpy = spyOn(RestQuery.prototype, 'execute').and.callThrough();
472+
const classSpy = spyOn(RestQuery._UnsafeRestQuery.prototype, 'execute').and.callThrough();
469473
const resultsOne = [];
470474
const resultsTwo = [];
471475
await queryOne.each(result => {

spec/rest.spec.js

+32
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,38 @@ describe('rest create', () => {
660660
});
661661
});
662662

663+
it('cannot get object in volatileClasses if not masterKey through pointer', async () => {
664+
const masterKeyOnlyClassObject = new Parse.Object('_PushStatus');
665+
await masterKeyOnlyClassObject.save(null, { useMasterKey: true });
666+
const obj2 = new Parse.Object('TestObject');
667+
// Anyone is can basically create a pointer to any object
668+
// or some developers can use master key in some hook to link
669+
// private objects to standard objects
670+
obj2.set('pointer', masterKeyOnlyClassObject);
671+
await obj2.save();
672+
const query = new Parse.Query('TestObject');
673+
query.include('pointer');
674+
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
675+
"Clients aren't allowed to perform the get operation on the _PushStatus collection."
676+
);
677+
});
678+
679+
it('cannot get object in _GlobalConfig if not masterKey through pointer', async () => {
680+
await Parse.Config.save({ privateData: 'secret' }, { privateData: true });
681+
const obj2 = new Parse.Object('TestObject');
682+
obj2.set('globalConfigPointer', {
683+
__type: 'Pointer',
684+
className: '_GlobalConfig',
685+
objectId: 1,
686+
});
687+
await obj2.save();
688+
const query = new Parse.Query('TestObject');
689+
query.include('globalConfigPointer');
690+
await expectAsync(query.get(obj2.id)).toBeRejectedWithError(
691+
"Clients aren't allowed to perform the get operation on the _GlobalConfig collection."
692+
);
693+
});
694+
663695
it('locks down session', done => {
664696
let currentUser;
665697
Parse.User.signUp('foo', 'bar')

src/Auth.js

+46-15
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,16 @@ const renewSessionIfNeeded = async ({ config, session, sessionToken }) => {
7777
throttle[sessionToken] = setTimeout(async () => {
7878
try {
7979
if (!session) {
80-
const { results } = await new RestQuery(
80+
const query = await RestQuery({
81+
method: RestQuery.Method.get,
8182
config,
82-
master(config),
83-
'_Session',
84-
{ sessionToken },
85-
{ limit: 1 }
86-
).execute();
83+
auth: master(config),
84+
runBeforeFind: false,
85+
className: '_Session',
86+
restWhere: { sessionToken },
87+
restOptions: { limit: 1 },
88+
});
89+
const { results } = await query.execute();
8790
session = results[0];
8891
}
8992
const lastUpdated = new Date(session?.updatedAt);
@@ -140,7 +143,15 @@ const getAuthForSessionToken = async function ({
140143
include: 'user',
141144
};
142145
const RestQuery = require('./RestQuery');
143-
const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions);
146+
const query = await RestQuery({
147+
method: RestQuery.Method.get,
148+
config,
149+
runBeforeFind: false,
150+
auth: master(config),
151+
className: '_Session',
152+
restWhere: { sessionToken },
153+
restOptions,
154+
});
144155
results = (await query.execute()).results;
145156
} else {
146157
results = (
@@ -179,12 +190,20 @@ const getAuthForSessionToken = async function ({
179190
});
180191
};
181192

182-
var getAuthForLegacySessionToken = function ({ config, sessionToken, installationId }) {
193+
var getAuthForLegacySessionToken = async function ({ config, sessionToken, installationId }) {
183194
var restOptions = {
184195
limit: 1,
185196
};
186197
const RestQuery = require('./RestQuery');
187-
var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions);
198+
var query = await RestQuery({
199+
method: RestQuery.Method.get,
200+
config,
201+
runBeforeFind: false,
202+
auth: master(config),
203+
className: '_User',
204+
restWhere: { _session_token: sessionToken },
205+
restOptions,
206+
});
188207
return query.execute().then(response => {
189208
var results = response.results;
190209
if (results.length !== 1) {
@@ -229,9 +248,15 @@ Auth.prototype.getRolesForUser = async function () {
229248
},
230249
};
231250
const RestQuery = require('./RestQuery');
232-
await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result =>
233-
results.push(result)
234-
);
251+
const query = await RestQuery({
252+
method: RestQuery.Method.find,
253+
runBeforeFind: false,
254+
config: this.config,
255+
auth: master(this.config),
256+
className: '_Role',
257+
restWhere,
258+
});
259+
await query.each(result => results.push(result));
235260
} else {
236261
await new Parse.Query(Parse.Role)
237262
.equalTo('users', this.user)
@@ -323,9 +348,15 @@ Auth.prototype.getRolesByIds = async function (ins) {
323348
});
324349
const restWhere = { roles: { $in: roles } };
325350
const RestQuery = require('./RestQuery');
326-
await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result =>
327-
results.push(result)
328-
);
351+
const query = await RestQuery({
352+
method: RestQuery.Method.find,
353+
config: this.config,
354+
runBeforeFind: false,
355+
auth: master(this.config),
356+
className: '_Role',
357+
restWhere,
358+
});
359+
await query.each(result => results.push(result));
329360
}
330361
return results;
331362
};

src/Controllers/PushController.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,16 @@ export class PushController {
5858

5959
// Force filtering on only valid device tokens
6060
const updateWhere = applyDeviceTokenExists(where);
61-
badgeUpdate = () => {
61+
badgeUpdate = async () => {
6262
// Build a real RestQuery so we can use it in RestWrite
63-
const restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);
63+
const restQuery = await RestQuery({
64+
method: RestQuery.Method.find,
65+
config,
66+
runBeforeFind: false,
67+
auth: master(config),
68+
className: '_Installation',
69+
restWhere: updateWhere,
70+
});
6471
return restQuery.buildRestWhere().then(() => {
6572
const write = new RestWrite(
6673
config,

src/Controllers/UserController.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class UserController extends AdaptableController {
6161
return true;
6262
}
6363

64-
verifyEmail(username, token) {
64+
async verifyEmail(username, token) {
6565
if (!this.shouldVerifyEmails) {
6666
// Trying to verify email when not enabled
6767
// TODO: Better error here.
@@ -83,8 +83,14 @@ export class UserController extends AdaptableController {
8383
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
8484
}
8585
const maintenanceAuth = Auth.maintenance(this.config);
86-
var findUserForEmailVerification = new RestQuery(this.config, maintenanceAuth, '_User', {
87-
username,
86+
var findUserForEmailVerification = await RestQuery({
87+
method: RestQuery.Method.get,
88+
config: this.config,
89+
auth: maintenanceAuth,
90+
className: '_User',
91+
restWhere: {
92+
username,
93+
},
8894
});
8995
return findUserForEmailVerification.execute().then(result => {
9096
if (result.results.length && result.results[0].emailVerified) {
@@ -123,7 +129,7 @@ export class UserController extends AdaptableController {
123129
});
124130
}
125131

126-
getUserIfNeeded(user) {
132+
async getUserIfNeeded(user) {
127133
if (user.username && user.email) {
128134
return Promise.resolve(user);
129135
}
@@ -135,7 +141,14 @@ export class UserController extends AdaptableController {
135141
where.email = user.email;
136142
}
137143

138-
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
144+
var query = await RestQuery({
145+
method: RestQuery.Method.get,
146+
config: this.config,
147+
runBeforeFind: false,
148+
auth: Auth.master(this.config),
149+
className: '_User',
150+
restWhere: where,
151+
});
139152
return query.execute().then(function (result) {
140153
if (result.results.length != 1) {
141154
throw undefined;

0 commit comments

Comments
 (0)