Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DEPRECATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h
| DEPPS9 | Rename LiveQuery `fields` option to `keys` | [#8389](https://github.com/parse-community/parse-server/issues/8389) | 6.0.0 (2023) | 7.0.0 (2024) | removed | - |
| DEPPS10 | Encode `Parse.Object` in Cloud Function and remove option `encodeParseObjectInCloudFunction` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 9.0.0 (2026) | deprecated | - |
| DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) | deprecated | - |
| DEPPS12 | Database option `allowPublicExplain` defaults to `true` | [#7519](https://github.com/parse-community/parse-server/issues/7519) | 8.3.0 (2025) | 9.0.0 (2027) | deprecated | - || 9.0.0 (2027) | deprecated | - |

[i_deprecation]: ## "The version and date of the deprecation."
[i_removal]: ## "The version and date of the planned removal."
Expand Down
6 changes: 6 additions & 0 deletions changelogs/CHANGELOG_alpha.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# [8.3.0-alpha.13](https://github.com/parse-community/parse-server/compare/8.3.0-alpha.12...8.3.0-alpha.13) (2025-10-25)

### Features

- Deprecation DEPPS12: database option `allowPublicExplain` defaults to `true` ([#7519](https://github.com/parse-community/parse-server/issues/7519)) ([DEPPS12](https://github.com/parse-community/parse-server/blob/alpha/DEPRECATIONS.md#depps12))

# [8.3.0-alpha.12](https://github.com/parse-community/parse-server/compare/8.3.0-alpha.11...8.3.0-alpha.12) (2025-10-25)


Expand Down
84 changes: 84 additions & 0 deletions spec/ParseQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const Parse = require('parse/node');
const request = require('../lib/request');
const ParseServerRESTController = require('../lib/ParseServerRESTController').ParseServerRESTController;
const ParseServer = require('../lib/ParseServer').default;
const Deprecator = require('../lib/Deprecator/Deprecator');

const masterKeyHeaders = {
'X-Parse-Application-Id': 'test',
Expand Down Expand Up @@ -5384,4 +5385,87 @@ describe('Parse.Query testing', () => {
expect(query1.length).toEqual(1);
});
});

it_id('a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d')(it_only_db('mongo'))(
'explain works with and without master key when allowPublicExplain is true',
async () => {
await reconfigureServer({
databaseURI: 'mongodb://localhost:27017/parse',
databaseAdapter: null,
databaseOptions: {
allowPublicExplain: true,
},
});

const obj = new TestObject({ foo: 'bar' });
await obj.save();

// Without master key
const query = new Parse.Query(TestObject);
query.explain();
const resultWithoutMasterKey = await query.find();
expect(resultWithoutMasterKey).toBeDefined();

// With master key
const queryWithMasterKey = new Parse.Query(TestObject);
queryWithMasterKey.explain();
const resultWithMasterKey = await queryWithMasterKey.find({ useMasterKey: true });
expect(resultWithMasterKey).toBeDefined();
}
);

it_id('b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e')(it_only_db('mongo'))(
'explain requires master key when allowPublicExplain is false',
async () => {
console.log("just before test")
await reconfigureServer({
databaseURI: 'mongodb://localhost:27017/parse',
databaseAdapter: null,
databaseOptions: {
allowPublicExplain: false,
},
});

const obj = new TestObject({ foo: 'bar' });
await obj.save();

// Without master key
const query = new Parse.Query(TestObject);
query.explain();
await expectAsync(query.find()).toBeRejectedWith(
new Parse.Error(
Parse.Error.INVALID_QUERY,
'Using the explain query parameter requires the master key'
)
);

// With master key
const queryWithMasterKey = new Parse.Query(TestObject);
queryWithMasterKey.explain();
const result = await queryWithMasterKey.find({ useMasterKey: true });
expect(result).toBeDefined();
}
);

it_id('c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f')(it_only_db('mongo'))(
'explain works with and without master key by default (allowPublicExplain not set)',
async () => {
await reconfigureServer({});

const obj = new TestObject({ foo: 'bar' });
await obj.save();

// Without master key (default behavior)
const query = new Parse.Query(TestObject);
query.explain();
const resultWithoutMasterKey = await query.find();
expect(resultWithoutMasterKey).toBeDefined();

// With master key
const queryWithMasterKey = new Parse.Query(TestObject);
queryWithMasterKey.explain();
const resultWithMasterKey = await queryWithMasterKey.find({ useMasterKey: true });
expect(resultWithMasterKey).toBeDefined();
}
);
});
9 changes: 7 additions & 2 deletions src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,13 @@ export class MongoStorageAdapter implements StorageAdapter {
this.enableSchemaHooks = !!mongoOptions.enableSchemaHooks;
this.schemaCacheTtl = mongoOptions.schemaCacheTtl;
this.disableIndexFieldValidation = !!mongoOptions.disableIndexFieldValidation;
for (const key of ['enableSchemaHooks', 'schemaCacheTtl', 'maxTimeMS', 'disableIndexFieldValidation']) {
delete mongoOptions[key];
for (const key of [
'enableSchemaHooks',
'schemaCacheTtl',
'maxTimeMS',
'disableIndexFieldValidation',
'allowPublicExplain',
]) {
delete this._mongoOptions[key];
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,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 `databaseOptions.allowPublicExplain must be a boolean`;
}
}

static validateRateLimit(rateLimit) {
Expand Down
6 changes: 6 additions & 0 deletions src/Deprecator/Deprecations.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@
module.exports = [
{ optionKey: 'encodeParseObjectInCloudFunction', changeNewDefault: 'true' },
{ optionKey: 'enableInsecureAuthAdapters', changeNewDefault: 'false' },
{
optionKey: 'databaseOptions.allowPublicExplain',
changeNewDefault: 'false',
solution:
'To prepare for the future change, set Parse Server option databaseOptions.allowPublicExplain to false and ensure explain queries are only made with master key.',
},
];
7 changes: 7 additions & 0 deletions src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,13 @@ module.exports.FileUploadOptions = {
},
};
module.exports.DatabaseOptions = {
allowPublicExplain: {
env: 'PARSE_SERVER_DATABASE_ALLOW_PUBLIC_EXPLAIN',
help:
'Set to `true` to allow explain queries without master key. This option is deprecated and the default will change to `false` in a future version.',
action: parsers.booleanParser,
default: true,
},
autoSelectFamily: {
env: 'PARSE_SERVER_DATABASE_AUTO_SELECT_FAMILY',
help:
Expand Down
1 change: 1 addition & 0 deletions src/Options/docs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,9 @@ export interface DatabaseOptions {
autoSelectFamilyAttemptTimeout: ?number;
/* 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 explain queries without master key. This option is deprecated and the default will change to `false` in a future version.
:DEFAULT: true */
allowPublicExplain: ?boolean;
}

export interface AuthAdapter {
Expand Down
11 changes: 11 additions & 0 deletions src/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading