Hi i am trying to use cancan but I have very irritating error, namely: uninitialized constant Edition
for controller :
class EditionsController < ApplicationController
before_filter :authenticate_user! #devise
load_and_authorize_resource
def index
end
end
with this route:
get "editions/index"
and such abilities:
user ||= User.new # guest user (not logged in)
if user.has_role? "admin"
can :manage, Edition
cannot :commission
else
can :read, :commission
end
And additional question, how i can create cancan ability for singular(name) controller ?
for example PhotoController
The problem is that you are using a custom class.
CanCan tries to singularize the controller class name to match the model class name and that sometimes isn't what we expect:
...
If the model class is namespaced differently than the controller you will need to specify the :class option.
source: https://github.com/ryanb/cancan/wiki/authorizing-controller-actions
In my case, I had a class with no model, so I just added ":class => false" at the end of the command:
load_and_authorize_resource :class => false
If you have load_and_authorize_resource defined in the ApplicationController and want to override it only for this controller:
class EditionsController < ApplicationController
skip_load_and_authorize_resource
def index
#editions = Edition.accessible_by(current_ability).order(:date)
end
end
Related
I am getting an unexpected behaviour for a simple cancancan authorization.
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
if user.is_admin?
can :manage, :all
elsif user.is_standard?
can :manage, ServiceOrder, {user_id: user.id}
can :manage, ServiceOrderDetail, :service_order => { :user_id => user.id }
end
service_order.rb controller (partially shown)
class ServiceOrdersController < ApplicationController
authorize_resource
def show
#service_order = ServiceOrder.includes(:service_order_details).find(params[:id])
end
end
This does not work, as it lets the controller show ANY service_order record, instead of just those owned by the current_user.
The only way that this works is if I manually authorize the controller adding:
authorize! :show, #service_order
like this:
def show
#service_order = ServiceOrder.includes(:service_order_details).find(params[:id])
authorize! :show, #service_order
end
which makes no sense since authorize_resource is supposed to be doing that.
What is happening is the authorize_resource is happening before the show action, and since the #service_order is not set yet, it is checking against the class, and the user does have access to show a ServiceOrder just under constraint.
Adding authorize_resource will install a before_action callback that calls authorize!, passing the resource instance variable if it exists. If the instance variable isn't set (such as in the index action) it will pass in the class name. For example, if we have a ProductsController it will do this before each action.
authorize!(params[:action].to_sym, #product || Product)
from Cancancan documentations
What you will need to do is load_and_authorize_resource as suggested by widjajayd. or (if you do not want to use the cancancan default load action) do a before_filter that loads the resource manually using your custom method before the authorize_resource call.
my suggestion: instead using authorize_resource you using load_and_authorize_resource, and below is the sample for your controller
just make sure your strong_parameters declaration :service_order_params
load_and_authorize_resource param_method: :service_order_params
I have a nested resource for which I'm using Cancan to do authorization. I need to be able to access the parent object in order to be able to authorize the :index action of the child (since no child instance is passed for an :index action).
# memberships_controller.rb
class MembershipsController < ApplicationController
...
load_and_authorize_resource :org
load_and_authorize_resource :membership, through: :org
..
end
ability.rb
can [:read, :write], Membership do |membership|
membership.org.has_member? user
end
This doesn't work for the :index action
Unfortunately the index action doesn't have any membership instance associated with it and so you can't work your way back up to check permissions.
In order to check the permissions, I need to interrogate the parent object (the org) and ask it whether the current user is a member e.g.
# ability.rb
...
can :index, Membership, org: { self.has_member? user }
Cancan almost lets me do this...
Cancan states that you can access the parent's attributes using the following mechanism:
https://github.com/ryanb/cancan/wiki/Nested-Resources#wiki-accessing-parent-in-ability
# in Ability
can :manage, Task, :project => { :user_id => user.id }
However this just works by comparing attributes which doesn't work for my case.
How can I access the parent object itself though?
Is there any way to access the parent object itself within the permissions?
I recently faced the same problem and ended up with the following (assuming you have Org model):
class MembershipsController < ApplicationController
before_action :set_org, only: [:index, :new, :create] # if shallow nesting is enabled (see link at the bottom)
before_action :authorize_org, only: :index
load_and_authorize_resource except: :index
# GET orgs/1/memberships
def index
#memberships = #org.memberships
end
# ...
private
def set_org
#org = Org.find(params[:org_id])
end
def authorize_org
authorize! :access_memberships, #org
end
end
ability.rb:
can :access_memberships, Org do |org|
org.has_member? user
end
Useful links
https://github.com/ryanb/cancan/issues/301
http://guides.rubyonrails.org/routing.html#shallow-nesting
Can't you do something like this?
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :index, Membership, org: {id: user.memberships.map(&:org_id)}
end
end
I want to "map" MyController to User model
#ability
class Ability
include CanCan::Ability
def initialize user
user ||= User.new
if user.has_role? :admin
can :manage, :all
else
can :read, :all
can :read, User do |u|
u && u.user_id == user.id
end
end
end
end
#routes
get 'my_controller/show/(:id)', to: 'MyController#show'
#controller
class MyController < ApplicationController
# load_and_authorize_resource
def show
# showing a user
end
end
So my_controller/show/(:id) can be accessible only for admin and current user. For example, the user with id == 2 can see the their own profile my_controller/show/2 and cannot others users' ones such as my_controller/show/1234 unless they are an admin.
There is no My model in the the project. Ideally, I have to rename MyController to UserController but I'm not allowed to do that for some reason.
If I uncomment load_and_authorize_resource at MyController, there will be an error of
NameError in MyController#show
uninitialized constant My
So how can I get rid of it?
You can specify the name of the type of resource to load and authorize:
load_and_authorize_resource :user
Otherwise cancan will attempt to work it out by convention (based on the name of the controller).
https://github.com/ryanb/cancan/wiki/authorizing-controller-actions
I have a controller named Administrator. This controller has actions to anothers models. When I try to authorize this actions with CanCan I get this response.
<h1>
NoMethodError
in AdministratorController#preproduct_delete
</h1>
<pre>undefined method `find' for Administrator:Class</pre>
The controller code begins with:
class AdministratorController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!
def users
authorize! :users, "Users List"
#users = User.all
end
end
The ability model has:
if user.admin? then :admin
can :users
end
The authenticate_user! method:
# See ControllerAdditions#authorize! for documentation.
def authorize!(action, subject, *args)
message = nil
if args.last.kind_of?(Hash) && args.last.has_key?(:message)
message = args.pop[:message]
end
if cannot?(action, subject, *args)
message ||= unauthorized_message(action, subject)
raise AccessDenied.new(message, action, subject)
end
subject
end
I tried to remove load_and_authorize_resource from the controller, but when any action is called, CanCan redirects me to login page every time.
Thanks a lot
You did not specified the permission, what does can :user mean ? specify a permission, for all permissions use :manage.
if user.admin?
can :manage, :users
end
<h1>
NoMethodError
in AdministratorController#preproduct_delete
</h1>
Please paste preproduct_delete of AdministratorController
<pre>undefined method `find' for Administrator:Class</pre>
Is Administrator inheriting from ActiveRecord::Base ? if it doesn't have find is because is not.
it should be
class Administrator < ActiveRecord::Base
The authenticate_user! method:
# See ControllerAdditions#authorize! for documentation.
def authorize!(action, subject, *args)
....
end
THIS IS NOT the authenticate_user! method, this is authorize! very different, you have before_filter authenticate_user! post this method here. Something is calling preproduct_delete.
One possible reason is that the class name needs to be explicitly set. In my example I had the comments resource nested under the thread resource, and I was writing the following in controller:
class Buy::CommentsController < ApplicationController
load_and_authorize_resource :thread
This gives me undefined find method error, because comment model was under the thread namespace. When I explicitly set it like the following it works.
class Buy::CommentsController < ApplicationController
load_and_authorize_resource :thread, class: Buy::Thread
I have an app that uses Devise and CanCan.
in the config>initializers>Abiliity.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.is? :superadmin
can :manage, :all
elsif user.is? :user
can :read, Project do |project|
project && project.users.include?(user)
end
end
end
end
I have problem with the index action of Project controller, the project controller is a normal stock RESTful controller. Basically, a user who's a normal user, when logged in, can see the projects#index. But not all projects have this user as 'normal user', why isn't cancan blocking his access?
Thanks
Make sure you're calling load_and_authorize_resource in your ProjectsController, along the lines of:
class ProjectsController < ApplicationController
load_and_authorize_resource
#...
end
If that still doesn't work, try calling the authorize! method inside the index action, to see if that makes a difference, eg:
class ProjectsController < ApplicationController
#...
def index
#projects = Project.all
authorize! :read, #projects
end
#...
end