Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strategy for Modeling RBAC with NoSQL Document Store

I'm getting ready to implement stripped down version of role based access control in my application and I'm contemplating how/what to model in my document store which happens to be mongodb with mongoose.js as my "convenience lib". But this question should apply to any document store.

It seems quite common that deciding between using embedded objects vs refs is a challenge when using a document store given the competing factors of duplication vs performance and what not. I'm trying to keep the RBAC as simple as possible and not go too crazy on nested Collections/Ref IDs which would mean a ton of loops, over using mongoose's populate, etc.

Question:

I'm already leaning toward having collections for User, Permission, and Role; but does it make sense to model Operations and Resources, or, just use key/vals for these?

See code example below or jsfiddle which should help to reason about the problem. Note it's not at all the way I want to implement this but just a way to examine to relationships!

/* 
Imagine this being used in a CMS ;)

User: have a role property (e.g. role:"admin" or role:"writer")

Operation: Create,Read,Update,Delete,etc.
Resource:  Page,Post,User, etc.
* For simplicity, we can represent operations and resource with simple strings.

Permission: A permission is an allowable "Operation" on a "Resource"

Role: A Role is just an abstraction of a set of possible "Permissions"
*/

// I could see this as a Permission model in mongo
var adminPerms = {
      create: ['pages','posts', 'users'],
      update: ['posts','pages','users'],
      update_others: ['posts','pages'],
      delete: ['posts','pages','users'],
      read:['pages','posts','users']
};

// I could see this as a Role model in mongo
var admin = {
  perms: adminPerms
};

var writerPerms = {
      create: ['pages','posts'],
      update: ['pages','posts'],
      update_others: [],
      delete: [],
      read:['pages','posts']
};
var writer = {
  perms: writerPerms
};

// Now we can just see if that user's perms has the operation on resource defined
function hasPerms(user, operation, resource) {
    var i, len, op;

    if(!user || !user.role || !operation || !resource) return false;

    if(typeof rolemap[user.role] !== 'undefined' && 
        typeof rolemap[user.role]['perms'] !== 'undefined' &&
        typeof rolemap[user.role]['perms'][operation] !== 'undefined') {

        op = rolemap[user.role]['perms'][operation];
        for(i=0, len=op.length; i<len; i++) {
            if(op[i] === resource) {
                return true;
            }
        }
    }
    return false;
}

var rolemap    = {"admin":admin, "writer":writer}    
var user_admin = {name:'Rob Levin',  role:'admin'}
var user_jack  = {name:'Jack Black', role:'writer'}

hasPerms(user_jack,  'create', 'users')
// false

hasPerms(user_admin, 'create', 'users')
// true

hasPerms(user_admin, 'update_others', 'posts')
// true

hasPerms(user_jack, 'update_others', 'posts')
// false

EDIT: Assume that roles must be editable on a per app basis so I might want to allow admin users to control access rights; this is why I want to use a database.

Regarding doing it all inside the application that won't work given the requirement to persist and possibly change. However, one compromise in this direction is I could just the role collection:

db.role.find({name:'writer'}).pretty()
{
    "_id" : ObjectId("4f4c2a510785b51c7b11bc45"),
    "name" : "writer",
    "perms" : {
        "create" : [
            "posts",
            "pages"
        ],
        "update" : [
            "posts",
            "pages"
        ],
        "update_others" : [ ],
        "delete" : [ ],
        "read" : [
            "posts",
            "pages"
        ]
    }
}

And than I could make changes like removal, etc., like the following (assuming I already have a reference to a role object retrieved from mongo at point of call):

function removePerm(role, op, resource) {
  if(!role || !role.perms || !role.perms[op]) {

    console.log("Something not defined!");
    return false;
  }
  var perm = role.perms[op];
  for(var i=0, len=perm.length; i<len; i++) {
    if(perm[i] === resource) {
      perm.splice(i,1);
      break;
    }
  }
}
like image 880
Rob Avatar asked Feb 27 '12 17:02

Rob


2 Answers

I recently used mongoosejs with a user/roles/permissions need I had with Drywall.js - the actual permissions are key/value based. They can be shared by group and also overridden granularly on the administrator level.

Although it's not exactly RBAC I'm hoping that reviewing another approach helps you get closer to achieving your goal.

Project Overview:

  • http://jedireza.github.com/drywall/

Mongoose Schemas:

  • https://github.com/jedireza/drywall/tree/master/schema

Specifically Look At:

  • /schema/User.js
  • /schema/Admin.js
  • /schema/AdminGroup.js
  • /schema/Account.js

I'm interested to see what you come up with.

like image 140
jedireza Avatar answered Nov 09 '22 03:11

jedireza


Your design is almost entirely dependent on the behavior of your application. What I would recommend based on the information you've provided here is to keep the data in key/values, not in the database--CRUD operations aren't going to change, so there's no reason to put that in the db. The 'resources' are classes that you've already built into your code, so you don't need to duplicate it in the db either.

like image 45
Barrie Avatar answered Nov 09 '22 02:11

Barrie