From 9ef19687ba0f26ee4a8c566d9d0049bb9e61736e Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Fri, 25 Mar 2022 19:47:05 +0100 Subject: [PATCH 01/28] ci: fix node engine check (#7891) --- ci/nodeEngineCheck.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ci/nodeEngineCheck.js b/ci/nodeEngineCheck.js index da68f314b1..a68f2c593c 100644 --- a/ci/nodeEngineCheck.js +++ b/ci/nodeEngineCheck.js @@ -75,17 +75,21 @@ class NodeEngineCheck { // For each file for (const file of files) { - // Get node version const contentString = await fs.readFile(file, 'utf-8'); - const contentJson = JSON.parse(contentString); - const version = ((contentJson || {}).engines || {}).node; - - // Add response - response.push({ - file: file, - nodeVersion: version - }); + try { + const contentJson = JSON.parse(contentString); + const version = ((contentJson || {}).engines || {}).node; + + // Add response + response.push({ + file: file, + nodeVersion: version + }); + } catch(e) { + console.log(`Ignoring file because it is not valid JSON: ${file}`); + core.warning(`Ignoring file because it is not valid JSON: ${file}`); + } } // If results should be cleaned by removing undefined node versions From f63fb2b338c908f0e7a648d338c26b9daa50c8f2 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 26 Mar 2022 13:39:16 +1100 Subject: [PATCH 02/28] fix: return correct response when revert is used in beforeSave (#7839) --- spec/CloudCode.spec.js | 134 +++++++++++++++++++++++++++++++++++++++++ src/RestWrite.js | 59 +++++++++--------- 2 files changed, 165 insertions(+), 28 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 4b8df9f9c9..faaa6b826a 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1494,6 +1494,110 @@ describe('Cloud Code', () => { }); }); + it('before save can revert fields', async () => { + Parse.Cloud.beforeSave('TestObject', ({ object }) => { + object.revert('foo'); + return object; + }); + + Parse.Cloud.afterSave('TestObject', ({ object }) => { + expect(object.get('foo')).toBeUndefined(); + return object; + }); + + const obj = new TestObject(); + obj.set('foo', 'bar'); + await obj.save(); + + expect(obj.get('foo')).toBeUndefined(); + await obj.fetch(); + + expect(obj.get('foo')).toBeUndefined(); + }); + + it('before save can revert fields with existing object', async () => { + Parse.Cloud.beforeSave( + 'TestObject', + ({ object }) => { + object.revert('foo'); + return object; + }, + { + skipWithMasterKey: true, + } + ); + + Parse.Cloud.afterSave( + 'TestObject', + ({ object }) => { + expect(object.get('foo')).toBe('bar'); + return object; + }, + { + skipWithMasterKey: true, + } + ); + + const obj = new TestObject(); + obj.set('foo', 'bar'); + await obj.save(null, { useMasterKey: true }); + + expect(obj.get('foo')).toBe('bar'); + obj.set('foo', 'yolo'); + await obj.save(); + expect(obj.get('foo')).toBe('bar'); + }); + + it('can unset in afterSave', async () => { + Parse.Cloud.beforeSave('TestObject', ({ object }) => { + if (!object.existed()) { + object.set('secret', true); + return object; + } + object.revert('secret'); + }); + + Parse.Cloud.afterSave('TestObject', ({ object }) => { + object.unset('secret'); + }); + + Parse.Cloud.beforeFind( + 'TestObject', + ({ query }) => { + query.exclude('secret'); + }, + { + skipWithMasterKey: true, + } + ); + + const obj = new TestObject(); + await obj.save(); + expect(obj.get('secret')).toBeUndefined(); + await obj.fetch(); + expect(obj.get('secret')).toBeUndefined(); + await obj.fetch({ useMasterKey: true }); + expect(obj.get('secret')).toBe(true); + }); + + it('should revert in beforeSave', async () => { + Parse.Cloud.beforeSave('MyObject', ({ object }) => { + if (!object.existed()) { + object.set('count', 0); + return object; + } + object.revert('count'); + return object; + }); + const obj = await new Parse.Object('MyObject').save(); + expect(obj.get('count')).toBe(0); + obj.set('count', 10); + await obj.save(); + expect(obj.get('count')).toBe(0); + await obj.fetch(); + expect(obj.get('count')).toBe(0); + }); + it('beforeSave should not sanitize database', async done => { const { adapter } = Config.get(Parse.applicationId).database; const spy = spyOn(adapter, 'findOneAndUpdate').and.callThrough(); @@ -1860,6 +1964,36 @@ describe('afterSave hooks', () => { const myObject = new MyObject(); myObject.save().then(() => done()); }); + + it('should unset in afterSave', async () => { + Parse.Cloud.afterSave( + 'MyObject', + ({ object }) => { + object.unset('secret'); + }, + { + skipWithMasterKey: true, + } + ); + const obj = new Parse.Object('MyObject'); + obj.set('secret', 'bar'); + await obj.save(); + expect(obj.get('secret')).toBeUndefined(); + await obj.fetch(); + expect(obj.get('secret')).toBe('bar'); + }); + + it('should unset', async () => { + Parse.Cloud.beforeSave('MyObject', ({ object }) => { + object.set('secret', 'hidden'); + }); + + Parse.Cloud.afterSave('MyObject', ({ object }) => { + object.unset('secret'); + }); + const obj = await new Parse.Object('MyObject').save(); + expect(obj.get('secret')).toBeUndefined(); + }); }); describe('beforeDelete hooks', () => { diff --git a/src/RestWrite.js b/src/RestWrite.js index 8b728731da..3e20328a9a 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -95,6 +95,7 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK // Shared SchemaController to be reused to reduce the number of loadSchema() calls per request // Once set the schemaData should be immutable this.validSchemaController = null; + this.pendingOps = {}; } // A convenient method to perform all the steps of processing the @@ -225,18 +226,11 @@ RestWrite.prototype.runBeforeSaveTrigger = function () { return Promise.resolve(); } - // Cloud code gets a bit of extra data for its objects - var extraData = { className: this.className }; - if (this.query && this.query.objectId) { - extraData.objectId = this.query.objectId; - } + const { originalObject, updatedObject } = this.buildParseObjects(); - let originalObject = null; - const updatedObject = this.buildUpdatedObject(extraData); - if (this.query && this.query.objectId) { - // This is an update for existing object. - originalObject = triggers.inflate(extraData, this.originalData); - } + const stateController = Parse.CoreManager.getObjectStateController(); + const [pending] = stateController.getPendingOps(updatedObject._getStateIdentifier()); + this.pendingOps = { ...pending }; return Promise.resolve() .then(() => { @@ -1531,20 +1525,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () { return Promise.resolve(); } - var extraData = { className: this.className }; - if (this.query && this.query.objectId) { - extraData.objectId = this.query.objectId; - } - - // Build the original object, we only do this for a update write. - let originalObject; - if (this.query && this.query.objectId) { - originalObject = triggers.inflate(extraData, this.originalData); - } - - // Build the inflated object, different from beforeSave, originalData is not empty - // since developers can change data in the beforeSave. - const updatedObject = this.buildUpdatedObject(extraData); + const { originalObject, updatedObject } = this.buildParseObjects(); updatedObject._handleSaveResponse(this.response.response, this.response.status || 200); this.config.database.loadSchema().then(schemaController => { @@ -1569,8 +1550,15 @@ RestWrite.prototype.runAfterSaveTrigger = function () { this.context ) .then(result => { - if (result && typeof result === 'object') { + const jsonReturned = result && !result._toFullJSON; + if (jsonReturned) { + this.pendingOps = {}; this.response.response = result; + } else { + this.response.response = this._updateResponseWithData( + (result || updatedObject)._toFullJSON(), + this.data + ); } }) .catch(function (err) { @@ -1604,7 +1592,13 @@ RestWrite.prototype.sanitizedData = function () { }; // Returns an updated copy of the object -RestWrite.prototype.buildUpdatedObject = function (extraData) { +RestWrite.prototype.buildParseObjects = function () { + const extraData = { className: this.className, objectId: this.query?.objectId }; + let originalObject; + if (this.query && this.query.objectId) { + originalObject = triggers.inflate(extraData, this.originalData); + } + const className = Parse.Object.fromJSON(extraData); const readOnlyAttributes = className.constructor.readOnlyAttributes ? className.constructor.readOnlyAttributes() @@ -1642,7 +1636,7 @@ RestWrite.prototype.buildUpdatedObject = function (extraData) { delete sanitized[attribute]; } updatedObject.set(sanitized); - return updatedObject; + return { updatedObject, originalObject }; }; RestWrite.prototype.cleanUserAuthData = function () { @@ -1662,6 +1656,15 @@ RestWrite.prototype.cleanUserAuthData = function () { }; RestWrite.prototype._updateResponseWithData = function (response, data) { + const { updatedObject } = this.buildParseObjects(); + const stateController = Parse.CoreManager.getObjectStateController(); + const [pending] = stateController.getPendingOps(updatedObject._getStateIdentifier()); + for (const key in this.pendingOps) { + if (!pending[key]) { + data[key] = this.originalData ? this.originalData[key] : { __op: 'Delete' }; + this.storage.fieldsChangedByTrigger.push(key); + } + } if (_.isEmpty(this.storage.fieldsChangedByTrigger)) { return response; } From b3199d739775dd1ecb2e000b1734f71120e18e3d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 26 Mar 2022 02:40:21 +0000 Subject: [PATCH 03/28] chore(release): 5.2.1-alpha.1 [skip ci] ## [5.2.1-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.0...5.2.1-alpha.1) (2022-03-26) ### Bug Fixes * return correct response when revert is used in beforeSave ([#7839](https://github.com/parse-community/parse-server/issues/7839)) ([f63fb2b](https://github.com/parse-community/parse-server/commit/f63fb2b338c908f0e7a648d338c26b9daa50c8f2)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 6938ec3c74..511c994d0e 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +## [5.2.1-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.0...5.2.1-alpha.1) (2022-03-26) + + +### Bug Fixes + +* return correct response when revert is used in beforeSave ([#7839](https://github.com/parse-community/parse-server/issues/7839)) ([f63fb2b](https://github.com/parse-community/parse-server/commit/f63fb2b338c908f0e7a648d338c26b9daa50c8f2)) + # [5.2.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.2.0-alpha.2...5.2.0-alpha.3) (2022-03-24) diff --git a/package-lock.json b/package-lock.json index 32800119bb..5468d2c4f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.2.0", + "version": "5.2.1-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2bc25fba02..a5ab014590 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.2.0", + "version": "5.2.1-alpha.1", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From 48bd512eeb47666967dff8c5e723ddc5b7801daa Mon Sep 17 00:00:00 2001 From: dblythy Date: Sun, 27 Mar 2022 01:29:39 +1100 Subject: [PATCH 04/28] perf: reduce database operations when using the constant parameter in Cloud Function validation (#7892) --- src/triggers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/triggers.js b/src/triggers.js index 8320b5fb74..360166d0aa 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -738,7 +738,7 @@ async function builtInTriggerValidator(options, request, auth) { } if (opt.constant && request.object) { if (request.original) { - request.object.set(key, request.original.get(key)); + request.object.revert(key); } else if (opt.default != null) { request.object.set(key, opt.default); } From e0cca580e7105b4a9a6ac0c73b02c8e7fa54839c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 26 Mar 2022 14:30:48 +0000 Subject: [PATCH 05/28] chore(release): 5.2.1-alpha.2 [skip ci] ## [5.2.1-alpha.2](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.1...5.2.1-alpha.2) (2022-03-26) ### Performance Improvements * reduce database operations when using the constant parameter in Cloud Function validation ([#7892](https://github.com/parse-community/parse-server/issues/7892)) ([48bd512](https://github.com/parse-community/parse-server/commit/48bd512eeb47666967dff8c5e723ddc5b7801daa)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 511c994d0e..dcbe2dd4ed 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +## [5.2.1-alpha.2](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.1...5.2.1-alpha.2) (2022-03-26) + + +### Performance Improvements + +* reduce database operations when using the constant parameter in Cloud Function validation ([#7892](https://github.com/parse-community/parse-server/issues/7892)) ([48bd512](https://github.com/parse-community/parse-server/commit/48bd512eeb47666967dff8c5e723ddc5b7801daa)) + ## [5.2.1-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.0...5.2.1-alpha.1) (2022-03-26) diff --git a/package-lock.json b/package-lock.json index 5468d2c4f4..d37e18d2dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.2.1-alpha.1", + "version": "5.2.1-alpha.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a5ab014590..a2395fafe0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.2.1-alpha.1", + "version": "5.2.1-alpha.2", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From 90155cf1680e5e0499b0000e071c6cb0ce3aef96 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 27 Mar 2022 03:59:16 +0200 Subject: [PATCH 06/28] feat: add MongoDB 5.1 compatibility (#7682) --- .github/workflows/ci.yml | 5 ++ README.md | 23 ++--- package.json | 9 +- spec/MongoStorageAdapter.spec.js | 36 +++++++- spec/ParseQuery.hint.spec.js | 144 +++++++++++++++++++++++++++++-- 5 files changed, 195 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2463901a91..f8584ff9cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,6 +101,11 @@ jobs: strategy: matrix: include: + - name: MongoDB 5.1, ReplicaSet, WiredTiger + MONGODB_VERSION: 5.1.0 + MONGODB_TOPOLOGY: replicaset + MONGODB_STORAGE_ENGINE: wiredTiger + NODE_VERSION: 14.18.1 - name: MongoDB 5.0, ReplicaSet, WiredTiger MONGODB_VERSION: 5.0.3 MONGODB_TOPOLOGY: replicaset diff --git a/README.md b/README.md index 7b507e9c80..5a5d88c3d6 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,8 @@ Before you start make sure you have installed: #### Node.js Parse Server is continuously tested with the most recent releases of Node.js to ensure compatibility. We follow the [Node.js Long Term Support plan](https://github.com/nodejs/Release) and only test against versions that are officially supported and have not reached their end-of-life date. -| Version | Latest Version | End-of-Life | Compatible | -|------------|----------------|-------------|---------------| +| Version | Latest Version | End-of-Life | Compatible | +|------------|----------------|-------------|--------------| | Node.js 12 | 12.22.7 | April 2022 | ✅ Yes | | Node.js 14 | 14.18.1 | April 2023 | ✅ Yes | | Node.js 16 | 16.13.0 | April 2024 | ✅ Yes | @@ -124,20 +124,21 @@ Parse Server is continuously tested with the most recent releases of MongoDB to | Version | Latest Version | End-of-Life | Compatible | |-------------|----------------|--------------|------------| -| MongoDB 4.0 | 4.0.27 | April 2022 | ✅ Yes | -| MongoDB 4.2 | 4.2.17 | TBD | ✅ Yes | -| MongoDB 4.4 | 4.4.10 | TBD | ✅ Yes | -| MongoDB 5.0 | 5.0.3 | January 2024 | ✅ Yes | - +| MongoDB 4.0 | 4.0.27 | April 2022 | ✅ Yes | +| MongoDB 4.2 | 4.2.17 | TBD | ✅ Yes | +| MongoDB 4.4 | 4.4.10 | TBD | ✅ Yes | +| MongoDB 5.0 | 5.0.3 | January 2024 | ✅ Yes | +| MongoDB 5.1 | 5.1.0 | January 2024 | ✅ Yes | + #### PostgreSQL Parse Server is continuously tested with the most recent releases of PostgreSQL and PostGIS to ensure compatibility, using [PostGIS docker images](https://registry.hub.docker.com/r/postgis/postgis/tags?page=1&ordering=last_updated). We follow the [PostgreSQL support schedule](https://www.postgresql.org/support/versioning) and [PostGIS support schedule](https://www.postgis.net/eol_policy/) and only test against versions that are officially supported and have not reached their end-of-life date. Due to the extensive PostgreSQL support duration of 5 years, Parse Server drops support if a version is older than 3.5 years and a newer version has been available for at least 2.5 years. | Version | PostGIS Version | End-of-Life | Parse Server Support End | Compatible | |-------------|-----------------|---------------|--------------------------|------------| -| Postgres 11 | 3.0, 3.1, 3.2 | November 2023 | April 2022 | ✅ Yes | -| Postgres 12 | 3.2 | November 2024 | April 2023 | ✅ Yes | -| Postgres 13 | 3.2 | November 2025 | April 2024 | ✅ Yes | -| Postgres 14 | 3.2 | November 2026 | April 2025 | ✅ Yes | +| Postgres 11 | 3.0, 3.1, 3.2 | November 2023 | April 2022 | ✅ Yes | +| Postgres 12 | 3.2 | November 2024 | April 2023 | ✅ Yes | +| Postgres 13 | 3.2 | November 2025 | April 2024 | ✅ Yes | +| Postgres 14 | 3.2 | November 2026 | April 2025 | ✅ Yes | ### Locally ```bash diff --git a/package.json b/package.json index a2395fafe0..d8d229ab38 100644 --- a/package.json +++ b/package.json @@ -120,12 +120,13 @@ "test:mongodb:4.2.17": "npm run test:mongodb --dbversion=4.2.17", "test:mongodb:4.4.10": "npm run test:mongodb --dbversion=4.4.10", "test:mongodb:5.0.5": "npm run test:mongodb --dbversion=5.0.5", + "test:mongodb:5.1.0": "npm run test:mongodb --dbversion=5.1.0", "posttest:mongodb": "mongodb-runner stop", - "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start", - "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 jasmine", + "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start", + "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 jasmine", "test": "npm run testonly", - "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner stop", - "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 nyc jasmine", + "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner stop", + "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 nyc jasmine", "start": "node ./bin/parse-server", "prettier": "prettier --write {src,spec}/{**/*,*}.js", "prepare": "npm run build", diff --git a/spec/MongoStorageAdapter.spec.js b/spec/MongoStorageAdapter.spec.js index a31a6134f7..130971a535 100644 --- a/spec/MongoStorageAdapter.spec.js +++ b/spec/MongoStorageAdapter.spec.js @@ -308,7 +308,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(undefined); }); - it('should use index for caseInsensitive query', async () => { + it_only_mongodb_version('<5.1')('should use index for caseInsensitive query', async () => { const user = new Parse.User(); user.set('username', 'Bugs'); user.set('password', 'Bunny'); @@ -342,6 +342,40 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { expect(postIndexPlan.executionStats.executionStages.stage).toBe('FETCH'); }); + it_only_mongodb_version('>=5.1')('should use index for caseInsensitive query', async () => { + const user = new Parse.User(); + user.set('username', 'Bugs'); + user.set('password', 'Bunny'); + await user.signUp(); + + const database = Config.get(Parse.applicationId).database; + await database.adapter.dropAllIndexes('_User'); + + const preIndexPlan = await database.find( + '_User', + { username: 'bugs' }, + { caseInsensitive: true, explain: true } + ); + + const schema = await new Parse.Schema('_User').get(); + + await database.adapter.ensureIndex( + '_User', + schema, + ['username'], + 'case_insensitive_username', + true + ); + + const postIndexPlan = await database.find( + '_User', + { username: 'bugs' }, + { caseInsensitive: true, explain: true } + ); + expect(preIndexPlan.queryPlanner.winningPlan.queryPlan.stage).toBe('COLLSCAN'); + expect(postIndexPlan.queryPlanner.winningPlan.queryPlan.stage).toBe('FETCH'); + }); + it('should delete field without index', async () => { const database = Config.get(Parse.applicationId).database; const obj = new Parse.Object('MyObject'); diff --git a/spec/ParseQuery.hint.spec.js b/spec/ParseQuery.hint.spec.js index 2685137801..8ceb441d5b 100644 --- a/spec/ParseQuery.hint.spec.js +++ b/spec/ParseQuery.hint.spec.js @@ -27,7 +27,7 @@ describe_only_db('mongo')('Parse.Query hint', () => { await TestUtils.destroyAllDataPermanently(false); }); - it('query find with hint string', async () => { + it_only_mongodb_version('<5.1')('query find with hint string', async () => { const object = new TestObject(); await object.save(); @@ -39,7 +39,18 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(explain.queryPlanner.winningPlan.inputStage.indexName).toBe('_id_'); }); - it('query find with hint object', async () => { + it_only_mongodb_version('>=5.1')('query find with hint string', async () => { + const object = new TestObject(); + await object.save(); + + const collection = await config.database.adapter._adaptiveCollection('TestObject'); + const explain = await collection._rawFind({ _id: object.id }, { hint: '_id_', explain: true }); + expect(explain.queryPlanner.winningPlan.queryPlan.stage).toBe('FETCH'); + expect(explain.queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('IXSCAN'); + expect(explain.queryPlanner.winningPlan.queryPlan.inputStage.indexName).toBe('_id_'); + }); + + it_only_mongodb_version('<5.1')('query find with hint object', async () => { const object = new TestObject(); await object.save(); @@ -53,6 +64,20 @@ describe_only_db('mongo')('Parse.Query hint', () => { }); }); + it_only_mongodb_version('>=5.1')('query find with hint object', async () => { + const object = new TestObject(); + await object.save(); + + const collection = await config.database.adapter._adaptiveCollection('TestObject'); + const explain = await collection._rawFind( + { _id: object.id }, + { hint: { _id: 1 }, explain: true } + ); + expect(explain.queryPlanner.winningPlan.queryPlan.stage).toBe('FETCH'); + expect(explain.queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('IXSCAN'); + expect(explain.queryPlanner.winningPlan.queryPlan.inputStage.keyPattern).toEqual({ _id: 1 }); + }); + it_only_mongodb_version('<4.4')('query aggregate with hint string', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -73,7 +98,7 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.indexName).toBe('_id_'); }); - it_only_mongodb_version('>=4.4')('query aggregate with hint string', async () => { + it_only_mongodb_version('>=4.4<5.1')('query aggregate with hint string', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -97,6 +122,30 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.inputStage.indexName).toBe('_id_'); }); + it_only_mongodb_version('>=5.1')('query aggregate with hint string', async () => { + const object = new TestObject({ foo: 'bar' }); + await object.save(); + + const collection = await config.database.adapter._adaptiveCollection('TestObject'); + let result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + explain: true, + }); + let { queryPlanner } = result[0].stages[0].$cursor; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('PROJECTION_SIMPLE'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('COLLSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage).toBeUndefined(); + + result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + hint: '_id_', + explain: true, + }); + queryPlanner = result[0].stages[0].$cursor.queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('PROJECTION_SIMPLE'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('FETCH'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.stage).toBe('IXSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); + }); + it_only_mongodb_version('<4.4')('query aggregate with hint object', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -117,7 +166,7 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.keyPattern).toEqual({ _id: 1 }); }); - it_only_mongodb_version('>=4.4')('query aggregate with hint object', async () => { + it_only_mongodb_version('>=4.4<5.1')('query aggregate with hint object', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -142,7 +191,32 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); }); - it('query find with hint (rest)', async () => { + it_only_mongodb_version('>=5.1')('query aggregate with hint object', async () => { + const object = new TestObject({ foo: 'bar' }); + await object.save(); + + const collection = await config.database.adapter._adaptiveCollection('TestObject'); + let result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + explain: true, + }); + let { queryPlanner } = result[0].stages[0].$cursor; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('PROJECTION_SIMPLE'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('COLLSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage).toBeUndefined(); + + result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + hint: { _id: 1 }, + explain: true, + }); + queryPlanner = result[0].stages[0].$cursor.queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('PROJECTION_SIMPLE'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('FETCH'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.stage).toBe('IXSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); + }); + + it_only_mongodb_version('<5.1')('query find with hint (rest)', async () => { const object = new TestObject(); await object.save(); let options = Object.assign({}, masterKeyOptions, { @@ -167,6 +241,31 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(explain.queryPlanner.winningPlan.inputStage.inputStage.indexName).toBe('_id_'); }); + it_only_mongodb_version('>=5.1')('query find with hint (rest)', async () => { + const object = new TestObject(); + await object.save(); + let options = Object.assign({}, masterKeyOptions, { + url: Parse.serverURL + '/classes/TestObject', + qs: { + explain: true, + }, + }); + let response = await request(options); + let explain = response.data.results; + expect(explain.queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('COLLSCAN'); + + options = Object.assign({}, masterKeyOptions, { + url: Parse.serverURL + '/classes/TestObject', + qs: { + explain: true, + hint: '_id_', + }, + }); + response = await request(options); + explain = response.data.results; + expect(explain.queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); + }); + it_only_mongodb_version('<4.4')('query aggregate with hint (rest)', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -194,7 +293,7 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.keyPattern).toEqual({ _id: 1 }); }); - it_only_mongodb_version('>=4.4')('query aggregate with hint (rest)', async () => { + it_only_mongodb_version('>=4.4<5.1')('query aggregate with hint (rest)', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); let options = Object.assign({}, masterKeyOptions, { @@ -226,4 +325,37 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.inputStage.indexName).toBe('_id_'); expect(queryPlanner.winningPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); }); + + it_only_mongodb_version('>=5.1')('query aggregate with hint (rest)', async () => { + const object = new TestObject({ foo: 'bar' }); + await object.save(); + let options = Object.assign({}, masterKeyOptions, { + url: Parse.serverURL + '/aggregate/TestObject', + qs: { + explain: true, + group: JSON.stringify({ objectId: '$foo' }), + }, + }); + let response = await request(options); + let { queryPlanner } = response.data.results[0].stages[0].$cursor; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('PROJECTION_SIMPLE'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('COLLSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage).toBeUndefined(); + + options = Object.assign({}, masterKeyOptions, { + url: Parse.serverURL + '/aggregate/TestObject', + qs: { + explain: true, + hint: '_id_', + group: JSON.stringify({ objectId: '$foo' }), + }, + }); + response = await request(options); + queryPlanner = response.data.results[0].stages[0].$cursor.queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('PROJECTION_SIMPLE'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('FETCH'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.stage).toBe('IXSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); + }); }); From 499cead1de6751c88442a7da4a24096017b231cb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 27 Mar 2022 02:00:17 +0000 Subject: [PATCH 07/28] chore(release): 5.3.0-alpha.1 [skip ci] # [5.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.2...5.3.0-alpha.1) (2022-03-27) ### Features * add MongoDB 5.1 compatibility ([#7682](https://github.com/parse-community/parse-server/issues/7682)) ([90155cf](https://github.com/parse-community/parse-server/commit/90155cf1680e5e0499b0000e071c6cb0ce3aef96)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index dcbe2dd4ed..a61aef3404 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.2...5.3.0-alpha.1) (2022-03-27) + + +### Features + +* add MongoDB 5.1 compatibility ([#7682](https://github.com/parse-community/parse-server/issues/7682)) ([90155cf](https://github.com/parse-community/parse-server/commit/90155cf1680e5e0499b0000e071c6cb0ce3aef96)) + ## [5.2.1-alpha.2](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.1...5.2.1-alpha.2) (2022-03-26) diff --git a/package-lock.json b/package-lock.json index d37e18d2dd..dacb8759d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.2.1-alpha.2", + "version": "5.3.0-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d8d229ab38..35ac12f220 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.2.1-alpha.2", + "version": "5.3.0-alpha.1", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From ef56e98ef65041b4d3b7b82cce3473269c27f6fd Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Mar 2022 15:17:48 +0200 Subject: [PATCH 08/28] fix: security upgrade parse push adapter from 4.1.0 to 4.1.2 (#7893) --- package-lock.json | 84 +++++++++++++++++++++++++---------------------- package.json | 4 +-- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index dacb8759d8..47bdc1593d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1109,11 +1109,11 @@ } }, "@babel/runtime-corejs3": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.6.tgz", - "integrity": "sha512-Xl8SPYtdjcMoCsIM4teyVRg7jIcgl8F2kRtoCcXuHzXswt9UxZCS6BzRo8fcnCuP6u2XtPgvyonmEPF57Kxo9Q==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz", + "integrity": "sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA==", "requires": { - "core-js-pure": "^3.14.0", + "core-js-pure": "^3.15.0", "regenerator-runtime": "^0.13.4" } }, @@ -1850,20 +1850,20 @@ "dev": true }, "@parse/node-apn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.0.tgz", - "integrity": "sha512-WT3iVwr1Y/Jf4nq4RGNwBdLwm3gTodsb+g3IY98MPSJ7LCNf+R81Nj/nQO5r/twJfN1v5B8cAgfvPGs2rPelvg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", + "integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==", "requires": { - "debug": "4.3.2", + "debug": "4.3.3", "jsonwebtoken": "8.5.1", - "node-forge": "0.10.0", - "verror": "1.10.0" + "node-forge": "1.3.0", + "verror": "1.10.1" }, "dependencies": { "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -1872,6 +1872,16 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } } } }, @@ -1886,42 +1896,36 @@ } }, "@parse/push-adapter": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.1.0.tgz", - "integrity": "sha512-8SOU4zgIr3+wn6Hbge4X/zAYAcJR7puJ3aY2ri+8fqMARgBria4JkIeAyKaTG/mUMHw6Qy5DpYYRe0LjImjZNw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.1.2.tgz", + "integrity": "sha512-034vZTlAzgdfefIY4+Q4j8DHS/VwUAIVoh1JeRkHNfyQmUQ++uKbQbUQdJ/nf11HHS69kwLENs13BmhlHMpyHQ==", "requires": { - "@parse/node-apn": "5.1.0", + "@parse/node-apn": "5.1.3", "@parse/node-gcm": "1.0.2", "npmlog": "4.1.2", - "parse": "3.3.0" + "parse": "3.4.0" }, "dependencies": { "@babel/runtime": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", - "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", + "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", "requires": { "regenerator-runtime": "^0.13.4" } }, - "crypto-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", - "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==", - "optional": true - }, "parse": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-3.3.0.tgz", - "integrity": "sha512-SQkTDupU7JQBJpYFIpO8TlQjUtjboUdkXaak57pjoC1ZVbhaiNyLsdYbrlM0B+sNYhlvcMh7zwZW48u10+zm0A==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-3.4.0.tgz", + "integrity": "sha512-FMZLxPW6PvrBgxkXc9AmnYsFKvPwiS4G2n9OI4mdfiSoNzIVLc+bXzlUdJ+I7hiqHsBTP0BrdQczw2/cnVkJ6w==", "requires": { - "@babel/runtime": "7.14.6", - "@babel/runtime-corejs3": "7.14.6", - "crypto-js": "4.0.0", + "@babel/runtime": "7.15.4", + "@babel/runtime-corejs3": "7.14.7", + "crypto-js": "4.1.1", "idb-keyval": "5.0.6", "react-native-crypto-js": "1.0.0", "uuid": "3.4.0", - "ws": "7.5.0", + "ws": "7.5.1", "xmlhttprequest": "1.8.0" } }, @@ -1931,9 +1935,9 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "ws": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", - "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==" + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==" } } }, @@ -10899,9 +10903,9 @@ } }, "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==" }, "node-netstat": { "version": "1.8.0", diff --git a/package.json b/package.json index 35ac12f220..147ac79f56 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,13 @@ ], "license": "BSD-3-Clause", "dependencies": { + "@apollo/client": "3.5.8", "@apollographql/graphql-playground-html": "1.6.29", "@graphql-tools/links": "8.2.2", - "@apollo/client": "3.5.8", "@graphql-tools/stitch": "6.2.4", "@graphql-tools/utils": "6.2.4", "@parse/fs-files-adapter": "1.2.1", - "@parse/push-adapter": "4.1.0", + "@parse/push-adapter": "4.1.2", "apollo-server-express": "2.25.2", "bcryptjs": "2.4.3", "body-parser": "1.19.1", From 119dbe0cd5c68b435736948553eba68e974a3334 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 27 Mar 2022 13:18:57 +0000 Subject: [PATCH 09/28] chore(release): 5.3.0-alpha.2 [skip ci] # [5.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.1...5.3.0-alpha.2) (2022-03-27) ### Bug Fixes * security upgrade parse push adapter from 4.1.0 to 4.1.2 ([#7893](https://github.com/parse-community/parse-server/issues/7893)) ([ef56e98](https://github.com/parse-community/parse-server/commit/ef56e98ef65041b4d3b7b82cce3473269c27f6fd)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index a61aef3404..c3e5136322 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.1...5.3.0-alpha.2) (2022-03-27) + + +### Bug Fixes + +* security upgrade parse push adapter from 4.1.0 to 4.1.2 ([#7893](https://github.com/parse-community/parse-server/issues/7893)) ([ef56e98](https://github.com/parse-community/parse-server/commit/ef56e98ef65041b4d3b7b82cce3473269c27f6fd)) + # [5.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.2...5.3.0-alpha.1) (2022-03-27) diff --git a/package-lock.json b/package-lock.json index 47bdc1593d..f1ebbfdc0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.3.0-alpha.1", + "version": "5.3.0-alpha.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 147ac79f56..a89abb4821 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.3.0-alpha.1", + "version": "5.3.0-alpha.2", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From 6b4b358f0842ae920e45652f5e8b2afebc6caf3a Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Mar 2022 22:44:02 +0200 Subject: [PATCH 10/28] feat: add MongoDB 5.2 support (#7894) --- .github/workflows/ci.yml | 9 +++- README.md | 15 +++--- package.json | 13 +++--- spec/ParseQuery.hint.spec.js | 88 ++++++++++++++++++++++++++++++++++-- 4 files changed, 107 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8584ff9cb..924139b944 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,13 +101,18 @@ jobs: strategy: matrix: include: + - name: MongoDB 5.2, ReplicaSet, WiredTiger + MONGODB_VERSION: 5.2.1 + MONGODB_TOPOLOGY: replicaset + MONGODB_STORAGE_ENGINE: wiredTiger + NODE_VERSION: 14.18.1 - name: MongoDB 5.1, ReplicaSet, WiredTiger - MONGODB_VERSION: 5.1.0 + MONGODB_VERSION: 5.1.1 MONGODB_TOPOLOGY: replicaset MONGODB_STORAGE_ENGINE: wiredTiger NODE_VERSION: 14.18.1 - name: MongoDB 5.0, ReplicaSet, WiredTiger - MONGODB_VERSION: 5.0.3 + MONGODB_VERSION: 5.0.6 MONGODB_TOPOLOGY: replicaset MONGODB_STORAGE_ENGINE: wiredTiger NODE_VERSION: 16.13.0 diff --git a/README.md b/README.md index 5a5d88c3d6..dc14dd7f1a 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,14 @@ Parse Server is continuously tested with the most recent releases of Node.js to #### MongoDB Parse Server is continuously tested with the most recent releases of MongoDB to ensure compatibility. We follow the [MongoDB support schedule](https://www.mongodb.com/support-policy) and only test against versions that are officially supported and have not reached their end-of-life date. -| Version | Latest Version | End-of-Life | Compatible | -|-------------|----------------|--------------|------------| -| MongoDB 4.0 | 4.0.27 | April 2022 | ✅ Yes | -| MongoDB 4.2 | 4.2.17 | TBD | ✅ Yes | -| MongoDB 4.4 | 4.4.10 | TBD | ✅ Yes | -| MongoDB 5.0 | 5.0.3 | January 2024 | ✅ Yes | -| MongoDB 5.1 | 5.1.0 | January 2024 | ✅ Yes | +| Version | Latest Version | End-of-Life | Compatible | +|-------------|----------------|-------------|------------| +| MongoDB 4.0 | 4.0.27 | April 2022 | ✅ Yes | +| MongoDB 4.2 | 4.2.17 | TBD | ✅ Yes | +| MongoDB 4.4 | 4.4.10 | TBD | ✅ Yes | +| MongoDB 5.0 | 5.0.6 | TBD | ✅ Yes | +| MongoDB 5.1 | 5.1.1 | TBD | ✅ Yes | +| MongoDB 5.2 | 5.2.1 | TBD | ✅ Yes | #### PostgreSQL Parse Server is continuously tested with the most recent releases of PostgreSQL and PostGIS to ensure compatibility, using [PostGIS docker images](https://registry.hub.docker.com/r/postgis/postgis/tags?page=1&ordering=last_updated). We follow the [PostgreSQL support schedule](https://www.postgresql.org/support/versioning) and [PostGIS support schedule](https://www.postgis.net/eol_policy/) and only test against versions that are officially supported and have not reached their end-of-life date. Due to the extensive PostgreSQL support duration of 5 years, Parse Server drops support if a version is older than 3.5 years and a newer version has been available for at least 2.5 years. diff --git a/package.json b/package.json index a89abb4821..01cac39632 100644 --- a/package.json +++ b/package.json @@ -119,14 +119,15 @@ "test:mongodb:4.0.27": "npm run test:mongodb --dbversion=4.0.27", "test:mongodb:4.2.17": "npm run test:mongodb --dbversion=4.2.17", "test:mongodb:4.4.10": "npm run test:mongodb --dbversion=4.4.10", - "test:mongodb:5.0.5": "npm run test:mongodb --dbversion=5.0.5", - "test:mongodb:5.1.0": "npm run test:mongodb --dbversion=5.1.0", + "test:mongodb:5.0.6": "npm run test:mongodb --dbversion=5.0.6", + "test:mongodb:5.1.1": "npm run test:mongodb --dbversion=5.1.1", + "test:mongodb:5.2.1": "npm run test:mongodb --dbversion=5.2.1", "posttest:mongodb": "mongodb-runner stop", - "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start", - "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 jasmine", + "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.2.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start", + "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.2.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 jasmine", "test": "npm run testonly", - "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner stop", - "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.1.0} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 nyc jasmine", + "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.2.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner stop", + "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.2.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 nyc jasmine", "start": "node ./bin/parse-server", "prettier": "prettier --write {src,spec}/{**/*,*}.js", "prepare": "npm run build", diff --git a/spec/ParseQuery.hint.spec.js b/spec/ParseQuery.hint.spec.js index 8ceb441d5b..db45106359 100644 --- a/spec/ParseQuery.hint.spec.js +++ b/spec/ParseQuery.hint.spec.js @@ -122,7 +122,7 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.inputStage.indexName).toBe('_id_'); }); - it_only_mongodb_version('>=5.1')('query aggregate with hint string', async () => { + it_only_mongodb_version('>=5.1<5.2')('query aggregate with hint string', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -146,6 +146,30 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); }); + it_only_mongodb_version('>=5.2')('query aggregate with hint string', async () => { + const object = new TestObject({ foo: 'bar' }); + await object.save(); + + const collection = await config.database.adapter._adaptiveCollection('TestObject'); + let result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + explain: true, + }); + let queryPlanner = result[0].queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('GROUP'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('COLLSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage).toBeUndefined(); + + result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + hint: '_id_', + explain: true, + }); + queryPlanner = result[0].queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('GROUP'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('FETCH'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.stage).toBe('IXSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); + }); + it_only_mongodb_version('<4.4')('query aggregate with hint object', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -191,7 +215,7 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); }); - it_only_mongodb_version('>=5.1')('query aggregate with hint object', async () => { + it_only_mongodb_version('>=5.1<5.2')('query aggregate with hint object', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); @@ -216,6 +240,31 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); }); + it_only_mongodb_version('>=5.2')('query aggregate with hint object', async () => { + const object = new TestObject({ foo: 'bar' }); + await object.save(); + + const collection = await config.database.adapter._adaptiveCollection('TestObject'); + let result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + explain: true, + }); + let queryPlanner = result[0].queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('GROUP'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('COLLSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage).toBeUndefined(); + + result = await collection.aggregate([{ $group: { _id: '$foo' } }], { + hint: { _id: 1 }, + explain: true, + }); + queryPlanner = result[0].queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('GROUP'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('FETCH'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.stage).toBe('IXSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); + }); + it_only_mongodb_version('<5.1')('query find with hint (rest)', async () => { const object = new TestObject(); await object.save(); @@ -326,7 +375,7 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); }); - it_only_mongodb_version('>=5.1')('query aggregate with hint (rest)', async () => { + it_only_mongodb_version('>=5.1<5.2')('query aggregate with hint (rest)', async () => { const object = new TestObject({ foo: 'bar' }); await object.save(); let options = Object.assign({}, masterKeyOptions, { @@ -358,4 +407,37 @@ describe_only_db('mongo')('Parse.Query hint', () => { expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); }); + + it_only_mongodb_version('>=5.2')('query aggregate with hint (rest)', async () => { + const object = new TestObject({ foo: 'bar' }); + await object.save(); + let options = Object.assign({}, masterKeyOptions, { + url: Parse.serverURL + '/aggregate/TestObject', + qs: { + explain: true, + group: JSON.stringify({ objectId: '$foo' }), + }, + }); + let response = await request(options); + let queryPlanner = response.data.results[0].queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('GROUP'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('COLLSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage).toBeUndefined(); + + options = Object.assign({}, masterKeyOptions, { + url: Parse.serverURL + '/aggregate/TestObject', + qs: { + explain: true, + hint: '_id_', + group: JSON.stringify({ objectId: '$foo' }), + }, + }); + response = await request(options); + queryPlanner = response.data.results[0].queryPlanner; + expect(queryPlanner.winningPlan.queryPlan.stage).toBe('GROUP'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.stage).toBe('FETCH'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.stage).toBe('IXSCAN'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.indexName).toBe('_id_'); + expect(queryPlanner.winningPlan.queryPlan.inputStage.inputStage.keyPattern).toEqual({ _id: 1 }); + }); }); From 75eca2d52fee63b21f4ce0e75a3043f533c046bb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 27 Mar 2022 20:46:31 +0000 Subject: [PATCH 11/28] chore(release): 5.3.0-alpha.3 [skip ci] # [5.3.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.2...5.3.0-alpha.3) (2022-03-27) ### Features * add MongoDB 5.2 support ([#7894](https://github.com/parse-community/parse-server/issues/7894)) ([6b4b358](https://github.com/parse-community/parse-server/commit/6b4b358f0842ae920e45652f5e8b2afebc6caf3a)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index c3e5136322..888add258c 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.3.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.2...5.3.0-alpha.3) (2022-03-27) + + +### Features + +* add MongoDB 5.2 support ([#7894](https://github.com/parse-community/parse-server/issues/7894)) ([6b4b358](https://github.com/parse-community/parse-server/commit/6b4b358f0842ae920e45652f5e8b2afebc6caf3a)) + # [5.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.1...5.3.0-alpha.2) (2022-03-27) diff --git a/package-lock.json b/package-lock.json index f1ebbfdc0a..46e7dab718 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.3.0-alpha.2", + "version": "5.3.0-alpha.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 01cac39632..5cd91c0978 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.3.0-alpha.2", + "version": "5.3.0-alpha.3", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From 132ccaf849e6ab51e0ef2c33858e894bdeb7f6c2 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 10 Apr 2023 14:31:10 +1000 Subject: [PATCH 12/28] fix: improved masterKeyIPs --- package-lock.json | 47 ++++++++++++++++++++-------------------- package.json | 2 +- spec/Middlewares.spec.js | 13 +++++++++++ src/middlewares.js | 23 +++++++++++++++++--- 4 files changed, 57 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18f5a16b4e..0b96daf2aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "graphql-relay": "0.10.0", "graphql-tag": "2.12.6", "intersect": "1.0.1", - "ip-range-check": "0.2.0", + "ipaddr.js": "^2.0.1", "jsonwebtoken": "9.0.0", "jwks-rsa": "2.1.5", "ldapjs": "2.3.3", @@ -9160,20 +9160,12 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" }, - "node_modules/ip-range-check": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ip-range-check/-/ip-range-check-0.2.0.tgz", - "integrity": "sha512-oaM3l/3gHbLlt/tCWLvt0mj1qUaI+STuRFnUvARGCujK9vvU61+2JsDpmkMzR4VsJhuFXWWgeKKVnwwoFfzCqw==", - "dependencies": { - "ipaddr.js": "^1.0.1" - } - }, "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", "engines": { - "node": ">= 0.10" + "node": ">= 10" } }, "node_modules/is-accessor-descriptor": { @@ -16647,6 +16639,14 @@ "node": ">= 0.10" } }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ps-node": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ps-node/-/ps-node-0.1.6.tgz", @@ -27335,18 +27335,10 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" }, - "ip-range-check": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ip-range-check/-/ip-range-check-0.2.0.tgz", - "integrity": "sha512-oaM3l/3gHbLlt/tCWLvt0mj1qUaI+STuRFnUvARGCujK9vvU61+2JsDpmkMzR4VsJhuFXWWgeKKVnwwoFfzCqw==", - "requires": { - "ipaddr.js": "^1.0.1" - } - }, "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==" }, "is-accessor-descriptor": { "version": "0.1.6", @@ -32972,6 +32964,13 @@ "requires": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + } } }, "ps-node": { diff --git a/package.json b/package.json index fc2dbe6250..c6b1dc5771 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "graphql-relay": "0.10.0", "graphql-tag": "2.12.6", "intersect": "1.0.1", - "ip-range-check": "0.2.0", + "ipaddr.js": "^2.0.1", "jsonwebtoken": "9.0.0", "jwks-rsa": "2.1.5", "ldapjs": "2.3.3", diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index 12bfc59bf7..f2b8806dcf 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -151,6 +151,19 @@ describe('middlewares', () => { ); }); + it('can allow all with masterKeyIPs', async () => { + const logger = require('../lib/logger').logger; + spyOn(logger, 'error').and.callFake(() => {}); + AppCache.put(fakeReq.body._ApplicationId, { + masterKey: 'masterKey', + masterKeyIps: ['::/0'], + }); + fakeReq.ip = '::ffff:192.168.0.101'; + fakeReq.headers['x-parse-master-key'] = 'masterKey'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); + }); + it('should not succeed if the ip does not belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', diff --git a/src/middlewares.js b/src/middlewares.js index 0dca33135e..ed18372bb5 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -10,7 +10,7 @@ import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageA import rateLimit from 'express-rate-limit'; import { RateLimitOptions } from './Options/Definitions'; import pathToRegexp from 'path-to-regexp'; -import ipRangeCheck from 'ip-range-check'; +import ipaddr from 'ipaddr.js'; import RedisStore from 'rate-limit-redis'; import { createClient } from 'redis'; @@ -23,6 +23,23 @@ const getMountForRequest = function (req) { return req.protocol + '://' + req.get('host') + mountPath; }; +const checkIPRange = (ip, ranges = []) => { + const addr = ipaddr.parse(ip); + for (const range of ranges) { + try { + const cidr = ipaddr.parseCIDR(range); + if (addr.match(cidr)) { + return true; + } + } catch (e) { + if (range === ip) { + return true; + } + } + } + return false; +}; + // Checks that the request is authorized for this app and checks user // auth too. // The bodyparser should run before this middleware. @@ -183,7 +200,7 @@ export function handleParseHeaders(req, res, next) { const isMaintenance = req.config.maintenanceKey && info.maintenanceKey === req.config.maintenanceKey; if (isMaintenance) { - if (ipRangeCheck(clientIp, req.config.maintenanceKeyIps || [])) { + if (checkIPRange(clientIp, req.config.maintenanceKeyIps)) { req.auth = new auth.Auth({ config: req.config, installationId: info.installationId, @@ -199,7 +216,7 @@ export function handleParseHeaders(req, res, next) { } let isMaster = info.masterKey === req.config.masterKey; - if (isMaster && !ipRangeCheck(clientIp, req.config.masterKeyIps || [])) { + if (isMaster && !checkIPRange(clientIp, req.config.masterKeyIps)) { const log = req.config?.loggerController || defaultLogger; log.error( `Request using master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'masterKeyIps'.` From 0df02bfd542498027ba5a031809c45fd9c2ce7b6 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 10 Apr 2023 15:00:34 +1000 Subject: [PATCH 13/28] Update middlewares.js --- src/middlewares.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/middlewares.js b/src/middlewares.js index ed18372bb5..50c04d766c 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -24,9 +24,13 @@ const getMountForRequest = function (req) { }; const checkIPRange = (ip, ranges = []) => { + const allowAll = ['::', '::/0', '0.0.0.0/0']; const addr = ipaddr.parse(ip); for (const range of ranges) { try { + if (allowAll.includes(range)) { + return true; + } const cidr = ipaddr.parseCIDR(range); if (addr.match(cidr)) { return true; From 4b74cf4350fed1e0876d039119be8eafa09c5cd7 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 10 Apr 2023 15:21:29 +1000 Subject: [PATCH 14/28] Update middlewares.js --- src/middlewares.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/middlewares.js b/src/middlewares.js index 50c04d766c..0b5a04e7db 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -24,9 +24,18 @@ const getMountForRequest = function (req) { }; const checkIPRange = (ip, ranges = []) => { + if (!ip) { + return false; + } const allowAll = ['::', '::/0', '0.0.0.0/0']; const addr = ipaddr.parse(ip); for (const range of ranges) { + if ( + (range === '::1' || range === '127.0.0.1') && + (ip === '::ffff:127.0.0.1' || ip === '127.0.0.1') + ) { + return true; + } try { if (allowAll.includes(range)) { return true; From 370564047186d1bc1f9b4a2f4a62d135fd1eb6d1 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 11 Apr 2023 11:32:25 +1000 Subject: [PATCH 15/28] Update middlewares.js --- src/middlewares.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/middlewares.js b/src/middlewares.js index 0b5a04e7db..ed8a515f2b 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -27,20 +27,17 @@ const checkIPRange = (ip, ranges = []) => { if (!ip) { return false; } - const allowAll = ['::', '::/0', '0.0.0.0/0']; + const getCIDR = ip => { + try { + return ipaddr.parseCIDR(ip); + } catch (e) { + return ipaddr.parseCIDR(`${ip}/32`); + } + }; const addr = ipaddr.parse(ip); for (const range of ranges) { - if ( - (range === '::1' || range === '127.0.0.1') && - (ip === '::ffff:127.0.0.1' || ip === '127.0.0.1') - ) { - return true; - } try { - if (allowAll.includes(range)) { - return true; - } - const cidr = ipaddr.parseCIDR(range); + const cidr = getCIDR(range); if (addr.match(cidr)) { return true; } From 8c21d1e0b91acd0d0ef498183c8ad8b5a130e7f9 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 11 Apr 2023 11:49:44 +1000 Subject: [PATCH 16/28] wip --- spec/Middlewares.spec.js | 28 +++++++++++++++++++++++++++- src/middlewares.js | 12 ++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index f2b8806dcf..af33251a07 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -164,7 +164,20 @@ describe('middlewares', () => { expect(fakeReq.auth.isMaster).toBe(true); }); - it('should not succeed if the ip does not belong to masterKeyIps list', async () => { + it('can allow localhost with masterKeyIPs', async () => { + const logger = require('../lib/logger').logger; + spyOn(logger, 'error').and.callFake(() => {}); + AppCache.put(fakeReq.body._ApplicationId, { + masterKey: 'masterKey', + masterKeyIps: ['::1'], + }); + fakeReq.ip = '::ffff:127.0.0.1'; + fakeReq.headers['x-parse-master-key'] = 'masterKey'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); + }); + + it('should not succeed if the ip does not belong to masterKeyIps list (ipv4)', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', masterKeyIps: ['10.0.0.1'], @@ -175,6 +188,19 @@ describe('middlewares', () => { expect(fakeReq.auth.isMaster).toBe(false); }); + it('should not succeed if the ip does not belong to masterKeyIps list (ipv6)', async () => { + const logger = require('../lib/logger').logger; + spyOn(logger, 'error').and.callFake(() => {}); + AppCache.put(fakeReq.body._ApplicationId, { + masterKey: 'masterKey', + masterKeyIps: ['::1'], + }); + fakeReq.ip = '::ffff:101.10.0.1'; + fakeReq.headers['x-parse-master-key'] = 'masterKey'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(false); + }); + it('should not succeed if the ip does not belong to maintenanceKeyIps list', async () => { const logger = require('../lib/logger').logger; spyOn(logger, 'error').and.callFake(() => {}); diff --git a/src/middlewares.js b/src/middlewares.js index ed8a515f2b..fcd157bd4e 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -27,14 +27,22 @@ const checkIPRange = (ip, ranges = []) => { if (!ip) { return false; } + const transformIp = ip => { + if (ip === '::1') { + ip = '::ffff:127.0.0.1'; + } + return ip; + }; + const getCIDR = ip => { + ip = transformIp(ip); try { return ipaddr.parseCIDR(ip); } catch (e) { - return ipaddr.parseCIDR(`${ip}/32`); + return ipaddr.parseCIDR(`${ip}/128`); } }; - const addr = ipaddr.parse(ip); + const addr = ipaddr.parse(transformIp(ip)); for (const range of ranges) { try { const cidr = getCIDR(range); From 113aa965587fa7eb1858c657c8da77a3087e0b74 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 11 Apr 2023 12:11:10 +1000 Subject: [PATCH 17/28] fix version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b96daf2aa..c858547e50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "graphql-relay": "0.10.0", "graphql-tag": "2.12.6", "intersect": "1.0.1", - "ipaddr.js": "^2.0.1", + "ipaddr.js": "2.0.1", "jsonwebtoken": "9.0.0", "jwks-rsa": "2.1.5", "ldapjs": "2.3.3", diff --git a/package.json b/package.json index c6b1dc5771..0327fc16b6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "graphql-relay": "0.10.0", "graphql-tag": "2.12.6", "intersect": "1.0.1", - "ipaddr.js": "^2.0.1", + "ipaddr.js": "2.0.1", "jsonwebtoken": "9.0.0", "jwks-rsa": "2.1.5", "ldapjs": "2.3.3", From de35146f1afd7e0d6e56d031a20429bcb059aa03 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 11 Apr 2023 18:07:53 +1000 Subject: [PATCH 18/28] Update middlewares.js --- src/middlewares.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/middlewares.js b/src/middlewares.js index fcd157bd4e..5aa1c74ae8 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -23,7 +23,7 @@ const getMountForRequest = function (req) { return req.protocol + '://' + req.get('host') + mountPath; }; -const checkIPRange = (ip, ranges = []) => { +const checkIpRanges = (ip, ranges = []) => { if (!ip) { return false; } @@ -218,7 +218,7 @@ export function handleParseHeaders(req, res, next) { const isMaintenance = req.config.maintenanceKey && info.maintenanceKey === req.config.maintenanceKey; if (isMaintenance) { - if (checkIPRange(clientIp, req.config.maintenanceKeyIps)) { + if (checkIpRanges(clientIp, req.config.maintenanceKeyIps)) { req.auth = new auth.Auth({ config: req.config, installationId: info.installationId, @@ -234,7 +234,7 @@ export function handleParseHeaders(req, res, next) { } let isMaster = info.masterKey === req.config.masterKey; - if (isMaster && !checkIPRange(clientIp, req.config.masterKeyIps)) { + if (isMaster && !checkIpRanges(clientIp, req.config.masterKeyIps)) { const log = req.config?.loggerController || defaultLogger; log.error( `Request using master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'masterKeyIps'.` From 543a8492240779aad9f55f4d8edf486cb3307d60 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Apr 2023 14:01:33 +1000 Subject: [PATCH 19/28] convert to ipv6 --- spec/Middlewares.spec.js | 4 ++-- src/middlewares.js | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index af33251a07..14189794b4 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -169,9 +169,9 @@ describe('middlewares', () => { spyOn(logger, 'error').and.callFake(() => {}); AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['::1'], + masterKeyIps: ['::'], }); - fakeReq.ip = '::ffff:127.0.0.1'; + fakeReq.ip = '127.0.0.1'; fakeReq.headers['x-parse-master-key'] = 'masterKey'; await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); expect(fakeReq.auth.isMaster).toBe(true); diff --git a/src/middlewares.js b/src/middlewares.js index 5aa1c74ae8..ab97eecc73 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -28,10 +28,14 @@ const checkIpRanges = (ip, ranges = []) => { return false; } const transformIp = ip => { - if (ip === '::1') { - ip = '::ffff:127.0.0.1'; + if (ip === '::1' || ip === '::') { + ip = '127.0.0.1'; } - return ip; + let asIp = ipaddr.parse(ip); + if (asIp.kind() === 'ipv4') { + asIp = ipaddr.parse(`::ffff:${ip}`); + } + return asIp.toString(); }; const getCIDR = ip => { From 5400928992e189ddf7148798238414faa3855b3e Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Apr 2023 14:55:56 +1000 Subject: [PATCH 20/28] Update middlewares.js --- src/middlewares.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/middlewares.js b/src/middlewares.js index ab97eecc73..33d82f5536 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -39,12 +39,10 @@ const checkIpRanges = (ip, ranges = []) => { }; const getCIDR = ip => { - ip = transformIp(ip); - try { + if (ip.includes('/')) { return ipaddr.parseCIDR(ip); - } catch (e) { - return ipaddr.parseCIDR(`${ip}/128`); } + return ipaddr.parseCIDR(`${transformIp(ip)}/128`); }; const addr = ipaddr.parse(transformIp(ip)); for (const range of ranges) { From d52d3895d6df6a822ced4ace509623ee401f4593 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Apr 2023 15:24:39 +1000 Subject: [PATCH 21/28] refactor to blocklist --- package-lock.json | 14 -------------- package.json | 1 - spec/index.spec.js | 13 ++++++------- src/middlewares.js | 39 ++++++++++++--------------------------- 4 files changed, 18 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index c858547e50..89636f2092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "graphql-relay": "0.10.0", "graphql-tag": "2.12.6", "intersect": "1.0.1", - "ipaddr.js": "2.0.1", "jsonwebtoken": "9.0.0", "jwks-rsa": "2.1.5", "ldapjs": "2.3.3", @@ -9160,14 +9159,6 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" }, - "node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", - "engines": { - "node": ">= 10" - } - }, "node_modules/is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -27335,11 +27326,6 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" }, - "ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==" - }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", diff --git a/package.json b/package.json index 0327fc16b6..bd93a45577 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "graphql-relay": "0.10.0", "graphql-tag": "2.12.6", "intersect": "1.0.1", - "ipaddr.js": "2.0.1", "jsonwebtoken": "9.0.0", "jwks-rsa": "2.1.5", "ldapjs": "2.3.3", diff --git a/spec/index.spec.js b/spec/index.spec.js index 08ef16a77b..4c4c91dda0 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -434,13 +434,12 @@ describe('server', () => { reconfigureServer({ revokeSessionOnPasswordReset: 'non-bool' }).catch(done); }); - it('fails if you provides invalid ip in masterKeyIps', done => { - reconfigureServer({ masterKeyIps: ['invalidIp', '1.2.3.4'] }).catch(error => { - expect(error).toEqual( - 'The Parse Server option "masterKeyIps" contains an invalid IP address "invalidIp".' - ); - done(); - }); + it('fails if you provides invalid ip in masterKeyIps', async () => { + await expectAsync( + reconfigureServer({ masterKeyIps: ['1.2.3.4/0', 'invalidIp'] }) + ).toBeRejectedWith( + 'The Parse Server option "masterKeyIps" contains an invalid IP address "invalidIp".' + ); }); it('should succeed if you provide valid ip in masterKeyIps', done => { diff --git a/src/middlewares.js b/src/middlewares.js index 33d82f5536..7818850f7d 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -10,9 +10,9 @@ import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageA import rateLimit from 'express-rate-limit'; import { RateLimitOptions } from './Options/Definitions'; import pathToRegexp from 'path-to-regexp'; -import ipaddr from 'ipaddr.js'; import RedisStore from 'rate-limit-redis'; import { createClient } from 'redis'; +import { BlockList, isIPv4 } from 'net'; export const DEFAULT_ALLOWED_HEADERS = 'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control'; @@ -24,40 +24,25 @@ const getMountForRequest = function (req) { }; const checkIpRanges = (ip, ranges = []) => { - if (!ip) { - return false; - } const transformIp = ip => { if (ip === '::1' || ip === '::') { ip = '127.0.0.1'; } - let asIp = ipaddr.parse(ip); - if (asIp.kind() === 'ipv4') { - asIp = ipaddr.parse(`::ffff:${ip}`); - } - return asIp.toString(); - }; - - const getCIDR = ip => { - if (ip.includes('/')) { - return ipaddr.parseCIDR(ip); - } - return ipaddr.parseCIDR(`${transformIp(ip)}/128`); + return ip; }; - const addr = ipaddr.parse(transformIp(ip)); + const blocklist = new BlockList(); for (const range of ranges) { - try { - const cidr = getCIDR(range); - if (addr.match(cidr)) { - return true; - } - } catch (e) { - if (range === ip) { - return true; - } + if (range.includes('/')) { + const [net, prefix] = range.split('/'); + const addr = transformIp(net); + blocklist.addSubnet(addr, Number(prefix), isIPv4(addr) ? 'ipv4' : 'ipv6'); + } else { + const addr = transformIp(range); + blocklist.addAddress(addr, isIPv4(addr) ? 'ipv4' : 'ipv6'); } } - return false; + const client = transformIp(ip); + return blocklist.check(client, isIPv4(client) ? 'ipv4' : 'ipv6'); }; // Checks that the request is authorized for this app and checks user From bd40ed518be1995adbf2a8b42a12dcaae2605f44 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Apr 2023 15:33:14 +1000 Subject: [PATCH 22/28] Update Middlewares.spec.js --- spec/Middlewares.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index 14189794b4..06c2c0c742 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -27,6 +27,7 @@ describe('middlewares', () => { expect(fakeReq.headers['content-type']).toEqual(undefined); const contentType = 'image/jpeg'; fakeReq.body._ContentType = contentType; + fakeReq.ip = '127.0.0.1'; middlewares.handleParseHeaders(fakeReq, fakeRes, () => { expect(fakeReq.headers['content-type']).toEqual(contentType); expect(fakeReq.body._ContentType).toEqual(undefined); From c2fdbd7ead7df24f1f34330245d87f892440f053 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 12 Apr 2023 15:47:34 +1000 Subject: [PATCH 23/28] Update middlewares.js --- src/middlewares.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/middlewares.js b/src/middlewares.js index 7818850f7d..a4e41c2c84 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -30,19 +30,20 @@ const checkIpRanges = (ip, ranges = []) => { } return ip; }; + const getType = address => (isIPv4(address) ? 'ipv4' : 'ipv6'); const blocklist = new BlockList(); for (const range of ranges) { if (range.includes('/')) { const [net, prefix] = range.split('/'); const addr = transformIp(net); - blocklist.addSubnet(addr, Number(prefix), isIPv4(addr) ? 'ipv4' : 'ipv6'); + blocklist.addSubnet(addr, Number(prefix), getType(addr)); } else { const addr = transformIp(range); - blocklist.addAddress(addr, isIPv4(addr) ? 'ipv4' : 'ipv6'); + blocklist.addAddress(addr, getType(addr)); } } const client = transformIp(ip); - return blocklist.check(client, isIPv4(client) ? 'ipv4' : 'ipv6'); + return blocklist.check(client, getType(client)); }; // Checks that the request is authorized for this app and checks user From 6602acf01f253e88b7cb1a990c7ca4d049552852 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 16 May 2023 16:53:53 +1000 Subject: [PATCH 24/28] Update middlewares.js --- src/middlewares.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/middlewares.js b/src/middlewares.js index a4e41c2c84..98fd33604b 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -24,26 +24,24 @@ const getMountForRequest = function (req) { }; const checkIpRanges = (ip, ranges = []) => { - const transformIp = ip => { - if (ip === '::1' || ip === '::') { - ip = '127.0.0.1'; - } - return ip; - }; const getType = address => (isIPv4(address) ? 'ipv4' : 'ipv6'); + const clientType = getType(ip); const blocklist = new BlockList(); for (const range of ranges) { - if (range.includes('/')) { - const [net, prefix] = range.split('/'); - const addr = transformIp(net); + if ((range === '::/0' || range === '::') && clientType === 'ipv6') { + return true; + } + if (range === '0.0.0.0' && clientType === 'ipv6') { + return true; + } + const [addr, prefix] = range.split('/'); + if (prefix) { blocklist.addSubnet(addr, Number(prefix), getType(addr)); } else { - const addr = transformIp(range); blocklist.addAddress(addr, getType(addr)); } } - const client = transformIp(ip); - return blocklist.check(client, getType(client)); + return blocklist.check(ip, clientType); }; // Checks that the request is authorized for this app and checks user From ec01674217e6a930dd2f112b0551c5c419566d91 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 20 Jun 2023 12:36:14 +1000 Subject: [PATCH 25/28] lint --- spec/Middlewares.spec.js | 46 +++++++++++++++++++++++++++++++--------- src/middlewares.js | 3 +-- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index 8399475b34..bb28caf8bc 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -153,16 +153,42 @@ describe('middlewares', () => { }); it('can allow all with masterKeyIPs', async () => { - const logger = require('../lib/logger').logger; - spyOn(logger, 'error').and.callFake(() => {}); - AppCache.put(fakeReq.body._ApplicationId, { - masterKey: 'masterKey', - masterKeyIps: ['::/0'], - }); - fakeReq.ip = '::ffff:192.168.0.101'; - fakeReq.headers['x-parse-master-key'] = 'masterKey'; - await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); - expect(fakeReq.auth.isMaster).toBe(true); + const combinations = [ + { + masterKeyIps: ['::/0'], + ips: ['::ffff:192.168.0.101', '192.168.0.101'], + id: 'allowAllIpV6', + }, + { + masterKeyIps: ['0.0.0.0'], + ips: ['192.168.0.101'], + id: 'allowAllIpV4', + }, + ]; + for (const combination of combinations) { + AppCache.put(combination.id, { + masterKey: 'masterKey', + masterKeyIps: combination.masterKeyIps, + }); + await new Promise(resolve => setTimeout(resolve, 10)); + for (const ip of combination.ips) { + fakeReq = { + originalUrl: 'http://example.com/parse/', + url: 'http://example.com/', + body: { + _ApplicationId: combination.id, + }, + headers: {}, + get: key => { + return fakeReq.headers[key.toLowerCase()]; + }, + }; + fakeReq.ip = ip; + fakeReq.headers['x-parse-master-key'] = 'masterKey'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); + } + } }); it('can allow localhost with masterKeyIPs', async () => { diff --git a/src/middlewares.js b/src/middlewares.js index fb2992b802..28c5a1eda1 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -10,7 +10,6 @@ import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageA import rateLimit from 'express-rate-limit'; import { RateLimitOptions } from './Options/Definitions'; import { pathToRegexp } from 'path-to-regexp'; -import ipRangeCheck from 'ip-range-check'; import RedisStore from 'rate-limit-redis'; import { createClient } from 'redis'; import { BlockList, isIPv4 } from 'net'; @@ -32,7 +31,7 @@ const checkIpRanges = (ip, ranges = []) => { if ((range === '::/0' || range === '::') && clientType === 'ipv6') { return true; } - if (range === '0.0.0.0' && clientType === 'ipv6') { + if (range === '0.0.0.0' && clientType === 'ipv4') { return true; } const [addr, prefix] = range.split('/'); From becf01c864d1ac8c7e392ee50455138adafcf08c Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 20 Jun 2023 12:37:46 +1000 Subject: [PATCH 26/28] Update Middlewares.spec.js --- spec/Middlewares.spec.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index bb28caf8bc..0f520878f0 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -216,8 +216,6 @@ describe('middlewares', () => { }); it('should not succeed if the ip does not belong to masterKeyIps list (ipv6)', async () => { - const logger = require('../lib/logger').logger; - spyOn(logger, 'error').and.callFake(() => {}); AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', masterKeyIps: ['::1'], From 8f4fa5d53ea9294c4976e7fce507fec000341f9d Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 20 Jun 2023 12:50:53 +1000 Subject: [PATCH 27/28] Update Middlewares.spec.js --- spec/Middlewares.spec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index 0f520878f0..aae0609937 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -192,13 +192,11 @@ describe('middlewares', () => { }); it('can allow localhost with masterKeyIPs', async () => { - const logger = require('../lib/logger').logger; - spyOn(logger, 'error').and.callFake(() => {}); AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', masterKeyIps: ['::'], }); - fakeReq.ip = '127.0.0.1'; + fakeReq.ip = '::ffff:127.0.0.1'; fakeReq.headers['x-parse-master-key'] = 'masterKey'; await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); expect(fakeReq.auth.isMaster).toBe(true); From d5c7ae944aae11f9be32f520600f9d083988981d Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 27 Jun 2023 14:50:42 +1000 Subject: [PATCH 28/28] add tests --- spec/Middlewares.spec.js | 35 +++++++++++++++++++++++++++++++++++ src/middlewares.js | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index aae0609937..571aa4c564 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -152,6 +152,41 @@ describe('middlewares', () => { ); }); + it('should match address', () => { + const ipv6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; + const anotherIpv6 = '::ffff:101.10.0.1'; + const ipv4 = '192.168.0.101'; + const localhostV6 = '::1'; + const localhostV62 = '::ffff:127.0.0.1'; + const localhostV4 = '127.0.0.1'; + + const v6 = [ipv6, anotherIpv6]; + v6.forEach(ip => { + expect(middlewares.checkIpRanges(ip, ['::/0'])).toBe(true); + expect(middlewares.checkIpRanges(ip, ['::'])).toBe(true); + expect(middlewares.checkIpRanges(ip, ['0.0.0.0'])).toBe(false); + expect(middlewares.checkIpRanges(ip, ['123.123.123.123'])).toBe(false); + }); + + expect(middlewares.checkIpRanges(ipv6, [anotherIpv6])).toBe(false); + expect(middlewares.checkIpRanges(ipv6, [ipv6])).toBe(true); + expect(middlewares.checkIpRanges(ipv6, ['2001:db8:85a3:0:0:8a2e:0:0/100'])).toBe(true); + + expect(middlewares.checkIpRanges(ipv4, ['::'])).toBe(false); + expect(middlewares.checkIpRanges(ipv4, ['::/0'])).toBe(true); + expect(middlewares.checkIpRanges(ipv4, ['0.0.0.0'])).toBe(true); + expect(middlewares.checkIpRanges(ipv4, ['123.123.123.123'])).toBe(false); + expect(middlewares.checkIpRanges(ipv4, [ipv4])).toBe(true); + expect(middlewares.checkIpRanges(ipv4, ['192.168.0.0/24'])).toBe(true); + + expect(middlewares.checkIpRanges(localhostV4, ['::1'])).toBe(false); + expect(middlewares.checkIpRanges(localhostV6, ['::1'])).toBe(true); + // ::ffff:127.0.0.1 is a padded ipv4 address but not ::1 + expect(middlewares.checkIpRanges(localhostV62, ['::1'])).toBe(false); + // ::ffff:127.0.0.1 is a padded ipv4 address and is a match for 127.0.0.1 + expect(middlewares.checkIpRanges(localhostV62, ['127.0.0.1'])).toBe(true); + }); + it('can allow all with masterKeyIPs', async () => { const combinations = [ { diff --git a/src/middlewares.js b/src/middlewares.js index 28c5a1eda1..a467ec7a4d 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -23,7 +23,7 @@ const getMountForRequest = function (req) { return req.protocol + '://' + req.get('host') + mountPath; }; -const checkIpRanges = (ip, ranges = []) => { +export const checkIpRanges = (ip, ranges = []) => { const getType = address => (isIPv4(address) ? 'ipv4' : 'ipv6'); const clientType = getType(ip); const blocklist = new BlockList();