Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User Roles for Admin UI #334

Closed
britztopher opened this issue May 9, 2014 · 22 comments
Closed

User Roles for Admin UI #334

britztopher opened this issue May 9, 2014 · 22 comments

Comments

@britztopher
Copy link

Currently there is only one role for AdminUI, where isAdmin==true. On the roadmap there is this:

"Support more sophisticated permissions in the Admin UI through some combination of a built-in roles / permissions architecture, and a plugin architecture for custom permissions systems"

I have the need for user roles in Keystone, so I was wondering if anyone has any kind of input on how it should be designed? Im looking to help, and just want to collaborate with the community on design and implementation.

Right now I have a dropdown on the User model that saves a role for a user to have. Once assigned an Admin role then in initNav function in /index.js file I check to make sure the currently logged in user has admin role set. IMHO, I dont see any other way around checking roles at specific times to make sure they have the authority to do those options. This however, limits the flexibility of keystone and allows only a default set of roles. Anyways, that is why I ask the community for suggestions/comments.

Maybe some kind of middleware that deals with authorization might work.

@talon
Copy link
Contributor

talon commented May 11, 2014

I'd imagine you'd have to use Jade to render the content based on roles that you set and also persist the roles in the backend.

ex.

if user.role === 'editor'
  p you can edit!
  input(type='text')

I don't exactly know where keystone inserts content from the admin screen to the database but you'd want to do a check there as well.

Seems like fairly hefty task to implement and add UI but very useful when completed.

@danielhanlon
Copy link

This would be very interesting to me - I hope I've understood correctly in that you'd be interested to hear about possible use cases for this type of functionality...

I've been experimenting with using Keystone.js to build a project registration system which would involve a user registering (accepting some terms and conditions) and then creating a Project entity made up of various fields including project members that are other users. It would be useful for the user registering the project to retain the ability to modify the projects that they have registered, but the Admin user would have an overall view of all Projects.

It is the fact that the creators of project would have something like an Admin view of their own projects that creates the need for an extra tier in the architecture.

@webteckie
Copy link
Contributor

My $.02 cents:

How about keystone defining a set of standard roles (I suppose flexible enough so the keystone users can augment or customize) and then allowing to set a default set of permissible roles for the model and perhaps allow each field to set/override the model object with its own set of roles:

var Post = new keystone.List('Post', {
allow: {
create: [role1, role2],
read: [role1, role2, role3],
update: [role1, role2],
delete: [role1, role2]
}

});

Perhaps:

Post.add({
field: { type: X, allow:{[]} },
...
});

Jade constructs can then be used to render the model correctly by pre-processing the model's "allow" configuration.

@talon
Copy link
Contributor

talon commented May 12, 2014

I was under the impression that the roles were strictly for the admin view however if that's not the case it's very simple for someone to create their own user role with a route middleware and a user model with a role property.
To completely limit access to a page:

// routes/middleware.js
exports.roleAuth = function (req, res, next) {
  if (!req.user.role === 'authorized') res.redirect('/login');
  next();
}

Then use this middleware like so:

app.get('/authorized', roleAuth, function (req, res) {
  // render the view or whatever.
});

Or if you just want them to see specific things on a page whether they have a role or not

if user.role === 'winner'
  p authorized content only
else
  p losers don't get to see the good stuff

just update your user model to have these role options however you want

var keystone = require('keystone')
  , Types = keystone.Fields.Types
  , User = new keystone.List('User');

User.add({
  name: {type: Types.Name, required: true, index: true},
  role: { 
    type: Types.Select,
    options: 'winner, loser, authorized', 
    required: true
  }
});

@Pinpickle
Copy link

@LegitTalon Letting the roles come from Keystone will allow the cleanest integration between front end roles and Keystone UI roles, which I think would be a must have for many apps.

@britztopher
Copy link
Author

@Pinpickle I totally agree with you that it needs to "come from keystone", as this will allow the functionality tied with the keystone product rather than just the AdminUI. Basically, allow the users of keystone the ability to define their own roles, and if they choose not to use the AdminUI they would still have roles functionality too.

@LegitTalon makes some good suggestions into how to accomplish this using the AdminUI by just including a Select with a set of default roles that one can select and by using custom middleware to check authorization and then use a route to get that info.

Is there a way to marry both ideas? Like have a custom route and middleware defined for authorization within keystones core, and have an option to set roles in keystone options that would allow the user to define those types of roles and what lists they can perform CRUD on? If thats possible, how do we check dynamically on keystone views what a user can view/edit dynamically with the roles given in the options?

@talon
Copy link
Contributor

talon commented May 12, 2014

@britztopher Yeah it would just be a matter of working what I scaffolded out above into core. Same logic.

Add roles to the user model

var keystone = require('keystone')
  , Types = keystone.Fields.Types
  , User = new keystone.List('User');

User.add({
  name: {type: Types.Name, required: true, index: true},
  role: { 
    type: Types.Select,
    options: 'winner, loser, authorized', //put whatever roles you want 
    required: true
  }
  // ...
});

Add roles to other models

var keystone = require('keystone')
  , Types = keystone.Fields.Types
  , Gallery = new keystone.List('Gallery', {roles: [/* match these to the user model */]});

Gallery.add({
  name: {type: Types.Name, required: true, index: true},
  // ...
});

Build admin menu based on permissions and roles

extends ../layout/base

block intro
    .page-header
        h1 Manage

block content
    if nav.flat
        each list in lists
            if (!list.get('hidden') && list.get('role')[user.role])
                h3: a(href='/keystone/' + list.path)= list.label
// ...

roles might need to be extracted out into a class? This could maybe keep the code clean/organized.

keystone.roles.add('editor');
// persist the roles to the database and add them to the defined user model automatically?

@britztopher
Copy link
Author

@LegitTalon i was thinking of having keystone.roles = {}. Then upon initialization we can add those to mongo as user roles collection. Then when lists are created then maybe on a field by field basis we add roles that reference the User model roles object for those that are editable rather than do the whole list. Jed did make a comment about adding this type of field validation on #323. What do you think?

The only thing I see fishy here is saving roles in db because when the roles change this collection will need to be updated too so the collection will need to be checked for changes and if they have changed update the collection.

@talon
Copy link
Contributor

talon commented May 12, 2014

Add a relationship between roles and collections and have pre save hook on roles to modify all affected collections?

@jamlen
Copy link
Contributor

jamlen commented May 13, 2014

Just for info I created #106 which has some of the use cases I require. They are in the Given/Then/When syntax which might be helpful, rather than thinking about how to change the code.

@nicolasembleton
Copy link

That might be totally off, but you guys ever considered incorporating Drywall to Keystone? https://github.com/jedireza/drywall/
It has a solid User Management system. It's actually what they do. And the tech stack is very similar (aside the Keystone layer). That would be such a huge win-win. You could even have ways to load the current User logged in the Admin through their APIs.

@talon
Copy link
Contributor

talon commented May 20, 2014

Drywall looks cool but I can't find anything on documentation. I guess it comes down to how easy it would be to integrate the two APIs.

@jamlen
Copy link
Contributor

jamlen commented May 20, 2014

I agree that Drywall looks epic, but I also agree with @LegitTalon that some documentation for how to integrate this into an existing system would be useful!

@digitalcraft
Copy link

Jed,

Sorry about the roughness of this.

in the model's I have something like this.

// Route Rules
Company.rules = {
'*': 'some_rule',
'list': 'view_companies'
};

// I have a Role model
Role.add({
name: { type: String, required: true, index: true },
permissions: { type: Types.Relationship, ref: 'Permission', index: true,
initial: true, required: true, many: true }
});

// I have a Permission Model
Permission.add({
name: { type: String, required: true, index: true }
});

// I have some middleware which I use on API routes
exports.modelAuth = function(_index) {
return function(req, res, next) {
var method = (_index) ? 'list' : req.method;
var rules = req.list.rules;

//if the permission is defined but its not present for the user
//check if a default rule exists, check if the user has the default

permission
if (rules) {
var rulesUndefined = !rules[method] && !rules[''];
var hasMethodPermission = rules[method] &&
req.permissions[rules[method]];
var hasStarPermission = rules['
'] && req.permissions[rules['*']];
var hasPermission = rulesUndefined || hasMethodPermission ||
hasStarPermission;

  if (!hasPermission) {
    console.log(method + ' not allowed on ' + req.params.list);
    return res.apiError(403, method + ' not allowed on ' +

req.params.list);
}
}

next();

}
}

// I've made some mods to keystone to not use cookies for auth and instead
use tokens
// I have this implemented with express-jwt, it works amazeingly well with
angular and mobile-apps
// there is some other code you need but this gives you a good idea on how
i'm doing roles and permissions with keystone.
// would love to see this make its way in, I was going to send a PR after
CampJS.
digital8.pre('routes', {
path: '/api',
fn: expressJwt( { secret: 'secret', skip: ['/login', '/logout'] } )
});

app.pre('routes', {
path: '/api',
fn: function(req, res, next) {
//dont break the middleware
var _next = function() { next(); };

if(req.user && req.user._id) {
  req.permissions = req.user.permissions;
  app.session.signin(req.user._id, req, res, _next, _next);
} else {
  _next();
}

}
});

regards,
Jeff Lynne

On Wed, May 21, 2014 at 4:26 AM, James Allen [email protected]:

I agree that Drywall looks epic, but I also agree with @LegitTalonhttps://github.com/LegitTalonthat some documentation for how to integrate this into an existing system
would be useful!


Reply to this email directly or view it on GitHubhttps://github.com//issues/334#issuecomment-43664554
.

@nicolasembleton
Copy link

Yes I'm totally in line with you.

I'm curious if it would be possible to either merge the 2 contents, or to be more like 'service oriented' with a dedicated authentication server that would send data through specific apis, with a separate UI for management.

It would have the awesome benefit of allowing fine grained access to each different module without tight coupling in the code. Would also allow for a SSO feature.

But I guess I'm diverging. Just sounds simpler that way but would generate lots of problems I'm not seeing I suppose...

How would you guys rather approach that? What is the level of flexibility that is targeted? A fine-grained, SharePoint-like Roles management with granular action rights and cascading, or a broader grained management like say WordPress?

@nicolasembleton
Copy link

If necessary I can try to take a spin at the integration from a service perspective and see how it goes? Because I'm quite new to KeystoneJS I can pair with someone who might know better?
Then we can reflect and see whether that would work at all and then either try from a merged integration perspective and/or purely reimplementing the whole thing. Let me know.

@Alexis-benoist
Copy link

@LegitTalon : Is your proposition working now or is it a suggestion about what should be done?

I'm wondering in which file is supposed to go "Build admin menu based on permissions and roles".

Thank you in advance for your answers.

Edit : I found that the file to modify is node_modules/keystone/templates/views/home.jade

@adamscybot
Copy link

@digitalcraft Came across your post when searching for a keystone + JWT solution (using keystone to power a REST API). Would you be willing to share all the changes required for this? I could even help by publishing this as a new module.

@peteruithoven
Copy link

I'd like to share how Drupal handles this. Not sure if you guys appreciate this, I understand you don't want to recreate Drupal but maybe it can serve as inspiration. I'm for one, very impressed by what you guys are creating, for one there is a lot less "black magic" than Drupal. Because it uses so many existing, known platforms I found very easy to learn (and very flexible).

Drupal has Roles and per Role several permissions. One benefit of this separation is that custom packages can describe permissions, and don't have to care about the specific roles developers use. Users can have multiple roles.
An example:

  • admin: all of moderator and:
    • edit admin ui menu
    • edit homepage
    • ...
  • moderator: all of member and:
    • delete all posts
    • edit all posts
    • ...
  • member; all of visitor and:
    • add posts
    • edit own posts
    • ...
  • anonymous visitor:
    • view menu
    • view published content
    • ...

Then in the admin you have:

  • A role edit page where you can add, edit and reorder roles. Reorder enables easy (optional) "inheritance" of permissions.
  • A permissions edit page. This is a checkbox matrix with vertically all the available permissions and horizontally all the roles. When you check a permission for a role all the roles behind it also get it checked (the inheritance trick). You can still uncheck these individually.
  • A user edit page where you can select which roles apply for the user.

One other trick Drupal does is that it automatically adds permissions like the following per available model type:

  • Create new
  • Edit own
  • Edit any
  • Delete own
  • Delete any

@peteruithoven
Copy link

Maybe you can send a list of the permissions the current uses has to the template, using a small core middleware function. When someone wants something else he can always register his own middleware and override something like res.local.permissions.

@JedWatson
Copy link
Member

Hey guys,

thanks for the suggestions - let's pick it up over at #803, which has been created to consolidate discussion about this development.

@wangpingsx
Copy link

Hi,

I got a solution without changing the source code of keystonejs.


. The supper admin functionality: (only supper admin can create, delete users, common admin users can only change themselves)
Analysis with solutions: http://baiduhix.blogspot.co.uk/2015/02/keystone-more-deeper-supperadmin.html
The implementing code: http://baiduhix.blogspot.co.uk/2015/02/keystone-more-deeper-supperadmin-only.html


. Also If you want to add authorization for tables (collections): ( Access Control / Roles)
http://baiduhix.blogspot.co.uk/2015/02/keystone-more-deeper-authorization-role.html


Let me know if you have any issues.

Thanks,
Peter

@keystonejs keystonejs locked and limited conversation to collaborators Feb 4, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests