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

Permissions #803

Closed
sebmck opened this issue Dec 19, 2014 · 37 comments
Closed

Permissions #803

sebmck opened this issue Dec 19, 2014 · 37 comments

Comments

@sebmck
Copy link
Contributor

sebmck commented Dec 19, 2014

Consolidates #269, #334 Keywords: ACL, Access Control List, rbac, role based access control

@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

@trentmillar
Copy link
Contributor

Is this on the roadmap, and if so when should we expect something more than "isAdmin"?

I am hoping to see role based security down to the field level. With respect this is a shortcoming in such a great project.

@arthurtalkgoal
Copy link
Contributor

Hi, is @wangpingsx standard ways to apply permission settings so far in Admin UI?

The permissions is very important for most projects. Looking fowards to some standard methods.

@wangpingsx
Copy link

@arthurtalkgoal , I haven't use keystone for a while. I have't check the latest version. but so far as i know, there is no standard way to do this.

However, the solution I suggested is kinds of standard way. I didn't use any hack method. what I did was all follow keystonejs architect, nothing special, nothing crazy. If you look into it you will know.

Thanks,
peter

@morenoh149
Copy link
Contributor

@wangpingsx could you share it as a pr?

@SlashmanX
Copy link

I'm starting work on this for my needs. So far after a very small amount of time I've added a filter property to a list that can be a function. This function is called when listing the list and has the context of the req object so it can refer to the user.

For example:

var Post = new keystone.List('Post', {
    filter: function() {
            return {createdBy: this.user._id}
        }
});

Will show the list filtered to only show items created by whichever user made the request. I did this just as a proof of concept as I believe this can now be expanded upon so only certain roles etc can see certain things.

Do you guys think this is worth continuing? Do you see me running into problems down the line?

My use case is that each client should get a login but that user should only see information relating to that client, not the whole system so a

var Post = new keystone.List('Post', {
    filter: function() {
            return {client: this.user.client}
        }
});

would be enough for me, but I can create a PR with a more fleshed out implementation? I suppose I can do a similar thing on a per-field basis too

@julsdelatierra
Copy link

What do you think about this implementation?

User.roles([{
  label: 'superuser',
  value: [{
    collection: 'restaurants',
    permissions: ['read', 'write'],
    filter: 'all'
  }]
}, {
  label: 'owner',
  value: [{
    collection: 'restaurants',
    permissions: ['read', 'write'],
    filter: 'mine'
  }]
}, {
  label: 'chef',
  value: [{
    collection: 'orders',
    permissions: ['read'],
    filter: 'mine'
  }]
}]);

@Charuru
Copy link

Charuru commented Oct 24, 2015

@JulianCeballos Looks good.

@webteckie
Copy link
Contributor

@JulianCeballos yeah, as far as configuration, that looks good to me as well. What is label though...is that the user id? Would it make more sense to think of roles per group? Then it would force users to be part of some group. Also the permission set would probably need expansion.

@webteckie webteckie mentioned this issue Oct 25, 2015
@julsdelatierra
Copy link

@webteckie is an identifier for the permission. I agree with your idea about the use of groups to attach roles.

@riyadhalnur
Copy link
Contributor

is it possible to toggle the noedit flag on fields at runtime? that can be an easy way to set up basic roles and permissions for the time being.

@SlashmanX
Copy link

If anyone is curious has to how I set my instance up, I simply added the following in admin/routes/list.js

    var tmpListFilter = req.list.options.filter;
    var listFilter;
    if(typeof tmpListFilter=== 'function') {
        listFilter = req.list.options.filter.bind(req)();
    }
    else {
        listFilter = tmpListFilter;
    }
    queryFilters = _.extend(queryFilters, listFilter);

    if(typeof req.list.get('nodelete') === 'function') {
        req.list.set('nodelete', req.list.get('nodelete').bind(req)());
    }

    if(typeof req.list.get('noedit') === 'function') {
        req.list.set('noedit', req.list.get('noedit').bind(req)());
    }

    if(typeof req.list.get('nocreate') === 'function') {
        req.list.set('nocreate', req.list.get('nocreate').bind(req)());
    }

    if(typeof req.list.get('hidden') === 'function') {
        req.list.set('hidden', req.list.get('hidden').bind(req)());
    }

No perfect by any means, but it means I can do this:

var Movie = new keystone.List('Movie', {
    map: { name: 'title' },
    nodelete: function() {
        return !this.user.role.canWriteMetadata;
    },
    nocreate: function() {
        return !this.user.role.canWriteMetadata;
    },
    noedit: function() {
        return !this.user.role.canWriteMetadata;
    },
    hidden: function() {
        return this.user.role.canReadMetadata;
    }
});

And:

var Client = new keystone.List('Client', {
    filter: function () {
        return {_id: this.user.client}; // User can only see their own client
    }
});

Note: They are simple examples I've created while testing out this functionality

Hidden option requires an additional code change in /lib/core/routes.js in initList

if(typeof req.list.get('hidden') === 'function') {
                req.list.set('hidden', req.list.get('hidden').bind(req)());
            }

@morenoh149 morenoh149 mentioned this issue Dec 3, 2015
22 tasks
@webteckie
Copy link
Contributor

Another possible implementation I can envision here is as follows:

  1. Define a Role list...just like there is a User list. The Role list would just have a name field:
var Role = new keystone.List('Role', {
    autokey: { path: 'key', from: 'name', unique: true },
    track: true
});

Role.add({
    name: { type: String, required: true, index: true }
})

// See below for Permission List definition
Role.relationship({ ref: 'User', path: 'users', refPath: 'roles' });
Role.relationship({ ref: 'Permission', path: 'createPermissions', refPath: 'create' });
Role.relationship({ ref: 'Permission', path: 'readPermissions', refPath: 'read' });
Role.relationship({ ref: 'Permission', path: 'updatePermissions', refPath: 'update' });
Role.relationship({ ref: 'Permission', path: 'deletePermissions', refPath: 'delete' });
  1. Configure a many:many relationship between User and Role:
User List:
    roles: { type: Types.Relationship, ref: 'Role', many: true }
  1. Just like keystone accepts a user model, it would also accept a role model:
'role model': 'Role'
  1. Now the tricky part is how to assign roles to lists. We can do it in code (in the list definition) but that would force code updates whenever a roles is modified. Instead, I propose that we do it via another keystone-special list (e.g., Permission), since that would allow list permissions to be controlled via the Admin UI. The list field corresponds to a similarly name list in the nav configuration. That way as keystone's Admin UI builds the nav structure it can also assimilate permissions based on the logged in user and his/her assigned roles matched against the list assigned roles:
var Permission = new keystone.List('Permission', {
    map: { name: 'list' },
    track: true
});
Permission.add({
    list: { type: String, required: true, index: true },
    create: { type: Types.Relationship, ref: 'Role', many: true },
    read: { type: Types.Relationship, ref: 'Role', many: true },
    update: { type: Types.Relationship, ref: 'Role', many: true },
    delete: { type: Types.Relationship, ref: 'Role', many: true },
})
  1. Finally, keystone should also be aware of the permission model:
'permission model': 'Permission'

cc @JedWatson

@mattcrum
Copy link

@webteckie thanks for the idea. I'm definitely new to Keystone, but I've tried testing out this implementation but for whatever reason I'm getting a TypeError: this.schema.virtual(...).get is not a function when I try to register the Permission model. Any idea what the issue could be?

@webteckie
Copy link
Contributor

@mattcrum the idea is not implemented, yet :-) working on it on my spare time, which is very little and this no easy task :-) hoping to have something soon for the community to comment on.

@mattcrum
Copy link

@webteckie Sorry for the goof! I was getting pretty hyped on how easy it would have been to get moving.

I've been playing with Keystone for the first time for a prototype that will require an Organization model which would contain users and those permissions would cascade down to the user (but can be changed at the user level as well). Great that you guys are thinking about these things and very much appreciated.

@webteckie
Copy link
Contributor

@mattcrum no worries stay tuned for updates!

@morenoh149
Copy link
Contributor

I just found out there are A LOT of acl and rbac implementations on npm https://www.npmjs.com/search?q=acl
https://www.npmjs.com/search?q=rbac
we could probably drop one in

@WillowHQ
Copy link

Just wondering where this is at on the roadmap, bc it's key to what I'm building and would increase the value of keystone by an order of magnitude.

@larsha
Copy link

larsha commented Feb 16, 2016

Also interested in knowing progress on this, right now we have implemented @wangpingsx suggestion, using middleware to check permissions before rendering the admin views.

@morenoh149
Copy link
Contributor

@larsha I think @WillowHQ is taking a shot at merging the rbac module to see if it could work.

@meelis79
Copy link

meelis79 commented Apr 8, 2016

Is there plan to include permission/role functionality in near future?

@mxstbr
Copy link
Collaborator

mxstbr commented Apr 8, 2016

Yeah, soon!

@adewalegeorge
Copy link

adewalegeorge commented Apr 18, 2016

I'm pretty new to Keystone, been using it for basics and performance has been awesome but now I want more and reading through all thats been explained here, I believe this is worth giving a try.

I'm currently developing an app and want to use a custom user model(not the shipped Keystonejs one). I have created this and associated all necessary Admin UI fields

BattleAxe.add({
    name: { type: Types.Name, required: true, index: true },
    email: { type: Types.Email, initial: true, required: true, index: true },
    mobile: { type: Types.Number, initial: true, index: true, format: false },
    reportPin: {
        type: Types.Text,
        default: Math.random().toString().substr(2,4),
        index: true,
        noedit: true
    },
    account: {
        verified: {
            type: Types.Select,
            label: 'Verify status',
            options: [
                { value: 0, label: 'Unverified' },
                { value: 1, label: 'Verified' }
            ],
            emptyOption: false,
            default: 0,
            index: true
        },
        enable: {
            type: Types.Select,
            label: 'Active status',
            options: [
                { value: 0, label: 'Disabled' },
                { value: 1, label: 'Enabled' }
            ],
            emptyOption: false,
            default: 1,
            index: true
        }
    }
});
BattleAxe.relationship({ ref: 'Report', path: 'reports', refPath: 'author' });
BattleAxe.defaultColumns = 'name, email, mobile, reportPin, account.verified, account.enable';
BattleAxe.register();

Will it be possible to have a user registration form and automatically assign role and allow user post into my Report model?

Thanks in advance.

@mxstbr mxstbr added this to the 1.0.0 milestone Apr 29, 2016
@jstockwin
Copy link
Contributor

tl;dr it would be amazing if we could implement complex permissions based on relationships and the state of fields in the models.

Four things I'd like to add on this:

  1. You've probably covered this, but I think the biggest problem at the moment is that allowing someone to edit the blog means they can remove me as a user from the website. Obviously this is going to be fixed, even then I feel that admin users shouldn't be able to remove me as a user. I'd suggest that this comes with some super admin user by default, which very few people have. You'd assign this role only to people high up the chain. Then, admin user should be able to do everything they can currently do, apart from edit super admins passwords. I think this should be easy to implement given the above, but was a little unsure as whether a user can edit another users passwords depends on BOTH their permissions levels (i.e. an admin can update an member, but not a super admin).

  2. Similarly, all that's been discussed above are adding read/write permissions. Keystone ships with a really nice default blog with draft and published states. For me, it would be amazing if I could have reporters who can edit DRAFT blog posts only, but are unable to make them publishes. The draft posts would then have to be checked by an editor before they are then published.

  3. By default, users should be able to update their own details. At the moment, we are having to create a page with a form for them to do this if they are not admins.

  4. Purely an example setting, but some users on my website are committee members. These committee members have a page which has a picture and a description of them. I have a committeeMember model for this. It would be good if linking a user to a committee position (using a relationship field) then allowed the user to be able to edit their corresponding committeeMember model, but not the other comittee members posts. Obviously this is quite specific, but if permissions are implemented so that this is possible, I think it opens up many possibilities compared to just allowing read/write for each model.

Not sure if this would already work given the above or if it's a bit of an extra effort. It's a little more specific than just giving read/write access to a field. I feel this is quite important and there are lots of examples of applications.

@webteckie
Copy link
Contributor

webteckie commented May 3, 2016

@jstockwin re: item 1, yes, any approach should account for that use case. Re: 2, that seems like some sort of workflow problem, which not sure fits here. Re: 3, any approach taken should let you do that. Re: 4, I think for specific use cases like this you would do via the rbac or acl api.

@niallobrien
Copy link

Is there an update on this? Thanks.

@niallobrien
Copy link

Huh, still no update?

@bassjacob
Copy link
Contributor

Hi @niallobrien,

no update on this issue specifically, other than to say that it's still a priority we're hoping to address in the 4.0 release or as a feature release soon after.

I see you've commented on the open PR on the test-project. Other than that work, I don't think this has progressed past that although we're happy to hear any feedback on that implementation?

@afilp
Copy link

afilp commented Nov 14, 2017

Any more progress on this? Thanks!

@acmoune
Copy link

acmoune commented Aug 7, 2018

I don't know what you are looking for, but for me there is only one thing missing in the current implementation.

Keystone already support permissions with this:

const User = new keystone.List('User')

User.add({
  name: { type: Types.Name, required: true, index: true },
  email: { type: Types.Email, initial: true, required: true, unique: true, index: true },
  password: { type: Types.Password, initial: true, required: true },
}, 'Permissions', { // << That is it
  isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
  isRoot: { type: Boolean, label: 'Administrator', index: true, dependsOn: { isRoot: true} },
  isManager: { type: Boolean, label: 'Channel manager', index: true, dependsOn: { isRoot: true} }
})

So you can consider them as roles: Admin, Root, Manager ...

For me the missing part is that I would like to be able to do this:

const Model = new keystone.List('Model', {
  noedit: function() { ... },
  nocreate: function() { ... },
  nodelete: function() { ...},
  hidden: function() { ...}
})

If it could be possible to use a function instead of a boolean to set noedit, nocreate ...
and if that function can be bound some how by Keystone to an object containing the logged in User instance, and the actual model instance (when applicable, for example in noedit and nodelete), so in that function I can do this.user.xxx and this.model.xxx, then I think we should be done with all this.

Is that possible ?

@VinayaSathyanarayana
Copy link

@acmoune Instead of isAdmin, isRoot, isManager should we not have a Select Field say "userRole" with option to choose the role?

@gautamsi
Copy link
Member

Keystone 4 is going in maintenance mode. No major changes expected. see #4913 for details.

v5 has more advanced access control which can be extended easily. see Access Control API

@alokkumarjha
Copy link

Any update on access contol , i want some user to have an access of read,create,edit and delete, but some have only on read and create

@jossmac
Copy link
Member

jossmac commented May 8, 2020

@alokkumarjha, please see @gautamsi's comment.

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

No branches or pull requests