I have this code:
can :create, Project { |instance| some_statement }
Later I want to add such condition: if user can create some Project, then he can read ProjectTemplate. But I can't do this like this:
if can? :create, Project
can :read, ProjectTemplate
end
Because if-condition always true. Also, I can't just put can? :create, Project.new, because in my can :create, Project {} statement is not just .new object.
Any suggestions? Am I doing something wrong?
To look at it a slightly different way, can you not have a role whereby the user is able to create projects (e.g. project_creator) and then just use the following
if user.role? :project_creator
can :create, Project { |instance| some_statement }
can :read, ProjectTemplate
end
I realize this isn't exactly what you want, but I am assuming it's not so much of a security risk for a wise group of people to read templates?
I'm also a bit confused that you have a condition on creating projects, the instance statement means that some logic is being applied on a specific instance. However, if a project has not been created there is no instance?
The way I have used that method is similar to the following
can [:read], User do |thisuser|
thisuser == user
end
Related
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
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
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?
I have two models:
Thread (id, title)
ThreadParticipation (id, thread_id, user_id)
I want to define something like:
can :create, ThreadParticipation if the user is a ThreadParticipation
example:
for
thread: (1, 'hello world')
thread_participation: (313, 1, 13) -- where 13 is the user_id
I tried:
can :create, ThreadParticipation, :thread_participations => { :user_id => current_user.id }
But that errors. Any ideas?
Thread in general is just a bad model name, because it will clash with the Thread class defined in Ruby. I implemented this just recently. In my example, I have forums and topics. A person shouldn't be able to create a new topic in a forum they don't have read access to.
I defined a custom permission on the Forum object called create_topic for this:
can :create_topic, Forem::Forum do |forum|
can?(:read, forum) && user.can_create_forem_topics?(forum)
end
Where can_create_forem_topics? is just defined on the User model like this:
def can_create_forem_topics?(forum)
# code goes here
end
Then I just use this custom :create_topic ability in the new and create actions in my TopicsController, where #forum is defined by a before_filter:
authorize! :create_topic, #forum
And if I need to use it in views:
can? :create_topic, #forum
by defining a custom permission on the parent object,
I'm currently trying to achieve something similiar, but I don't have the whole sight into this. Try this:
can :create, ThreadParticipation => Thread, do |participation, thread|
# your definition here
end
This should also work without a block.
EDIT: drop the part above, that doesn't work yet in CanCan. I implemented my own solution, but it requires you to authorize controller actions manually, which is not as beautify, but more secure in my opinion.
I implemented it this way for my project:
https://gist.github.com/822208
Normally you would user
user
not
current_user
within ability.rb. Is this your error? As well your ability is specified incorrectly. You want
can :create, ThreadParticipation, :user_id => user.id
Note that :user_id is a property of ThreadParticipation model