I'm using CanCanCan for authorization. An admin can manage all, so they don't have per-user-id rules. The result is that they can't create items for self by default. It looks like I need to add a bunch of extra plumbing to make create in my controllers work the same for admins as it does for general users. The reason seems to be Ability#attributes_for doesn't provide the admin user with the user_id attribute.
How are other people getting around this? Are you writing code to specifically handle the admin use case in your view or controller?
Relevant parts of the Ability class
if user.admin?
can manage, :all
else
can manage, Purchase, user_id: user.id
end
Example interaction
2.6.2 :012 > Ability.new(User.find(3)).attributes_for(:create, Purchase)
=> {:user_id=>3}
2.6.2 :013 > Ability.new(User.find(4)).attributes_for(:create, Purchase)
=> {}
User 3 is general_user, User 4 is an admin
In the controller
# relying on load_and_authorize_resource
def create
puts #purchase.user_id # => nil for admin, 3 for general user
# have to add this for admin use case
#purchase.user = current_user
...
end
I wouldn't rely on the Ability object to assign user_ids to your new objects. I think it's better to explicitly write it in the controller. It seems more clear what's happening. So basically just initiate a new object in the create method and don't rely on load_and_auhtorize_resource
def create
#purchase = current_user.purchases.new(purchase_attributes)
end
If the user wasn't authorized, CanCanCan would already have interfered.
ps. I have been a CanCanCan user for years, but recently moved to Pundit. I think the way it was designed is way better and clearer than CanCanCan. Check it out if you have the time!
There's a bug in CanCanCan. Defining the ability can(:manage) needs an id. Creating a record doesn't. So the ability file doesn't allow you to create bc it's looking only for record that it can find with an id. if you define can :create, Purchase above where you define the :manage you should be good :D
if user.admin?
can :manage, :all
else
can :create, Purchase
can :manage, Purchase, user_id: user.id
end
Related
I have a rails project using cancan for authorization. When a record is updated I want to notify users who are authorized to read it that the record is updated. Cancan easily lets me check if a specific user is authorized to perform an action on a record or get retrieve records that the specific user is allowed to operate on. But can I do the reverse through cancan? I.e. given a specific record retrieve all users authorized to perform an action on it?
I am averagely familiar with Cancan. Having said that, this is what I could think of:
#myrecord = MyRecord.find(1)
authorized_user_ids = User.find_each do |user|
user.id if Ability.new(user).can? :read, #myrecord
end.compact
puts authorized_user_ids
# => [1, 2, 5, 10, 78]
Note however, this is extremely inefficient because it will loop through all users. You might want to perform caching this with a separate table/model that you would implement.
given a specific record retrieve all users authorized to perform an action on it?
No.
That's like asking "can Devise show me all the users who are currently logged in?" - it doesn't have the capacity to do it because the scope is not there.
There are several ways to achieve what you want.
The simplest is to push the notification (I'm not sure how you're doing this) to the front-end of your application (so that, potentially, anyone could read it).
Then, as CallmeSurge said, you'll be able to use can? read on it to make it so that only the users who were eligible could actually invoke the data:
#app/views/layouts/application.html.erb
<%= [[notification]] if can? :read, [[notification]] %>
--
The other way is to use ActiveRecord to return the users eligible to read the notification with the criteria you use already. This can be done using the following:
#app/models/user.rb
class User < ActiveRecord::Base
def self.notify
where(x: y)
end
end
Then you'd be able to set the notifications as follows:
User.notify.each do |user|
user.notification.create .....
end
I have a Rails app where I want some users to be super users with special privileges, like being able to access menus where they can edit content of the site. I'm not 100% sure that my implementation is secure, or the Rails way. It looks like this:
I have created an environment variable using the Figaro gem where I store the email-address of the original site creator:
ENV["site_manager_email"]
As I understand it, Figaro's application.yml works like Rails secrets.yml. My user model have a site_manager BOOLEAN attribute. The original site manager should be able to add other site managers by setting this property to true. In my user model I have a method to check whether users are site managers or not, it looks like this:
def is_site_manager?
self.email.eql?(ENV["site_manager_email"]) || self.site_manager == true
end
In my views where I have content that only site managers should be able to see I use the above method like this:
- if current_user.is_site_manager?
SUPER SECRET CONTENT THAT ONLY SITE MANAGERS SHOULD SEE
In my controllers I'm also planning to add a before action filter to only give access to some actions.
So my question is, is this a secure way to do it? If not, what should I change?
And, is there a more Rails way to do it? If so, how does it look?
What are you using for authentication? Devise?
For admins you can look into roles-based authorization and CanCan:
https://github.com/ryanb/cancan
There is a RailsCast that will show how to set it up:
http://railscasts.com/episodes/192-authorization-with-cancan
Once you install it and add a .role column to your Users you can create Ability.rb in your app/models that will look like something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role? :administrator
can :manage, :all
else
can :read, :all
can :create, Product
end
end
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.
In my new project, I have a resource Bet which other users can only read if they are the owners of the bet or friends of him. The main problem comes when I want to define abilities for the index action. In an index action, the block does not get executed, so I guess it's not an option.
Let's illustrate it. If I wanted only the owner to be able to index the bets, this would be enough:
can :read, Bet, :user => { :id => user.id }
But I need the acceptable ids to be a range, one defined by all the friends of the user. Something like:
if (bet.user == user) || (bet.user.friends.include? user)
can :read, Bet
end
But this is not correct CanCan syntax.
I guess that a lot of people has had problems with CanCan and nested resources, but I still haven't seen any answer to this.
In your Bet model, create a method:
def is_accessible_by? (user)
owner = self.user
owner == user || owner.friends.include?(user)
end
Now in ability.rb, set your CanCan permission:
can :read, Bet { |bet| bet.is_accessible_by?(user) }
EDIT
As you point out, since index actions don't have an instance of the object, the block above won't get executed.
However, it sounds like what you are trying to do - list the bets owned by the user or their friends - should not be handled using CanCan or permissions. I would create a function in my User model:
def bet_listings
friend_bets = friends.inject([]){ |bets, friend| bets<<friend.bets; bets }
self.bets + friend_bets
end
Then in your index action:
#bets = user.bet_listings
Just now I have found a solution, but I don't like it much. It's about creating a custom action and defining abilities for it. For example...
In the controller:
def index
authorize! :index_bets, #user
end
In ability.rb:
can :index_bets, User do |friend|
user == friend || user.friends.include?(friend)
end
It works, but I don't feel great about using it. Isn't out there anything more elegant?
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