feat: Add Parse Server option allowPublicExplain to allow Parse.Query.explain without master key (#9890)
This commit is contained in:
@@ -148,7 +148,7 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
this._uri = uri;
|
||||
this._collectionPrefix = collectionPrefix;
|
||||
this._mongoOptions = { ...mongoOptions };
|
||||
this._onchange = () => { };
|
||||
this._onchange = () => {};
|
||||
|
||||
// MaxTimeMS is not a global MongoDB client option, it is applied per operation.
|
||||
this._maxTimeMS = mongoOptions.maxTimeMS;
|
||||
@@ -157,10 +157,12 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
this.schemaCacheTtl = mongoOptions.schemaCacheTtl;
|
||||
this.disableIndexFieldValidation = !!mongoOptions.disableIndexFieldValidation;
|
||||
this._logClientEvents = mongoOptions.logClientEvents;
|
||||
|
||||
// Remove Parse Server-specific options that should not be passed to MongoDB client
|
||||
// Note: We only delete from this._mongoOptions, not from the original mongoOptions object,
|
||||
// because other components (like DatabaseController) need access to these options
|
||||
for (const key of [
|
||||
'allowPublicExplain',
|
||||
'enableSchemaHooks',
|
||||
'schemaCacheTtl',
|
||||
'maxTimeMS',
|
||||
|
||||
@@ -659,6 +659,11 @@ export class Config {
|
||||
} else if (typeof databaseOptions.schemaCacheTtl !== 'number') {
|
||||
throw `databaseOptions.schemaCacheTtl must be a number`;
|
||||
}
|
||||
if (databaseOptions.allowPublicExplain === undefined) {
|
||||
databaseOptions.allowPublicExplain = DatabaseOptions.allowPublicExplain.default;
|
||||
} else if (typeof databaseOptions.allowPublicExplain !== 'boolean') {
|
||||
throw `Parse Server option 'databaseOptions.allowPublicExplain' must be a boolean.`;
|
||||
}
|
||||
}
|
||||
|
||||
static validateRateLimit(rateLimit) {
|
||||
|
||||
@@ -18,4 +18,5 @@
|
||||
module.exports = [
|
||||
{ optionKey: 'encodeParseObjectInCloudFunction', changeNewDefault: 'true' },
|
||||
{ optionKey: 'enableInsecureAuthAdapters', changeNewDefault: 'false' },
|
||||
{ optionKey: 'databaseOptions.allowPublicExplain', changeNewDefault: 'false' },
|
||||
];
|
||||
|
||||
@@ -1137,6 +1137,13 @@ module.exports.LogClientEvent = {
|
||||
},
|
||||
};
|
||||
module.exports.DatabaseOptions = {
|
||||
allowPublicExplain: {
|
||||
env: 'PARSE_SERVER_DATABASE_ALLOW_PUBLIC_EXPLAIN',
|
||||
help:
|
||||
'Set to `true` to allow `Parse.Query.explain` without master key.<br><br>\u26A0\uFE0F Enabling this option may expose sensitive query performance data to unauthorized users and could potentially be exploited for malicious purposes.',
|
||||
action: parsers.booleanParser,
|
||||
default: true,
|
||||
},
|
||||
appName: {
|
||||
env: 'PARSE_SERVER_DATABASE_APP_NAME',
|
||||
help:
|
||||
|
||||
@@ -257,6 +257,7 @@
|
||||
|
||||
/**
|
||||
* @interface DatabaseOptions
|
||||
* @property {Boolean} allowPublicExplain Set to `true` to allow `Parse.Query.explain` without master key.<br><br>⚠️ Enabling this option may expose sensitive query performance data to unauthorized users and could potentially be exploited for malicious purposes.
|
||||
* @property {String} appName The MongoDB driver option to specify the name of the application that created this MongoClient instance.
|
||||
* @property {String} authMechanism The MongoDB driver option to specify the authentication mechanism that MongoDB will use to authenticate the connection.
|
||||
* @property {Any} authMechanismProperties The MongoDB driver option to specify properties for the specified authMechanism as a comma-separated list of colon-separated key-value pairs.
|
||||
|
||||
@@ -751,6 +751,9 @@ export interface DatabaseOptions {
|
||||
createIndexRoleName: ?boolean;
|
||||
/* Set to `true` to disable validation of index fields. When disabled, indexes can be created even if the fields do not exist in the schema. This can be useful when creating indexes on fields that will be added later. */
|
||||
disableIndexFieldValidation: ?boolean;
|
||||
/* Set to `true` to allow `Parse.Query.explain` without master key.<br><br>⚠️ Enabling this option may expose sensitive query performance data to unauthorized users and could potentially be exploited for malicious purposes.
|
||||
:DEFAULT: true */
|
||||
allowPublicExplain: ?boolean;
|
||||
/* An array of MongoDB client event configurations to enable logging of specific events. */
|
||||
logClientEvents: ?(LogClientEvent[]);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,21 @@ class CheckGroupServerConfig extends CheckGroup {
|
||||
}
|
||||
},
|
||||
}),
|
||||
new Check({
|
||||
title: 'Public database explain disabled',
|
||||
warning:
|
||||
'Database explain queries are publicly accessible, which may expose sensitive database performance information and schema details.',
|
||||
solution:
|
||||
"Change Parse Server configuration to 'databaseOptions.allowPublicExplain: false'. You will need to use master key to run explain queries.",
|
||||
check: () => {
|
||||
if (
|
||||
config.databaseOptions?.allowPublicExplain === true ||
|
||||
config.databaseOptions?.allowPublicExplain == null
|
||||
) {
|
||||
throw 1;
|
||||
}
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
11
src/rest.js
11
src/rest.js
@@ -35,6 +35,17 @@ async function runFindTriggers(
|
||||
) {
|
||||
const { isGet } = options;
|
||||
|
||||
if (restOptions && restOptions.explain && !auth.isMaster) {
|
||||
const allowPublicExplain = config.databaseOptions?.allowPublicExplain ?? true;
|
||||
|
||||
if (!allowPublicExplain) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'Using the explain query parameter requires the master key'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Run beforeFind trigger - may modify query or return objects directly
|
||||
const result = await triggers.maybeRunQueryTrigger(
|
||||
triggers.Types.beforeFind,
|
||||
|
||||
Reference in New Issue
Block a user