diff --git a/src/externalldap.js b/src/externalldap.js index 516cea27e..b27c5cda5 100644 --- a/src/externalldap.js +++ b/src/externalldap.js @@ -14,7 +14,7 @@ exports = module.exports = { sync }; -var assert = require('assert'), +const assert = require('assert'), async = require('async'), auditSource = require('./auditsource.js'), BoxError = require('./boxerror.js'), @@ -23,9 +23,11 @@ var assert = require('assert'), groups = require('./groups.js'), ldap = require('ldapjs'), once = require('once'), + safe = require('safetydance'), settings = require('./settings.js'), tasks = require('./tasks.js'), - users = require('./users.js'); + users = require('./users.js'), + util = require('util'); function injectPrivateFields(newConfig, currentConfig) { if (newConfig.bindPassword === constants.SECRET_PLACEHOLDER) newConfig.bindPassword = currentConfig.bindPassword; @@ -445,11 +447,11 @@ function syncGroups(externalLdapConfig, progressCallback, callback) { let step = 30/(ldapGroups.length+1); // ensure no divide by 0 // we ignore all non internal errors here and just log them for now - async.eachSeries(ldapGroups, function (ldapGroup, iteratorCallback) { + async.eachSeries(ldapGroups, async function (ldapGroup) { var groupName = ldapGroup[externalLdapConfig.groupnameField]; - if (!groupName) return iteratorCallback(); + if (!groupName) return; // some servers return empty array for unknown properties :-/ - if (typeof groupName !== 'string') return iteratorCallback(); + if (typeof groupName !== 'string') return; // groups are lowercase groupName = groupName.toLowerCase(); @@ -457,22 +459,17 @@ function syncGroups(externalLdapConfig, progressCallback, callback) { percent += step; progressCallback({ percent, message: `Syncing... ${groupName}` }); - groups.getByName(groupName, function (error, result) { - if (error && error.reason !== BoxError.NOT_FOUND) return iteratorCallback(error); + let [error, result] = await safe(groups.getByName(groupName)); + if (error && error.reason !== BoxError.NOT_FOUND) throw error; - if (!result) { - debug(`[adding group] groupname=${groupName}`); + if (!result) { + debug(`[adding group] groupname=${groupName}`); - groups.create(groupName, 'ldap', function (error) { - if (error) debug('syncGroups: Failed to create group', groupName, error); - iteratorCallback(); - }); - } else { - debug(`[up-to-date group] groupname=${groupName}`); - - iteratorCallback(); - } - }); + [error] = await safe(groups.add({ name: groupName, source: 'ldap' })); + if (error) debug('syncGroups: Failed to create group', groupName, error); + } else { + debug(`[up-to-date group] groupname=${groupName}`); + } }, function (error) { if (error) return callback(error); @@ -494,20 +491,22 @@ function syncGroupUsers(externalLdapConfig, progressCallback, callback) { return callback(null, []); } - groups.getAll(function (error, result) { + const getAllGroups = util.callbackify(groups.getAll); + + getAllGroups(function (error, result) { if (error) return callback(error); - var ldapGroups = result.filter(function (g) { return g.source === 'ldap'; }); + const ldapGroups = result.filter(function (g) { return g.source === 'ldap'; }); debug(`Found ${ldapGroups.length} groups to sync users`); - async.eachSeries(ldapGroups, function (group, iteratorCallback) { + async.eachSeries(ldapGroups, function (group, iteratorDone) { debug(`Sync users for group ${group.name}`); ldapGroupSearch(externalLdapConfig, {}, function (error, result) { - if (error) return callback(error); + if (error) return iteratorDone(error); if (!result || result.length === 0) { debug(`syncGroupUsers: Unable to find group ${group.name} ignoring for now.`); - return callback(); + return iteratorDone(); } // since our group names are lowercase we cannot use potentially case matching ldap filters @@ -518,7 +517,7 @@ function syncGroupUsers(externalLdapConfig, progressCallback, callback) { if (!found) { debug(`syncGroupUsers: Unable to find group ${group.name} ignoring for now.`); - return callback(); + return iteratorDone(); } var ldapGroupMembers = found.member || found.uniqueMember || []; @@ -540,21 +539,20 @@ function syncGroupUsers(externalLdapConfig, progressCallback, callback) { const username = result[externalLdapConfig.usernameField]; if (!username) return iteratorCallback(); - users.getByUsername(username, function (error, result) { + users.getByUsername(username, async function (error, result) { if (error) { debug(`syncGroupUsers: Failed to get user by username ${username}`, error); return iteratorCallback(); } - groups.addMember(group.id, result.id, function (error) { - if (error && error.reason !== BoxError.ALREADY_EXISTS) debug('syncGroupUsers: Failed to add member', error); - iteratorCallback(); - }); + [error] = await safe(groups.addMember(group.id, result.id)); + if (error && error.reason !== BoxError.ALREADY_EXISTS) debug('syncGroupUsers: Failed to add member', error); + iteratorCallback(); }); }); }, function (error) { if (error) debug('syncGroupUsers: ', error); - iteratorCallback(); + iteratorDone(); }); }); }, callback); diff --git a/src/groupdb.js b/src/groupdb.js deleted file mode 100644 index c0ef0bfb8..000000000 --- a/src/groupdb.js +++ /dev/null @@ -1,275 +0,0 @@ -'use strict'; - -exports = module.exports = { - get, - getByName, - getWithMembers, - getAll, - getAllWithMembers, - add, - update, - del, - count, - - getMembers, - addMember, - removeMember, - setMembers, - isMember, - - getMembership, - setMembership, - - _clear: clear -}; - -var assert = require('assert'), - BoxError = require('./boxerror.js'), - database = require('./database.js'); - -var GROUPS_FIELDS = [ 'id', 'name', 'source' ].join(','); - -function get(groupId, callback) { - assert.strictEqual(typeof groupId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT ' + GROUPS_FIELDS + ' FROM userGroups WHERE id = ? ORDER BY name', [ groupId ], function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - - callback(null, result[0]); - }); -} - -function getByName(name, callback) { - assert.strictEqual(typeof name, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT ' + GROUPS_FIELDS + ' FROM userGroups WHERE name = ?', [ name ], function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - - callback(null, result[0]); - }); -} - -function getWithMembers(groupId, callback) { - assert.strictEqual(typeof groupId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT ' + GROUPS_FIELDS + ',GROUP_CONCAT(groupMembers.userId) AS userIds ' + - ' FROM userGroups LEFT OUTER JOIN groupMembers ON userGroups.id = groupMembers.groupId ' + - ' WHERE userGroups.id = ? ' + - ' GROUP BY userGroups.id', [ groupId ], function (error, results) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - - var result = results[0]; - result.userIds = result.userIds ? result.userIds.split(',') : [ ]; - - callback(null, result); - }); -} - -function getAll(callback) { - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT ' + GROUPS_FIELDS + ' FROM userGroups ORDER BY name', function (error, results) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null, results); - }); -} - -function getAllWithMembers(callback) { - database.query('SELECT ' + GROUPS_FIELDS + ',GROUP_CONCAT(groupMembers.userId) AS userIds ' + - ' FROM userGroups LEFT OUTER JOIN groupMembers ON userGroups.id = groupMembers.groupId ' + - ' GROUP BY userGroups.id ORDER BY name', function (error, results) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - results.forEach(function (result) { result.userIds = result.userIds ? result.userIds.split(',') : [ ]; }); - - callback(null, results); - }); -} - -function add(id, name, source, callback) { - assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof name, 'string'); - assert.strictEqual(typeof source, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('INSERT INTO userGroups (id, name, source) VALUES (?, ?, ?)', [ id, name, source ], function (error, result) { - if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, error)); - if (error || result.affectedRows !== 1) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null); - }); -} - -function update(id, data, callback) { - assert.strictEqual(typeof id, 'string'); - assert(data && typeof data === 'object'); - assert.strictEqual(typeof callback, 'function'); - - var args = [ ]; - var fields = [ ]; - for (var k in data) { - if (k === 'name') { - assert.strictEqual(typeof data.name, 'string'); - fields.push(k + ' = ?'); - args.push(data.name); - } - } - args.push(id); - - database.query('UPDATE userGroups SET ' + fields.join(', ') + ' WHERE id = ?', args, function (error, result) { - if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('userGroups_name') !== -1) return callback(new BoxError(BoxError.ALREADY_EXISTS, 'name already exists')); - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - - return callback(null); - }); -} - -function del(id, callback) { - assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof callback, 'function'); - - // also cleanup the groupMembers table - var queries = []; - queries.push({ query: 'DELETE FROM groupMembers WHERE groupId = ?', args: [ id ] }); - queries.push({ query: 'DELETE FROM userGroups WHERE id = ?', args: [ id ] }); - - database.transaction(queries, function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result[1].affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - - callback(error); - }); -} - -function count(callback) { - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT COUNT(*) AS total FROM userGroups', function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - return callback(null, result[0].total); - }); -} - -function clear(callback) { - database.query('DELETE FROM groupMembers', function (error) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - database.query('DELETE FROM userGroups', function (error) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(error); - }); - }); -} - -function getMembers(groupId, callback) { - assert.strictEqual(typeof groupId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT userId FROM groupMembers WHERE groupId=?', [ groupId ], function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - // if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); // need to differentiate group with no members and invalid groupId - - callback(error, result.map(function (r) { return r.userId; })); - }); -} - -function setMembers(groupId, userIds, callback) { - assert.strictEqual(typeof groupId, 'string'); - assert(Array.isArray(userIds)); - assert.strictEqual(typeof callback, 'function'); - - var queries = []; - queries.push({ query: 'DELETE FROM groupMembers WHERE groupId = ?', args: [ groupId ] }); - for (var i = 0; i < userIds.length; i++) { - queries.push({ query: 'INSERT INTO groupMembers (groupId, userId) VALUES (?, ?)', args: [ groupId, userIds[i] ] }); - } - - database.transaction(queries, function (error) { - if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.CONFLICT, 'Duplicate member in list')); - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(error); - }); -} - -function getMembership(userId, callback) { - assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT groupId FROM groupMembers WHERE userId=? ORDER BY groupId', [ userId ], function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - // if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); // need to differentiate group with no members and invalid groupId - - callback(error, result.map(function (r) { return r.groupId; })); - }); -} - -function setMembership(userId, groupIds, callback) { - assert.strictEqual(typeof userId, 'string'); - assert(Array.isArray(groupIds)); - assert.strictEqual(typeof callback, 'function'); - - var queries = [ ]; - queries.push({ query: 'DELETE from groupMembers WHERE userId = ?', args: [ userId ] }); - groupIds.forEach(function (gid) { - queries.push({ query: 'INSERT INTO groupMembers (groupId, userId) VALUES (? , ?)', args: [ gid, userId ] }); - }); - - database.transaction(queries, function (error) { - if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new BoxError(BoxError.NOT_FOUND, error.message)); - if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.CONFLICT, 'Already member')); - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null); - }); -} - -function addMember(groupId, userId, callback) { - assert.strictEqual(typeof groupId, 'string'); - assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('INSERT INTO groupMembers (groupId, userId) VALUES (?, ?)', [ groupId, userId ], function (error, result) { - if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, error)); - if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - if (error || result.affectedRows !== 1) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null); - }); -} - -function removeMember(groupId, userId, callback) { - assert.strictEqual(typeof groupId, 'string'); - assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('DELETE FROM groupMembers WHERE groupId = ? AND userId = ?', [ groupId, userId ], function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Group not found')); - - callback(null); - }); -} - -function isMember(groupId, userId, callback) { - assert.strictEqual(typeof groupId, 'string'); - assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT 1 FROM groupMembers WHERE groupId=? AND userId=?', [ groupId, userId ], function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null, result.length !== 0); - }); -} diff --git a/src/groups.js b/src/groups.js index c0305908a..b6a86648c 100644 --- a/src/groups.js +++ b/src/groups.js @@ -1,7 +1,7 @@ 'use strict'; exports = module.exports = { - create, + add, remove, get, getByName, @@ -18,16 +18,16 @@ exports = module.exports = { setMembership, getMembership, - - count }; -var assert = require('assert'), +const assert = require('assert'), BoxError = require('./boxerror.js'), constants = require('./constants.js'), - groupdb = require('./groupdb.js'), - uuid = require('uuid'), - _ = require('underscore'); + database = require('./database.js'), + safe = require('safetydance'), + uuid = require('uuid'); + +const GROUPS_FIELDS = [ 'id', 'name', 'source' ].join(','); // keep this in sync with validateUsername function validateGroupname(name) { @@ -52,199 +52,189 @@ function validateGroupSource(source) { return null; } -function create(name, source, callback) { - assert.strictEqual(typeof name, 'string'); - assert.strictEqual(typeof source, 'string'); - assert.strictEqual(typeof callback, 'function'); +async function add(group) { + assert.strictEqual(typeof group, 'object'); - // we store names in lowercase - name = name.toLowerCase(); + let { name, source } = group; - var error = validateGroupname(name); - if (error) return callback(error); + name = name.toLowerCase(); // we store names in lowercase + source = source || ''; + + let error = validateGroupname(name); + if (error) throw error; error = validateGroupSource(source); - if (error) return callback(error); + if (error) throw error; - var id = 'gid-' + uuid.v4(); - groupdb.add(id, name, source, function (error) { - if (error) return callback(error); + const id = `gid-${uuid.v4()}`; - callback(null, { id: id, name: name }); - }); + [error] = await safe(database.query('INSERT INTO userGroups (id, name, source) VALUES (?, ?, ?)', [ id, name, source ])); + if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, error); + if (error) throw error; + + return { id, name }; } -function remove(id, callback) { +async function remove(id) { assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.del(id, function (error) { - if (error) return callback(error); + // also cleanup the groupMembers table + let queries = []; + queries.push({ query: 'DELETE FROM groupMembers WHERE groupId = ?', args: [ id ] }); + queries.push({ query: 'DELETE FROM userGroups WHERE id = ?', args: [ id ] }); - callback(null); - }); + const result = await database.transaction(queries); + if (result[1].affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Group not found'); } -function get(id, callback) { +async function get(id) { assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.get(id, function (error, result) { - if (error) return callback(error); + const result = await database.query(`SELECT ${GROUPS_FIELDS} FROM userGroups WHERE id = ? ORDER BY name`, [ id ]); + if (result.length === 0) return null; - return callback(null, result); - }); + return result[0]; } -function getByName(name, callback) { +async function getByName(name) { assert.strictEqual(typeof name, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.getByName(name, function (error, result) { - if (error) return callback(error); + const result = await database.query(`SELECT ${GROUPS_FIELDS} FROM userGroups WHERE name = ?`, [ name ]); + if (result.length === 0) return null; - return callback(null, result); - }); + return result[0]; } -function getWithMembers(id, callback) { +async function getWithMembers(id) { assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.getWithMembers(id, function (error, result) { - if (error) return callback(error); + const results = await database.query('SELECT ' + GROUPS_FIELDS + ',GROUP_CONCAT(groupMembers.userId) AS userIds ' + + ' FROM userGroups LEFT OUTER JOIN groupMembers ON userGroups.id = groupMembers.groupId ' + + ' WHERE userGroups.id = ? ' + + ' GROUP BY userGroups.id', [ id ]); - return callback(null, result); - }); + if (results.length === 0) return null; + + const result = results[0]; + result.userIds = result.userIds ? result.userIds.split(',') : [ ]; + + return result; } -function getAll(callback) { - assert.strictEqual(typeof callback, 'function'); - - groupdb.getAll(function (error, result) { - if (error) return callback(error); - - return callback(null, result); - }); +async function getAll() { + const results = await database.query('SELECT ' + GROUPS_FIELDS + ' FROM userGroups ORDER BY name'); + return results; } -function getAllWithMembers(callback) { - assert.strictEqual(typeof callback, 'function'); +async function getAllWithMembers() { + const results = await database.query('SELECT ' + GROUPS_FIELDS + ',GROUP_CONCAT(groupMembers.userId) AS userIds ' + + ' FROM userGroups LEFT OUTER JOIN groupMembers ON userGroups.id = groupMembers.groupId ' + + ' GROUP BY userGroups.id ORDER BY name'); - groupdb.getAllWithMembers(function (error, result) { - if (error) return callback(error); - - return callback(null, result); - }); + results.forEach(function (result) { result.userIds = result.userIds ? result.userIds.split(',') : [ ]; }); + return results; } -function getMembers(groupId, callback) { +async function getMembers(groupId) { assert.strictEqual(typeof groupId, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.getMembers(groupId, function (error, result) { - if (error) return callback(error); + const result = await database.query('SELECT userId FROM groupMembers WHERE groupId=?', [ groupId ]); - return callback(null, result); - }); + return result.map(function (r) { return r.userId; }); } -function getMembership(userId, callback) { +async function getMembership(userId) { assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.getMembership(userId, function (error, result) { - if (error) return callback(error); + const result = await database.query('SELECT groupId FROM groupMembers WHERE userId=? ORDER BY groupId', [ userId ]); - return callback(null, result); - }); + return result.map(function (r) { return r.groupId; }); } -function setMembership(userId, groupIds, callback) { +async function setMembership(userId, groupIds) { assert.strictEqual(typeof userId, 'string'); assert(Array.isArray(groupIds)); - assert.strictEqual(typeof callback, 'function'); - groupdb.setMembership(userId, groupIds, function (error) { - if (error) return callback(error); - - return callback(null); + let queries = [ ]; + queries.push({ query: 'DELETE from groupMembers WHERE userId = ?', args: [ userId ] }); + groupIds.forEach(function (gid) { + queries.push({ query: 'INSERT INTO groupMembers (groupId, userId) VALUES (? , ?)', args: [ gid, userId ] }); }); + + const [error] = await safe(database.transaction(queries)); + if (error && error.code === 'ER_NO_REFERENCED_ROW_2') throw new BoxError(BoxError.NOT_FOUND, error.message); + if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.CONFLICT, 'Already member'); + if (error) throw error; } -function addMember(groupId, userId, callback) { +async function addMember(groupId, userId) { assert.strictEqual(typeof groupId, 'string'); assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.addMember(groupId, userId, function (error) { - if (error) return callback(error); - - return callback(null); - }); + const [error] = await safe(database.query('INSERT INTO groupMembers (groupId, userId) VALUES (?, ?)', [ groupId, userId ])); + if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, error); + if (error && error.code === 'ER_NO_REFERENCED_ROW_2' && error.sqlMessage.includes('userId')) throw new BoxError(BoxError.NOT_FOUND, 'User not found'); + if (error && error.code === 'ER_NO_REFERENCED_ROW_2') throw new BoxError(BoxError.NOT_FOUND, 'Group not found'); + if (error) throw error; } -function setMembers(groupId, userIds, callback) { +async function setMembers(groupId, userIds) { assert.strictEqual(typeof groupId, 'string'); assert(Array.isArray(userIds)); - assert.strictEqual(typeof callback, 'function'); - groupdb.setMembers(groupId, userIds, function (error) { - if (error) return callback(error); + let queries = []; + queries.push({ query: 'DELETE FROM groupMembers WHERE groupId = ?', args: [ groupId ] }); + for (let i = 0; i < userIds.length; i++) { + queries.push({ query: 'INSERT INTO groupMembers (groupId, userId) VALUES (?, ?)', args: [ groupId, userIds[i] ] }); + } - return callback(null); - }); + const [error] = await safe(database.transaction(queries)); + if (error && error.code === 'ER_NO_REFERENCED_ROW_2') throw new BoxError(BoxError.NOT_FOUND, 'Group not found'); + if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.CONFLICT, 'Duplicate member in list'); + if (error) throw error; } -function removeMember(groupId, userId, callback) { +async function removeMember(groupId, userId) { assert.strictEqual(typeof groupId, 'string'); assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.removeMember(groupId, userId, function (error) { - if (error) return callback(error); - - return callback(null); - }); + const result = await database.query('DELETE FROM groupMembers WHERE groupId = ? AND userId = ?', [ groupId, userId ]); + if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Group not found'); } -function isMember(groupId, userId, callback) { +async function isMember(groupId, userId) { assert.strictEqual(typeof groupId, 'string'); assert.strictEqual(typeof userId, 'string'); - assert.strictEqual(typeof callback, 'function'); - groupdb.isMember(groupId, userId, function (error, result) { - if (error) return callback(error); - - return callback(null, result); - }); + const result = await database.query('SELECT 1 FROM groupMembers WHERE groupId=? AND userId=?', [ groupId, userId ]); + return result.length !== 0; } -function update(groupId, data, callback) { - assert.strictEqual(typeof groupId, 'string'); +async function update(id, data) { + assert.strictEqual(typeof id, 'string'); assert(data && typeof data === 'object'); - assert.strictEqual(typeof callback, 'function'); let error; if ('name' in data) { assert.strictEqual(typeof data.name, 'string'); error = validateGroupname(data.name); - if (error) return callback(error); + if (error) throw error; } - groupdb.update(groupId, _.pick(data, 'name'), function (error) { - if (error) return callback(error); + let args = [ ]; + let fields = [ ]; + for (let k in data) { + if (k === 'name') { + assert.strictEqual(typeof data.name, 'string'); + fields.push(k + ' = ?'); + args.push(data.name); + } + } + args.push(id); - callback(null); - }); -} - -function count(callback) { - assert.strictEqual(typeof callback, 'function'); - - groupdb.count(function (error, count) { - if (error) return callback(error); - - callback(null, count); - }); + let result; + [error, result] = safe(database.query('UPDATE userGroups SET ' + fields.join(', ') + ' WHERE id = ?', args)); + if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('userGroups_name') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'name already exists'); + if (error) throw error; + if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Group not found'); } diff --git a/src/ldap.js b/src/ldap.js index a10f243c4..ca374d395 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -502,27 +502,26 @@ function authorizeUserForApp(req, res, next) { }); } -function verifyMailboxPassword(mailbox, password, callback) { +async function verifyMailboxPassword(mailbox, password, callback) { assert.strictEqual(typeof mailbox, 'object'); assert.strictEqual(typeof password, 'string'); assert.strictEqual(typeof callback, 'function'); if (mailbox.ownerType === mail.OWNERTYPE_USER) return users.verify(mailbox.ownerId, password, users.AP_MAIL /* identifier */, callback); - groups.getMembers(mailbox.ownerId, function (error, userIds) { - if (error) return callback(error); + const [error, userIds] = await safe(groups.getMembers(mailbox.ownerId)); + if (error) return callback(error); - let verifiedUser = null; - async.someSeries(userIds, function iterator(userId, iteratorDone) { - users.verify(userId, password, users.AP_MAIL /* identifier */, function (error, result) { - if (error) return iteratorDone(null, false); - verifiedUser = result; - iteratorDone(null, true); - }); - }, function (error, result) { - if (!result) return callback(new BoxError(BoxError.INVALID_CREDENTIALS)); - callback(null, verifiedUser); + let verifiedUser = null; + async.someSeries(userIds, function iterator(userId, iteratorDone) { + users.verify(userId, password, users.AP_MAIL /* identifier */, function (error, result) { + if (error) return iteratorDone(null, false); + verifiedUser = result; + iteratorDone(null, true); }); + }, function (error, result) { + if (!result) return callback(new BoxError(BoxError.INVALID_CREDENTIALS)); + callback(null, verifiedUser); }); } diff --git a/src/routes/groups.js b/src/routes/groups.js index 639cff733..f5da69fca 100644 --- a/src/routes/groups.js +++ b/src/routes/groups.js @@ -3,88 +3,77 @@ exports = module.exports = { get, list, - create, + add, update, remove, updateMembers }; -var assert = require('assert'), +const assert = require('assert'), BoxError = require('../boxerror.js'), groups = require('../groups.js'), HttpError = require('connect-lastmile').HttpError, - HttpSuccess = require('connect-lastmile').HttpSuccess; + HttpSuccess = require('connect-lastmile').HttpSuccess, + safe = require('safetydance'); -function create(req, res, next) { +async function add(req, res, next) { assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be string')); - var source = ''; // means local + const [error, group] = await safe(groups.add({ name: req.body.name, source : '' })); + if (error) return next(BoxError.toHttpError(error)); - groups.create(req.body.name, source, function (error, group) { - if (error) return next(BoxError.toHttpError(error)); - - var groupInfo = { - id: group.id, - name: group.name - }; - - next(new HttpSuccess(201, groupInfo)); - }); + next(new HttpSuccess(201, { id: group.id, name: group.name })); } -function get(req, res, next) { +async function get(req, res, next) { assert.strictEqual(typeof req.params.groupId, 'string'); - groups.getWithMembers(req.params.groupId, function (error, result) { - if (error) return next(BoxError.toHttpError(error)); + const [error, result] = await safe(groups.getWithMembers(req.params.groupId)); + if (error) return next(BoxError.toHttpError(error)); + if (!result) return next(new HttpError(404, 'Group not found')); - next(new HttpSuccess(200, result)); - }); + next(new HttpSuccess(200, result)); } -function update(req, res, next) { +async function update(req, res, next) { assert.strictEqual(typeof req.params.groupId, 'string'); assert.strictEqual(typeof req.body, 'object'); if ('name' in req.body && typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string')); - groups.update(req.params.groupId, req.body, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(groups.update(req.params.groupId, req.body)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(200, { })); - }); + next(new HttpSuccess(200, { })); } -function updateMembers(req, res, next) { +async function updateMembers(req, res, next) { assert.strictEqual(typeof req.params.groupId, 'string'); if (!req.body.userIds) return next(new HttpError(404, 'missing or invalid userIds fields')); if (!Array.isArray(req.body.userIds)) return next(new HttpError(404, 'userIds must be an array')); if (req.body.userIds.some((u) => typeof u !== 'string')) return next(new HttpError(400, 'userIds array must contain strings')); - groups.setMembers(req.params.groupId, req.body.userIds, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(groups.setMembers(req.params.groupId, req.body.userIds)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(200, { })); - }); + next(new HttpSuccess(200, { })); } -function list(req, res, next) { - groups.getAllWithMembers(function (error, result) { - if (error) return next(BoxError.toHttpError(error)); +async function list(req, res, next) { + const [error, groups] = await safe(groups.getAllWithMembers()); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(200, { groups: result })); - }); + next(new HttpSuccess(200, { groups })); } -function remove(req, res, next) { +async function remove(req, res, next) { assert.strictEqual(typeof req.params.groupId, 'string'); - groups.remove(req.params.groupId, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(groups.remove(req.params.groupId)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(204)); - }); + next(new HttpSuccess(204)); } diff --git a/src/routes/users.js b/src/routes/users.js index b30c394d8..c9476baf7 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -180,18 +180,17 @@ function sendInvite(req, res, next) { }); } -function setGroups(req, res, next) { +async function setGroups(req, res, next) { assert.strictEqual(typeof req.body, 'object'); assert.strictEqual(typeof req.resource, 'object'); if (!Array.isArray(req.body.groupIds)) return next(new HttpError(400, 'API call requires a groups array.')); if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); - users.setMembership(req.resource, req.body.groupIds, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(users.setMembership(req.resource, req.body.groupIds)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(204)); - }); + next(new HttpSuccess(204)); } function changePassword(req, res, next) { diff --git a/src/server.js b/src/server.js index e87a1cd0e..56c74b7c4 100644 --- a/src/server.js +++ b/src/server.js @@ -182,7 +182,7 @@ function initializeExpressSync() { // Group management router.get ('/api/v1/groups', token, authorizeUserManager, routes.groups.list); - router.post('/api/v1/groups', json, token, authorizeUserManager, routes.groups.create); + router.post('/api/v1/groups', json, token, authorizeUserManager, routes.groups.add); router.get ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.get); router.put ('/api/v1/groups/:groupId/members', json, token, authorizeUserManager, routes.groups.updateMembers); router.post('/api/v1/groups/:groupId', json, token, authorizeUserManager, routes.groups.update); diff --git a/src/test/apps-test.js b/src/test/apps-test.js index 0b949efdb..9e04d7223 100644 --- a/src/test/apps-test.js +++ b/src/test/apps-test.js @@ -13,8 +13,6 @@ const appdb = require('../appdb.js'), database = require('../database.js'), domains = require('../domains.js'), expect = require('expect.js'), - groupdb = require('../groupdb.js'), - groups = require('../groups.js'), hat = require('../hat.js'), provision = require('../provision.js'), userdb = require('../userdb.js'); @@ -182,9 +180,6 @@ describe('Apps', function () { userdb.add.bind(null, ADMIN_0.id, ADMIN_0), userdb.add.bind(null, USER_0.id, USER_0), userdb.add.bind(null, USER_1.id, USER_1), - groupdb.add.bind(null, GROUP_0.id, GROUP_0.name, GROUP_0.source), - groupdb.add.bind(null, GROUP_1.id, GROUP_1.name, GROUP_1.source), - groups.addMember.bind(null, GROUP_0.id, USER_1.id), appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0), appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, apps._translatePortBindings(APP_1.portBindings, APP_1.manifest), APP_1), appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, apps._translatePortBindings(APP_2.portBindings, APP_2.manifest), APP_2), diff --git a/src/test/common.js b/src/test/common.js index d9813aaa5..171c71a25 100644 --- a/src/test/common.js +++ b/src/test/common.js @@ -7,12 +7,14 @@ const appdb = require('../appdb.js'), database = require('../database.js'), domains = require('../domains.js'), fs = require('fs'), + hat = require('../hat.js'), mailer = require('../mailer.js'), nock = require('nock'), path = require('path'), rimraf = require('rimraf'), settings = require('../settings.js'), settingsdb = require('../settingsdb.js'), + userdb = require('../userdb.js'), users = require('../users.js'); const MANIFEST = { @@ -56,7 +58,7 @@ const DOMAIN = { const AUDIT_SOURCE = { ip: '1.2.3.4' }; const ADMIN = { - id: 'admin123', + id: null, username: 'admin123', password: 'secret123', email: 'admin@me.com', @@ -69,6 +71,21 @@ const ADMIN = { source: '' }; +const USER = { + id: 'userid', + username: 'uuid213', + password: 'secret', + email: 'safe@me.com', + fallbackEmail: 'safefallback@me.com', + role: 'user', + salt: 'morton', + createdAt: 'sometime back', + resetToken: hat(256), + displayName: '', + source: '', + permissions: null +}; + const APP = { id: 'appid', appStoreId: 'appStoreId', @@ -101,6 +118,7 @@ exports = module.exports = { AUDIT_SOURCE, DOMAIN, MANIFEST, + USER, APPSTORE_TOKEN: 'atoken' }; @@ -146,6 +164,7 @@ function setup(done) { }, appdb.add.bind(null, APP.id, APP.appStoreId, APP.manifest, APP.location, APP.domain, APP.portBindings, APP), settingsdb.set.bind(null, settings.CLOUDRON_TOKEN_KEY, exports.APPSTORE_TOKEN), // appstore token + userdb.add.bind(null, USER.id, USER), ], done); } diff --git a/src/test/database-test.js b/src/test/database-test.js index 20db381ee..cfbcd5b21 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -14,7 +14,6 @@ const appdb = require('../appdb.js'), database = require('../database'), domaindb = require('../domaindb'), expect = require('expect.js'), - groupdb = require('../groupdb.js'), hat = require('../hat.js'), mailboxdb = require('../mailboxdb.js'), maildb = require('../maildb.js'), @@ -487,28 +486,6 @@ describe('database', function () { userdb.update(USER_0.id, { email: null }, function () {}); }).to.throwError(); }); - - it('cannot del non-existing user', function (done) { - userdb.del(USER_0.id + USER_0.id, function (error) { - expect(error).to.be.ok(); - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); - }); - - it('can del existing user', function (done) { - userdb.del(USER_0.id, function (error) { - expect(error).to.not.be.ok(); - done(); - }); - }); - - it('did remove the user', function (done) { - userdb.count(function (error, count) { - expect(count).to.equal(2); - done(); - }); - }); }); describe('apps', function () { @@ -592,6 +569,7 @@ describe('database', function () { before(function (done) { async.series([ + database._clear, userdb.add.bind(null, USER_0.id, USER_0), domaindb.add.bind(null, DOMAIN_0.domain, DOMAIN_0) ], done); @@ -1108,139 +1086,6 @@ describe('database', function () { }); - describe('groups', function () { - before(function (done) { - async.series([ - database.initialize, - database._clear, - userdb.add.bind(null, USER_0.id, USER_0), - userdb.add.bind(null, USER_1.id, USER_1), - userdb.add.bind(null, USER_2.id, USER_2) - ], done); - }); - - var GROUP_ID_1 = 'foundersid'; - - it('can create a group', function (done) { - groupdb.add(GROUP_ID_1, 'founders', 'ldap', function (error) { - expect(error).to.be(null); - done(); - }); - }); - - it('can get existing group', function (done) { - groupdb.get(GROUP_ID_1, function (error, result) { - expect(error).to.be(null); - expect(result.name).to.be('founders'); - done(); - }); - }); - - it('can add member to the group', function (done) { - groupdb.addMember(GROUP_ID_1, USER_0.id, function (error) { - expect(error).to.be(null); - done(); - }); - }); - - it('cannot add invalid user to group', function (done) { - groupdb.addMember(GROUP_ID_1, 'random', function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); - }); - - it('can set members', function (done) { - groupdb.setMembers(GROUP_ID_1, [ USER_1.id, USER_2.id ], function (error) { - expect(error).to.be(null); - done(); - }); - }); - - it('can list users of group', function (done) { - groupdb.getMembers(GROUP_ID_1, function (error, result) { - expect(error).to.be(null); - expect(result).to.eql([ USER_1.id, USER_2.id ]); - done(); - }); - }); - - it('cannot delete non-existent member', function (done) { - groupdb.removeMember(GROUP_ID_1, 'random', function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); - }); - - it('can remove existing member', function (done) { - groupdb.removeMember(GROUP_ID_1, USER_1.id, function (error) { - expect(error).to.be(null); - done(); - }); - }); - - it('can getWithMembers', function (done) { - groupdb.getWithMembers(GROUP_ID_1, function (error, result) { - expect(error).to.be(null); - expect(result.name).to.be('founders'); - expect(result.userIds).to.eql([ USER_2.id ]); - done(); - }); - }); - - it('can create more groups for list order test', function (done) { - groupdb.add(GROUP_ID_1 + 1, 'aaaa', 'ldap', function (error) { - expect(error).to.be(null); - - groupdb.add(GROUP_ID_1 + 2, 'zzzz', 'ldap', function (error) { - expect(error).to.be(null); - - done(); - }); - }); - }); - - it('can getAll', function (done) { - groupdb.getAll(function (error, result) { - expect(error).to.be(null); - expect(result.length).to.be(3); - expect(result[0].name).to.be('aaaa'); - expect(result[1].name).to.be('founders'); - expect(result[2].name).to.be('zzzz'); - done(); - }); - }); - - it('can getAllWithMembers', function (done) { - groupdb.getAllWithMembers(function (error, result) { - expect(error).to.be(null); - expect(result.length).to.be(3); - - expect(result[0].name).to.be('aaaa'); - expect(result[1].name).to.be('founders'); - expect(result[1].userIds).to.eql([ USER_2.id ]); - expect(result[2].name).to.be('zzzz'); - - done(); - }); - }); - - it('can set groups', function (done) { - groupdb.setMembership(USER_0.id, [ GROUP_ID_1 ], function (error) { - expect(error).to.be(null); - done(); - }); - }); - - it('can get groups', function (done) { - groupdb.getMembership(USER_0.id, function (error, result) { - expect(error).to.be(null); - expect(result).to.eql([ GROUP_ID_1 ]); - done(); - }); - }); - }); - describe('importFromFile', function () { before(function (done) { async.series([ diff --git a/src/test/externalldap-test.js b/src/test/externalldap-test.js index c3edbf857..666fa84c7 100644 --- a/src/test/externalldap-test.js +++ b/src/test/externalldap-test.js @@ -5,23 +5,21 @@ 'use strict'; -var async = require('async'), +const async = require('async'), BoxError = require('../boxerror.js'), database = require('../database.js'), constants = require('../constants.js'), expect = require('expect.js'), externalldap = require('../externalldap.js'), - groupdb = require('../groupdb.js'), groups = require('../groups.js'), domains = require('../domains.js'), ldap = require('ldapjs'), - mailboxdb = require('../mailboxdb.js'), mailer = require('../mailer.js'), server = require('../server.js'), settings = require('../settings.js'), superagent = require('superagent'), - userdb = require('../userdb.js'), users = require('../users.js'), + util = require('util'), _ = require('underscore'); var USERNAME = 'noBody'; @@ -65,9 +63,7 @@ function cleanupUsers(done) { mailer._mailQueue = []; async.series([ - groupdb._clear, - userdb._clear, - mailboxdb._clear, + database._clear, ], done); } @@ -513,15 +509,13 @@ describe('External LDAP', function () { groupname: 'extGroup1' }); - externalldap.sync(function progress() {}, function (error) { + externalldap.sync(function progress() {}, async function (error) { expect(error).to.equal(null); - groups.getAll(function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(0); + const result = await groups.getAll(); + expect(result.length).to.equal(0); - done(); - }); + done(); }); }); @@ -539,15 +533,13 @@ describe('External LDAP', function () { it('succeeds with groups enabled', function (done) { gLdapGroups = []; - externalldap.sync(function progress() {}, function (error) { + externalldap.sync(function progress() {}, async function (error) { expect(error).to.equal(null); - groups.getAll(function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(0); + const result = await groups.getAll(); + expect(result.length).to.equal(0); - done(); - }); + done(); }); }); @@ -556,15 +548,13 @@ describe('External LDAP', function () { groupname: 'extGroup1' }); - externalldap.sync(function progress() {}, function (error) { + externalldap.sync(function progress() {}, async function (error) { expect(error).to.equal(null); - groups.getAll(function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(1); + const result = await groups.getAll(); + expect(result.length).to.equal(1); - done(); - }); + done(); }); }); @@ -573,37 +563,28 @@ describe('External LDAP', function () { groupname: 'extGroup2' }); - externalldap.sync(function progress() {}, function (error) { + externalldap.sync(function progress() {}, async function (error) { expect(error).to.equal(null); - groups.getAll(function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(2); + const result = await groups.getAll(); + expect(result.length).to.equal(2); - done(); - }); + done(); }); }); - it('does not create already existing group', function (done) { + it('does not create already existing group', async function () { gLdapGroups.push({ groupname: 'INTERNALgroup' // also tests lowercasing }); - groups.create('internalgroup', '', function (error) { - expect(error).to.equal(null); + const externalldapSync = util.promisify(externalldap.sync); - externalldap.sync(function progress() {}, function (error) { - expect(error).to.equal(null); + await groups.add({ name: 'internalgroup' }); + await externalldapSync(function progress() {}); - groups.getAll(function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(3); - - done(); - }); - }); - }); + const result = await groups.getAll(); + expect(result.length).to.equal(3); }); it('adds users of groups', function (done) { @@ -612,19 +593,15 @@ describe('External LDAP', function () { member: gLdapUsers.slice(-2).map(function (u) { return `cn=${u.username},${LDAP_CONFIG.baseDn}`; }) }); - externalldap.sync(function progress() {}, function (error) { + externalldap.sync(function progress() {}, async function (error) { expect(error).to.equal(null); - groups.getByName('nonemptygroup', function (error, result) { - expect(error).to.equal(null); + const result = await groups.getByName('nonemptygroup'); + expect(result).to.be.ok(); - groups.getMembers(result.id, function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(2); - - done(); - }); - }); + const result2 = await groups.getMembers(result.id); + expect(result2.length).to.equal(2); + done(); }); }); @@ -634,19 +611,16 @@ describe('External LDAP', function () { member: gLdapUsers.map(function (u) { return `cn=${u.username},${LDAP_CONFIG.baseDn}`; }) // has 2 entries }); - externalldap.sync(function progress() {}, function (error) { + externalldap.sync(function progress() {}, async function (error) { expect(error).to.equal(null); - groups.getByName('nonemptygroup', function (error, result) { - expect(error).to.equal(null); + const result = await groups.getByName('nonemptygroup'); + expect(result).to.be.ok(); - groups.getMembers(result.id, function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(2); + const result2 = await groups.getMembers(result.id); + expect(result2.length).to.equal(2); - done(); - }); - }); + done(); }); }); @@ -656,23 +630,18 @@ describe('External LDAP', function () { member: `cn=${gLdapUsers[0].username},${LDAP_CONFIG.baseDn}` }); - externalldap.sync(function progress() {}, function (error) { + externalldap.sync(function progress() {}, async function (error) { expect(error).to.equal(null); - groups.getByName('onemembergroup', function (error, result) { + const result = await groups.getByName('onemembergroup'); + const result2 = await groups.getMembers(result.id); + expect(result2.length).to.equal(1); + + users.get(result2[0], function (error, result) { expect(error).to.equal(null); + expect(result.username).to.equal(gLdapUsers[0].username); - groups.getMembers(result.id, function (error, result) { - expect(error).to.equal(null); - expect(result.length).to.equal(1); - - users.get(result[0], function (error, result) { - expect(error).to.equal(null); - expect(result.username).to.equal(gLdapUsers[0].username); - - done(); - }); - }); + done(); }); }); }); diff --git a/src/test/groups-test.js b/src/test/groups-test.js index 490c66c7a..d82d3b0b2 100644 --- a/src/test/groups-test.js +++ b/src/test/groups-test.js @@ -6,384 +6,194 @@ 'use strict'; -var async = require('async'), - BoxError = require('../boxerror.js'), - database = require('../database.js'), +const BoxError = require('../boxerror.js'), + common = require('./common.js'), expect = require('expect.js'), groups = require('../groups.js'), - hat = require('../hat.js'), - mailboxdb = require('../mailboxdb.js'), - userdb = require('../userdb.js'); - -var GROUP0_NAME = 'administrators', - group0Object; - -var GROUP1_NAME = 'externs', - group1Object; - -const DOMAIN_0 = { - domain: 'example.com', - zoneName: 'example.com', - config: { provider: 'manual' }, - wellKnown: null -}; - -var USER_0 = { - id: 'uuid213', - username: 'uuid213', - password: 'secret', - email: 'safe@me.com', - fallbackEmail: 'safefallback@me.com', - role: 'user', - salt: 'morton', - createdAt: 'sometime back', - resetToken: hat(256), - displayName: '', - source: '', - permissions: null -}; - -var USER_1 = { // this user has not signed up yet - id: 'uuid222', - username: null, - password: '', - email: 'safe2@me.com', - fallbackEmail: 'safe2fallback@me.com', - role: 'user', - salt: 'morton', - createdAt: 'sometime back', - resetToken: hat(256), - displayName: '', - source: '', - permissions: null -}; - -function setup(done) { - // ensure data/config/mount paths - async.series([ - database.initialize, - database._clear - ], done); -} - -function cleanup(done) { - async.series([ - database._clear, - database.uninitialize - ], done); -} + safe = require('safetydance'); describe('Groups', function () { + const { setup, cleanup, ADMIN, USER } = common; + before(setup); after(cleanup); - it('cannot create group - too small', function (done) { - groups.create('', '', function (error) { - expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); - }); - }); + describe('add/get/del', function () { + let group0Name = 'administrators', group0Object; + let group1Name = 'externs', group1Object; - it('cannot create group - too big', function (done) { - groups.create(new Array(256).join('a'), '', function (error) { + it('cannot add group - too small', async function () { + const [error] = await safe(groups.add({ name: '' })); expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); }); - }); - it('cannot create group - bad name', function (done) { - groups.create('bad:name', '', function (error) { + it('cannot add group - too big', async function () { + const [error] = await safe(groups.add({ name: new Array(256).join('a') })); expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); }); - }); - it('cannot create group - reserved', function (done) { - groups.create('users', '', function (error) { + it('cannot add group - bad name', async function () { + const [error] = await safe(groups.add({ name: 'bad:name' })); expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); }); - }); - it('cannot create group - invalid', function (done) { - groups.create('cloudron+admin', '', function (error) { + it('cannot add group - reserved', async function () { + const [error] = await safe(groups.add({ name: 'users' })); expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); }); - }); - it('cannot create group - invalid source', function (done) { - groups.create('cloudron+admin', 'unknownsource', function (error) { + it('cannot add group - invalid', async function () { + const [error] = await safe(groups.add({ name: 'cloudron+admin' })); expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); }); - }); - it('can create valid group', function (done) { - groups.create(GROUP0_NAME, '', function (error, result) { + it('cannot add group - invalid source', async function () { + const [error] = await safe(groups.add({ name: 'somegroup', source: 'unknownsource' })); + expect(error.reason).to.be(BoxError.BAD_FIELD); + }); + + it('can add valid groups', async function () { + let [error, result] = await safe(groups.add({ name: group0Name })); expect(error).to.be(null); group0Object = result; - done(); - }); - }); - it('cannot create existing group with mixed case', function (done) { - var name = GROUP0_NAME[0].toUpperCase() + GROUP0_NAME.substr(1); - groups.create(name, '', function (error) { + [error, result] = await safe(groups.add({ name: group1Name })); + expect(error).to.be(null); + group1Object = result; + }); + + it('cannot add existing group with mixed case', async function () { + const name = group0Name[0].toUpperCase() + group0Name.substr(1); + const [error] = await safe(groups.add({ name })); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); - done(); }); - }); - it('cannot add existing group', function (done) { - groups.create(GROUP0_NAME, 'ldap', function (error) { + it('cannot add existing group', async function () { + const [error] = await safe(groups.add({name: group0Name, source: 'ldap' })); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); - done(); }); - }); - it('cannot get invalid group', function (done) { - groups.get('sometrandom', function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); + it('cannot get invalid group', async function () { + const result = await groups.get('sometrandom'); + expect(result).to.be(null); }); - }); - it('can get valid group', function (done) { - groups.get(group0Object.id, function (error, group) { - expect(error).to.be(null); - expect(group.name).to.equal(GROUP0_NAME); - done(); + it('can get valid group', async function () { + const result = await groups.get(group0Object.id); + expect(result.name).to.equal(group0Name); }); - }); - it('cannot delete invalid group', function (done) { - groups.remove('random', function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); + it('isMember returns false', async function () { + const isMember = await groups.isMember(group0Object.id, ADMIN.id); + expect(isMember).to.be(false); }); - }); - it('can delete valid group', function (done) { - groups.remove(group0Object.id, function (error) { - expect(error).to.be(null); - done(); + it('can add member to the group', async function () { + await groups.addMember(group0Object.id, ADMIN.id); }); - }); - it('did delete mailbox', function (done) { - mailboxdb.getList(GROUP0_NAME.toLowerCase(), DOMAIN_0.domain, function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); - }); -}); - -describe('Group membership', function () { - before(function (done) { - async.series([ - setup, - function (next) { - groups.create(GROUP0_NAME, '', function (error, result) { - if (error) return next(error); - group0Object = result; - next(); - }); - }, - userdb.add.bind(null, USER_0.id, USER_0), - userdb.add.bind(null, USER_1.id, USER_1) - ], done); - }); - after(cleanup); - - it('cannot add non-existent user', function (done) { - groups.addMember(group0Object.id, 'randomuser', function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); - }); - - it('cannot add non-existent group', function (done) { - groups.addMember('randomgroup', USER_0.id, function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); - }); - - it('isMember returns false', function (done) { - groups.isMember(group0Object.id, USER_0.id, function (error, member) { - expect(error).to.be(null); - expect(member).to.be(false); - done(); - }); - }); - - it('can add member', function (done) { - groups.addMember(group0Object.id, USER_0.id, function (error) { - expect(error).to.be(null); - done(); - }); - }); - - it('cannot add same member again', function (done) { - groups.addMember(group0Object.id, USER_0.id, function (error) { + it('cannot add same member to the group', async function () { + const [error] = await safe(groups.addMember(group0Object.id, ADMIN.id)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); - done(); }); - }); - it('can add member without username', function (done) { - groups.addMember(group0Object.id, USER_1.id, function (error) { - expect(error).to.be(null); - done(); + it('isMember returns true', async function () { + const isMember = await groups.isMember(group0Object.id, ADMIN.id); + expect(isMember).to.be(true); }); - }); - it('isMember returns true', function (done) { - groups.isMember(group0Object.id, USER_0.id, function (error, member) { - expect(error).to.be(null); - expect(member).to.be(true); - done(); + it('cannot add invalid user to group', async function () { + const [error] = await safe(groups.addMember(group0Object.id, 'random')); + expect(error.reason).to.be(BoxError.NOT_FOUND); }); - }); - it('can get members', function (done) { - groups.getMembers(group0Object.id, function (error, result) { - expect(error).to.be(null); - expect(result.length).to.be(2); - expect(result[0]).to.be(USER_0.id); - expect(result[1]).to.be(USER_1.id); - done(); + it('cannot add non-existent group', async function () { + const [error] = await safe(groups.addMember('randomgroup', ADMIN.id)); + expect(error.reason).to.be(BoxError.NOT_FOUND); }); - }); - it('cannot get members of non-existent group', function (done) { - groups.getMembers('randomgroup', function (error, result) { + it('can set members', async function () { + await groups.setMembers(group0Object.id, [ ADMIN.id, USER.id ]); + }); + + it('cannot set duplicate members', async function () { + const [error] = await safe(groups.setMembers(group0Object.id, [ ADMIN.id, USER.id, ADMIN.id ])); + expect(error.reason).to.be(BoxError.CONFLICT); + }); + + it('can list users of group', async function () { + const result = await groups.getMembers(group0Object.id); + expect(result).to.eql([ ADMIN.id, USER.id ]); + }); + + it('cannot list members of non-existent group', async function () { + const result = await groups.getMembers('randomgroup'); expect(result.length).to.be(0); // currently, we cannot differentiate invalid groups and empty groups - done(); }); - }); - it('cannot remove non-existent user', function (done) { - groups.removeMember(group0Object.id, 'randomuser', function (error) { + it('cannot delete non-existent member', async function () { + const [error] = await safe(groups.removeMember(group0Object.id, 'random')); expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); }); - }); - it('cannot remove non-existent group', function (done) { - groups.removeMember('randomgroup', USER_0.id, function (error) { + it('cannot remove member from non-existent group', async function () { + const [error] = await safe(groups.removeMember('randomgroup', ADMIN.id)); expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); }); - }); - it('can set members', function (done) { - groups.setMembers(group0Object.id, [ USER_0.id ], function (error) { - expect(error).to.be(null); - done(); + it('can remove existing member', async function () { + await groups.removeMember(group0Object.id, USER.id); }); - }); - it('cannot set duplicate members', function (done) { - groups.setMembers(group0Object.id, [ USER_0.id, USER_0.id ], function (error) { + it('can getWithMembers', async function () { + const result = await groups.getWithMembers(group0Object.id); + expect(result.name).to.be(group0Name); + expect(result.userIds).to.eql([ ADMIN.id ]); + }); + + it('can set groups', async function () { + await groups.setMembership(ADMIN.id, [ group0Object.id ]); + }); + + it('cannot set user to same group twice', async function () { + const [error] = await safe(groups.setMembership(ADMIN.id, [ group0Object.id, group0Object.id ])); expect(error.reason).to.be(BoxError.CONFLICT); - done(); }); - }); - it('can remove member', function (done) { - groups.removeMember(group0Object.id, USER_0.id, function (error) { - expect(error).to.be(null); - done(); + it('can set user to multiple groups', async function () { + await groups.setMembership(ADMIN.id, [ group0Object.id, group1Object.id ]); }); - }); - it('has no members', function (done) { - groups.getMembers(group0Object.id, function (error, result) { - expect(error).to.be(null); - expect(result.length).to.be(0); - done(); + it('can get groups membership', async function () { + const groupIds = await groups.getMembership(ADMIN.id); + expect(groupIds.length).to.be(2); + expect(groupIds.sort()).to.eql([ group0Object.id, group1Object.id ].sort()); }); - }); - it('can remove group with no members', function (done) { - groups.remove(group0Object.id, function (error) { - expect(error).to.be(null); - done(); + it('can getAll', async function () { + const result = await groups.getAll(); + expect(result.length).to.be(2); + expect(result[0].name).to.be(group0Name); + expect(result[1].name).to.be(group1Name); }); - }); - it('can remove group with member', function (done) { - groups.create(GROUP0_NAME, '', function (error, result) { - expect(error).to.eql(null); - group0Object = result; - - groups.addMember(group0Object.id, USER_0.id, function (error) { - expect(error).to.be(null); - - groups.remove(group0Object.id, function (error) { - expect(error).to.eql(null); - done(); - }); - }); - }); - }); -}); - -describe('Set user groups', function () { - before(function (done) { - async.series([ - setup, - function (next) { - groups.create(GROUP0_NAME, '', function (error, result) { - if (error) return next(error); - group0Object = result; - next(); - }); - }, - function (next) { - groups.create(GROUP1_NAME, '', function (error, result) { - if (error) return next(error); - group1Object = result; - next(); - }); - }, - userdb.add.bind(null, USER_0.id, USER_0) - ], done); - }); - after(cleanup); - - it('can set user to single group', function (done) { - groups.setMembership(USER_0.id, [ group0Object.id ], function (error) { - expect(error).to.be(null); - - groups.getMembership(USER_0.id, function (error, groupIds) { - expect(error).to.be(null); - expect(groupIds.length).to.be(1); - expect(groupIds[0]).to.be(group0Object.id); - done(); - }); - }); - }); - - it('cannot set user to same group twice', function (done) { - groups.setMembership(USER_0.id, [ group0Object.id, group0Object.id ], function (error) { - expect(error.reason).to.be(BoxError.CONFLICT); - done(); - }); - }); - - it('can set user to multiple groups', function (done) { - groups.setMembership(USER_0.id, [ group0Object.id, group1Object.id ], function (error) { - expect(error).to.be(null); - - groups.getMembership(USER_0.id, function (error, groupIds) { - expect(error).to.be(null); - expect(groupIds.length).to.be(2); - expect(groupIds.sort()).to.eql([ group0Object.id, group1Object.id ].sort()); - done(); - }); + it('can getAllWithMembers', async function () { + const result = await groups.getAllWithMembers(); + expect(result.length).to.be(2); + expect(result[0].name).to.be(group0Name); + expect(result[1].userIds).to.eql([ ADMIN.id ]); + expect(result[1].name).to.be(group1Name); }); + + it('cannot delete invalid group', async function () { + const [error] = await safe(groups.remove('random')); + expect(error.reason).to.be(BoxError.NOT_FOUND); + }); + + it('can delete valid group', async function () { + await groups.setMembers(group0Object.id, [ ADMIN.id, USER.id ]); // ensure group has some members + await groups.remove(group0Object.id); + }); + }); }); diff --git a/src/test/ldap-test.js b/src/test/ldap-test.js index f1dd42584..58367183d 100644 --- a/src/test/ldap-test.js +++ b/src/test/ldap-test.js @@ -133,20 +133,14 @@ function setup(done) { callback(null); }); }, - function (callback) { - groups.create(GROUP_NAME, '', function (error, result) { - if (error) return callback(error); + async function () { + const result = await groups.add({ name: GROUP_NAME, source: '' }); - GROUP_ID = result.id; - - callback(); - }); + GROUP_ID = result.id; }, - function (callback) { - async.series([ - groups.addMember.bind(null, GROUP_ID, USER_0.id), - groups.addMember.bind(null, GROUP_ID, USER_1.id) - ], callback); + async function () { + await groups.addMember(GROUP_ID, USER_0.id); + await groups.addMember(GROUP_ID, USER_1.id); } ], done); } diff --git a/src/test/volumes-test.js b/src/test/volumes-test.js index fec0de57f..1d8c22640 100644 --- a/src/test/volumes-test.js +++ b/src/test/volumes-test.js @@ -35,31 +35,31 @@ describe('Volumes', function () { after(cleanup); it('cannot add bad name', async function () { - const [error] = await safe(volumes.add({ name: 'music/is', hostPath: '/tmp/music', mountType: 'noop', mountOptions: {} }, AUDIT_SOURCE)); + const [error] = await safe(volumes.add({ name: 'music/is', hostPath: '/tmp/music', mountType: 'filesystem', mountOptions: {} }, AUDIT_SOURCE)); if (!error) throw new Error('Expecting bad field error'); expect(error.reason).to.be(BoxError.BAD_FIELD); }); it('cannot add bad path', async function () { - const [error] = await safe(volumes.add({ name: 'music', hostPath: '/tmp/music', mountType: 'noop', mountOptions: {} }, AUDIT_SOURCE)); + const [error] = await safe(volumes.add({ name: 'music', hostPath: '/tmp/music', mountType: 'filesystem', mountOptions: {} }, AUDIT_SOURCE)); if (!error) throw new Error('Expecting bad field error'); expect(error.reason).to.be(BoxError.BAD_FIELD); }); let volume; it('can add volume', async function () { - const id = await volumes.add({ name: 'music', hostPath: '/mnt/cloudron-test-music', mountType: 'noop', mountOptions: {} }, AUDIT_SOURCE); + const id = await volumes.add({ name: 'music', hostPath: '/mnt/cloudron-test-music', mountType: 'filesystem', mountOptions: {} }, AUDIT_SOURCE); expect(id).to.be.a('string'); volume = { id, name: 'music', hostPath: '/mnt/cloudron-test-music' }; }); it('cannot add duplicate path', async function () { - const [error] = await safe(volumes.add({ name: 'music-dup', hostPath: '/mnt/cloudron-test-music', mountType: 'noop', mountOptions: {} }, AUDIT_SOURCE)); + const [error] = await safe(volumes.add({ name: 'music-dup', hostPath: '/mnt/cloudron-test-music', mountType: 'filesystem', mountOptions: {} }, AUDIT_SOURCE)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); }); it('cannot add duplicate name', async function () { - const [error] = await safe(volumes.add({ name: 'music', hostPath: '/mnt/cloudron-test-music2', mountType: 'noop', mountOptions: {} }, AUDIT_SOURCE)); + const [error] = await safe(volumes.add({ name: 'music', hostPath: '/mnt/cloudron-test-music2', mountType: 'filesystem', mountOptions: {} }, AUDIT_SOURCE)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); }); diff --git a/src/users.js b/src/users.js index 9d4d4aaf1..628cec228 100644 --- a/src/users.js +++ b/src/users.js @@ -381,16 +381,16 @@ function get(userId, callback) { assert.strictEqual(typeof userId, 'string'); assert.strictEqual(typeof callback, 'function'); - userdb.get(userId, function (error, result) { + userdb.get(userId, async function (error, result) { if (error) return callback(error); - groups.getMembership(userId, function (error, groupIds) { - if (error) return callback(error); + let groupIds; + [error, groupIds] = await safe(groups.getMembership(userId)); + if (error) return callback(error); - result.groupIds = groupIds; + result.groupIds = groupIds; - return callback(null, result); - }); + return callback(null, result); }); } @@ -471,16 +471,11 @@ function update(user, data, auditSource, callback) { }); } -function setMembership(user, groupIds, callback) { +async function setMembership(user, groupIds) { assert.strictEqual(typeof user, 'object'); assert(Array.isArray(groupIds)); - assert.strictEqual(typeof callback, 'function'); - groups.setMembership(user.id, groupIds, function (error) { - if (error) return callback(error); - - callback(null); - }); + await groups.setMembership(user.id, groupIds); } function getAdmins(callback) {