Skip to content

Commit

Permalink
feat(api): add routes to add and remove users from groups
Browse files Browse the repository at this point in the history
  • Loading branch information
targos committed Nov 15, 2016
1 parent 6fa2aa8 commit 9fcd324
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 40 deletions.
16 changes: 0 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,6 @@
[![npm download][download-image]][download-url]

Interface to CouchDB that allows the control of permissions on the documents.

### Node.js API

TODO

### CLI

#### Import a file

| Command | Description |
| ------ | ----------- |
| ```rest-on-couch import``` | Import files |
| ```rest-on-couch server``` | Launch server |
| ```rest-on-couch log``` | get/set log entries |

```rest-on-couch <command> --help``` for more details

### Configuration

Expand Down
21 changes: 5 additions & 16 deletions src/couch/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ const nanoPromise = require('../util/nanoPromise');
const util = require('./util');
const validate = require('./validate');
const nanoMethods = require('./nano');
const ensureStringArray = require('../util/ensureStringArray');

const methods = {
async getDocUuidFromId(id, user, type) {
return nanoMethods.getUuidFromId(this._db, id, user, type);
},

async getDocByRights(uuid, user, rights, type, options) {
await this.open();
debug.trace('getDocByRights');
await this.open();
if (!util.isManagedDocumentType(type)) {
throw new CouchError(`invalid type argument: ${type}`);
}
Expand All @@ -39,18 +38,18 @@ const methods = {
},

async addOwnersToDoc(uuid, user, owners, type, options) {
await this.open();
debug.trace('addOwnersToDoc');
owners = ensureOwnersArray(owners);
await this.open();
owners = util.ensureOwnersArray(owners);
const doc = await this.getDocByRights(uuid, user, 'owner', type, options);
doc.$owners = _.union(doc.$owners, owners);
return nanoMethods.save(this._db, doc, user);
},

async removeOwnersFromDoc(uuid, user, owners, type, options) {
await this.open();
debug.trace('removeOwnersFromDoc');
owners = ensureOwnersArray(owners);
await this.open();
owners = util.ensureOwnersArray(owners);
const doc = await this.getDocByRights(uuid, user, 'owner', type, options);
const mainOwner = doc.$owners[0];
if (includes(owners, mainOwner)) {
Expand All @@ -63,16 +62,6 @@ const methods = {
}
};

function ensureOwnersArray(owners) {
owners = ensureStringArray(owners);
for (const owner of owners) {
if (!util.isValidOwner(owner)) {
throw new CouchError(`invalid owner: ${owner}`, 'invalid');
}
}
return owners;
}

module.exports = {
methods
};
23 changes: 21 additions & 2 deletions src/couch/group.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const _ = require('lodash');

const CouchError = require('../util/CouchError');
const debug = require('../util/debug')('main:group');
const nanoPromise = require('../util/nanoPromise');
Expand Down Expand Up @@ -117,8 +119,25 @@ const methods = {
// Search inside groups
const userGroups = await nanoPromise.queryView(this._db, 'groupByUserAndRight', {key: [user, right]}, {onlyValue: true});
// Merge both lists
const union = new Set([...defaultGroups, ...userGroups]);
return Array.from(union);
return _.union(defaultGroups, userGroups);
},

async addUsersToGroup(uuid, user, usernames) {
debug(`addUserToGroup (${uuid}, ${user}, ${usernames})`);
await this.open();
usernames = util.ensureUsersArray(usernames);
const group = await this.getDocByRights(uuid, user, 'write', 'group');
group.users = _.union(group.users, usernames);
return nanoMethods.save(this._db, group, user);
},

async removeUsersFromGroup(uuid, user, usernames) {
debug(`removeUsersFromGroup (${uuid}, ${user}, ${usernames})`);
await this.open();
usernames = util.ensureUsersArray(usernames);
const group = await this.getDocByRights(uuid, user, 'write', 'group');
_.pullAll(group.users, usernames);
return nanoMethods.save(this._db, group, user);
}
};

Expand Down
26 changes: 25 additions & 1 deletion src/couch/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const includes = require('array-includes');

const CouchError = require('../util/CouchError');
const ensureStringArray = require('../util/ensureStringArray');
const isEmail = require('../util/isEmail');
const constants = require('../constants');

Expand Down Expand Up @@ -39,6 +41,26 @@ function isManagedDocumentType(type) {
return type === 'entry' || type === 'group';
}

function ensureOwnersArray(owners) {
owners = ensureStringArray(owners);
for (const owner of owners) {
if (!isValidOwner(owner)) {
throw new CouchError(`invalid owner: ${owner}`, 'invalid');
}
}
return owners;
}

function ensureUsersArray(users) {
users = ensureStringArray(users);
for (const user of users) {
if (!isValidUsername(user)) {
throw new CouchError(`invalid user: ${user}`, 'invalid');
}
}
return users;
}

module.exports = {
isSpecialUser,
isValidGroupName,
Expand All @@ -47,5 +69,7 @@ module.exports = {
isValidGlobalRightUser,
isValidGlobalRightType,
isAllowedFirstLevelKey,
isManagedDocumentType
isManagedDocumentType,
ensureOwnersArray,
ensureUsersArray
};
15 changes: 15 additions & 0 deletions src/server/middleware/couch.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,21 @@ exports.getGroups = composeWithError(function*() {
this.body = yield this.state.couch.getGroupsByRight(this.state.userEmail, right);
});

exports.getGroupUsers = composeWithError(function*() {
const group = yield this.state.couch.getDocByRights(this.params.uuid, this.state.userEmail, 'read', 'group');
this.body = group.users;
});

exports.addUserToGroup = composeWithError(function*() {
yield this.state.couch.addUsersToGroup(this.params.uuid, this.state.userEmail, this.params.username);
this.body = OK;
});

exports.removeUserFromGroup = composeWithError(function*() {
yield this.state.couch.removeUsersFromGroup(this.params.uuid, this.state.userEmail, this.params.username);
this.body = OK;
});

exports.getRights = composeWithError(function*() {
const right = this.params.right;
const uuid = this.params.uuid;
Expand Down
7 changes: 5 additions & 2 deletions src/server/routes/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,11 @@ router.get('/:dbname/groups', couch.getGroups);
router.get('/:dbname/group/:name', couch.getGroup);
//router.put('/:dbname/group/:name', parseJson1mb, couch.createOrUpdateGroup);
router.delete('/:dbname/group/:name', couch.deleteGroup);
// router.put('/:dbname/group/:name/user/Username', couch.addUserToGroup);
// router.delete('/:dbname/group/:name/user/:username', couch.deleteUserFromGroup);

// Group users management
router.get('/:dbname/group/:name/users', getUuidFromGroupName, couch.getGroupUsers);
router.put('/:dbname/group/:name/user/:username', getUuidFromGroupName, couch.addUserToGroup);
router.delete('/:dbname/group/:name/user/:username', getUuidFromGroupName, couch.removeUserFromGroup);

// Group owners
router.get('/:dbname/group/:name/_owner', getUuidFromGroupName, couch.getOwners('group'));
Expand Down
38 changes: 35 additions & 3 deletions test/group.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use strict';

const data = require('./data/data');
const noRights = require('./data/noRights');
const noRights = require('./data/noRights');

describe('group methods', function () {
before(data);
beforeEach(data);
it('anyone should be able to create a group', function () {
return couch.createGroup('groupX', 'a@a.com').should.be.fulfilled();
});
Expand All @@ -24,10 +24,42 @@ describe('group methods', function () {
it('should throw if deleting non-existant group', function () {
return couch.deleteGroup('inexistant', 'a@a.com').should.be.rejectedWith(/group does not exist/);
});

it('should add one user to group', function () {
return couch.addUsersToGroup('groupA', 'a@a.com', 'test123@example.com').then(function () {
return couch.getDocByRights('groupA', 'a@a.com', 'read', 'group');
}).then(function (group) {
group.users.should.have.lengthOf(2);
group.users[1].should.equal('test123@example.com');
});
});

it('should add several users to group', function () {
return couch.addUsersToGroup('groupA', 'a@a.com', ['test123@example.com', 'dup@example.com', 'dup@example.com'])
.then(function () {
return couch.getDocByRights('groupA', 'a@a.com', 'read', 'group');
}).then(function (group) {
group.users.should.have.lengthOf(3);
group.users[1].should.equal('test123@example.com');
group.users[2].should.equal('dup@example.com');
});
});

it('should remove users from group', function () {
return couch.addUsersToGroup('groupA', 'a@a.com', ['test123@example.com'])
.then(function () {
return couch.removeUsersFromGroup('groupA', 'a@a.com', 'a@a.com');
}).then(function () {
return couch.getDocByRights('groupA', 'a@a.com', 'read', 'group');
}).then(function (group) {
group.users.should.have.lengthOf(1);
group.users[0].should.equal('test123@example.com');
});
});
});

describe('group methods (no default rights)', function () {
before(noRights);
beforeEach(noRights);

it('anyone cannot create group', function () {
return couch.createGroup('groupX', 'a@a.com').should.be.rejectedWith(/does not have createGroup right/);
Expand Down

0 comments on commit 9fcd324

Please sign in to comment.