I'm trying to put a very simple authorization on my Property class in Rails 5. I've added the can :read condition to ability.rb and used load_and_authorize_resource in my controller and I can't even get it to hit the pry, let alone authorize the :show action. Am I missing something obvious?
# ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Property do |property|
binding.pry
PropertyUser.find_by(property_id: property.id, user_id: user.id)
end
end
end
# properties_controller.rb
class PropertiesController < ApplicationController
before_action :set_property, only: [:show, :edit, :update]
load_and_authorize_resource
skip_authorize_resource :only => [:new, :create]
def show
respond_to do |format|
format.html
format.js
end
end
private
def set_property
#property = Property.find(params[:id])
end
end
Thanks.
You might need to have this code in your user.rb
delegate :can?, :cannot?, to: :ability
def ability
Ability.new(self)
end
instead of load_and_authorize_resource, you can just use authorize_resource and then check. I don't think we need delegate here because the CanCanCan will do that automatically. It will automatically add these methods to User model.
Related
I'm using devise_invitable for inviting users, and cancancan for defining abilities in the app. How do I define the ability for who can send invites?
You can override the method authenticate_inviter! in your ApplicationController
def authenticate_inviter!
send(:"authenticate_#{resource_name}!", force: true).tap do |inviter|
#current_ability = ::Ability.new(inviter)
authorize! :invite, SysManager
end
end
and then use cancancan to define who can :invite, SysManager
# app/controllers/invitations_controller.rb
class Devise::InvitationsController < DeviseController
...
before_action :is_admin?, :only => [:new, :create]
...
private
def is_admin?
current_user.role == 'admin'
end
end
I'm new to pundit and trying to come up with the best approach for handling nested resources for the index action. I found a similar question however it doesn't deal with admin privileges and I'm just not sure if my solution feels quite right.
Let's say I have two models, a User can have many notes and a Note which belongs to a single user. Users cannot look at notes from other users unless they're an admin. At the same time, admin's are able to create their own notes and therefore must also have the ability to retrieve a list of them via their own index action.
routes.rb
resources :users, only: :show do
resources :notes
end
notes_controller.rb
class NotesController < ApplicationController
#would probably move to application_controller.rb
after_action :verify_authorized
after_action :verify_policy_scoped
def index
user = User.find(params[:user_id])
#notes = policy_scope(user.notes)
authorize user
end
#additional code
end
note_policy.rb
class NotePolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin? && scope != user.notes
scope
else
user.notes
end
end
end
#additional code
end
user_policy.rb
class UserPolicy < ApplicationPolicy
def index?
user == record || user.admin?
end
#additional code
end
You are overthinking it:
class NotePolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(user: user)
end
end
def index?
record == user || user.admin?
end
# ...
end
Note here that its a good idea to chain from the scope being passed in from policy_scope. It lets your controller set up any scopes unrelated to authorization like for example pagination.
Also in index? we are cheating slightly. Instead of passing a note instance we are just passing the user.
class NotesController < ApplicationController
before_action :set_user!, only: [:index] # ...
before_action :set_note!, only: [:show, :edit, :update, :destroy]
def index
#notes = policy_scope(Note.all)
authorize(#user)
end
# ...
private
def set_user!
#user = User.find(params[:user_id])
end
def set_note!
#note = authorize( Note.find(params[:id]) )
end
end
Using before_action in this way is a pretty good pattern as it sets up all the "member" actions for authorization.
I recently integrated cancan (1.6.9) into my Rails (3.2.10) project for authorization, and I'm having an issue with manually loading a resource in a before_filter. Here's a brief description of my scenario.
In config/routes.rb, I have the following entries...
resources :users
match '/profile', :to => 'users#show'
Here's what my users_controller.rb looks like...
class UsersController < ApplicationController
before_filter :load_show_resource, :only => :show
load_and_authorize_resource
...
def show
end
...
private
def load_show_resource
#user = params[:id] ? User.find(params[:id]) : current_user
end
end
If my current_user's id is 1, this code will let me access localhost:3000/users/1 but not localhost:3000/profile.
The entry in my cancan ability.rb class that's blocking this access is seen below - it's the cannot section. If I comment out the cannot entry, both urls work.
...
can [:show, :update], User, :id => user.id
cannot [:show, :update, :destroy], User do |u|
u.site_id != user.site_id
end
....
Shouldn't cancan use the resource that's being manually loaded in the before_filter for the show action regardless of whether or not params[:id] is present?
Interestingly enough, if I modify my users controller (see below) to use skip_load_and_authorize_resource :only => :show and manually calling authorize! in the show action, both urls work. Also, if I remove cancan altogether both urls work.
class UsersController < ApplicationController
load_and_authorize_resource
skip_load_and_authorize_resource :only => :show
...
def show
#user = params[:id] ? User.find(params[:id]) : current_user
authorize! :show, #user
end
...
private
def load_show_resource
#user = params[:id] ? User.find(params[:id]) : current_user
end
end
So, my question is why does what's explained in the Override loading in the cancan documentation, not work in this situation?
Thanks!
# controller
class UsersController < ApplicationController
load_and_authorize_resource, :except => :show
# skip_load_and_authorize_resource :only => :show
...
def show
#user = params[:id] ? User.find(params[:id]) : current_user
authorize! :show, #user
end
...
end
# ability.rb
can [:show, :update, :destroy], User do |u|
u.id == user.id && u.site_id == user.site_id
end
I use devise and cancan gems and have simple model association: user has_many subscriptions, subscription belongs_to :user. Have following SubscriptionsController:
class SubscriptionsController < ApplicationController
load_and_authorize_resource :user
load_and_authorize_resource :subscription, through: :user
before_filter :authenticate_user!
def index
#subscriptions = #user.subscriptions.paginate(:page => params[:page]).order(:created_at)
end
#other actions
end
And Cancan Ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||=User.new
can [:read], [Edition, Kind]
if user.admin?
can :manage, :all
elsif user.id
can [:read, :create, :destroy, :pay], Subscription, user_id: user.id
can [:delete_from_cart, :add_to_cart, :cart], User, id: user.id
end
end
end
The problem is that i cannot use subscriptions actions as a user but can as a admin. And have no problems with UsersController. When i delete following lines from SubscriptionsController:
load_and_authorize_resource :user
load_and_authorize_resource :subscription, through: :user
before_filter :authenticate_user!
Have no problems at all. So the issue in these lines or in Ability.rb. Any suggestions?
UPDATE: It's interesting that if i add smth like can? :index, Subscription to html template it displays true. If add smth like can? :index, Subscription.first (subscription of another user) it shows false. Looks like Cancan works normally. But what's the problem?..
UPDATE: If change SubscriptionsControlle like:
class SubscriptionsController < ApplicationController
#load_and_authorize_resource :user
#load_and_authorize_resource :subscription, through: :user
before_filter :authenticate_user!
def show
#user = User.find params[:user_id] #line 1
#subscription = #user.subscriptions.find params[:id] #line 2
#container_items = #subscription.container_items.paginate(:page => params[:page])
authorize! :show, #subscription #line 4
end
#some actions
end
It works perfect and prevent unauthorized user access when need.
Are the lines #1, 2 and 4 not equivalent to commented?..
UPDATE: Have the following in routes.rb:
resources :users, except: [:show] do
member do
get 'cart'
delete 'delete_from_cart' => 'users#delete_from_cart'
post 'add_to_cart' => 'users#add_to_cart'
end
resources :subscriptions do
member do
post 'pay'
end
end
end
UPDATE: Next solution prevent unauthorized access to all of subscriptions actions except index:
class SubscriptionsController < ApplicationController
load_resource :user
load_resource :subscription, through: :user
authorize_resource through: :current_user
before_filter :authenticate_user!
#actions
end
So what's the best way to prevent access to index action?
Found only following solution:
before_filter :authorize_index, only: [:index]
def authorize_index
raise CanCan::AccessDenied unless params[:user_id] == current_user.id.to_s
end
It should be
load_and_authorize_resource :subscription
or just
load_and_authorize_resource
in your case, when you want nested resource, then
load_and_authorize_resource :through => :current_user
see https://github.com/ryanb/cancan/wiki/Nested-Resources
I have a number of resources (Trips, Schedules, etc) with actions that should be limited to just the resource's owner.
How do you implement code with a #require_owner method defined in ApplicationController to achieve this? Ideally, the code will look up the inheritance chain for the owner so the before_filter will work on a :comment that belongs_to :trip that belongs_to :user.
class TripsController < ApplicationController
belongs_to :member
before_filter :require_owner
...
end
I don't fully follow the description (would a comment really be owned by the trip owner?), but expanding slightly on jonnii's answer, here is an example that restricts the trip controller:
class ApplicationController < ActionController::Base
...
protected
# relies on the presence of an instance variable named after the controller
def require_owner
object = instance_variable_get("##{self.controller_name.singularize}")
unless current_user && object.is_owned_by?(current_user)
resond_to do |format|
format.html { render :text => "Not Allowed", :status => :forbidden }
end
end
end
end
class TripsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
# only require these filters for actions that act on single resources
before_filter :get_trip, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_trip
#trip = Trip.find(params[:id])
end
end
Assuming the model looks like this:
class Trip < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
...
def is_owned_by?(agent)
self.owner == agent
# or, if you can safely assume the agent is always a User, you can
# avoid the additional user query:
# self.owner_id == agent.id
end
end
The login_required method (provided by or relying on an auth plugin like restful_authentication or authlogic) makes sure that the user is logged in and provides the user with a current_user method, get_trip sets the trip instance variable which is then checked in require_owner.
This same pattern can be adapted to just about any other resource, provided the model has implemented the is_owned_by? method. If you are trying to check it when the resource is a comment, then you'd be in the CommentsController:
class CommentsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
before_filter :get_comment, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_comment
#comment = Comment.find(params[:id])
end
end
with a Comment model that looks like:
class Comment < ActiveRecord::Base
belongs_to :trip
# either
# delegate :is_owned_by?, :to => :trip
# or the long way:
def is_owned_by?(agent)
self.trip.is_owned_by?(agent)
end
end
Make sure to check the logs as you are doing this since association-dependent checks can balloon into a lot of queries if you aren't careful.
There's a few different ways to do this. You should definitely check out the acl9 plugin (https://github.com/be9/acl9/wiki/tutorial:-securing-a-controller).
If you decide you want to do this yourself, I'd suggest doing something like:
class Trip < ...
def owned_by?(user)
self.user == user
end
end
class Comment < ...
delegate :owned_by?, :to => :trip
end
# in your comment controller, for example
before_filter :find_comment
before_filter :require_owner
def require_owner
redirect_unless_owner_of(#commemt)
end
# in your application controller
def redirect_unless_owner_of(model)
redirect_to root_url unless model.owned_by?(current_user)
end
Forgive me if there are any syntax errors =) I hope this helps!
Acl9 is a authorization plugin. I'd give you the link, but I don't have cut and paste on my iPhone. If no one else provides the link by the time I get to a computer, I'll get it for you. Or you can google. Whichever. :)
I have only just started using it, but it has an extremely simple interface. You just have to create a roles table and a roles_user. Let me know how it goes if you decide to use it.
Or just use inherited resources:
InheritedResources also introduces another method called begin_of_association_chain. It’s mostly used when you want to create resources based on the #current_user and you have urls like “account/projects”. In such cases you have to do #current_user.projects.find or #current_user.projects.build in your actions.
You can deal with it just by doing:
class ProjectsController < InheritedResources::Base
protected
def begin_of_association_chain
#current_user
end
end