accesscontrol
accesscontrol copied to clipboard
Get possible actions for role(s)
Hi !
I'm looking for a well-designed solution for showing possibles actions to users.
For example with 2 roles, "admin" and "user" :
var grants = {
admin : {
task : {
"create:any" : ['*'],
"read:any" : ['*'],
"update:any" : ['*'],
"delete:any" : ['*'],
}
},
user : {
task : {
"read:any" : ['*']
}
}
};
var ac = new AccessControl(grants);
When someone load the list of tasks, I would like to pass an object of "possible actions doable based on the role or roles of this person".
Probably something like :
var role = ["user"];
var actions_possible = ac.getGrants(role);
which returned something like :
task : {
"read:any" : ['*']
}
Then I can show only the view button to a "user" and view/edit/delete/create to an "admin"
With this simple example, I could just do a "getGrants()" and parse the result, but when a user have multiple roles, with some of them are inherited from others, it could be really complicated to do so.
So simple questions, am I missing something in the documentation ? And based on the implementation of this library, is it possible to create such a function (inside the library or outside in my own code).
Thank you very much and great job, very nice library to use ;-)
Hi. I think that's a pseudo/hypothetical example but to be clear; .getGrants()
gets the full, inner grants model. It doesn't take any arguments. That'll not work for your purpose.
Currently AC does not have such method but let's walk through this:
You need to watch for 3 things, at least:
- Actions on which resource?
- Possession of the resource (own or any): If your application makes use of possessions, you must include this. For some roles, some actions may only be possible on own resources.
- Role inheritance (as you mentioned): Since inheritance is done by reference, whatever grants model you'll have will not include all the resources and actions explicitly on the object. So you should use the built-in permission query methodology. Otherwise, this will get complex very quickly.
So, a quick implementation would be this:
// `role` can be an array if the target user being checked, has multiple roles assigned.
function getGrantedActionsFor({ role, resource, possession = 'any' }) {
const q = {
role,
resource,
possession
};
// storage for possible actions
const actions = [];
// check permission for each action and add to array
q.action = 'create';
if (ac.permission(q).granted) actions.push('create');
q.action = 'read';
if (ac.permission(q).granted) actions.push('read');
q.action = 'update';
if (ac.permission(q).granted) actions.push('update');
q.action = 'delete';
if (ac.permission(q).granted) actions.push('delete');
return actions;
}
When we use your grants object;
console.log(getGrantedActionsFor({
role: 'admin',
resource: 'task'
}));
// » ["create", "read", "update", "delete"]
console.log(getGrantedActionsFor({
role: 'user',
resource: 'task'
}));
// » ["read"]
Here is a demo on runkit.
But be careful,
- This returned array does not indicate that each action can be performed freely by this role. Always check (and filter) the attributes.
- If using possessions, you should explicitly check for actual resource ownership.
Hope this helps. I'll consider this for a feature request.
That's the idea. I will need to add a loop to check for every ressources i have. getRessources() will do the job for that. (or passing an array of ressources i want to check) Thank you very much !
Ok i find my way and this is working for me
var getGrantedActionsFor = function({ roles, resources = ac.getResources()}) {
if (!Array.isArray(roles)) roles = [roles];
if (!Array.isArray(resources)) resources = [resources];
const actions = ['create','read','update','delete'];
const possessions = ['own','any'];
const list = {};
var role = roles;
for (var i = 0 ; i < resources.length ; i++){
var resource = resources[i];
list[resource] = {};
for (var j = 0 ; j < actions.length ; j++){
var action = actions[j];
list[resource][action] = {};
for (var k = 0 ; k < possessions.length ; k++){
var possession = possessions[k];
const q = Object.assign({}, {
role,
resource,
action,
possession
});
list[resource][action][possession] = ac.permission(q).granted;
}
}
}
return list;
}
Not specifically the proper way to do it, but i use this only to show or hide specific buttons to user. I always check before the action is done if ac rules are OK (ownership, etc ...)
Thanks for your help
Glad to help.
Ok, here are my notes:
-
I've slightly changed the code in my previous post. We're not mutating anything since we get individual parameters of the function (destructing object parameters). Also in you code, you can safely remove
Object.assign()
statement and just doconst q = { role, resource, action, possession };
-
(To be clear) Remember that you should pass multiple roles (as an array) to this function, only if the actual user has multiple roles assigned. In other words, this function should not be used to gather resource/actions for multiple separate roles at once.
-
The returned value of the first example in my post (array of possible actions) is safe for client side. But this last version in your example may not be that safe. This partially exposes your grants model (resources and permissions) to the client. Especially without a conditional; that default value
resources = ac.getResources()
returns all resource names even if the user has no privilege. A better way would be to omit the item if the role has no privilege:
const actions = ['create', 'read', 'update', 'delete'];
const possessions = ['own', 'any'];
function getGrantedActionsFor({ roles, resources = ac.getResources()}) {
if (!Array.isArray(resources)) resources = [resources];
const list = {};
resources.forEach(resource => {
actions.forEach(action => {
possessions.forEach(possession => {
const perm = ac.permission({
role: roles,
resource,
action,
possession
});
// only including resource, action and possession if the role(s) has privilege.
if (perm.granted) {
list[resource] = list[resource] || {};
list[resource][action] = list[resource][action] || {};
list[resource][action][possession] = true;
}
});
});
});
return list;
}
That's true. Exposing getressources() is problematic. But in my case (it's not in the code I provided), I don't exposed anything client side. The return value by my function is use by templating engine (so, server-side, and Twig in my case), so in fact, the complete list is never exposed. The client only see (or don't see) buttons and stuff based on his roles.
But checking if value is defined or not, rather than taking it and see if it is true or false ; is a way better idea.
That's good design. Server-side template renderers really helpful, in that sense. cheers