Devise and CanCan cannot :destroy User - ruby-on-rails

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

Related

CanCanCan :read block not working

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.

Block redirect to other posts pages

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

ArgumentError in Users::RegistrationsController#new

I am trying to get Cancan to work with Devise on my Rails application. I tried to follow the directions here to set it up:
http://www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/
I am stuck as I am getting the following error:
ArgumentError in Users::RegistrationsController#new
wrong number of arguments (2 for 1)
app/models/ability.rb:7:in initialize'
app/controllers/users/registrations_controller.rb:6:incheck_permissions'
My ability.rb reads as follows:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role? :super_admin
can :manage, :all
elsif user.role? :admin
can :manage, :all
elsif user.role? :user
can :read, :all
# manage products, assets he owns
can :manage, Product do |product|
product.try(:owner) == user
end
can :manage, Asset do |asset|
asset.assetable.try(:owner) == user
end
end
end
end
My registrations controller reads:
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :check_permissions, :only => [:new, :create, :cancel]
skip_before_filter :require_no_authentication
def check_permissions
authorize! :create, resource
end
end
This only occurs on the signup page. All other pages within the app are working fine.
When using cancan u have to use following line in your controller.
load_and_authorize_resource

Cancan 2.0 - restricting permissions to a field

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

CanCan index action ability

I'm having some trouble defining permissions for my albums#index action. The path to it is /user/:user_id/albums - this is the ability for my :show action (:read => [:index, :show]) which is working well. The path to it is /user/:user_id/albums/:id/.
can :read, Album do |album|
#user.friends_with?(album.user_id)
end
I'm not sure how to write a similar rule for the index action, or if I even want to use CanCan here. The rule is:
current_user MUST be .friends_with?(user_id) to view any albums belonging to user_id.
user_id is of course taken from params[:user_id]. Note: /user/eml/albums/ would be the path, I'm not fetching users by their .id but by their .username!
class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new # for guest, probably not needed
#user.roles.each { |role| send(role) }
end
def user
can :read, Album do |album|
#user.friends_with?(album.user_id)
end
can :manage, Album do |album|
album.user_id == #user.id
end
end
end
UPDATE:
Turns out the solution is really simple, I was just not paying attention to my routes:
resources users do
resources albums
end
In the controller that becomes pretty easy then:
load_and_authorize_resource :user, :find_by => :username
load_and_authorize_resource :album, :through => :user
And the rule:
can :read, Album, :user_id => #user.friend_ids # I don't need #user.id
I'm not perfectly happy with it though, as using the user.friends_with?(other_user) method would be much quicker and doesn't have to fetch (potentially) a thousand ids from the database. Any other solution is most welcome.
On IRC you told me that the .roles_mask isn't important... then shouldn't this be something like:
class Ability
include CanCan::Ability
def initialize(user)
if user
can :read, Album, :user_id => [user.id] + user.friend_ids
can :manage, Album, :user_id => user.id
end
end
end

Resources