2018-04-26 15:54:53 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
2018-06-25 16:45:15 -07:00
|
|
|
SCOPE_APPS_READ: 'apps:read',
|
|
|
|
|
SCOPE_APPS_MANAGE: 'apps:manage',
|
2018-05-01 11:32:12 -07:00
|
|
|
SCOPE_CLIENTS: 'clients',
|
2018-04-26 15:54:53 -07:00
|
|
|
SCOPE_CLOUDRON: 'cloudron',
|
2018-06-25 15:12:20 -07:00
|
|
|
SCOPE_DOMAINS_READ: 'domains:read',
|
|
|
|
|
SCOPE_DOMAINS_MANAGE: 'domains:manage',
|
2018-05-01 11:32:12 -07:00
|
|
|
SCOPE_MAIL: 'mail',
|
|
|
|
|
SCOPE_PROFILE: 'profile',
|
2018-04-26 15:54:53 -07:00
|
|
|
SCOPE_SETTINGS: 'settings',
|
2018-06-25 15:54:24 -07:00
|
|
|
SCOPE_USERS_READ: 'users:read',
|
|
|
|
|
SCOPE_USERS_MANAGE: 'users:manage',
|
2018-06-17 18:09:13 -07:00
|
|
|
SCOPE_APPSTORE: 'appstore',
|
2018-06-27 14:07:25 -07:00
|
|
|
VALID_SCOPES: [ 'apps', 'appstore', 'clients', 'cloudron', 'domains', 'mail', 'profile', 'settings', 'users' ], // keep this sorted
|
2018-04-26 15:54:53 -07:00
|
|
|
|
2018-04-27 21:47:11 -07:00
|
|
|
SCOPE_ANY: '*',
|
|
|
|
|
|
2018-06-14 21:12:52 -07:00
|
|
|
ROLE_OWNER: 'owner',
|
|
|
|
|
|
2018-06-18 14:21:54 -07:00
|
|
|
scopesForRoles: scopesForRoles,
|
|
|
|
|
|
2018-06-14 21:12:52 -07:00
|
|
|
validateRoles: validateRoles,
|
|
|
|
|
|
2018-06-17 22:29:17 -07:00
|
|
|
validateScopeString: validateScopeString,
|
2018-06-14 16:32:24 -07:00
|
|
|
hasScopes: hasScopes,
|
2018-06-17 22:38:14 -07:00
|
|
|
intersectScopes: intersectScopes,
|
2018-06-17 22:42:18 -07:00
|
|
|
canonicalScopeString: canonicalScopeString
|
2018-04-26 15:54:53 -07:00
|
|
|
};
|
|
|
|
|
|
2018-06-18 14:21:54 -07:00
|
|
|
// https://docs.microsoft.com/en-us/azure/role-based-access-control/role-definitions
|
|
|
|
|
const ROLE_DEFINITIONS = {
|
|
|
|
|
'owner': {
|
|
|
|
|
scopes: exports.VALID_SCOPES
|
|
|
|
|
},
|
|
|
|
|
'manage_apps': {
|
2018-06-25 15:54:24 -07:00
|
|
|
scopes: [ 'apps', 'domains:read', 'users:read' ]
|
2018-06-18 14:21:54 -07:00
|
|
|
},
|
|
|
|
|
'manage_users': {
|
|
|
|
|
scopes: [ 'users' ]
|
2018-06-18 17:32:07 -07:00
|
|
|
},
|
|
|
|
|
'manage_domains': {
|
|
|
|
|
scopes: [ 'domains' ]
|
2018-06-18 14:21:54 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2018-04-26 15:54:53 -07:00
|
|
|
var assert = require('assert'),
|
2018-04-30 21:21:18 -07:00
|
|
|
debug = require('debug')('box:accesscontrol'),
|
|
|
|
|
_ = require('underscore');
|
2018-04-26 15:54:53 -07:00
|
|
|
|
2018-06-27 14:07:25 -07:00
|
|
|
// returns scopes that does not have wildcards and is sorted
|
2018-06-17 22:42:18 -07:00
|
|
|
function canonicalScopeString(scope) {
|
2018-06-27 14:07:25 -07:00
|
|
|
if (scope === exports.SCOPE_ANY) return exports.VALID_SCOPES.join(',');
|
|
|
|
|
|
|
|
|
|
return scope.split(',').sort().join(',');
|
2018-05-02 12:36:35 -07:00
|
|
|
}
|
|
|
|
|
|
2018-06-17 22:38:14 -07:00
|
|
|
function intersectScopes(allowedScopes, wantedScopes) {
|
2018-06-27 14:07:25 -07:00
|
|
|
assert(Array.isArray(allowedScopes), 'Expecting sorted array');
|
|
|
|
|
assert(Array.isArray(wantedScopes), 'Expecting sorted array');
|
2018-05-01 13:34:46 -07:00
|
|
|
|
2018-06-17 22:38:14 -07:00
|
|
|
return _.intersection(allowedScopes, wantedScopes);
|
2018-05-01 13:34:46 -07:00
|
|
|
}
|
|
|
|
|
|
2018-06-14 21:12:52 -07:00
|
|
|
function validateRoles(roles) {
|
|
|
|
|
assert(Array.isArray(roles));
|
|
|
|
|
|
2018-06-18 17:32:07 -07:00
|
|
|
for (let role of roles) {
|
|
|
|
|
if (Object.keys(ROLE_DEFINITIONS).indexOf(role) === -1) return new Error(`Invalid role ${role}`);
|
|
|
|
|
}
|
2018-06-14 21:12:52 -07:00
|
|
|
|
2018-06-18 17:32:07 -07:00
|
|
|
return null;
|
2018-06-14 21:12:52 -07:00
|
|
|
}
|
|
|
|
|
|
2018-06-17 22:29:17 -07:00
|
|
|
function validateScopeString(scope) {
|
2018-04-26 15:54:53 -07:00
|
|
|
assert.strictEqual(typeof scope, 'string');
|
|
|
|
|
|
|
|
|
|
if (scope === '') return new Error('Empty scope not allowed');
|
|
|
|
|
|
2018-05-02 12:36:35 -07:00
|
|
|
// 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
|
2018-06-14 16:37:38 -07:00
|
|
|
var allValid = scope.split(',').every(function (s) { return exports.VALID_SCOPES.indexOf(s.split(':')[0]) !== -1; });
|
2018-05-01 11:32:12 -07:00
|
|
|
if (!allValid) return new Error('Invalid scope. Available scopes are ' + exports.VALID_SCOPES.join(', '));
|
2018-04-26 15:54:53 -07:00
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-14 16:32:24 -07:00
|
|
|
// tests if all requiredScopes are attached to the request
|
2018-06-17 19:54:05 -07:00
|
|
|
function hasScopes(authorizedScopes, requiredScopes) {
|
|
|
|
|
assert(Array.isArray(authorizedScopes), 'Expecting array');
|
2018-06-14 16:32:24 -07:00
|
|
|
assert(Array.isArray(requiredScopes), 'Expecting array');
|
2018-04-26 15:54:53 -07:00
|
|
|
|
2018-06-17 19:54:05 -07:00
|
|
|
if (authorizedScopes.indexOf(exports.SCOPE_ANY) !== -1) return null;
|
2018-04-26 15:54:53 -07:00
|
|
|
|
2018-06-14 16:32:24 -07:00
|
|
|
for (var i = 0; i < requiredScopes.length; ++i) {
|
2018-06-14 16:37:38 -07:00
|
|
|
const scopeParts = requiredScopes[i].split(':');
|
|
|
|
|
|
|
|
|
|
// this allows apps:write if the token has a higher apps scope
|
2018-06-17 19:54:05 -07:00
|
|
|
if (authorizedScopes.indexOf(requiredScopes[i]) === -1 && authorizedScopes.indexOf(scopeParts[0]) === -1) {
|
2018-06-14 16:32:24 -07:00
|
|
|
debug('scope: missing scope "%s".', requiredScopes[i]);
|
|
|
|
|
return new Error('Missing required scope "' + requiredScopes[i] + '"');
|
2018-04-26 15:54:53 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2018-06-18 14:21:54 -07:00
|
|
|
|
|
|
|
|
function scopesForRoles(roles) {
|
|
|
|
|
assert(Array.isArray(roles), 'Expecting array');
|
|
|
|
|
|
2018-06-25 16:45:15 -07:00
|
|
|
var scopes = [ 'profile', 'apps:read' ];
|
2018-06-18 14:21:54 -07:00
|
|
|
|
|
|
|
|
for (let r of roles) {
|
|
|
|
|
if (!ROLE_DEFINITIONS[r]) continue; // unknown or some legacy role
|
|
|
|
|
|
|
|
|
|
scopes = scopes.concat(ROLE_DEFINITIONS[r].scopes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _.uniq(scopes.sort(), true /* isSorted */);
|
|
|
|
|
}
|