chaps-io / access-granted Goto Github PK
View Code? Open in Web Editor NEWMulti-role and whitelist based authorization gem for Rails (and not only Rails!)
License: MIT License
Multi-role and whitelist based authorization gem for Rails (and not only Rails!)
License: MIT License
Looking at this line from documentation at https://blog.chaps.io/2015/11/13/role-based-authorization-in-rails.html
Roles inherit from less important roles below them, but only from roles which apply to the given user. So we only need to add additional permissions on top of them. In this case, we will let members create posts and topics:
I have not been able to get this backward inheritance to work. I have something similar to this in my config.
class AccessPolicy
include AccessGranted::Policy
def configure
role :admin, { role: 'admin' } do
# can :view, Admin::DashboardController
end
role :staff, { role: 'staff' } do
can :view, Admin::DashboardController
end
role :view_only, { role: 'view_only' } do
end
end
end
However calling can? :view, Admin::DashboardController only returns true for the :staff role, and returns false for the higher level :admin role. Unless I uncomment the line in the :admin block.
I'm using an access policy in my rails 4 app that checks the user role, very similar to the example here.
role :moderator, proc { |u| u.moderator? } do
can [:update, :destroy], Post
can :update, User
end
role :guest do
can :read, :all
end
I got an error when there are no user logged in, because the call u.moderator?
expects an user and u
is nil
.
To fix this error I needed to override the initialize
method:
def initialize(user)
user ||= User.new
super user
end
My question is: do I need to check when there is an @user
in my policy or do I did something wrong applying the gem?
If I need to check the @user
i think that would be great update the readme with this info.
It would be nice not having to call authorize! in every controller method. Do you have any plans to include this functionality?
I'm curious, how much work/how complicated is it?
I might be out of my depth, but I'm willing to give it a crack if you think it's useful, and if I can get some guidance.
Hi.
We've been using this gem with rolify, and encountered a problem with performance. The thing was, we were doing a lot "can?" on one page. And by a lot I mean a LOT. It took 7 seconds to load everything every time we were reloading the page. The thing is, most of the queries where duplicated, so we could cache them, instead of loading them from DB (DB caching seems not very efficient). We have solved the problem by overriding the "can?" method like this:
def can?(action, subject = nil)
@user.can = {} unless @user.can
key = "#{action}_#{subject.try(:to_s)}".to_sym
return @user.can[key] unless @user.can[key].nil?
roles.each do |role|
next unless role.applies_to?(@user)
permission = role.find_permission(action, subject)
if permission
@user.can[key] = true
return permission.granted
end
end
@user.can[key] = false
false
end
Caching feature would be a great thing. In our case, the code above increased performance on the given page from 7 sec load time, to 2 sec load time. Of course it depends on the amount of data, more data, bigger improvement, on the other pages it is not noticeable at all. It's downside is the fact, that we are caching it inside a user, which doesn't seem like the best idea. Changing accesses while user is logged in will have no effect, unless we force him to relog. Also it is a security danger, if user finds a way to somehow write something to the "can" attribute, he will gain access, and we will not be checking again if he really does have an access.
Anyways, that's what happened, I just wanted to share what we did. Great work by the way.
Hello.
I have such policy:
can [:read, :update, :destroy], User do |user, merchant_admin|
user.merchant.present? && created_user_is_of_creator_merchant?(merchant_admin, user)
end
and check
<% if can? :read, User %>
<li><%= link_to 'Users', users_path %></li>
<% end %>
When I add byebug
to policy block the execution even doesn't get stopped. So, looks like it looks at User
at decides that it's enough.
Only when I comment out the policy definition, the check works.
Hi,
What would be the correct approach to use access-granted with the rails-api gem? In rails-api the ActionController::Base is replaced with a scaled-down version so the controller methods won't be applied automatically by access-granted in https://github.com/chaps-io/access-granted/blob/master/lib/access-granted.rb.
Would a solution be to manually include the module like with cancan (https://github.com/ryanb/cancan/wiki/Rails-API-Gem) or is there any cleaner ways to solve this?
What is the thinking behind ignoring a block and always attempting to evaluate the conditions hash when passed a Class as a subject.
def matches_conditions?(subject)
if @block && !subject.is_a?(Class)
@block.call(subject, @user)
else
matches_hash_conditions?(subject)
end
end
I would always like to evaluate a block if it is present (I've got some user related logic I need to execute to determine whether to allow access)
Would it be acceptable to change the logic so that blocks are always evaluated if present?
This relates to #32, but since it's been a couple years I thought it was worth opening a new issue.
I would LOVE it if access-granted could have a special "role" that is checked for nil current_user.
With the advice in #32, you can create a special NullUser, which is not so so bad, but you do have to keep it in sync with any possible methods on your real user you may reference in a role condition lambda.
I think there could be a pretty simple implementation in access-control that would save you from boilerplate code requiring ongoing maintenance -- for what I'd think would be a fairly common use case, I'm kind of surprised the authors and current users of access-granted haven't needed it.
If current_user is nil, assume no roles match, for present roles, short circuit it and don't try on nil.
But add some way to declare a "role" that looks just like current role definitions, but applies always and only when current_user is nil. It might be easiest to simply add new syntax:
nil_user_role do
can :read, Post do |post|
post.public?
end
end
How can I define rule for a subobject extends object rule.
es.
can :delete, Object do |obj, user|
post.author_id == user.id
end
can :delete, Subobject do |sub, user|
can? :delete, sub.parent && sub.id>3
end
I've got a configure block defined in a pretty standard way:
role :admin, proc { |user| user.is_admin } do
can :manage, Post
end
When I view the page as a non logged in user though I get an error:
undefined method `is_admin' for nil:NilClass
I must be missing something pretty simple -- unless it depends on authentication before authorization? Little help please?
Hi,
Had some issues yesterday setting up this gem in my rails app. Seems like the latest version that is published on rubygems (0.1.1) doesn't include the rails lib. However I managed to solve this by fetching the directly from the git repo so maybe the docs needs to be updated or 0.2 should be published to rubygems.
Thanks!
I'm doing a simple permission check in my view file like <% if can? :create, Team %>
and I'm getting an ArgumentError for 'wrong number of arguments (0 for 1)'
here is the full block:
<% if can? :create, Team %>
<%= link_to 'New Team', new_team_path %>
<% end %>
I am getting an undefined method can?
when attempting to use access-granted inside a view component.
https://github.com/github/view_component
Any ideas/suggestions in order to get this to work?
CanCanCan has a nice shortcut for managing all models: can :manage, :all
and it would be great if this gem had that too, or something similar. I noticed a quick fix that would allow something like this to work:
can :read, ApplicationRecord
In permission.rb
:
def matches_subject?(subject)
subject == @subject || subject.class <= @subject
end
Adding .class
onto the end of @subject
allows the second condition to work successfully. Otherwise, it evaluates to nil
.
def matches_subject?(subject)
subject == @subject || subject.class <= @subject.class
end
@pokonski Hello, Piotr. Something wrong goes with v1.0.4.
Policy file:
role :admin, proc { |user| user.admin? } do
can :access, :direct_user_creation
can :read, User
can :create, User
can :destroy, User
end
role :planner, proc { |user| user.planner? } do
can :manage, Merchant
end
This way, admin cannot :edit
a particular merchant (checking with authorize! :edit, @merchant
) of even :read
Merchants.
I change it to
# ...
role :planner, proc { |user| user.planner? } do
can :read, Merchant
can :edit, Merchant
end
# ...
and still no way for :admin
to :read
or :edit
Merchants
Then I go with:
role :admin, proc { |user| user.admin? } do
can :access, :direct_user_creation
can :read, User
can :create, User
can :destroy, User
can :read, Merchant
can :edit, Merchant
end
role :planner, proc { |user| user.planner? } do
can :read, Merchant
can :edit, Merchant
end
And only this setup allows my :admin
to both :read
and :edit
Merchants.
Can you please check what could be the reason? I can provide any additional details that you may require. Thanks.
For instance
@artists = Artist.all
authorize! :read, @artists
Will not work, no matter if can :read, Artist
is specified in the policy. This does work:
@artists = Artist.all
authorize! :read, Artist
But is insufficient for obvious reasons. Each one needs to be checked individually to make sure this collection is viable for this user in this case.
Also, something like this seems like it may work:
@artists = Artist.all
authorize! :read, @artists.first
But if @artists.first == nil
in this case of an empty collection, this will result in access denied.
First Congratulation on your work.
In our application One admin can have multiple roles as listed below. We use role_model
gem.
Roles are defined in admin.rb
roles :account_manager, :customer_support, :data_entry, :sales_rep, :advertising_rep, :management, :tech_support, :super_admin
It works fine with cancan
but I liked your gem a lot. Can this ability.rb of cancan
be replaced in access_policy.rb
. If yes please help me out
class Ability
include CanCan::Ability
def initialize(user)
if user.admin?
can :manage, :all
else
cannot :manage, OperatingExpense
cannot :manage, AttendanceRecord
end
can :read, Admin, group: user.group
can :manage, Admin, id: user.id
if user.manager?
can :manage, Admin, group: user.group
cannot :manage, BlockedLocation
end
can :manage, Shop, admin_group: user.group # renders the account_manager role moot
can :manage, Order do |order|
order.shop.admin_group == user.group
end
# # role-based abilities
if user.has_role? :account_manager, :customer_support, :data_entry, :sales_rep, :advertising_rep, :management
can :manage, Admin do |admin|
admin.id == user.id
end
can :manage, Shop
can :manage, Coupon
can :manage, Comment
can :manage, Order
can :manage, OrderAdjustment
can :manage, ReceivedCall
can :manage, Ledger
can :manage, Payout
can :manage, Ticket
can :manage, Chain
can :manage, User
can :manage, BlockedIpAddress
can :manage, BlockedLocation
end
if user.has_role? :super_admin, :tech_support
can :manage, :all?
end
end
end
README says:
Note: cannot is still available, but has a very specifc [sic] use. See Usage below.
However, no example is actually given of cannot
in Usage or elsewhere. Controller/view method cannot?
is described, but not the quite different cannot
method in permission definitions.
At first I thought cannot
was maybe not actually there, the README reference was wrong or I misunderstood it.
But then I saw it in the specs. Apparently it does exist, hooray!
But the README needs a short explained example as promised. :)
Asking because last update was 7 months ago, last release almost a year ago and some outstanding issues.
I found what I think is an inconvenient inconsistency around inheritance; I also have a simple PR to fix it.
access_granted normally works great with inheritance. Imagine we have a Vehicle
and a Bicycle < Vehicle
. And we have a policy:
class AccessPolicy
include AccessGranted::Policy
role :user do
can :read, Vehicle
end
end
AccessPolicy.new(user).can? :read, Vehicle.new # true, of course
AccessPolicy.new(user).can? :read, Bicycle.new # still true, it works with the sub-class
But access_granted also has a convenient feature where you can check on the class instead of an instance, intended to be used with the meaning of sort of generic/any/all objects, like often for create
, but you can use it for anything:
AccessPolicy.new(user).can? :read, Vehicle # this is true too, okay
That one does NOT work with inheritance:
AccessPolicy.new(user).can? :read, Bicycle # FALSE
# I believe it should be true
I think this is a bug, or would be an improvement if above were true also. I think I have a simple PR to make it so.
Thoughts?
Hey there. I've spotted a couple of cases where behavior doesn't seem logical:
role :admin, proc { |user| user.admin? } do
can :manage, Merchant # can? :edit @merchant is AccessDenied, :read is OK
end
role :admin, proc { |user| user.admin? } do
can [:manage], Merchant # can? :edit @merchant is AccessDenied, :read is AccessDenied
end
role :admin, proc { |user| user.admin? } do
can [:manage, :edit], Merchant # can? :edit @merchant is AccessDenied, :read is AccessDenied
end
❓ ❓ ❓
I propose to add an interface for introspection uses. It can help in different situations. For example, if you need to serialize some object along with current_user's privileges on it now I should carefully copy it from policy one by one. Getting this list dynamically in run-time make things a lot of easier and less error-prone.
Hey Piotr, I read your blog post http://blog.chaps.io/2015/11/13/role-based-authorization-in-rails.html a few minutes ago. Because im not unhappy with CanCan too, I was exited to read how your library works. It looks good, it looks better than CanCan! But one thing I read in your blog post confused me:
:manage is a meta-action borrowed from CanCan, which is just a shortcut for defining all default CRUD actions ([:read, :create, :update, :destroy]).
Because I have had some issues with the :manage action, I already had to look a little bit deeper onto it. A must say that :mage isn't ":read, :create, :update, :destroy". It's more than that. Please take a look at this lines of code:
I have thought that :mange means only ":read, :create, :update, :destroy" for a long time. But it means "all".I can image that there are many other people, who are thinking that. Therefore I wish you wouldn't use :manage. The idea of an shortcut for ":read, :create, :update, :destroy" is great, but maybe with an other name. Maybe with a simple :crud :) How do you think about that point?
Best regards, Michał
This means that you define what the user can do, which results in clean, readable policies regardless of application complexity. You don't have to worry about juggling cans and cannots in a very convoluted way!
Based on the README, my understanding is that access to controller actions should raise an error if not specified in access_policy.rb. That doesn't seem to be the case in my example. I am setting current_user when User has not logged in yet and assigning a role = 'locked'.
Based on access_policy.rb, a user with a role of 'locked' should only be able to read from one specific action, however, in my example the user is allowed to perform any action, it appears that the application_policy is not being applied to this new user, not sure why.
Yes, I am specifying authorize! in each controller action.
application_controller.rb
def current_user
super || User.new
end
user.rb
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= 'locked'
end
access_policy.rb
class AccessPolicy
include AccessGranted::Policy
def configure
role :superhero do
can :manage, Company
can :index, Company
can :manage, Event
end
role :admin do
can :manage, User
can :manage, App
can :manage, Event
end
role :member do
can :index, App
can :index, User
can :index, Event
end
role :locked do
can :read, @page
end
end
end
This is my policy
# The most important admin role, gets checked first
role :admin, proc {|u| u.admin? } do
can [:new, :create, :edit, :update, :destroy], Project
end
# Less privileged moderator role
role :editor, proc {|u| u.editor? } do
can [:new, :create], Project
can [:edit, :update], Project do |project, user|
project.user_id == user.id
end
end
...
In Project controller:
def edit
authorize! :edit, Project
...
There is no restriction, my editor role is able to edit any project whether project.user_id matches user.id or not.
Since AccessGranted does not support CanCan's accessible_by
, how can I achieve something similar to it? It's handy to have such a scope to retrieve only permitted objects.
Are you considering to create a tutorial for beginners ? I mean sth which will show how to start or example based on simple rails application. I asked because doc, wiki is poor.
I'm pretty new to Rails, however, I work on the app where I have this trio: users, wikis, wiki_users
. Many users can be assigned to many wikis and vice versa.
I struggle with connecting my access_policy.rb
file with Rails. How could I let the gem know that it should look for user, assigned to a specific wiki with a specific role on that specific wiki?
# wiki_user.rb
belongs_to :user
belongs_to :wiki
enum role: { owner: 1, administrator: 2, moderator: 3, contributor: 4, reader: 5 }
# user.rb
...
has_many :wiki_users, dependent: :destroy
has_many :wikis, through: :wiki_users
...
def wiki_user_role(role)
wiki = Wiki.find_by_subdomain request.subdomain
wiki.users.include?(self) && wiki.wiki_users.find_by_user_id(self.id).role == role
end
# project.rb
...
has_many :wiki_users, dependent: :destroy
has_many :users, through: :wiki_users
...
My project_users table contains columns id
(of the relationship), user_id
, project_id
, and role
. My access_policy.rb
file looks like this so far, with some effort to make it work in role :reader
block. However, it all feels just like a workaround and I am wondering whether this scenario can't be handeled in easier manner?
def configure
# ROLES
# Owner — a person who handles payments, e.g. owner of the company
# Administrator — administrator who does not care ab payments only ab the wiki itself
# Moderator — can create and edit other people articles
# Contributor — can only create and edit his own articles
# Reader (guest) — only the permission to read
role :owner, proc { |user| user.present? } do
can [:edit, :update, :destroy], Article
can [:edit, :update, :destroy], Comment
can [:edit, :update, :destroy], User
can [:edit, :update, :destroy], Wiki
end
role :administrator, proc { |user| user.present? } do
can [:edit, :update, :destroy], Article
can [:edit, :update, :destroy], Comment
can [:edit, :update, :destroy], User
end
role :moderator, proc { |user| user.present? } do
can [:edit, :update, :destroy], Article
can [:edit, :update, :destroy], Comment
end
role :contributor, proc { |user| user.present? } do
can [:edit, :create, :destroy], Article do |article, user|
article.user_id == user.id
end
can [:edit, :create, :destroy], Comment do |comment, user|
comment.user_id == user.id
end
end
role :reader, proc { |user| user.present? && user.wiki_user_role("reader") } do
can :read, Article do |article, user|
article.wiki.users.include? user
end
can :read, User do |selected_user_and_wiki, user|
selected_user = selected_user_and_wiki.first
wiki = selected_user_and_wiki.second
wiki.users.include?(selected_user) && wiki.users.include?(user)
end
can [:edit, :destroy], User do |edited_user, user|
edited_user == user
end
end
end
As I'm trying to do something a bit more convoluted in my app, I'm realizing access-granted doesn't work quite as I thought for being able to kind of interchangeably use a Class (with the semantics "all of them", as in the can? :create, Post
example) and an instance of a class interchangeably.
I was imagining on the same permission you could kind of use both interchangeably. But if we try say:
role :admin, { role: "admin" } do
can :manage, Post
end
role :user do
can :manage, Post, { published: true }
end
and then we try can? :create, Post
, then of course you get undefined method
published' for Post:Class`.
Because it's trying to call published
on the specific Post
object you passed in, of course it is, you did specify that condition naturally. And Post.published
is not a thing.
So I guess any given permission, you need to decide you are going to only use with a class object (like in README example for :create), or only use it with an instance (like in README examples with :update
or :make_manager
).
In my example above, I shouldn't say can :manage, Post
, but instead maybe:
can [:read, :update, :destroy], Post, { published: false }
and then maybe only can :create, Post
only on admin
role.
My actual use case, I was hoping to mix and match. I had a read
role defined like above, where admin's can read everything, everyone else can only read published things.
So when I have a specific item, of course I can just ask can? :read, specific_post
,
But I want to know if I should show a UI widget, say, "include unpublished posts", and I should only show that widget to those who can see even unpublished posts, and I was hoping to be able to do can? :read, Post
and have it be only true for Post.
But that won't work.
Curious if you have any advice. Should I just define a new permission :can_see_unpublished_posts
which I give only to admin
, so I can check that to decide whether to show the "include unpublished posts" button? It seems duplicative, but...
I suppose we could do another PR where access_granted, for hash conditions, first checks to make sure the method exists (with respond_to?), and if it doesn't, that's just false?
Or I guess I could write the condition long-hand:
can :read, Post do |post, user|
post.respond_to?(:published) && post.published
end
And now I guess it'll work if I ask can? :read, Post
(answer is no if they can only read published posts, because Post.respond_to?(:published)
is false.... but still return properly for Post instances (based on published
).
I'm not super happy with any of these solutions, I am curious your feedback!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.