From 323c9e8fe41c9906514ba92b43773f1e43aaacc8 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sun, 4 Apr 2021 23:11:34 +1000 Subject: [PATCH 01/11] new: restrict public read on Parse.User --- spec/ParseUser.spec.js | 55 ++++++++++++++++++++++++---------- src/Config.js | 5 ++++ src/Deprecator/Deprecations.js | 2 +- src/Options/Definitions.js | 7 +++++ src/Options/docs.js | 1 + src/Options/index.js | 4 +++ src/RestWrite.js | 4 ++- 7 files changed, 61 insertions(+), 17 deletions(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 91aeb4920a..33fa21608c 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -13,20 +13,6 @@ const passwordCrypto = require('../lib/password'); const Config = require('../lib/Config'); const cryptoUtils = require('../lib/cryptoUtils'); -function verifyACL(user) { - const ACL = user.getACL(); - expect(ACL.getReadAccess(user)).toBe(true); - expect(ACL.getWriteAccess(user)).toBe(true); - expect(ACL.getPublicReadAccess()).toBe(true); - expect(ACL.getPublicWriteAccess()).toBe(false); - const perms = ACL.permissionsById; - expect(Object.keys(perms).length).toBe(2); - expect(perms[user.id].read).toBe(true); - expect(perms[user.id].write).toBe(true); - expect(perms['*'].read).toBe(true); - expect(perms['*'].write).not.toBe(true); -} - describe('Parse.User testing', () => { it('user sign up class method', async done => { const user = await Parse.User.signUp('asdf', 'zxcv'); @@ -146,7 +132,17 @@ describe('Parse.User testing', () => { await Parse.User.signUp('asdf', 'zxcv'); const user = await Parse.User.logIn('asdf', 'zxcv'); equal(user.get('username'), 'asdf'); - verifyACL(user); + const ACL = user.getACL(); + expect(ACL.getReadAccess(user)).toBe(true); + expect(ACL.getWriteAccess(user)).toBe(true); + expect(ACL.getPublicReadAccess()).toBe(true); + expect(ACL.getPublicWriteAccess()).toBe(false); + const perms = ACL.permissionsById; + expect(Object.keys(perms).length).toBe(2); + expect(perms[user.id].read).toBe(true); + expect(perms[user.id].write).toBe(true); + expect(perms['*'].read).toBe(true); + expect(perms['*'].write).not.toBe(true); done(); }); @@ -3930,6 +3926,35 @@ describe('Parse.User testing', () => { } }); + it('should throw when enforcePrivateUsers is invalid', async () => { + try { + await reconfigureServer({ + enforcePrivateUsers: [], + }); + fail(); + } catch (err) { + expect(err).toEqual('enforcePrivateUsers must be a boolean value'); + } + }); + + it('user login with enforcePrivateUsers', async done => { + await reconfigureServer({ enforcePrivateUsers: true }); + await Parse.User.signUp('asdf', 'zxcv'); + const user = await Parse.User.logIn('asdf', 'zxcv'); + equal(user.get('username'), 'asdf'); + const ACL = user.getACL(); + expect(ACL.getReadAccess(user)).toBe(true); + expect(ACL.getWriteAccess(user)).toBe(true); + expect(ACL.getPublicReadAccess()).toBe(false); + expect(ACL.getPublicWriteAccess()).toBe(false); + const perms = ACL.permissionsById; + expect(Object.keys(perms).length).toBe(1); + expect(perms[user.id].read).toBe(true); + expect(perms[user.id].write).toBe(true); + expect(perms['*']).toBeUndefined(); + done(); + }); + describe('issue #4897', () => { it_only_db('mongo')('should be able to login with a legacy user (no ACL)', async () => { // This issue is a side effect of the locked users and legacy users which don't have ACL's diff --git a/src/Config.js b/src/Config.js index 302347c5ed..40c5fe2f79 100644 --- a/src/Config.js +++ b/src/Config.js @@ -75,6 +75,7 @@ export class Config { fileUpload, pages, security, + enforcePrivateUsers, }) { if (masterKey === readOnlyMasterKey) { throw new Error('masterKey and readOnlyMasterKey should be different'); @@ -111,6 +112,10 @@ export class Config { this.validateIdempotencyOptions(idempotencyOptions); this.validatePagesOptions(pages); this.validateSecurityOptions(security); + + if (typeof enforcePrivateUsers !== 'boolean') { + throw 'enforcePrivateUsers must be a boolean value'; + } } static validateSecurityOptions(security) { diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index 5c7cc3630f..75167e5240 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -11,4 +11,4 @@ * * If there are no deprecations this must return an empty array anyway. */ -module.exports = []; +module.exports = [{ optionKey: 'enforcePrivateUsers', changeNewDefault: true }]; diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 01bd22333f..8a410818b8 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -153,6 +153,13 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_ENCRYPTION_KEY', help: 'Key for encrypting your files', }, + enforcePrivateUsers: { + env: 'PARSE_SERVER_ENFORCE_PRIVATE_USERS', + help: + 'Is true if Parse Server should set public read and write access on new Parse.Users to false', + action: parsers.booleanParser, + default: false, + }, expireInactiveSessions: { env: 'PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS', help: 'Sets wether we should expire the inactive sessions, defaults to true', diff --git a/src/Options/docs.js b/src/Options/docs.js index dccf436d8c..ff6f8a8f2c 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -28,6 +28,7 @@ * @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors * @property {String} encryptionKey Key for encrypting your files + * @property {Boolean} enforcePrivateUsers Is true if Parse Server should set public read and write access on new Parse.Users to false * @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true * @property {String} fileKey Key for your files * @property {Adapter} filesAdapter Adapter module for the files sub-system diff --git a/src/Options/index.js b/src/Options/index.js index b0c8993cb2..944b1ee376 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -226,6 +226,10 @@ export interface ParseServerOptions { /* The security options to identify and report weak security settings. :DEFAULT: {} */ security: ?SecurityOptions; + /* Is true if Parse Server should set public read and write access on new Parse.Users to false + :ENV: PARSE_SERVER_ENFORCE_PRIVATE_USERS + :DEFAULT: false */ + enforcePrivateUsers: ?boolean; } export interface SecurityOptions { diff --git a/src/RestWrite.js b/src/RestWrite.js index 38b318100c..37af779da7 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1374,7 +1374,9 @@ RestWrite.prototype.runDatabaseOperation = function () { // default public r/w ACL if (!ACL) { ACL = {}; - ACL['*'] = { read: true, write: false }; + if (!this.config.enforcePrivateUsers) { + ACL['*'] = { read: true, write: false }; + } } // make sure the user is not locked down ACL[this.data.objectId] = { read: true, write: true }; From 15a61995cc663a2590bc0fd575792cf683ef3fb5 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sun, 4 Apr 2021 23:23:44 +1000 Subject: [PATCH 02/11] add security check --- .../CheckGroups/CheckGroupServerConfig.js | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Security/CheckGroups/CheckGroupServerConfig.js b/src/Security/CheckGroups/CheckGroupServerConfig.js index a0dc41ec47..18a6f1abf4 100644 --- a/src/Security/CheckGroups/CheckGroupServerConfig.js +++ b/src/Security/CheckGroups/CheckGroupServerConfig.js @@ -8,9 +8,9 @@ import Config from '../../Config'; import Parse from 'parse/node'; /** -* The security checks group for Parse Server configuration. -* Checks common Parse Server parameters such as access keys. -*/ + * The security checks group for Parse Server configuration. + * Checks common Parse Server parameters such as access keys. + */ class CheckGroupServerConfig extends CheckGroup { setName() { return 'Parse Server Configuration'; @@ -21,7 +21,8 @@ class CheckGroupServerConfig extends CheckGroup { new Check({ title: 'Secure master key', warning: 'The Parse Server master key is insecure and vulnerable to brute force attacks.', - solution: 'Choose a longer and/or more complex master key with a combination of upper- and lowercase characters, numbers and special characters.', + solution: + 'Choose a longer and/or more complex master key with a combination of upper- and lowercase characters, numbers and special characters.', check: () => { const masterKey = config.masterKey; const hasUpperCase = /[A-Z]/.test(masterKey); @@ -41,7 +42,7 @@ class CheckGroupServerConfig extends CheckGroup { new Check({ title: 'Security log disabled', warning: 'Security checks in logs may expose vulnerabilities to anyone access to logs.', - solution: 'Change Parse Server configuration to \'security.enableCheckLog: false\'.', + solution: "Change Parse Server configuration to 'security.enableCheckLog: false'.", check: () => { if (config.security && config.security.enableCheckLog) { throw 1; @@ -50,14 +51,25 @@ class CheckGroupServerConfig extends CheckGroup { }), new Check({ title: 'Client class creation disabled', - warning: 'Attackers are allowed to create new classes without restriction and flood the database.', - solution: 'Change Parse Server configuration to \'allowClientClassCreation: false\'.', + warning: + 'Attackers are allowed to create new classes without restriction and flood the database.', + solution: "Change Parse Server configuration to 'allowClientClassCreation: false'.", check: () => { if (config.allowClientClassCreation || config.allowClientClassCreation == null) { throw 1; } }, }), + new Check({ + title: 'Public Users on signup', + warning: 'Users will be publicly readable on signup.', + solution: "Change Parse Server configuration to 'enforcePrivateUsers: true'.", + check: () => { + if (!config.enforcePrivateUsers) { + throw 1; + } + }, + }), ]; } } From 439f7ec95e1ef4b093d0a4329cfd96feb055a3d7 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 5 Apr 2021 00:16:03 +1000 Subject: [PATCH 03/11] Update Deprecations.js --- src/Deprecator/Deprecations.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index 75167e5240..3a33fc72df 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -2,13 +2,13 @@ * The deprecations. * * Add deprecations to the array using the following keys: - * - `optionKey`: The option key incl. its path, e.g. `security.enableCheck`. - * - `envKey`: The environment key, e.g. `PARSE_SERVER_SECURITY`. - * - `changeNewKey`: Set the new key name if the current key will be replaced, + * - `optionKey` {String}: The option key incl. its path, e.g. `security.enableCheck`. + * - `envKey` {String}: The environment key, e.g. `PARSE_SERVER_SECURITY`. + * - `changeNewKey` {String}: Set the new key name if the current key will be replaced, * or set to an empty string if the current key will be removed without replacement. - * - `changeNewDefault`: Set the new default value if the key's default value + * - `changeNewDefault` {String}: Set the new default value if the key's default value * will change in a future version. * * If there are no deprecations this must return an empty array anyway. */ -module.exports = [{ optionKey: 'enforcePrivateUsers', changeNewDefault: true }]; +module.exports = [{ optionKey: 'enforcePrivateUsers', changeNewDefault: 'true' }]; From df15f86e1751165ee0a36131f4ea3a082e82be8a Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 5 Apr 2021 13:20:39 +1000 Subject: [PATCH 04/11] change tests --- spec/ParseUser.spec.js | 12 ++++-------- src/Config.js | 5 ++++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 33fa21608c..854b73e330 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -3926,14 +3926,10 @@ describe('Parse.User testing', () => { } }); - it('should throw when enforcePrivateUsers is invalid', async () => { - try { - await reconfigureServer({ - enforcePrivateUsers: [], - }); - fail(); - } catch (err) { - expect(err).toEqual('enforcePrivateUsers must be a boolean value'); + fit('should throw when enforcePrivateUsers is invalid', async () => { + const options = [[], 'a', 0, {}]; + for (const option of options) { + await expectAsync(reconfigureServer({ enforcePrivateUsers: option })).toBeRejected(); } }); diff --git a/src/Config.js b/src/Config.js index 40c5fe2f79..b8cce93c37 100644 --- a/src/Config.js +++ b/src/Config.js @@ -112,9 +112,12 @@ export class Config { this.validateIdempotencyOptions(idempotencyOptions); this.validatePagesOptions(pages); this.validateSecurityOptions(security); + this.validateEnforcePrivateUsers(enforcePrivateUsers); + } + static validateEnforcePrivateUsers(enforcePrivateUsers) { if (typeof enforcePrivateUsers !== 'boolean') { - throw 'enforcePrivateUsers must be a boolean value'; + throw 'Parse Server option enforcePrivateUsers must be a boolean value'; } } From 653719d9603a29ac49ff37909d491c2599bd6a27 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 5 Apr 2021 13:23:16 +1000 Subject: [PATCH 05/11] Update ParseUser.spec.js --- spec/ParseUser.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 854b73e330..d88f473496 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -3926,7 +3926,7 @@ describe('Parse.User testing', () => { } }); - fit('should throw when enforcePrivateUsers is invalid', async () => { + it('should throw when enforcePrivateUsers is invalid', async () => { const options = [[], 'a', 0, {}]; for (const option of options) { await expectAsync(reconfigureServer({ enforcePrivateUsers: option })).toBeRejected(); From 0283a90b2ccc1069c630ef57fa618df6167ada87 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 5 Apr 2021 17:25:10 +0200 Subject: [PATCH 06/11] Fixed typo --- src/Config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.js b/src/Config.js index b8cce93c37..250880efbc 100644 --- a/src/Config.js +++ b/src/Config.js @@ -117,7 +117,7 @@ export class Config { static validateEnforcePrivateUsers(enforcePrivateUsers) { if (typeof enforcePrivateUsers !== 'boolean') { - throw 'Parse Server option enforcePrivateUsers must be a boolean value'; + throw 'Parse Server option enforcePrivateUsers must be a boolean.'; } } From a74cbacfa9f1082305c8cbc1fa53fa6ee757ae0d Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 9 Apr 2021 00:45:09 +1000 Subject: [PATCH 07/11] Update CheckGroupServerConfig.js --- src/Security/CheckGroups/CheckGroupServerConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/CheckGroups/CheckGroupServerConfig.js b/src/Security/CheckGroups/CheckGroupServerConfig.js index 18a6f1abf4..1c9fae3b58 100644 --- a/src/Security/CheckGroups/CheckGroupServerConfig.js +++ b/src/Security/CheckGroups/CheckGroupServerConfig.js @@ -61,8 +61,8 @@ class CheckGroupServerConfig extends CheckGroup { }, }), new Check({ - title: 'Public Users on signup', - warning: 'Users will be publicly readable on signup.', + title: 'Users are created without public access', + warning: 'Users are created with public read access.', solution: "Change Parse Server configuration to 'enforcePrivateUsers: true'.", check: () => { if (!config.enforcePrivateUsers) { From c391a9ea23d6e2d8358774661dae4a7c03bbaeab Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 9 Apr 2021 00:49:34 +1000 Subject: [PATCH 08/11] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4720b1e5..b31be36d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ ___ - EXPERIMENTAL: Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification (Manuel Trezza) [#6891](https://github.com/parse-community/parse-server/issues/6891) - EXPERIMENTAL: Added custom routes to easily customize flows for password reset, email verification or build entirely new flows (Manuel Trezza) [#7231](https://github.com/parse-community/parse-server/issues/7231) - Remove support for Node 10 which has reached its End-of-Life support date (Manuel Trezza) [#7314](https://github.com/parse-community/parse-server/pull/7314) +- Added Parse Server Configuration `enforcePrivateUsers`, which will remove public access by default on new Parse.Users (dblythy) [#7319](https://github.com/parse-community/parse-server/pull/7319) ### Other Changes - Fix error when a not yet inserted job is updated (Antonio Davi Macedo Coelho de Castro) [#7196](https://github.com/parse-community/parse-server/pull/7196) - request.context for afterFind triggers (dblythy) [#7078](https://github.com/parse-community/parse-server/pull/7078) From b706870a61fb9dd3092142735e1c1d4c51e3f015 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 9 Apr 2021 01:08:11 +1000 Subject: [PATCH 09/11] change definitions --- src/Options/Definitions.js | 3 +-- src/Options/docs.js | 2 +- src/Options/index.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 00dd060fef..c84a45b463 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -156,8 +156,7 @@ module.exports.ParseServerOptions = { }, enforcePrivateUsers: { env: 'PARSE_SERVER_ENFORCE_PRIVATE_USERS', - help: - 'Is true if Parse Server should set public read and write access on new Parse.Users to false', + help: 'Set to true if new users should be created without public read and write access.', action: parsers.booleanParser, default: false, }, diff --git a/src/Options/docs.js b/src/Options/docs.js index d3b9007e5c..6cf6a94ae1 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -28,7 +28,7 @@ * @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors * @property {String} encryptionKey Key for encrypting your files - * @property {Boolean} enforcePrivateUsers Is true if Parse Server should set public read and write access on new Parse.Users to false + * @property {Boolean} enforcePrivateUsers Set to true if new users should be created without public read and write access. * @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true * @property {String} fileKey Key for your files * @property {Adapter} filesAdapter Adapter module for the files sub-system diff --git a/src/Options/index.js b/src/Options/index.js index 30a00a3938..9d877cf419 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -246,7 +246,7 @@ export interface ParseServerOptions { /* The security options to identify and report weak security settings. :DEFAULT: {} */ security: ?SecurityOptions; - /* Is true if Parse Server should set public read and write access on new Parse.Users to false + /* Set to true if new users should be created without public read and write access. :ENV: PARSE_SERVER_ENFORCE_PRIVATE_USERS :DEFAULT: false */ enforcePrivateUsers: ?boolean; From f9aade3ff26d50bd131feac3a40c973b535c9561 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 20 Sep 2021 12:08:04 +1000 Subject: [PATCH 10/11] change text --- src/Options/index.js | 1 - src/Security/CheckGroups/CheckGroupServerConfig.js | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Options/index.js b/src/Options/index.js index 3bc52c0ef1..34fa5198c2 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -247,7 +247,6 @@ export interface ParseServerOptions { :DEFAULT: {} */ security: ?SecurityOptions; /* Set to true if new users should be created without public read and write access. - :ENV: PARSE_SERVER_ENFORCE_PRIVATE_USERS :DEFAULT: false */ enforcePrivateUsers: ?boolean; } diff --git a/src/Security/CheckGroups/CheckGroupServerConfig.js b/src/Security/CheckGroups/CheckGroupServerConfig.js index 1c9fae3b58..a9c6e671cc 100644 --- a/src/Security/CheckGroups/CheckGroupServerConfig.js +++ b/src/Security/CheckGroups/CheckGroupServerConfig.js @@ -41,7 +41,8 @@ class CheckGroupServerConfig extends CheckGroup { }), new Check({ title: 'Security log disabled', - warning: 'Security checks in logs may expose vulnerabilities to anyone access to logs.', + warning: + 'Security checks in logs may expose vulnerabilities to anyone with access to logs.', solution: "Change Parse Server configuration to 'security.enableCheckLog: false'.", check: () => { if (config.security && config.security.enableCheckLog) { @@ -62,7 +63,8 @@ class CheckGroupServerConfig extends CheckGroup { }), new Check({ title: 'Users are created without public access', - warning: 'Users are created with public read access.', + warning: + 'Users with public read access are exposed to anyone who knows their object IDs, or to anyone who can query the Parse.User class.', solution: "Change Parse Server configuration to 'enforcePrivateUsers: true'.", check: () => { if (!config.enforcePrivateUsers) { From 08a2adf8502108b8ea297b41707988bb96e4f233 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 8 Oct 2021 14:11:29 +1100 Subject: [PATCH 11/11] Update DEPRECATIONS.md --- DEPRECATIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 87a4e3b927..05ef29bc75 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -6,6 +6,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h |--------|-------------------------------------------------|----------------------------------------------------------------------|---------------------------------|---------------------------------|-----------------------|-------| | DEPPS1 | 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 | - | | DEPPS2 | 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 | - | +| DEPPS3 | Config option `enforcePrivateUsers` defaults to `true` | [#7319](https://github.com/parse-community/parse-server/pull/7319) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_removal]: ## "The version and date of the planned removal."