diff --git a/src/accesscontrol.js b/src/accesscontrol.js index 9ff4e0608..63f5e8a55 100644 --- a/src/accesscontrol.js +++ b/src/accesscontrol.js @@ -164,7 +164,7 @@ function validateScope(scope) { // NOTE: this function intentionally does not allow '*'. This is only allowed in the db to allow // us not write a migration script every time we add a new scope - var allValid = scope.split(',').every(function (s) { return exports.VALID_SCOPES.indexOf(s) !== -1; }); + var allValid = scope.split(',').every(function (s) { return exports.VALID_SCOPES.indexOf(s.split(':')[0]) !== -1; }); if (!allValid) return new Error('Invalid scope. Available scopes are ' + exports.VALID_SCOPES.join(', ')); return null; @@ -182,7 +182,10 @@ function hasScopes(authInfo, requiredScopes) { if (scopes.indexOf(exports.SCOPE_ANY) !== -1) return null; for (var i = 0; i < requiredScopes.length; ++i) { - if (scopes.indexOf(requiredScopes[i]) === -1) { + const scopeParts = requiredScopes[i].split(':'); + + // this allows apps:write if the token has a higher apps scope + if (scopes.indexOf(requiredScopes[i]) === -1 && scopes.indexOf(scopeParts[0]) === -1) { debug('scope: missing scope "%s".', requiredScopes[i]); return new Error('Missing required scope "' + requiredScopes[i] + '"'); } diff --git a/src/test/accesscontrol-test.js b/src/test/accesscontrol-test.js index 212044b91..2f5f345f6 100644 --- a/src/test/accesscontrol-test.js +++ b/src/test/accesscontrol-test.js @@ -50,6 +50,8 @@ describe('access control', function () { it('allows valid scopes', function () { expect(accesscontrol.validateScope('apps')).to.be(null); expect(accesscontrol.validateScope('apps,mail')).to.be(null); + expect(accesscontrol.validateScope('apps:read,mail')).to.be(null); + expect(accesscontrol.validateScope('apps,mail:write')).to.be(null); }); it('disallows invalid scopes', function () { @@ -64,11 +66,20 @@ describe('access control', function () { expect(accesscontrol.hasScopes({ scope: 'apps' }, [ 'apps' ])).to.be(null); expect(accesscontrol.hasScopes({ scope: 'apps,mail' }, [ 'mail' ])).to.be(null); expect(accesscontrol.hasScopes({ scope: 'clients,*,apps,mail' }, [ 'mail' ])).to.be(null); + + // subscope + expect(accesscontrol.hasScopes({ scope: 'apps' }, [ 'apps:read' ])).to.be(null); + expect(accesscontrol.hasScopes({ scope: 'apps:read' }, [ 'apps:read' ])).to.be(null); + expect(accesscontrol.hasScopes({ scope: 'apps,mail' }, [ 'apps:*' ])).to.be(null); + expect(accesscontrol.hasScopes({ scope: '*' }, [ 'apps:read' ])).to.be(null); }); it('fails if it does not contain the scope', function () { expect(accesscontrol.hasScopes({ scope: 'apps' }, [ 'mail' ])).to.be.an(Error); expect(accesscontrol.hasScopes({ scope: 'apps,mail' }, [ 'clients' ])).to.be.an(Error); + + // subscope + expect(accesscontrol.hasScopes({ scope: 'apps:write' }, [ 'apps:read' ])).to.be.an(Error); }); }); });