Files
cloudron-box/src/accesscontrol.js

122 lines
3.7 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
SCOPE_APPS_READ: 'apps:read',
SCOPE_APPS_MANAGE: 'apps:manage',
SCOPE_CLIENTS: 'clients',
SCOPE_CLOUDRON: 'cloudron',
SCOPE_DOMAINS_READ: 'domains:read',
SCOPE_DOMAINS_MANAGE: 'domains:manage',
SCOPE_MAIL: 'mail',
SCOPE_PROFILE: 'profile',
SCOPE_SETTINGS: 'settings',
SCOPE_USERS_READ: 'users:read',
SCOPE_USERS_MANAGE: 'users:manage',
SCOPE_APPSTORE: 'appstore',
VALID_SCOPES: [ 'apps', 'appstore', 'clients', 'cloudron', 'domains', 'mail', 'profile', 'settings', 'users' ], // keep this sorted
SCOPE_ANY: '*',
ROLE_OWNER: 'owner',
2018-06-18 14:21:54 -07:00
scopesForRoles: scopesForRoles,
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,
canonicalScopeString: canonicalScopeString
};
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': {
scopes: [ 'apps', 'domains:read', 'users:read' ]
2018-06-18 14:21:54 -07:00
},
'manage_users': {
scopes: [ 'users' ]
},
'manage_domains': {
scopes: [ 'domains' ]
2018-06-18 14:21:54 -07:00
}
};
var assert = require('assert'),
debug = require('debug')('box:accesscontrol'),
_ = require('underscore');
// returns scopes that does not have wildcards and is sorted
function canonicalScopeString(scope) {
if (scope === exports.SCOPE_ANY) return exports.VALID_SCOPES.join(',');
return scope.split(',').sort().join(',');
}
2018-06-17 22:38:14 -07:00
function intersectScopes(allowedScopes, wantedScopes) {
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
}
function validateRoles(roles) {
assert(Array.isArray(roles));
for (let role of roles) {
if (Object.keys(ROLE_DEFINITIONS).indexOf(role) === -1) return new Error(`Invalid role ${role}`);
}
return null;
}
2018-06-17 22:29:17 -07:00
function validateScopeString(scope) {
assert.strictEqual(typeof scope, 'string');
if (scope === '') return new Error('Empty scope not allowed');
// 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.split(':')[0]) !== -1; });
if (!allValid) return new Error('Invalid scope. Available scopes are ' + exports.VALID_SCOPES.join(', '));
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-06-17 19:54:05 -07:00
if (authorizedScopes.indexOf(exports.SCOPE_ANY) !== -1) return null;
2018-06-14 16:32:24 -07:00
for (var i = 0; i < requiredScopes.length; ++i) {
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] + '"');
}
}
return null;
}
2018-06-18 14:21:54 -07:00
function scopesForRoles(roles) {
assert(Array.isArray(roles), 'Expecting array');
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 */);
}