From b1cfa6e3425a8db64d236cb1da7c06265f9b9cfb Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Tue, 22 Aug 2017 10:59:01 +0100 Subject: [PATCH] Improved version match logic (#8922) closes #8821 - Use semver to do constraint matching - Use client to generate a caret constraint - E.g. if the client is 1.1, then the constraint ^1.1.0 will match >=1.1.0 <2.0.0 - Updated tests --- core/server/middleware/api/version-match.js | 16 +++-- core/server/translations/en.json | 2 +- .../unit/middleware/api/version-match_spec.js | 70 ++++++++++++++++--- 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/core/server/middleware/api/version-match.js b/core/server/middleware/api/version-match.js index bcfbaa2293..d8b2dbf324 100644 --- a/core/server/middleware/api/version-match.js +++ b/core/server/middleware/api/version-match.js @@ -1,13 +1,19 @@ -var errors = require('../../errors'), +var semver = require('semver'), + errors = require('../../errors'), i18n = require('../../i18n'); function checkVersionMatch(req, res, next) { - var requestVersion = req.get('X-Ghost-Version'), - currentVersion = res.locals.safeVersion; + var clientVersion = req.get('X-Ghost-Version'), + serverVersion = res.locals.version, + constraint = '^' + clientVersion + '.0'; - if (requestVersion && requestVersion !== currentVersion) { + // no error when client is on an earlier minor version than server + // error when client is on a later minor version than server + // always error when the major version is different + + if (clientVersion && !semver.satisfies(serverVersion, constraint)) { return next(new errors.VersionMismatchError({ - message: i18n.t('errors.middleware.api.versionMismatch', {requestVersion: requestVersion, currentVersion: currentVersion}) + message: i18n.t('errors.middleware.api.versionMismatch', {clientVersion: clientVersion, serverVersion: serverVersion}) })); } diff --git a/core/server/translations/en.json b/core/server/translations/en.json index 100ed65074..6e4f7cbd63 100644 --- a/core/server/translations/en.json +++ b/core/server/translations/en.json @@ -66,7 +66,7 @@ }, "middleware": { "api": { - "versionMismatch": "Request for version {requestVersion} does not match current version {currentVersion}." + "versionMismatch": "Client request for {clientVersion} does not match server version {serverVersion}." }, "auth": { "clientAuthenticationFailed": "Client Authentication Failed", diff --git a/core/test/unit/middleware/api/version-match_spec.js b/core/test/unit/middleware/api/version-match_spec.js index b830e13e9b..713fc706be 100644 --- a/core/test/unit/middleware/api/version-match_spec.js +++ b/core/test/unit/middleware/api/version-match_spec.js @@ -19,30 +19,80 @@ describe('Version Mismatch', function () { get: getStub }; res = { - locals: { - safeVersion: '0.7' - } + locals: {} }; }); + function testVersionMatch(serverVersion, clientVersion) { + // Set the server version + res.locals.version = serverVersion; + + if (clientVersion) { + // Optionally set the client version + getStub.returns(clientVersion); + } + + versionMatch(req, res, nextStub); + } + it('should call next if request does not include a version', function () { - versionMatch(req, res, nextStub); + var server = '1.5.1'; + + testVersionMatch(server); nextStub.calledOnce.should.be.true(); nextStub.firstCall.args.should.be.empty(); }); - it('should call next if versions match', function () { - getStub.returns('0.7'); - versionMatch(req, res, nextStub); + it('should call next if versions are an exact match', function () { + var server = '1.5.0', + client = '1.5'; + + testVersionMatch(server, client); nextStub.calledOnce.should.be.true(); nextStub.firstCall.args.should.be.empty(); }); - it('should throw VersionMismatchError if request includes incorrect version', function () { - getStub.returns('0.6'); - versionMatch(req, res, nextStub); + it('should call next if client version is earlier than server', function () { + var server = '1.5.0', + client = '1.3'; + + testVersionMatch(server, client); + + nextStub.calledOnce.should.be.true(); + nextStub.firstCall.args.should.be.empty(); + }); + + it('should throw VersionMismatchError if client version is earlier by a major version', function () { + var server = '2.5.0', + client = '1.3'; + + testVersionMatch(server, client); + + nextStub.calledOnce.should.be.true(); + nextStub.firstCall.args.should.have.lengthOf(1); + nextStub.firstCall.args[0].should.have.property('errorType', 'VersionMismatchError'); + nextStub.firstCall.args[0].should.have.property('statusCode', 400); + }); + + it('should throw VersionMismatchError if client version is later than server', function () { + var server = '1.3.0', + client = '1.5'; + + testVersionMatch(server, client); + + nextStub.calledOnce.should.be.true(); + nextStub.firstCall.args.should.have.lengthOf(1); + nextStub.firstCall.args[0].should.have.property('errorType', 'VersionMismatchError'); + nextStub.firstCall.args[0].should.have.property('statusCode', 400); + }); + + it('should throw VersionMismatchError if client version is later by a major version', function () { + var server = '1.5.0', + client = '2.3'; + + testVersionMatch(server, client); nextStub.calledOnce.should.be.true(); nextStub.firstCall.args.should.have.lengthOf(1);