Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Allow multiple actions to be tested

  • Loading branch information...
commit 90cf23722b6a79d67ecf9d54693ceddc5edbe564 1 parent 27b5a48
@tschaub authored
View
31 README.md
@@ -62,11 +62,13 @@ Now define what roles are required for your actions.
```js
auth.action('add members to organization', ['admin', 'organization.owner']);
+auth.action('delete organization', ['admin']);
```
To perform the provided action, a user must have at least one of the given
-roles. In this case, a user must be `admin` or `organization.owner` to add
-members to an organization.
+roles. In the first case, a user must be `admin` or `organization.owner` to add
+members to an organization. In the second case, a user must be `admin` to be
+able to delete an organization.
Note that entity and role getters can be added in any order, but you cannot
configure actions until all entity and role getters have been added.
@@ -102,6 +104,31 @@ app.post(
});
```
+If you have a view that might allow a user to perform multiple actions, you
+can create middleware that allows the view to be rendered if any of a list of
+actions are allowed. In this case, the view will also have access to which
+specific actions are allowed so you can conditionally render page elements.
+
+```js
+app.get(
+ '/organizations/:orgId/manage',
+ auth.can('add members to organization', 'delete organization'),
+ function(req, res, next) {
+ /**
+ * We've reached this point because the user can either add members or
+ * delete the organization.
+ */
+ var view = auth.view(req);
+ /**
+ * To determine which actions are allowed, call the `can` method (or
+ * inspect all of `view.actions`).
+ */
+ res.render('manage.html', {
+ actions: view.actions
+ });
+ });
+```
+
### Handling unauthorized actions
If the auth manager decides a user is not authorized to perform a specific
View
31 lib/manager.js
@@ -115,26 +115,41 @@ Manager.prototype.actionAllowed_ = function(action, req, done) {
/**
* Create action based authorization middleware.
* @param {string} action Action name (e.g. 'add members to organization').
+ * May also be called with multiple action arguments.
* @return {function(Object, Object, function)} Authorization middleware.
*/
Manager.prototype.can = function(action) {
- if (!this.actionDefs_.hasOwnProperty(action)) {
- throw new errors.ConfigError('Action not found: ' + action);
+ var actions = Array.prototype.slice.call(arguments);
+ for (var i = 0, ii = actions.length; i < ii; ++i) {
+ if (!this.actionDefs_.hasOwnProperty(actions[i])) {
+ throw new errors.ConfigError('Action not found: ' + actions[i]);
+ }
}
var self = this;
var pauseStream = this.options.pauseStream;
return function(req, res, next) {
var paused = pauseStream ? pause(req) : null;
- self.actionAllowed_(action, req, function(err, can) {
+ var canSome = false;
+ var disallowed = [];
+ async.each(actions, function(action, done) {
+ self.actionAllowed_(action, req, function(err, can) {
+ if (!can) {
+ disallowed.push(action);
+ } else {
+ canSome = true;
+ }
+ done(err);
+ });
+ }, function(err) {
+ if (!err && !canSome) {
+ err = new errors.UnauthorizedError(
+ 'Action not allowed: ' + disallowed.join(','));
+ }
if (err) {
next(err);
} else {
- if (can) {
- next();
- } else {
- next(new errors.UnauthorizedError('Action not allowed: ' + action));
- }
+ next();
}
if (paused) {
paused.resume();
View
2  package.json
@@ -45,7 +45,7 @@
"watch": "grunt watch"
},
"dependencies": {
- "async": "0.2.7",
+ "async": "0.2.9",
"pause": "0.0.1"
}
}
View
64 test/lib/manager.spec.js
@@ -74,7 +74,7 @@ describe('Manager', function() {
});
- it('accepts a single action string instead of an array', function() {
+ it('accepts a single role string instead of an array', function() {
auth.role('admin', function(req, done) {
// pretend everybody is admin
done(null, true);
@@ -150,6 +150,8 @@ describe('Manager', function() {
auth.action('add members to organization', ['admin', 'organization.owner']);
+ auth.action('delete organization', ['admin']);
+
it('creates a middleware function', function() {
var middleware = auth.can('add members to organization');
assert.isFunction(middleware, 'is a function');
@@ -183,6 +185,66 @@ describe('Manager', function() {
});
});
+ it('calls next if any of the actions can be performed', function(done) {
+ var middleware = auth.can(
+ 'add members to organization', 'delete organization');
+
+ var req = new EventEmitter();
+ req.url = '/org.1';
+ req.user = {
+ admin: true
+ };
+
+ middleware(req, {}, function(err) {
+ assert.lengthOf(arguments, 0, 'next called with no arguments');
+
+ var view = auth.view(req);
+
+ assert.strictEqual(view.entities.organization, organizations['org.1'],
+ 'got organization');
+ assert.strictEqual(view.roles['organization.owner'], false,
+ 'is organization.owner');
+ assert.strictEqual(view.roles.admin, true,
+ 'is not admin');
+ assert.strictEqual(view.actions['add members to organization'], true,
+ 'can add members to organization');
+ assert.strictEqual(view.actions['delete organization'], true,
+ 'can delete organization');
+
+ done();
+ });
+ });
+
+ it('adds all actions to the view', function(done) {
+ var middleware = auth.can(
+ 'add members to organization', 'delete organization');
+
+ var req = new EventEmitter();
+ req.url = '/org.1';
+ req.user = {
+ id: 'user.1'
+ };
+
+ middleware(req, {}, function(err) {
+ assert.lengthOf(arguments, 0, 'next called with no arguments');
+
+ var view = auth.view(req);
+
+ assert.strictEqual(view.entities.organization, organizations['org.1'],
+ 'got organization');
+ assert.strictEqual(view.roles['organization.owner'], true,
+ 'is organization.owner');
+ assert.strictEqual(view.roles.admin, false,
+ 'is not admin');
+ assert.strictEqual(view.actions['add members to organization'], true,
+ 'can add members to organization');
+ assert.strictEqual(view.actions['delete organization'], false,
+ 'can delete organization');
+
+ done();
+ });
+ });
+
it('calls next error if action is not allowed', function(done) {
var middleware = auth.can('add members to organization');
Please sign in to comment.
Something went wrong with that request. Please try again.