diff --git a/src/accesscontrol.js b/src/accesscontrol.js index 783f90919..eb3274ff9 100644 --- a/src/accesscontrol.js +++ b/src/accesscontrol.js @@ -16,6 +16,8 @@ exports = module.exports = { ROLE_OWNER: 'owner', + scopesForRoles: scopesForRoles, + validateRoles: validateRoles, validateScopeString: validateScopeString, @@ -24,6 +26,19 @@ exports = module.exports = { canonicalScopeString: canonicalScopeString }; +// 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', 'users' ] + }, + 'manage_users': { + scopes: [ 'users' ] + } +}; + var assert = require('assert'), debug = require('debug')('box:accesscontrol'), _ = require('underscore'); @@ -80,3 +95,17 @@ function hasScopes(authorizedScopes, requiredScopes) { return null; } + +function scopesForRoles(roles) { + assert(Array.isArray(roles), 'Expecting array'); + + var scopes = [ 'profile' ]; + + 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 */); +} diff --git a/src/groupdb.js b/src/groupdb.js index 2f2813054..51e8c7790 100644 --- a/src/groupdb.js +++ b/src/groupdb.js @@ -291,7 +291,6 @@ function getGroups(userId, callback) { database.query('SELECT ' + GROUPS_FIELDS + ' ' + ' FROM groups INNER JOIN groupMembers ON groups.id = groupMembers.groupId AND groupMembers.userId = ?', [ userId ], function (error, results) { if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); - if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); results.forEach(postProcess); diff --git a/src/groups.js b/src/groups.js index 2c72bf4f8..fd922db60 100644 --- a/src/groups.js +++ b/src/groups.js @@ -275,7 +275,6 @@ function getGroups(userId, callback) { assert.strictEqual(typeof callback, 'function'); groupdb.getGroups(userId, function (error, results) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new GroupsError(GroupsError.NOT_FOUND)); if (error) return callback(new GroupsError(GroupsError.INTERNAL_ERROR, error)); callback(null, results); diff --git a/src/routes/accesscontrol.js b/src/routes/accesscontrol.js index 5b8f490c0..19f39aef0 100644 --- a/src/routes/accesscontrol.js +++ b/src/routes/accesscontrol.js @@ -101,13 +101,13 @@ function accessTokenAuth(accessToken, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, null /* user */, 'Invalid Token'); // will end up as a 401 if (error) return callback(error); // this triggers 'internal error' in passport - users.get(token.identifier, function (error, user) { + users.getWithRoles(token.identifier, function (error, user) { if (error && error.reason === UsersError.NOT_FOUND) return callback(null, null /* user */, 'Invalid Token'); // will end up as a 401 if (error) return callback(error); // scopes here can define what capabilities that token carries // passport put the 'info' object into req.authInfo, where we can further validate the scopes - const userScopes = user.groupIds.indexOf(constants.ADMIN_GROUP_ID) !== -1 ? accesscontrol.VALID_SCOPES : [ 'profile' ]; + const userScopes = accesscontrol.scopesForRoles(user.roles); var authorizedScopes = accesscontrol.intersectScopes(userScopes, token.scope.split(',')); // these clients do not require password checks unlike UI const skipPasswordVerification = token.clientId === 'cid-sdk' || token.clientId === 'cid-cli'; diff --git a/src/users.js b/src/users.js index 0a21d7a40..db2896d62 100644 --- a/src/users.js +++ b/src/users.js @@ -13,6 +13,7 @@ exports = module.exports = { verifyWithEmail: verifyWithEmail, remove: removeUser, get: get, + getWithRoles: getWithRoles, getByResetToken: getByResetToken, getAllAdmins: getAllAdmins, resetPasswordByIdentifier: resetPasswordByIdentifier, @@ -330,6 +331,24 @@ function get(userId, callback) { }); } +function getWithRoles(userId, callback) { + assert.strictEqual(typeof userId, 'string'); + assert.strictEqual(typeof callback, 'function'); + + userdb.get(userId, function (error, result) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND)); + if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error)); + + groups.getGroups(userId, function (error, userGroups) { + if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error)); + + result.roles = _.uniq(_.flatten(userGroups.map(function (r) { return r.roles; }))); + + return callback(null, result); + }); + }); +} + function getByResetToken(email, resetToken, callback) { assert.strictEqual(typeof email, 'string'); assert.strictEqual(typeof resetToken, 'string');