I'm using cancancan for authorisation.
I want to allow anyone read access to users within a scope.
I have this in user.rb -
class User < ActiveRecord::Base
scope :published, -> { describes scope, works happily
}
end
The cancancan docs describe using scopes like this -
can :read, Photo, Photo.unowned do |photo|
photo.groups.empty?
end
... a block acting on the scope. I just want the scope.
This seems to allow anyone to be read -
can :read, User, User.published do |user|
true
end
I can't reason what block I'm aiming for.
I was making it one step more complicated than it needed to be. All that is required is -
can :read, User.published
Which on reflection makes perfect sense, but I could not get my head around.
Related
This is my Ability.rb file:
class Ability
include CanCan::Ability
def initialize(user)
user.permissions.each do |permission|
can permission.action.to_sym,
permission.thing_type.constantize {|thing| thing.nil? || permission.thing.nil? || permission.thing_id == thing.id}
end
end
end
thing is a polymorphic association. I'm trying to understand how passing a block to can works. I've searched throughout the wiki for CanCan but haven't been able to find an explanation.
Passing a block to cancan allows you to implement more complicated permission checks that depend on the state of the object itself.
When it's just attributes on the object you want to check then you don't need a block:
can :read, Project, :active => true
allows a user to only read active projects. If you need to call project's editable method then you could instead do
can :read, Project do |project|
project.editable?
end
At the point that cancan checks whether you can read a particular project (ie when the before_filter fires or you call `can? :read, some_project) then the block gets called
There's a page about this on the wiki: Defining abilities with blocks.
In your case it looks like the intent is that the permission object can either grant access to a whole class (if thing_type is set but thing_id is null) or to a specific instance of the class.
However the code you've posted doesn't actually do this. Of the 2 ways of passing a block, {} binds more tightly than do...end so the block isn't passed to can at all. It is instead passed to constantize, which ignores it. You could disambiguate this by using parentheses, or by using do...end
I have a Rails 3 application that uses Cancan for authorization. I have the following logic in my application: An admin has access to manage all other users, and has can :manage, User set in the ability file. A company owner has access to manage all users under his company, and has can :manage, User, company_id: user.company_id set in the ability file.
In my UserController#index, i need to call one method if the user has access to manage all the other users, and another method if the user only can access users from his company. Is there any way to do this with CanCan?
As discussed in the question comments, this sounds like a case of roles vs abilities.
If there already is a role system in place, and the logic for choosing between methods maps directly to these roles, then going through CanCan abilities is unnecessarily complex. CanCan is good at checking for abilities on specific model objects, classes and collections but not at going back to the original logic behind why those abilities were awarded in the first place.
In this specific case, there would need to be a way to refer to the case "can manage all Users in Company X but NOT all Users". It might be possible to accomplish with some if-else structure, but I don't think it is what you actually want. Especially if your ability logic changes over time it may not make sense anymore. One example is the corner case where all users belong to the same company, would it be desirable that the "all Users" method is called even for non-admin company owners?
My suggestion therefore is to check the roles directly, much like you already do in your Abilities class. But I feel your pain. ;)
i'm using cancan with device, in my projet i used probably the same you want
if user_signed_in?
if current_user.has_role? :admin
#users = User.all
end
else
#users = User.where(current.user.company_id == company_id)
end
In controller action you should do something like:
#load #user variable
begin
authorize! :manage, #user
#code when access is granted
rescue CanCan::AccessDenied
#code when access is denied
end
I know this an old question but I also ran into this situation. I was also looking to do all role checking in ability.rb file in one place.
What I ended up doing was far from ideal, but just noting it down if anyone wants to go that road. I defined a new ability like this in the ability.rb:
can :manage, :all_users if user.admin?
Please note that :all_users is just a random name that I chose and not some magic method in cancan.
After defining this ability, I was able to do like this in the controller:
if can? :manage, :all_users
call_method_which_can_access_all_users
else
call_method_which_can_access_only_some_users
end
But, it would have been great if Cancan gave us something like can? :manage, User, :all depending on whether there is a hash for filtering users after the User parameter.
I am using devise and cancan gem to manage authentication and permissions in my rails application. The application is about library management. I have defined methods inside ability.rb like:
def lend
can :manage, Book
can :manage, Transactions
end
Then in ability.rb, I have assigned permissions as
if user.role? == "librarian"
can lend
end
In the views, I can use can?(:manage, Book) or can?(manage, Transaction) for users with "librarian" role.
However, I want to be able to use current_user.can?(:lend). I followed this github page to make can? method available inside user model. This allows me to use
current_user.can?(:manage, Book)
But, current_user.can?(:lend) results into wrong number of arguments(1 for 2+). I have a feeling that I am missing a bit of intellect here. I tried google and the related post here but to no avail. Please help.
can? has two required arguments:
def can?(action, subject, *extra_args)
Hence: wrong number of arguments(1 for 2+)
See cancan/lib/cancan/ability.rb:56
Also, in your ability.rb (below) there is neither an action nor a subject named :lend. The method name is irrelevant to cancan.
def lend
can :manage, Book
can :manage, Transactions
end
if user.role? == "librarian"
can lend
end
My rails app is pretty much a front-end to several web services. I persist my User model, and that's about it. I need to add authorization to my web app, using Devise for authentication. I've noticed CanCan and acl9 seem to work mostly on instances of ActiveRecord models. Would CanCan or acl9 still fit my needs? Any tips on using either of these libraries in my situation?
Should I look for something that works more on actions instead of instances?
Also, these are both Role based systems, and I'm thinking of using a permission based system. Would they still be a good fit?
I can't speak for acl9. However, the cancan wiki does claim that "It is easy to make your own [model] adapter if one is not provided." https://github.com/ryanb/cancan/wiki/Model-Adapter In other words, even though you're not using ActiveRecord, you might still be able to use cancan.
Then again, if you're not planning on having roles, your ability definitions in cancan might be a little redundant looking, eg.:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :create, Widget if user.has_permission(:create_widgets)
can :delete, Widget if user.has_permission(:delete_widgets)
can :herp, Derp if user.has_permission(:herp_derp)
end
end
It would be great if you could use cancan just for its controller action authorization methods, but I don't know if that's possible. Good luck.
Just to (finally :) answer for acl9.
Acl9 is composed of two very separate pieces, the Access Control Subsystem which is all the authorizing stuff you put in your controller, and the Role Subsystem which is setting/checking/removing roles from an authenticated user.
The only thing that the Access Control Subsystem ever calls is current_user.has_role?( role, obj=nil). So, the Role Subsystem has zero dependency on ActiveRecord, associations, database, etc. There is a helper (acts_as_authorization_subject) which adds an ActiveRecord-dependent has_role? method to a class, but that's entirely optional and you're free to implement your own has_role? method (which can also fallback to calling super to get the acl9 one) and implement your access checks however you please. So, you said that you do persist your user model, but let's say you want a role for your user to be the admin of a school, but that school is a web service call into some remote system.
## in your model
class User < ActiveRecord::Base
def has_role? role, obj=nil
role == :admin &&
obj == school &&
school[:admin] == id # <-- just making up how we know we're the admin of the remote school
end
end
def school
#school ||= School.get_by_user_id(id)
end
end
## in your controller
class SomeController < ApplicationController
before_action :set_school
access_control do
allow :admin, of: :school
end
private
def set_school
#school = School.get_by_id(params[:school_id])
end
end
This seems like an easy question that I just can't wrap my head around.
Using Devise for authentication and CanCan for authorization on a new Rails 3 app.
How can I access methods defined in ApplicationController within the Ability class that CanCan provides?
a.k.a., something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # Guest user.
can :create, Post if user_signed_in?
end
end
where user_signed_in? is defined in ApplicationController.
This might not be the answer you wanted, but it seems like you are wanting to mix code concerns that shouldn't be mixed.
Is it a good idea to access user_signed_in? inside your authorisation rules? ... Since authorisation is only concerned with what someone can do, and should not be concerned with if that someone is authenticated (or not).
A before filter (before_filter :authenticate_user!) on your Posts controller to check that your user is authenticated should be enough to do achieve your objective; Your authorisation rules can be run alongside the authentication check, rather than mixed up with it's code.
It's a layered approach :-)