Add route to set the users groups

This commit is contained in:
Girish Ramakrishnan
2016-02-09 15:47:02 -08:00
parent 2b0791f4a3
commit f413bfb3a0
7 changed files with 186 additions and 16 deletions
+27 -1
View File
@@ -14,6 +14,7 @@ exports = module.exports = {
isMember: isMember,
getGroups: getGroups,
setGroups: setGroups,
_clear: clear
};
@@ -115,6 +116,9 @@ function clear(callback) {
}
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 DatabaseError(DatabaseError.INTERNAL_ERROR, error));
// if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); // need to differentiate group with no members and invalid groupId
@@ -124,7 +128,10 @@ function getMembers(groupId, callback) {
}
function getGroups(userId, callback) {
database.query('SELECT userId FROM groupMembers WHERE userId=?', [ userId ], function (error, result) {
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 DatabaseError(DatabaseError.INTERNAL_ERROR, error));
// if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); // need to differentiate group with no members and invalid groupId
@@ -132,6 +139,25 @@ function getGroups(userId, callback) {
});
}
function setGroups(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 DatabaseError(DatabaseError.NOT_FOUND, error.message));
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
callback(null);
});
}
function addMember(groupId, userId, callback) {
assert.strictEqual(typeof groupId, 'string');
assert.strictEqual(typeof userId, 'string');
+14
View File
@@ -17,6 +17,7 @@ exports = module.exports = {
isMember: isMember,
getGroups: getGroups,
setGroups: setGroups,
ADMIN_GROUP_ID: 'admin' // see db migration code and groupdb._clear
};
@@ -153,6 +154,19 @@ function getGroups(userId, callback) {
});
}
function setGroups(userId, groupIds, callback) {
assert.strictEqual(typeof userId, 'string');
assert(Array.isArray(groupIds));
assert.strictEqual(typeof callback, 'function');
groupdb.setGroups(userId, groupIds, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new GroupError(GroupError.NOT_FOUND));
if (error) return callback(new GroupError(GroupError.INTERNAL_ERROR, error));
return callback(null, result);
});
}
function addMember(groupId, userId, callback) {
assert.strictEqual(typeof groupId, 'string');
assert.strictEqual(typeof userId, 'string');
+40
View File
@@ -11,6 +11,7 @@ var appdb = require('../../appdb.js'),
config = require('../../config.js'),
database = require('../../database.js'),
expect = require('expect.js'),
groups = require('../../groups.js'),
superagent = require('superagent'),
server = require('../../server.js'),
settings = require('../../settings.js'),
@@ -168,4 +169,43 @@ describe('Groups API', function () {
});
});
});
describe('Set groups', function () {
before(function (done) {
async.series([
groups.create.bind(null, 'group0'),
groups.create.bind(null, 'group1')
], done);
});
it('cannot add user to invalid group', function (done) {
superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME + '/set_groups')
.query({ access_token: token })
.send({ groupIds: [ 'admin', 'something' ]})
.end(function (error, result) {
expect(result.statusCode).to.equal(404);
done();
});
});
it('can add user to valid group', function (done) {
superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME + '/set_groups')
.query({ access_token: token })
.send({ groupIds: [ 'admin', 'group0', 'group1' ]})
.end(function (error, result) {
expect(result.statusCode).to.equal(204);
done();
});
});
it('can remove last user from admin', function (done) {
superagent.put(SERVER_URL + '/api/v1/users/' + USERNAME + '/set_groups')
.query({ access_token: token })
.send({ groupIds: [ 'group0', 'group1' ]})
.end(function (error, result) {
expect(result.statusCode).to.equal(403); // not allowed
done();
});
});
});
});
+17 -1
View File
@@ -13,7 +13,8 @@ exports = module.exports = {
remove: removeUser,
verifyPassword: verifyPassword,
requireAdmin: requireAdmin,
sendInvite: sendInvite
sendInvite: sendInvite,
setGroups: setGroups
};
var assert = require('assert'),
@@ -226,3 +227,18 @@ function sendInvite(req, res, next) {
next(new HttpSuccess(200, {}));
});
}
function setGroups(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.userId, 'string');
if (!Array.isArray(req.body.groupIds)) return next(new HttpError(400, 'API call requires a groups array.'));
user.setGroups(req.params.userId, req.body.groupIds, function (error) {
if (error && error.reason === UserError.NOT_FOUND) return next(new HttpError(404, 'One or more groups not found'));
if (error && error.reason === UserError.NOT_ALLOWED) return next(new HttpError(403, 'Last admin'));
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(204));
});
}
+1
View File
@@ -107,6 +107,7 @@ function initializeExpressSync() {
router.del ('/api/v1/users/:userId', usersScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.user.remove);
router.post('/api/v1/users/:userId/password', usersScope, routes.user.changePassword); // changePassword verifies password
router.post('/api/v1/users/:userId/admin', usersScope, routes.user.requireAdmin, routes.user.changeAdmin);
router.put('/api/v1/users/:userId/set_groups', usersScope, routes.user.requireAdmin, routes.user.setGroups);
router.post('/api/v1/users/:userId/invite', usersScope, routes.user.requireAdmin, routes.user.sendInvite);
// Group management
+62 -13
View File
@@ -17,6 +17,22 @@ var async = require('async'),
var GROUP0_NAME = 'administrators',
GROUP0_ID = GROUP0_NAME;
var GROUP1_NAME = 'externs',
GROUP1_ID = GROUP1_NAME;
var USER_0 = {
id: 'uuid213',
username: 'uuid213',
password: 'secret',
email: 'safe@me.com',
admin: false,
salt: 'morton',
createdAt: 'sometime back',
modifiedAt: 'now',
resetToken: hat(256),
displayName: ''
};
function setup(done) {
// ensure data/config/mount paths
database.initialize(function (error) {
@@ -55,6 +71,13 @@ describe('Groups', function () {
});
});
it('cannot create group - reserved', function (done) {
groups.create('users', function (error) {
expect(error.reason).to.be(GroupError.BAD_NAME);
done();
});
});
it('can create valid group', function (done) {
groups.create(GROUP0_NAME, function (error) {
expect(error).to.be(null);
@@ -100,19 +123,6 @@ describe('Groups', function () {
});
describe('Group membership', function () {
var USER_0 = {
id: 'uuid213',
username: 'uuid213',
password: 'secret',
email: 'safe@me.com',
admin: false,
salt: 'morton',
createdAt: 'sometime back',
modifiedAt: 'now',
resetToken: hat(256),
displayName: ''
};
before(function (done) {
async.series([
setup,
@@ -218,3 +228,42 @@ describe('Group membership', function () {
});
});
});
describe('Set user groups', function () {
before(function (done) {
async.series([
setup,
groups.create.bind(null, GROUP0_NAME),
groups.create.bind(null, GROUP1_NAME),
userdb.add.bind(null, USER_0.id, USER_0)
], done);
});
after(cleanup);
it('can set user to single group', function (done) {
groups.setGroups(USER_0.id, [ GROUP0_ID ], function (error) {
expect(error).to.be(null);
groups.getGroups(USER_0.id, function (error, groupIds) {
expect(error).to.be(null);
expect(groupIds.length).to.be(1);
expect(groupIds[0]).to.be(GROUP0_ID);
done();
});
});
});
it('can set user to multiple groups', function (done) {
groups.setGroups(USER_0.id, [ GROUP0_ID, GROUP1_ID ], function (error) {
expect(error).to.be(null);
groups.getGroups(USER_0.id, function (error, groupIds) {
expect(error).to.be(null);
expect(groupIds.length).to.be(2);
expect(groupIds[0]).to.be(GROUP0_ID);
expect(groupIds[1]).to.be(GROUP1_ID);
done();
});
});
});
});
+25 -1
View File
@@ -20,7 +20,8 @@ exports = module.exports = {
update: updateUser,
createOwner: createOwner,
getOwner: getOwner,
sendInvite: sendInvite
sendInvite: sendInvite,
setGroups: setGroups
};
var assert = require('assert'),
@@ -28,6 +29,7 @@ var assert = require('assert'),
crypto = require('crypto'),
DatabaseError = require('./databaseerror.js'),
groups = require('./groups.js'),
GroupError = groups.GroupError,
hat = require('hat'),
mailer = require('./mailer.js'),
tokendb = require('./tokendb.js'),
@@ -314,6 +316,28 @@ function changeAdmin(username, admin, callback) {
});
}
function setGroups(userId, groupIds, callback) {
assert.strictEqual(typeof userId, 'string');
assert(Array.isArray(groupIds));
assert.strictEqual(typeof callback, 'function');
userdb.getAllAdmins(function (error, result) {
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
// protect from a system where there is no admin left
if (result.length <= 1 && result[0].id === userId && groupIds.indexOf(groups.ADMIN_GROUP_ID) === -1) {
return callback(new UserError(UserError.NOT_ALLOWED, 'Only admin'));
}
groups.setGroups(userId, groupIds, function (error) {
if (error && error.reason === GroupError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND, 'One or more groups not found'));
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
callback();
});
});
}
function getAllAdmins(callback) {
assert.strictEqual(typeof callback, 'function');