Skip to content

Commit 00363b4

Browse files
committed
Added a new server option, nonMasterExplain
Manages whether non-master users are allowed to use the "explain" parameter on queries Currently defaults to true, for backwards compatibility. However, this behaviour is deprecated, and at some point will default to false. Added tests for it
1 parent 2f557f8 commit 00363b4

File tree

7 files changed

+75
-0
lines changed

7 files changed

+75
-0
lines changed

DEPRECATIONS.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h
66
|-------------------------------------------------|----------------------------------------------------------------------|---------------------------------|---------------------------------|-----------------------|-------|
77
| Native MongoDB syntax in aggregation pipeline | [#7338](https://github.com/parse-community/parse-server/issues/7338) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
88
| Config option `directAccess` defaults to `true` | [#6636](https://github.com/parse-community/parse-server/pull/6636) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
9+
| Config option `nonMasterExplain` defaults to `false` | [#7519](https://github.com/parse-community/parse-server/issues/7519) | XXX | XXX | deprecated | - |
910

1011
[i_deprecation]: ## "The version and date of the deprecation."
1112
[i_removal]: ## "The version and date of the planned removal."

spec/ParseQuery.spec.js

+52
Original file line numberDiff line numberDiff line change
@@ -5218,4 +5218,56 @@ describe('Parse.Query testing', () => {
52185218
// Validate
52195219
expect(result.executionStats).not.toBeUndefined();
52205220
});
5221+
5222+
it('users cannot user explain unless nonMasterExplain is set', async () => {
5223+
// Create an object
5224+
const obj = new TestObject({ foo: 'baz', hello: 'world' });
5225+
await obj.save();
5226+
// Explicitly allow non-master explain
5227+
await reconfigureServer({ nonMasterExplain: true });
5228+
// Query TestObject with explain.
5229+
let query = new Parse.Query('TestObject');
5230+
query.equalTo('objectId', obj.id);
5231+
query.explain();
5232+
let result = await query.find(); // Must not throw
5233+
// Explicitly disallow non-master explain
5234+
await reconfigureServer({ nonMasterExplain: false });
5235+
try {
5236+
await query.find();
5237+
fail('users can use explain even if nonMasterExplain is set to false');
5238+
} catch (e) {
5239+
equal(e.code, Parse.Error.OPERATION_FORBIDDEN);
5240+
equal(e.message, 'Cannot explain');
5241+
}
5242+
try {
5243+
await new Parse.Query('TestObject').explain().get(obj.id);
5244+
fail('users can use explain even if nonMasterExplain is set to false');
5245+
} catch (e) {
5246+
equal(e.code, Parse.Error.OPERATION_FORBIDDEN);
5247+
equal(e.message, 'Cannot explain');
5248+
}
5249+
// Non-explain queries should still work, of course
5250+
query = new Parse.Query('TestObject');
5251+
query.equalTo('objectId', obj.id);
5252+
result = await query.find();
5253+
equal(result.length, 1);
5254+
equal(result[0].id, obj.id);
5255+
});
5256+
it('the master key can use explain no matter nonMasterExplain', async () => {
5257+
const obj = new TestObject({ foo: 'baz', hello: 'world' });
5258+
await obj.save();
5259+
const queryWithExplain = new Parse.Query('TestObject');
5260+
queryWithExplain.equalTo('objectId', obj.id);
5261+
queryWithExplain.explain();
5262+
const queryWoExplain = new Parse.Query('TestObject');
5263+
queryWoExplain.equalTo('objectId', obj.id);
5264+
// Explicitly disallow non-master explain
5265+
await reconfigureServer({ nonMasterExplain: false });
5266+
await queryWithExplain.find({ useMasterKey: true }); // Must not throw
5267+
await queryWoExplain.find({ useMasterKey: true }); // Must not throw
5268+
// Explicitly allow non-master explain
5269+
await reconfigureServer({ nonMasterExplain: true });
5270+
await queryWithExplain.find({ useMasterKey: true }); // Must not throw
5271+
await queryWoExplain.find({ useMasterKey: true }); // Must not throw
5272+
});
52215273
});

src/Deprecator/Deprecations.js

+5
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ module.exports = [
2222
solution:
2323
"Additionally, the environment variable 'PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS' will be deprecated and renamed to 'PARSE_SERVER_DIRECT_ACCESS' in a future version; it is currently possible to use either one.",
2424
},
25+
{
26+
optionKey: 'nonMasterExplain',
27+
envKey: 'PARSE_SERVER_NON_MASTER_EXPLAIN',
28+
changeNewDefault: 'false',
29+
},
2530
];

src/Options/Definitions.js

+6
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ module.exports.ParseServerOptions = {
277277
action: parsers.booleanParser,
278278
default: false,
279279
},
280+
nonMasterExplain: {
281+
env: 'PARSE_SERVER_NON_MASTER_EXPLAIN',
282+
help: 'Allow non-master users to use the `explain` query parameter.',
283+
action: parsers.booleanParser,
284+
default: true,
285+
},
280286
objectIdSize: {
281287
env: 'PARSE_SERVER_OBJECT_ID_SIZE',
282288
help: "Sets the number of characters in generated object id's, default 10",

src/Options/docs.js

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* @property {Boolean} mountGraphQL Mounts the GraphQL endpoint
5353
* @property {String} mountPath Mount path for the server, defaults to /parse
5454
* @property {Boolean} mountPlayground Mounts the GraphQL Playground - never use this option in production
55+
* @property {Boolean} nonMasterExplain Allow non-master users to use the `explain` query parameter, defaults to true
5556
* @property {Number} objectIdSize Sets the number of characters in generated object id's, default 10
5657
* @property {PagesOptions} pages The options for pages such as password reset and email verification. Caution, this is an experimental feature that may not be appropriate for production.
5758
* @property {PasswordPolicyOptions} passwordPolicy The password policy for enforcing password related rules.

src/Options/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ export interface ParseServerOptions {
209209
cluster: ?NumberOrBoolean;
210210
/* middleware for express server, can be string or function */
211211
middleware: ?((() => void) | string);
212+
212213
/* Starts the liveQuery server */
213214
startLiveQueryServer: ?boolean;
214215
/* Live query server configuration options (will start the liveQuery server) */
@@ -239,6 +240,9 @@ export interface ParseServerOptions {
239240
:ENV: PARSE_SERVER_PLAYGROUND_PATH
240241
:DEFAULT: /playground */
241242
playgroundPath: ?string;
243+
/* Allow non-master users to use the `explain` query parameter.
244+
:DEFAULT: true */
245+
nonMasterExplain: ?boolean;
242246
/* Callback when server has started */
243247
serverStartComplete: ?(error: ?Error) => void;
244248
/* Callback when server has closed */

src/rest.js

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ function checkLiveQuery(className, config) {
2626
// Returns a promise for an object with optional keys 'results' and 'count'.
2727
function find(config, auth, className, restWhere, restOptions, clientSDK, context) {
2828
enforceRoleSecurity('find', className, auth);
29+
if (!config.nonMasterExplain && restOptions.explain && !auth.isMaster) {
30+
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Cannot explain');
31+
}
2932
return triggers
3033
.maybeRunQueryTrigger(
3134
triggers.Types.beforeFind,
@@ -57,6 +60,9 @@ function find(config, auth, className, restWhere, restOptions, clientSDK, contex
5760
const get = (config, auth, className, objectId, restOptions, clientSDK, context) => {
5861
var restWhere = { objectId };
5962
enforceRoleSecurity('get', className, auth);
63+
if (!config.nonMasterExplain && restOptions.explain && !auth.isMaster) {
64+
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Cannot explain');
65+
}
6066
return triggers
6167
.maybeRunQueryTrigger(
6268
triggers.Types.beforeFind,

0 commit comments

Comments
 (0)