Skip to content

feat: Prevent Parse Server start in case of unknown option in server configuration #8987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b3924bd
feat: Log warning in case of a unknown options
vivekjoshi556 Mar 4, 2024
70e7a02
Merge branch 'parse-community:alpha' into issue_8938
vivekjoshi556 Mar 5, 2024
99a03d2
fix: splitting test cases | ignored internal configs | covered all co…
vivekjoshi556 Mar 14, 2024
a9e7875
fix: resolve conflicts
vivekjoshi556 Mar 14, 2024
946d4b4
Merge branch 'alpha' into issue_8938
mtrezza Mar 14, 2024
b70592e
doc: included documentation for using this feature
vivekjoshi556 Mar 16, 2024
e791e40
Merge branch 'issue_8938' of https://github.com/vivekjoshi556/parse-s…
vivekjoshi556 Mar 16, 2024
d4ab4f0
Apply suggestions from code review
vivekjoshi556 Mar 16, 2024
e9311b3
fix: CacheAdapter does not connect when using a CacheAdapter with a J…
dblythy Mar 15, 2024
8aaa172
chore(release): 7.0.0-alpha.27 [skip ci]
semantic-release-bot Mar 15, 2024
8ef8f54
test: Fix flaky tests for comment in MongoDB query (#9015)
Meglali20 Mar 15, 2024
80fb070
refactor: Security upgrade follow-redirects from 1.15.5 to 1.15.6 (#9…
parseplatformorg Mar 16, 2024
79f41fc
Update src/ParseServer.js
vivekjoshi556 Mar 16, 2024
93022fe
fix: error message on server crash
vivekjoshi556 Mar 16, 2024
b754139
Merge branch 'alpha' into issue_8938
mtrezza Mar 16, 2024
7819bdb
refactor: removing duplication
vivekjoshi556 Mar 18, 2024
744fb9e
Merge branch 'alpha' into issue_8938
mtrezza Mar 21, 2024
05ce806
refactor: removing duplication
vivekjoshi556 Mar 18, 2024
49fdbf4
Merge branch 'issue_8938' of https://github.com/vivekjoshi556/parse-s…
vivekjoshi556 Mar 21, 2024
c5c2b5d
fix: import error
vivekjoshi556 Mar 21, 2024
53f1d93
nits
mtrezza Mar 24, 2024
e333d73
suggestion
mtrezza Mar 24, 2024
2297f47
Update ParseServer.js
mtrezza Mar 24, 2024
3ce2ae6
Merge branch 'alpha' into issue_8938
mtrezza Apr 5, 2024
753bbd8
refactor
mtrezza Apr 5, 2024
2c058d8
remove centralized logic suggestion
mtrezza Apr 5, 2024
024c154
refactor
mtrezza Apr 5, 2024
7699453
Merge branch 'alpha' into issue_8938
mtrezza Apr 6, 2024
b25cfef
refactor: v2
vivekjoshi556 Apr 6, 2024
adfed9d
Merge branch 'issue_8938' of https://github.com/vivekjoshi556/parse-s…
vivekjoshi556 Apr 6, 2024
5756cca
docs: reverting contributing guidelines
vivekjoshi556 Apr 6, 2024
f9ed251
Merge branch 'alpha' into issue_8938
mtrezza Apr 6, 2024
7ac43a5
removing empty newlines
mtrezza Apr 6, 2024
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
93 changes: 93 additions & 0 deletions spec/ParseConfigKey.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const ParseServer = require('../lib/index').ParseServer;
const controllers = require('../lib/Controllers/');
const Config = require('../lib/Config');

describe('Config Keys', () => {
it('should throw a warning for incorrect key name in Config', () => {
const doNothing = () => {
return {};
};

spyOn(controllers, 'getControllers').and.callFake(doNothing);
spyOn(Config, 'put').and.callFake(doNothing);
// Spy on the console.warn method to capture warnings
spyOn(console, 'warn');

new ParseServer({
...defaultConfiguration,
unknownKeyName: 'unknownValue', // invalid key
masterKeyIPs: '', // invalid key
accountLockout: {
duration: 10,
threshold: 10,
UnlockOnPasswordReset: false, // invalid key
},
passwordPolicy: {
MaxPasswordAge: 10, // invalid key
},
fileUpload: {
EnableForAnonymousUser: false, // invalid key
},
schema: {
Strict: true, // invalid key
},
rateLimit: {
requestPath: '',
requestTimeWindow: 10,
requestCount: 10,
IncludeInternalRequests: false, // invalid key
},
security: {
EnableCheck: true, // invalid key
},
pages: {
EnableRouter: true, // invalid key
},
idempotencyOptions: {
Ttl: 10, // invalid key
},
databaseOptions: {
SchemaCacheTtl: 10, // invalid key
},
logLevels: {
CloudFunctionError: 'error', // invalid key
},
});

// Check if console.warn was called with the expected message
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: unknownKeyName'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: masterKeyIPs'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: UnlockOnPasswordReset'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: MaxPasswordAge'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: EnableForAnonymousUser'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: Strict'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: IncludeInternalRequests'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: EnableCheck'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: EnableRouter'
);
expect(console.warn).toHaveBeenCalledWith('Warning: The following key is not recognized: Ttl');
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: SchemaCacheTtl'
);
expect(console.warn).toHaveBeenCalledWith(
'Warning: The following key is not recognized: CloudFunctionError'
);
});
});
89 changes: 61 additions & 28 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
ParseServerOptions,
SchemaOptions,
SecurityOptions,
PasswordPolicyOptions,
RateLimitOptions,
} from './Options/Definitions';
import ParseServer from './cloud-code/Parse.Server';

Expand Down Expand Up @@ -63,34 +65,45 @@ export class Config {
return serverConfiguration;
}

static validateOptions({
publicServerURL,
revokeSessionOnPasswordReset,
expireInactiveSessions,
sessionLength,
defaultLimit,
maxLimit,
accountLockout,
passwordPolicy,
masterKeyIps,
masterKey,
maintenanceKey,
maintenanceKeyIps,
readOnlyMasterKey,
allowHeaders,
idempotencyOptions,
fileUpload,
pages,
security,
enforcePrivateUsers,
schema,
requestKeywordDenylist,
allowExpiredAuthDataToken,
logLevels,
rateLimit,
databaseOptions,
extendSessionOnUse,
}) {
static validateConfigKeyNames(actual, given) {
for (const key of given) {
if (!actual.includes(key)) {
console.warn(`Warning: The following key is not recognized: ${key}`);
}
}
}

static validateOptions(options) {
const {
publicServerURL,
revokeSessionOnPasswordReset,
expireInactiveSessions,
sessionLength,
defaultLimit,
maxLimit,
accountLockout,
passwordPolicy,
masterKeyIps,
masterKey,
maintenanceKey,
maintenanceKeyIps,
readOnlyMasterKey,
allowHeaders,
idempotencyOptions,
fileUpload,
pages,
security,
enforcePrivateUsers,
schema,
requestKeywordDenylist,
allowExpiredAuthDataToken,
logLevels,
rateLimit,
databaseOptions,
extendSessionOnUse,
} = options;

Config.validateConfigKeyNames(Object.keys(ParseServerOptions), Object.keys(options));
if (masterKey === readOnlyMasterKey) {
throw new Error('masterKey and readOnlyMasterKey should be different');
}
Expand Down Expand Up @@ -178,6 +191,7 @@ export class Config {
if (Object.prototype.toString.call(security) !== '[object Object]') {
throw 'Parse Server option security must be an object.';
}
Config.validateConfigKeyNames(Object.keys(SecurityOptions), Object.keys(security));
if (security.enableCheck === undefined) {
security.enableCheck = SecurityOptions.enableCheck.default;
} else if (!isBoolean(security.enableCheck)) {
Expand All @@ -192,6 +206,8 @@ export class Config {

static validateSchemaOptions(schema: SchemaOptions) {
if (!schema) return;

Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema));
if (Object.prototype.toString.call(schema) !== '[object Object]') {
throw 'Parse Server option schema must be an object.';
}
Expand Down Expand Up @@ -236,6 +252,8 @@ export class Config {
if (Object.prototype.toString.call(pages) !== '[object Object]') {
throw 'Parse Server option pages must be an object.';
}

Config.validateConfigKeyNames(Object.keys(PagesOptions), Object.keys(pages));
if (pages.enableRouter === undefined) {
pages.enableRouter = PagesOptions.enableRouter.default;
} else if (!isBoolean(pages.enableRouter)) {
Expand Down Expand Up @@ -295,6 +313,7 @@ export class Config {
if (!idempotencyOptions) {
return;
}
Config.validateConfigKeyNames(Object.keys(IdempotencyOptions), Object.keys(idempotencyOptions));
if (idempotencyOptions.ttl === undefined) {
idempotencyOptions.ttl = IdempotencyOptions.ttl.default;
} else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) {
Expand All @@ -311,6 +330,10 @@ export class Config {

static validateAccountLockoutPolicy(accountLockout) {
if (accountLockout) {
Config.validateConfigKeyNames(
Object.keys(AccountLockoutOptions),
Object.keys(accountLockout)
);
if (
typeof accountLockout.duration !== 'number' ||
accountLockout.duration <= 0 ||
Expand All @@ -337,6 +360,10 @@ export class Config {

static validatePasswordPolicy(passwordPolicy) {
if (passwordPolicy) {
Config.validateConfigKeyNames(
Object.keys(PasswordPolicyOptions),
Object.keys(passwordPolicy)
);
if (
passwordPolicy.maxPasswordAge !== undefined &&
(typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0)
Expand Down Expand Up @@ -453,6 +480,8 @@ export class Config {
}
throw e;
}

Config.validateConfigKeyNames(Object.keys(FileUploadOptions), Object.keys(fileUpload));
if (fileUpload.enableForAnonymousUser === undefined) {
fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default;
} else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') {
Expand Down Expand Up @@ -543,6 +572,7 @@ export class Config {
}

static validateLogLevels(logLevels) {
Config.validateConfigKeyNames(Object.keys(LogLevels), Object.keys(logLevels));
for (const key of Object.keys(LogLevels)) {
if (logLevels[key]) {
if (validLogLevels.indexOf(logLevels[key]) === -1) {
Expand All @@ -558,6 +588,8 @@ export class Config {
if (databaseOptions == undefined) {
return;
}

Config.validateConfigKeyNames(Object.keys(DatabaseOptions), Object.keys(databaseOptions));
if (Object.prototype.toString.call(databaseOptions) !== '[object Object]') {
throw `databaseOptions must be an object`;
}
Expand All @@ -583,6 +615,7 @@ export class Config {
) {
throw `rateLimit must be an array or object`;
}
Config.validateConfigKeyNames(Object.keys(RateLimitOptions), Object.keys(rateLimit));
const options = Array.isArray(rateLimit) ? rateLimit : [rateLimit];
for (const option of options) {
if (Object.prototype.toString.call(option) !== '[object Object]') {
Expand Down
4 changes: 4 additions & 0 deletions src/ParseServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { SecurityRouter } from './Routers/SecurityRouter';
import CheckRunner from './Security/CheckRunner';
import Deprecator from './Deprecator/Deprecator';
import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas';
import { ParseServerOptions as ParseServerOptionDef } from './Options/Definitions';

// Mutate the Parse object to add the Cloud Code handlers
addParseCloud();
Expand All @@ -59,6 +60,9 @@ class ParseServer {
constructor(options: ParseServerOptions) {
// Scan for deprecated Parse Server options
Deprecator.scanParseServerOptions(options);

Config.validateConfigKeyNames(Object.keys(ParseServerOptionDef), Object.keys(options));

// Set option defaults
injectDefaults(options);
const {
Expand Down