diff --git a/src/dashboard.js b/src/dashboard.js index ce4cc74fc..81b74c751 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -32,7 +32,7 @@ const apps = require('./apps.js'), settings = require('./settings.js'), system = require('./system.js'), tasks = require('./tasks.js'), - users = require('./users.js'); + userDirectory = require('./user-directory.js'); async function getLocation() { const domain = await settings.get(settings.DASHBOARD_DOMAIN_KEY); @@ -56,7 +56,7 @@ async function clearLocation() { async function getConfig() { const ubuntuVersion = await system.getUbuntuVersion(); - const profileConfig = await users.getProfileConfig(); + const profileConfig = await userDirectory.getProfileConfig(); const externalLdapConfig = await externalLdap.getConfig(); const { fqdn:adminFqdn, domain:adminDomain } = await getLocation(); diff --git a/src/routes/index.js b/src/routes/index.js index 681a7a822..df8cba903 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -32,6 +32,7 @@ exports = module.exports = { tasks: require('./tasks.js'), tokens: require('./tokens.js'), updater: require('./updater.js'), + userDirectory: require('./user-directory.js'), users: require('./users.js'), volumes: require('./volumes.js'), wellknown: require('./wellknown.js') diff --git a/src/routes/profile.js b/src/routes/profile.js index c49009ce2..0e303557d 100644 --- a/src/routes/profile.js +++ b/src/routes/profile.js @@ -27,12 +27,13 @@ const assert = require('assert'), path = require('path'), paths = require('../paths.js'), safe = require('safetydance'), + userDirectory = require('../user-directory.js'), users = require('../users.js'); async function canEditProfile(req, res, next) { assert.strictEqual(typeof req.user, 'object'); - const [error, profileConfig] = await safe(users.getProfileConfig()); + const [error, profileConfig] = await safe(userDirectory.getProfileConfig()); if (error) return next(BoxError.toHttpError(error)); if (profileConfig.lockUserProfiles) return next(new HttpError(403, 'admin has disallowed users from editing profiles')); diff --git a/src/routes/user-directory.js b/src/routes/user-directory.js new file mode 100644 index 000000000..c167e1e05 --- /dev/null +++ b/src/routes/user-directory.js @@ -0,0 +1,32 @@ +'use strict'; + +exports = module.exports = { + getProfileConfig, + setProfileConfig +}; + +const assert = require('assert'), + BoxError = require('../boxerror.js'), + HttpError = require('connect-lastmile').HttpError, + HttpSuccess = require('connect-lastmile').HttpSuccess, + safe = require('safetydance'), + userDirectory = require('../user-directory.js'); + +async function getProfileConfig(req, res, next) { + const [error, directoryConfig] = await safe(userDirectory.getProfileConfig()); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, directoryConfig)); +} + +async function setProfileConfig(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.lockUserProfiles !== 'boolean') return next(new HttpError(400, 'lockUserProfiles is required')); + if (typeof req.body.mandatory2FA !== 'boolean') return next(new HttpError(400, 'mandatory2FA is required')); + + const [error] = await safe(userDirectory.setProfileConfig(req.body)); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, {})); +} diff --git a/src/routes/users.js b/src/routes/users.js index 5dda1b7a6..e9edc44cb 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -18,9 +18,6 @@ exports = module.exports = { getPasswordResetLink, sendPasswordResetEmail, - setProfileConfig, - getProfileConfig, - getInviteLink, sendInviteEmail, @@ -275,22 +272,3 @@ async function sendInviteEmail(req, res, next) { next(new HttpSuccess(202, {})); } - -async function getProfileConfig(req, res, next) { - const [error, directoryConfig] = await safe(users.getProfileConfig()); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, directoryConfig)); -} - -async function setProfileConfig(req, res, next) { - assert.strictEqual(typeof req.body, 'object'); - - if (typeof req.body.lockUserProfiles !== 'boolean') return next(new HttpError(400, 'lockUserProfiles is required')); - if (typeof req.body.mandatory2FA !== 'boolean') return next(new HttpError(400, 'mandatory2FA is required')); - - const [error] = await safe(users.setProfileConfig(req.body)); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, {})); -} diff --git a/src/server.js b/src/server.js index 74e49d555..34838b30a 100644 --- a/src/server.js +++ b/src/server.js @@ -215,8 +215,8 @@ async function initializeExpressSync() { router.del ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.load, routes.groups.remove); // User directory - router.get ('/api/v1/user_directory/profile_config', token, authorizeAdmin, routes.users.getProfileConfig); - router.post('/api/v1/user_directory/profile_config', json, token, authorizeAdmin, routes.users.setProfileConfig); + router.get ('/api/v1/user_directory/profile_config', token, authorizeAdmin, routes.userDirectory.getProfileConfig); + router.post('/api/v1/user_directory/profile_config', json, token, authorizeAdmin, routes.userDirectory.setProfileConfig); // External LDAP router.get ('/api/v1/external_ldap/config', token, authorizeAdmin, routes.externalLdap.getConfig); diff --git a/src/test/user-directory-test.js b/src/test/user-directory-test.js new file mode 100644 index 000000000..62e511da4 --- /dev/null +++ b/src/test/user-directory-test.js @@ -0,0 +1,36 @@ +/* global it:false */ +/* global describe:false */ +/* global before:false */ +/* global after:false */ + +'use strict'; + +const common = require('./common.js'), + expect = require('expect.js'), + tokens = require('../tokens.js'), + userDirectory = require('../user-directory.js'); + +describe('User Directory', function () { + const { setup, cleanup, admin } = common; + + before(setup); + after(cleanup); + + describe('profile config', function () { + it('can get default profile config', async function () { + const profileConfig = await userDirectory.getProfileConfig(); + expect(profileConfig.lockUserProfiles).to.be(false); + expect(profileConfig.mandatory2FA).to.be(false); + }); + + it('can set default profile config', async function () { + await tokens.add({ name: 'token1', identifier: admin.id, clientId: tokens.ID_WEBADMIN, expires: Number.MAX_SAFE_INTEGER, lastUsedTime: null }); + let result = await tokens.listByUserId(admin.id); + expect(result.length).to.be(1); // just confirm the token was really added! + + await userDirectory.setProfileConfig({ mandatory2FA: true, lockUserProfiles: true }); + result = await tokens.listByUserId(admin.id); + expect(result.length).to.be(0); // should have been removed by mandatory 2fa setting change + }); + }); +}); diff --git a/src/test/users-test.js b/src/test/users-test.js index 002a05806..d7b40e09b 100644 --- a/src/test/users-test.js +++ b/src/test/users-test.js @@ -10,7 +10,6 @@ const BoxError = require('../boxerror.js'), expect = require('expect.js'), safe = require('safetydance'), speakeasy = require('speakeasy'), - tokens = require('../tokens.js'), users = require('../users.js'), _ = require('underscore'); @@ -199,7 +198,7 @@ describe('User', function () { }); it('can listPaged (search)', async function () { - let results = await users.listPaged(admin.email.slice(0, 8), null, 1, 1); + const results = await users.listPaged(admin.email.slice(0, 8), null, 1, 1); expect(results.length).to.be(1); checkUser(results[0], admin); }); @@ -632,24 +631,6 @@ describe('User', function () { it('can re-create user after user was removed', createOwner); }); - describe('profile config', function () { - it('can get default profile config', async function () { - const profileConfig = await users.getProfileConfig(); - expect(profileConfig.lockUserProfiles).to.be(false); - expect(profileConfig.mandatory2FA).to.be(false); - }); - - it('can set default profile config', async function () { - await tokens.add({ name: 'token1', identifier: admin.id, clientId: tokens.ID_WEBADMIN, expires: Number.MAX_SAFE_INTEGER, lastUsedTime: null }); - let result = await tokens.listByUserId(admin.id); - expect(result.length).to.be(1); // just confirm the token was really added! - - await users.setProfileConfig({ mandatory2FA: true, lockUserProfiles: true }); - result = await tokens.listByUserId(admin.id); - expect(result.length).to.be(0); // should have been removed by mandatory 2fa setting change - }); - }); - describe('parseDisplayName', function () { it('parses names', function () { const names = [ diff --git a/src/user-directory.js b/src/user-directory.js new file mode 100644 index 000000000..c46451278 --- /dev/null +++ b/src/user-directory.js @@ -0,0 +1,41 @@ +'use strict'; + +exports = module.exports = { + getProfileConfig, + setProfileConfig +}; + +const assert = require('assert'), + BoxError = require('./boxerror.js'), + constants = require('./constants.js'), + debug = require('debug')('box:user-directory'), + oidc = require('./oidc.js'), + settings = require('./settings.js'), + tokens = require('./tokens.js'), + users = require('./users.js'); + +async function getProfileConfig() { + const value = await settings.getJson(settings.PROFILE_CONFIG_KEY); + return value || { lockUserProfiles: false, mandatory2FA: false }; +} + +async function setProfileConfig(profileConfig) { + assert.strictEqual(typeof profileConfig, 'object'); + + if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); + + const oldConfig = await getProfileConfig(); + await settings.setJson(settings.PROFILE_CONFIG_KEY, profileConfig); + + if (profileConfig.mandatory2FA && !oldConfig.mandatory2FA) { + debug('setProfileConfig: logging out non-2FA users to enforce 2FA'); + + const allUsers = await users.list(); + for (const user of allUsers) { + if (user.twoFactorAuthenticationEnabled) continue; + + await tokens.delByUserIdAndType(user.id, tokens.ID_WEBADMIN); + await oidc.revokeByUserId(user.id); + } + } +} diff --git a/src/users.js b/src/users.js index 90a9df853..834db9d96 100644 --- a/src/users.js +++ b/src/users.js @@ -51,9 +51,6 @@ exports = module.exports = { getBackgroundImage, setBackgroundImage, - getProfileConfig, - setProfileConfig, - resetSource, parseDisplayName, @@ -91,15 +88,15 @@ const appPasswords = require('./apppasswords.js'), mail = require('./mail.js'), mailer = require('./mailer.js'), mysql = require('mysql'), - oidc = require('../oidc.js'), qrcode = require('qrcode'), safe = require('safetydance'), settings = require('./settings.js'), speakeasy = require('speakeasy'), tokens = require('./tokens.js'), translation = require('./translation.js'), - uuid = require('uuid'), uaParser = require('ua-parser-js'), + userDirectory = require('./user-directory.js'), + uuid = require('uuid'), superagent = require('superagent'), util = require('util'), validator = require('validator'), @@ -826,7 +823,7 @@ async function getInviteLink(user, auditSource) { if (user.source) throw new BoxError(BoxError.CONFLICT, 'User is from an external directory'); if (!user.inviteToken) throw new BoxError(BoxError.BAD_STATE, 'User already used invite link'); - const directoryConfig = await getProfileConfig(); + const directoryConfig = await userDirectory.getProfileConfig(); const { fqdn:dashboardFqdn } = await dashboard.getLocation(); let inviteLink = `https://${dashboardFqdn}/setupaccount.html?inviteToken=${user.inviteToken}&email=${encodeURIComponent(user.email)}`; @@ -854,7 +851,7 @@ async function setupAccount(user, data, auditSource) { assert.strictEqual(typeof data, 'object'); assert(auditSource && typeof auditSource === 'object'); - const profileConfig = await getProfileConfig(); + const profileConfig = await userDirectory.getProfileConfig(); const tmp = { inviteToken: '' }; @@ -972,32 +969,6 @@ async function setBackgroundImage(id, backgroundImage) { if (result.length === 0) throw new BoxError(BoxError.NOT_FOUND, 'User not found'); } -async function getProfileConfig() { - const value = await settings.getJson(settings.PROFILE_CONFIG_KEY); - return value || { lockUserProfiles: false, mandatory2FA: false }; -} - -async function setProfileConfig(profileConfig) { - assert.strictEqual(typeof profileConfig, 'object'); - - if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); - - const oldConfig = await getProfileConfig(); - await settings.setJson(settings.PROFILE_CONFIG_KEY, profileConfig); - - if (profileConfig.mandatory2FA && !oldConfig.mandatory2FA) { - debug('setProfileConfig: logging out non-2FA users to enforce 2FA'); - - const allUsers = await list(); - for (const user of allUsers) { - if (!user.twoFactorAuthenticationEnabled) { - await tokens.delByUserIdAndType(user.id, tokens.ID_WEBADMIN); - await oidc.revokeByUserId(user.id); - } - } - } -} - async function resetSource() { await database.query('UPDATE users SET source = ?', [ '' ]); }