Is it possible to access controller parameters when defining abilities in ability.rb?
I have an event and users that can participate in or create that event. It seems like I could create a different controller action for every possible scenario, e.g. a user signs himself up for an event or a creator deletes someone from the event. However I think it would be a lot easier to read to have less actions and be able to define abilities based on what parameters are being passed in from the client.
Answer
#chumakoff has some good info down below that helped explain how CanCanCan is working. I decided to authorize these actions by default in ability.rb, and then raise an error, e.g. raise CanCan::AccessDenied.new("You cannot delete someone else from this event"), in the controller if I detect incorrect user/event parameter IDs being sent in.
If I understand correctly, you are using cancan's authorize_resource or load_and_authorize_resource controller helper that calculates user abilities based on controller actions names.
But it's not obligatory to use this helper for all actions. You can skip it for actions having complex ability logic and check abilities manually.
For example:
class ParticipationsController < ApplicationController
authorize_resource except: :create # skiping `authorize_resource` for `create` action
# ...
def create
if creator_adds_someone_to_event?
authorize! :add_to, #event
end
if user_signs_up_for_event?
authorize! :sign_up_for, #event
end
# ...
end
So, you can check many different abilities in the same controller action. Just disable default cancancan's behaviour for the action.
Yes there is a debugging tool Named as " pry" . Use that it would help u out. Just use binding.pry wherever u want to check the value of parameters in the code and the console will stop executing at that moment so u can check the value of the parameters.
Related
I'm new with the Pundit gem and I'm stuck on something.
I try to show a list with multiple records but I always get the error AuthorizationNotPreformedError.
What do I do wrong?
Controller:
def planning
#plans = Order.all
authorize #plans
end
Policy:
def planning?
user.present?
end
Planning.html.haml
%h1 test
When I add this: after_action :verify_policy_scoped, :only => planning, if: verify_policy_scoped?
I get the PolicyScopingNotPerformedError.
From the README:
Pundit tracks whether you have called authorize anywhere in your controller action. Pundit also adds a method to your controllers called verify_authorized
...
Pundit also adds verify_policy_scoped to your controller. This will raise an exception similar to verify_authorized. However, it tracks if policy_scope is used instead of authorize.
You have mixed the two methods up. You've called authorize, but are checking whether policy_scope was called.
policy_scope is typically used for collections of record (such as your example, or more typically index actions), whereas authorize is typically used for individual records (such as show/edit/update/destroy actions).
In your case, however, what you've got currently doesn't necessarily warrant a policy at all - all you're checking is whether the user is signed in!
If you require a user to be signed in, but they are not, then your application should respond with a 401 error, not 403. You may do this with, for example, before_action :authorize in your controller. (It depends how you have implemented authorization -- see the documentation on whatever library you're using, e.g. devise).
I'm trying to get cancan incorporated into my first ever Ruby on Rails app.
I'm having a problem getting started... its surely something basic.
My application has a list of projects, and a user may or may not have permission to see any number of them.
I added this to my ProjectsController:
class ProjectsController < ApplicationController
load_and_authorize_resource
My initialize method looks like this:
def initialize(user)
user ||= User.new # guest user
puts "******** Evaluating cancan permissions for: " + user.inspect
can :read, Project do |project|
puts "******** Evaluating project permissions for: " + project.inspect
# project.try(project_users).any?{|project_user| project_user.user == user}
1 == 1 #POC test!
end
end
When I have this, the project index page appears, but no projects are listed.
2 questions I have here:
Shouldn't all of the projects appear since true is returned for all
projects?
The second puts statement is not written to the rails
server console, but the first one is. Why is that???
If I change the initialize method to:
def initialize(user)
user ||= User.new # guest user
puts "******** Evaluating cancan permissions for: " + user.inspect
can :read, Project
end
... I see all of the projects as I would expect
If I remove the can :read, Project line, I get a security exception trying to hit the projects index page.... also what I'd expect.
The block being passed to the :read ability is only evaluated when an instance of the project is available (#project). Because you are talking about the index action, only the collection is available (#projects). This explains why your second puts statement is never appearing. In order to limit your index actions, you need to either pass a hash of conditions into the can method, or use a scope (in addition to the block). All of this information is clearly outlined in the CanCan wiki on Github.
So the puts problem is explainable. What does not make sense is how no projects are showing. When evaluating the index action, CanCan will actually default to ignoring the block entirely. This means that your ability is essentialy can :read, Project anyway (even in the first example) for the index action.
I would be interested to have you try to add a simple scope, just to see if it will work. Try:
can :read, Project, Project.scoped do |project|
true
end
And then see what happens for the index action.
edit:
Given that you can see the projects in the index now, it seems like you need to pass a scope into the ability as well as a block. Please read this Github issue where Ryan explains why the block is not evaluated on the index action.
Blocks are only intended to be used for defining abilities based on an
object's attributes. [...] That is the only case when a block should
be used because the block is only executed when an object is
available. All other conditions should be defined outside the block.
Keep in mind that if your ability is not too complex for a hash of conditions, you should use that instead. The hash of conditions is explained on this CanCan wiki page on Github. If you do need a scope, you will need to pass in the scope and the block. Lets say you have the ability shown above.
On the index action, CanCan will disregard the block because a Project object (#project) is not available. It will instead return projects that are within the scope given, in this case Project.scoped (which will just be all projects).
On the show action, #project is available, so CanCan will evaluate the block and allow the action if the block evaluates to true.
So the reason you need to pass both is so that CanCan can handle both the index and show actions. In most cases, your block will define the same thing as the scope does, only the block will be written in Ruby while your scope will be written Rails' ActiveRecord syntax. You can fine more information about here: Defining Abilities with Blocks.
I have a user maintenance page. This page has a list of users where the admin can do bulk updates on the users he selects. Bulk updates include: activate, deactivate, and update roles to.
Should there be one URL I POST to, such as /users/bulk_update.json, where I then pass in the list of IDs and the method type. And in my bulk_update action, I update the IDs according to the method.
Or should there be a URL for each method, such /users/bulk_update_activate, /users/bulk_update_deactivate, and /users/bulk_update_roles?
The quick answers is: it depends! :)
If your updates type share many of the code logic.
1) Use filter:
class FirstController < ApplicationController
# Other controller code
before_filter :prepare_update
after_filter :finalize_update
def bulk_update_activate
# Do something here
end
def bulk_update_deactivate
# Do something here
end
end
2) Use a single action:
class SecondController < ApplicationController
# Other controller code
def bulk_update
case params[:operation]
when :activate then
# Do something here
when :deactivate then
# Do something here
end
end
end
If your updates are completely indipendent, then you should write different actions.
In my projects I usually find myself in using the first approach.
Hope it will be useful.
I have a controller like this, but no model class backing it.
class UserInviteController < ApplicationController
def process_email
authorize! :process_email, :abc
...
method body
...
end
end
In my ability class, I have
can :process_email, :abc if user.role == 1
I am following the wiki Non RESTful Controllers authorization by Cancan and it says that you can pass a symbol as the second argument to both authorize! and can.
It doesn't have to be a model class or instance.
Generally the first argument is the "action" one is trying to perform
and
the second argument is the "subject" the action is being performed on. It can be anything.
I have done the same, but still It doesn't work.
NOTE: I also tried doing it the other way round by
authorize_resource :class => false
as said in the wiki, but that also doesn't work.
If its of any help, my controller doesn't have any actions like show, new, create or any other basic CRUD actions.
Cancan: 1.4.1
I tried to debug following the link Debugging Abilities and figured out what was wrong.
The abilities were correct, I was doing a small mistake by specifying them in the wrong order.
For others coming on to this page, if you face any problems with Cancan abilities not working, try to debug using the above link. You will eventually figure out whats not working and why.
I have a permission model in my app, that ties (Users, Roles, Projects) together.
What I'm looking to learn how to do is prevent a user for removing himself for their project...
Can you give me feedback on the following?
class Permission < ActiveRecord::Base
.
.
.
#admin_lock makes sure the user who created the project, is always the admin
before_save :admin_lock
def before_save
#Get the Project Object
project = Find(self.project_id)
if project.creator_id == current_user.id
# SOME HOW ABORT OR SEND BACK Not Allowed?
else
#continue, do nothing
end
end
end
Is that look like the right approach?
Also, I'm not sure how to do the following two things above:
How to abort prevent the save, and send back an error msg?
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Thanks for reading through
How to abort prevent the save, and send back an error msg?
return false during the callback chain tells activemodel to stop (similar to how adding errors to the model during a validation tells it to stop at that point)
self.errors.add_to_base "msg" will add an error to the model, which can then be rendered on the view.
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Models shouldn't really know about things like the current request, if at all possible, you should be locking things down at the controller/action level.
EDIT:
So, the role of controllers is to deal with everything involved in getting the correct information together based on the request, and passing it to the view (which becomes the response). People often say "make your models fat and your controllers skinny", but that could be said of any system that embraces object oriented design -- your logic should be in objects when possible.
That being said, the whole point of controllers is to deal with routing the right things to the right places, and authentication is definitely a concern of routing.
You could easily move the line comparing creator_id to user id in the action, and react based on that.
Now, sometimes you genuinely need that stuff in the model and there is no way around it. That becomes a problem, because you need to fight rails to get it there. One way would be to attr_accessor a current_user field on your model, and pass that in on initialize. Another would be to remove the fields from the params hash that a user is not allowed to change in the action. Neither is really that nice though.
Agreed with Matt that you should try to use the controller for the redirect. The model should have the logic to determine if the redirect is appropriate. Maybe something like
class ProjectsController < ApplicationController
def update
redirect_to(projects_url, :alert => "You can't remove yourself from this project.") and return if Role.unauthorized_action?(:update, params[:project])
#project = Project.find(params[:id])
if #project.update_attributes(params[:project])
...
end
class Role
def self.unauthorized_action?(action, params)
# your logic here
end
You should check out CanCan for some ideas.
In permission model take one field project_creater as boolean
In project modelbefore_create :set_project_ownership
def set_project_ownership
self.permissions.build(user_id: User.current.id, project_creater: true)
end
In project controllerbefore_filter :set_current_user
In Application controllerdef set_current_user
User.current = current_user
end