clinical:roles

v2.5.0Published 5 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

clinical:roles

Roles based authorization package for Meteor - compatible with built-in accounts package; forked from alanning:meteor-roles.

=====================================

Table of Contents

=====================================

Authorization

This package lets you attach permissions to a user which you can then check against later when deciding whether to grant access to Meteor methods or publish data. The core concept is very simple, essentially you are attaching strings to a user object and then checking for the existance of those strings later. In some sense, it is very similar to tags on blog posts. This package provides helper methods to make the process of adding, removing, and verifying those permissions easier.

=====================================

Permissions vs roles (or What's in a name...)

Although the name of this package is 'roles', you can define your permissions however you like. They are essentially just tags that you assign on a user and which you can check for later.

You can have traditional roles like, "admin" or "webmaster", or you can assign more granular permissions such as, "view-secrets", "users.view", or "users.manage". Often times more granular is actually better because you are able to handle all those pesky edge cases that come up in real-life usage without creating a ton of higher-level 'roles'. To the roles package, it's all strings.

=====================================

What are "groups"?

Sometimes it's useful to let a user have independent sets of permissions. The roles package calls these independent sets, "groups" for lack of a better term. You can think of them as "partitions" if that is more clear. Users can have one set of permissions in group A and another set of permissions in group B. Let's go through an example of this using soccer/football teams as groups.

Roles.addUsersToRoles(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com')
Roles.addUsersToRoles(joesUserId, ['player','goalie'], 'real-madrid.com')

Roles.userIsInRole(joesUserId, 'manage-team', 'manchester-united.com')  // => true
Roles.userIsInRole(joesUserId, 'manage-team', 'real-madrid.com')  // => false

In this example we can see that Joe manages Manchester United and plays for Real Madrid. By using groups, we can assign permissions independently and make sure that they don't get mixed up between groups.

NOTE: If you use groups for ANY of your users, you should use groups for ALL of your users. This is due to how the roles package stores the roles internally in the database. In roles 2.0, you won't need to worry about this anymore, we'll have a default group that will hold roles not assigned to a specific group.

Now, let's take a look at how to use the Global Group. Say we want to give Joe permission to do something across all of our groups. That's what the Global Group is for:

Roles.addUsersToRoles(joesUserId, 'super-admin', Roles.GLOBAL_GROUP)

if (Roles.userIsInRole(joesUserId, ['manage-team', 'super-admin'], 'real-madrid.com')) {

  // True!  Even though Joe doesn't manage Real Madrid, he is 'super-admin' in
  // the Global Group so this check succeeds.

}

=====================================

Changes to default Meteor behavior

  1. User entries in the Meteor.users collection gain a new field named roles corresponding to the user's roles. †
  2. A new collection Meteor.roles contains a global list of defined role names. ††
  3. The currently logged-in user's roles field is automatically published to the client.

† The type of the roles field depends on whether or not groups are used:

1Roles.addUsersToRoles(bobsUserId, ['manage-team','schedule-game'])
2// internal representation - no groups
3// user.roles = ['manage-team','schedule-game']
4
5Roles.addUsersToRoles(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com')
6Roles.addUsersToRoles(joesUserId, ['player','goalie'], 'real-madrid.com')
7// internal representation - groups
8// NOTE: MongoDB uses periods to represent hierarchy so periods in group names
9//   are converted to underscores.
10//
11// user.roles = {
12//   'manchester-united_com': ['manage-team','schedule-game'],
13//   'real-madrid_com': ['player','goalie']
14// }

Note: See the addUsersToRoles documentation for restrictions on group names.

†† The Meteor.roles collection is currently only for convenience on the UI-side and is not used functionally within this package. In the future it may be used to support role hierarchies. Since it is not currently required, it is not automatically published to the client. Here's how you would publish it to every client without needing a subscription:

1// in server/publish.js
2Meteor.publish(null, function (){
3  return Meteor.roles.find({})
4})

=====================================

Installing

  1. Add one of the built-in accounts packages so the Meteor.users collection exists. From a command prompt:

    meteor add accounts-password
  2. Add this package to your project. From a command prompt:

    meteor add clinical:roles
  3. Run your application:

    meteor

=====================================

Usage Examples

Here are some potential use cases:

-- Server --

Add users to roles:

1var users = [
2  {name:"Normal User",email:"normal@example.com",roles:[]},
3  {name:"View-Secrets User",email:"view@example.com",roles:['view-secrets']},
4  {name:"Manage-Users User",email:"manage@example.com",roles:['manage-users']},
5  {name:"Admin User",email:"admin@example.com",roles:['admin']}
6];
7
8
9users.forEach(function(user){
10  let id = Accounts.createUser({
11    email: user.email,
12    password: "apple1",
13    profile: { name: user.name }
14  });
15
16  if (user.roles.length > 0) {
17    // Need _id of existing user record so this call must come
18    // after `Accounts.createUser` or `Accounts.onCreate`
19    Roles.addUsersToRoles(id, user.roles, 'default-group');
20  }
21})

Check user roles before publishing sensitive data:

1// server/publish.js
2
3// Give authorized users access to sensitive data by group
4Meteor.publish('secrets', function (group) {
5  if (Roles.userIsInRole(this.userId, ['view-secrets','admin'], group)) {
6
7    return Meteor.secrets.find({group: group});
8
9  } else {
10
11    // user not authorized. do not publish secrets
12    this.stop();
13    return;
14
15  }
16});

Prevent non-authorized users from creating new users:

1Accounts.validateNewUser(function (user) {
2  var loggedInUser = Meteor.user();
3
4  if (Roles.userIsInRole(loggedInUser, ['admin','manage-users'])) {
5    // NOTE: This example assumes the user is not using groups.
6    return true;
7  }
8
9  throw new Meteor.Error(403, "Not authorized to create new users");
10});

Prevent access to certain functionality, such as deleting a user:

1// server/userMethods.js
2
3Meteor.methods({
4  /**
5   * delete a user from a specific group
6   *
7   * @method deleteUser
8   * @param {String} targetUserId _id of user to delete
9   * @param {String} group Company to update permissions for
10   */
11  deleteUser: function (targetUserId, group) {
12    var loggedInUser = Meteor.user()
13
14    if (!loggedInUser ||
15        !Roles.userIsInRole(loggedInUser,
16                            ['manage-users', 'support-staff'], group)) {
17      throw new Meteor.Error(403, "Access denied")
18    }
19
20    // remove permissions for target group
21    Roles.setUserRoles(targetUserId, [], group)
22
23    // do other actions required when a user is removed...
24  }
25})

Manage a user's permissions:

1// server/userMethods.js
2
3Meteor.methods({
4  /**
5   * update a user's permissions
6   *
7   * @param {Object} targetUserId Id of user to update
8   * @param {Array} roles User's new permissions
9   * @param {String} group Company to update permissions for
10   */
11  updateRoles: function (targetUserId, roles, group) {
12    var loggedInUser = Meteor.user()
13
14    if (!loggedInUser ||
15        !Roles.userIsInRole(loggedInUser,
16                            ['manage-users', 'support-staff'], group)) {
17      throw new Meteor.Error(403, "Access denied")
18    }
19
20    Roles.setUserRoles(targetUserId, roles, group)
21  }
22})

=====================================

Tests

To run tests:

  1. cd meteor-roles
  2. meteor test-packages ./roles
  3. point browser at http://localhost:3000/

NOTE: If you see an error message regarding "The package named roles does not exist" that means you are either: a) in the wrong directory or b) left off the './' in front of 'roles' in step 2.

Step 2 needs to be run in the main 'meteor-roles' directory and the './' is needed because otherwise Meteor only looks in directories named 'packages'.

=====================================

Contributions

Manuy thanks to:

=======================================

Licensing

MIT License