I have 2 tables: posts and users(their relation is many-to-many), User has many favorite_posts(with FavoritePost table(it consists of user_id and post_id).
So, i have a route:
get 'favorite_posts', to: 'favorite_posts#index'
(users/:user_id/favorite_posts)
In my ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.new_record?
can :read, [Post]
else
can :read, [Post]
can :manage, [Post], owner_id: user.id
can :manage, [FavoritePost], user_id: user.id
end
end
end
In my controller(favorite_posts_controller.rb):
class FavoritePostsController < ApplicationController
load_and_authorize_resource through: :current_user
def index
#favorite_posts = User.find(params[:user_id]).favorite_posts
end
So, i need to block redirect to pages with favorite posts of other user through ability.rb. What i need to do?
Take a look at this quote from a CanCan maintainer:
CanCan can answer the question whether the user can or can't do
something, but what the app does from there is very context specific
and I don't think is a good fit for the ability.rb file.
If you don't want to let other users view the current user's favorite posts, it's best to put this in a before_action filter in your favorite_posts_controller:
class FavoritePostsController < ApplicationController
load_and_authorize_resource through: :current_user
before_action :correct_user, only: [:index] # add any other actions you want
def index
#favorite_posts = current_user.favorite_posts.all
end
private
def correct_user
user = User.find(params[:user_id])
redirect_to root_url unless current_user && current_user == user
end
end
Related
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.
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 have an app that is using CacCan and Devise. I am having Devise handle the User destroy action
The route
DELETE /users(.:format) devise/registrations#destroy
My Controller
class UsersController < ApplicationController
skip_before_filter :authenticate_user!, :only => :portfolio
def index
redirect_to dashboard_user_path(current_user)
end
def dashboard
...
end
def portfolio
end
end
My ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role?('Administrator')
can :access, :rails_admin
can :dashboard
can :manage, :all
else
cannot :destroy, User
can :read, :all
...
end
end
end
This code above does not work. A user who is not an administrator still has the ability to delete a user. I am assuming the reason is that I do not have UsersController#destroy method.
So my question is, How do I make CanCan prevent a user who is not an administrator from being able to delete a user?
Any help would be greatly appreciated.
Thanks
It seems to me that you defined abilities but not use them at all.
I suggest to read at least this (gem has a reach wiki with very useful information so it worth to read all articles).
I prefer to use powerful load_and_authorize_resource but in you case maybe enough authorize! (code is NOT tested!)
def destroy
#user = User.find(params[:id])
authorize! :destroy, #user
...
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 the following in my ability model :
class Ability
include CanCan::Ability
#...
def superuser_rules
can :access, :items
cannot :update, :items
can :update, :items, :foo_attributes
end
end
I have a form which mirrors that by only displaying the foo_attributes nested form.
However, when submitting the form, it says the access is denied to update the item.
Is there a way to circumvent this without adding new routes/actions ?
Many thanks !
You can create new actions to handle these "special attributes".
First you can clean up the special attributes of the params.
class UserController
before_filter :only => [:create, :update] { params[:user].delete(:accepted_at) }
end
Then you create a special action to change a special attribute:
def accept
User.find(params[:user_id]).update_attributes :accepted_at => Time.now
end
Now you can set different permissions for create, update, and accept actions.
class Ability
include CanCan::Ability
def initialize(user)
if user && user.admin?
can :accept, User
elsif user
can :update, User
end
can :create, User
end
end
Take a look at this too