cancan is not evaluating an instance level ability check - ruby-on-rails

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.

Related

Rails 6 - Pundit "policy wrapper"

I am a little stuck with Pundit: It feels that the solution should be easy - but I am not getting it. Actually I have a bunch of models which are all dependent on one main model. My main model is a script. The script has many roles, many scenes, many costumes, etc. Additionally it has some joined connections like scene.roles. I simply want to authorize the user who created the script to do everything (adding roles, deleting scenes, just everything that is in the scope of her own script) and everybody else to do (and see) nothing. Do I need to create a policy for every model or can I just (re-)use somehow one "script policy"?
How would an authorization look like in a dependent controller (i.e. 'index' in roles or 'new' in scenes)?
The authentication is handled by Device. A user must be logged in to see or do anything.
This is my first post on stack overflow, happy to join the community:-)
Pundit policies are POROs (plain old ruby objects). So you can easily create a policy for the main model:
class ScriptPolicy
attr_reader :user, :script
def initialize(user, script)
#user = user
#script = script
end
def update?
user.actor?
end
def delete?
user.admin?
end
end
And then for every model that you have, simply create an empty class definition that inherits from the ScriptPolicy
class ScenePolicy < ScriptPolicy
end
Another approach would be to overwrite the policy name that Pundit is referencing directly in the child model. So assuming that you have a model Scene that shall use the same policy as Script, you can add a method:
class Scene < ActiveRecord::Base
def self.policy_class
ScriptPolicy
end
end
In the latter, you do not need to create empty policy classes for every model, but you trade it for the decreased flexibility in defining specific permissions for models.
When calling the authorize method in your controller, you can specify the policy class:
authorize #model, policy_class: ScriptPolicy
I find that this solution generates less boilerplate files.
To authorize a scope:
scenes = policy_scope(Scene, policy_scope_class: ScriptPolicy::Scope)
By the way, this is all covered in pundit's README: https://github.com/varvet/pundit

Checking action parameters in Rails CanCanCan Authorization

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.

CanCan, what does passing a block to the can method in Ability#initialize do?

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

method_missing and association_proxy in rails

So, here's my problem. I currently am building a simple authentication system for a rails site. I have 3 classes for this: Person, Session, and Role. In my Person model I have defined method_missing to dynamically capture roles according to this guide.
In my application_controller I have some logic to deal with logins and log-outs, the result of which gives me the currently logged in user via:
#user = #application_session.person
Where #application_session is the current session
Now in one of my controllers, I don't want anyone to be able to do anything unless they are an admin, so I included:
before_filter #user.is_an_admin?
This raises a NoMethodError, even though I have method_missing defined in my model. I tried defining is_an_admin?, having it always return true as a test, and that works.
According to this question, I think the problem might have something to do with proxy associations. When I run:
puts #user.proxy_owner
I get a session object, since each user (Person) can have many sessions, and I got my user (Person) from the current session.
I am very confused why #user.is_an_admin? is not calling the method_missing method in my Person controller. Please let me know if you need more information or code snippets.
I am using Rails 3 on Ruby 1.9
I'd consider a method_missing an overkill for such task.
Now, if you have Session class, which belongs_to User, then you can have this:
class Session
belongs_to :user, :extend => PermissionMixin
end
class User
include PermissionMixin
end
module PermissionMixin
def admin?
if cond
true
else
false
end
end
end
P.S. Check cancan, perhaps it'll suit your needs better.
I use a similar permissions check in my system to check the User > Role > Permissions association:
User.current_user.can_sysadmin?
In my controllers I have to instead use:
User.current_user.send('can_sysadmin?')
This may work for you as well.
I have solved this by moving the method_missing method to my application_controller.rb. I change the logic of the method a little to check for a user, and if found, dynamically check the role. If things were not kosher, I had the method redirect to root_url or return true if the user matched the requested roles.
Finally, in my reports controller, I used before_filter :is_an_admin? and got my desired results. However, I am still unclear as to why method_missing had to be defined in my application controller as opposed to directly in the Person (aka #user) model?

Cancan authorization in Non RESTful controller

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.

Resources