CanCanCan User edit his own profile - ruby-on-rails

I got a really strange issue here. Here is the line causing all the trouble in my ability.rb
can [:edit, :update, :destroy], User, id: user.id
When I launch the rails console, I got the expected behaviour:
u = User.last
a = Ability.new(u)
a.can?(:edit, u)
=> true
a.can?(:edit, User.first)
=> false
However when I launch a web browser, log me in as a user and try to edit another one, CanCanCan remains silent.
If I replace can by cannot, I can't edit any user. It's as if it didn't lookup the condition.
My UsersController got this line on top
authorize_resource
I'm stuck with this, any help would be gladly appreciated.
cancancan 2.3.0
rails 5.2.1

Make sure that your instance (#user) is loaded before authorize_resource action runs, otherwise it will check if user can access some Users (can?(:edit, User), which is always true), instead of exact user.
before_action :load_user, except:[:index, :new, :create]
authorize_resource
...
private def load_user
#user = User.accessible_by(current_ability, action_name.to_sym).find(params[:id])
end

Related

Cancancan ActiveAdmin: skip authorization for :create

I'm using Cancancan for authorization in ActiveAdmin.
Everything work fine except the :create. When create a new admin, cancancan will check is admin_user.id = id. However, ActiveAdmin make id = nil, so I can't create a new admin.
include CanCan::Ability
def initialize(admin_user)
can :manage, AdminUser, id: admin_user.id
....
end
end
My solution is everyone can skip authorization for create.
My application controller:
class ApplicationController < ActionController::Base
load_and_authorize_resource
skip_authorize_resource :only => :new
end
but it does nothing. Please help!
You can specify what actions need authorization like this...
def initialize(admin_user)
can [:edit, :destroy], AdminUser, id: admin_user.id
end
Just replace/add to [:edit, :destroy] with whatever actions you need that authorization for.
If you want all admin users to be able to perform an action on AdminUser...
can :some_action, AdminUser

How to avoid undefined method error

I want to check if a current object's user-id is the same as the id of the current user, so I can allow some operations only to logged-in users. I am using the Devise gem to help me with authentication.
That said, I want to ask a question with a broader scope. I have build associations, at least I think so, but when I open the corresponding pages in the browser I get the error:
undefined method 'user' for nil:NilClass
I know that this error often happens when a particular object in the database is not instantiated or has no entries, but I am using the console and a PostgreSQL GUI tool, to check if the data is present.
This is a screenshot https://www.evernote.com/shard/s233/sh/305c5194-87e0-4019-9eba-9a7f5d7a2839/7c89b4842cc6efc1/res/b7879832-7829-4fe3-b81a-386b6f81cc11/skitch.png?resizeSmall&width=832
First to clarify that I understand right, here's what some things do:
If you define a method (def x) within a controller's "private" section this means, that the data is only available within your controller?
With a callback (before_action) you populate your app's REST methods with the data of the private method, it might want to use?
Now I have an image model with:
class Image < ActiveRecord::Base
mount_uploader :image, ImageUploader
belongs_to :user
belongs_to :game, inverse_of: :images
end
The user model reads like this:
class User < ActiveRecord::Base
...
has_many :images
has_many :games
validates :first_name, :last_name, presence: true
end
In the corresponding image controller I use:
class ImagesController < ApplicationController
before_action :set_image, only: [:show, :edit, :update, :destroy]
before_action :set_game
before_action :authenticate_user!
before_action :check_user
...
private
def set_image
#image = Image.find(params[:id])
end
def set_game
#game = Game.all
end
def check_user
unless (#image.user == current_user) || (current_user.admin?)
redirect_to root_url, alert: "Sorry but you are not allowed to visit this page."
end
end
def image_params
params.require(:image).permit(:title, :alt, :desc, :image, :category)
end
end
With #image.user in the check_user method I try to get the user's id. If I only use the current_user.admin? it works but not as intended, obviously.
As you can see in the screenshot above, the user_id field is populated, so I have no idea why I get this error. Maybe I forgot something?
Based on you error message, the problem is on #image.user in check_user method. Here, #image is nil. You should check if #image.nil? there.
Probably change it to:
#image = Image.find(params[:id])
unless !#image.nil? && ((#image.user == current_user) || (current_user.admin?))
BTW, you should only check user in :show, :edit, :update, :destroy like:
before_action :check_user, only: [:show, :edit, :update, :destroy]
What you're asking is something called authorization.
Authentication - does user exist?
Authorization - does user have permission?
Devise provides authentication, whilst authorization has no "standard" process for Rails.
What you're asking is the base line requirement for authorization in a Rails based application. The way to fix this is to use one of the authorization gems, namely CanCanCan or Pundit to ensure the user can change the required objects.
I'd personally set up authorization as follows:
#Gemfile
gem 'cancancan'
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Image, user_id: user.id
end
end
This will allow you to simply call can? :read, #image to validate the authorization for the user.
Fix
The real problem you have is that you're trying to call .user on a non-existent variable.
for nil:NilClass
When you see the above error, it means that you're calling a method on an undeclared variable.
Unlike other programming languages, Ruby doesn't so much treat the variable as undeclared, but as nil - confusing many developers. In short, the error means you're trying to call .user on a variable which does not have the method present; the solution being to ensure #image is declared.
-
The error seems to be caused by this:
#image.user #-> #image does not exist
Therefore, you have to check why #image has not been declared.
I would hazard a guess that the error is caused by your routes. You need to make sure you're calling the images controller properly:
#config/routes.rb
resources :images
#app/controllers/images_controller.rb
class ImagesController < ApplicationController
def show
#image = Image.find params[:id]
authorize! :read, #image
end
end
This should enable only users who own the image to view it. You'll not have to worry about authentication because that will be handled by Devise.
You can use respond_to? method to check if an object can respond to a particular method before calling it. Just like this.
object.respond_to?(:method_name)

before_action for standard/admin users : how to give admin the rights of all other users?

I'd like to give some more rights to my admin users that the regular users, here are my before_action :
# users_controller.rb
before_action :correct_user, only: [:edit, :update, :show]
before_action :admin_user, only: [:destroy, :index, :admin_toggle]
So if you're the correct user, you can : edit, update, show your own profile.
If you're an admin_user, you can see the list (index), destroy your own profile, toggle admin any user, edit/update/show your own profile (admin_user is also a correct_user).
I'd like an admin_user to be able to edit, update, show others members profiles : do I need to write specific methods or is there a trick with before_action to do ?
It seems to be like admin_user should have the rights of correct_user -with any user ID given-.
If you use somekind of specific authentication tools like Devise for ex. then I suggest implementing authorization solution with
cancancan gem. You have a specific ability file there under your models directory where you can declare access-rights for different user roles.
Makes your future code much cleaner and easier to read also.
###EDIT:
As previous answer points out then there is also a CanCan gem but as much as I know then it is not supported in Rails4. While writing this answer, CanCanCan build-status in github is marked as failing but I've been using it for a long time now in my projects and I'm happy :)
###
ability.rb example:
def secretary_abilities
can [:create, :read, :update], Person
end
def superadmin_abilities
# superadmin can do everything that secretary can
secretary_abilities
# ...and can also do destructive actions
can [:destroy], Person
end
After that you can add checks into your views like this:
<% if can? :show, Person %>
<%= link_to 'Show', person_path(#person) %>
<% end %>

use cancan and devise in my application

I want to use cancan in order to limit the users that want to view some pages in my application.
so I try to do it by this tutorial:
http://www.roberthuberdeau.com/articles/9-Blog-tutorial-part-3
I have two roles: Admin and Worker, and I have two controllers: Tasksadmins and Workers.
I want to define the next thing:
1) Workers can manage and see all the things of the Workerscontroller.
2) Admins can manage and see all the things of the Tasksadminscontroller.
I'm not sure if I defined it correctly:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role? :Admin
can :manage, :tasksadmins
elsif user.role? :Worker
can :manage, :workers
end
end
end
the next thing that I think I don't need to implement is: "the def initialize user bit is for guest users." I force the users to sign_in with: before_filter :authenticate_user
the next thing is: start restricting access to the blog application based on user role:
I don't know what and where I should write.
in the example, he wrote:
authorize! :edit, #article
so I tried to write the next followings in the tasksadmins controller:
authorize! :edit, #tasksadmins
authorize! :new, #tasksadmins
authorize! :index, #tasksadmins
authorize! :create, #tasksadmins
authorize! :show, #tasksadmins
authorize! :destroy, #tasksadmins
but I got an error: undefined method 'authorize!' for TasksadminsController:Class
please help me, I'm in the end of the definition of cancan.
This article is pretty dated, I would suggest looking at the CanCan docs on the github page, particularly this one. This looks like what you want to do but as shown in the doc, you do not have to authorize every action individually. Also, this should help with your second issue with devise. If you are specifying the version for devise like as shown in the article I would highly recommend upgrading your gems if possible. Good luck!

require_owner code to limit controller actions not recognizing current user as owner [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
before_filter :require_owner
I am trying to restrict access to certain actions using a before_filter which seems easy enough. Somehow the ApplicationController is not recognizing that the current_user is the owner of the user edit action. When I take the filter off the controller correctly routes the current_user to their edit view information. Here is the code.
Link to call edit action from user controller (views/questions/index.html.erb):
<%= link_to "Edit Profile", edit_user_path(:current) %>
ApplicationController (I am only posting the code that I think is affecting this but can post the whole thing if needed).
class ApplicationController < ActionController::Base
def require_owner
obj = instance_variable_get("##{controller_name.singularize.camelize.underscore}") # LineItem becomes #line_item
return true if current_user_is_owner?(obj)
render_error_message("You must be the #{controller_name.singularize.camelize} owner to access this page", root_url)
return false
end
end
and the before_filter
class UsersController < ApplicationController
before_filter :require_owner, :only => [:edit, :update, :destroy]
#...
end
I simply get the rendering of the error message from the ApplicationController#require_owner action.
UPDATE: the link_to provides this address: localhost:3000/users/current/edit
Ok, this is the second bounty question that I have posted and then answered myself. Both times I have found the answer within an hour of my bounty post. Ha.
I simply changed the before filter method to get this to work. I left the application controller as it is in the code above but in the UsersController (the only one that wasn't cooperating) I did the following:
before_filter :require_user, :only => [:edit, :update, :destroy] # all actions require user to be logged in
before_filter :init_data # create a member variable called #post, initialized based on the action
before_filter :require_user_owner, :only => [:edit, :update, :destroy] #edit, update, and destroy actions require ownership
and then
private
def require_user_owner
obj = instance_variable_get("##{controller_name.singularize.camelize.underscore}") # LineItem becomes #line_item
return true if current_user.id == #user.id
render_error_message("You must be the #{controller_name.singularize.camelize} owner to access this page", root_url)
return false
end
That seemed to do it.
Seems to me that the current_user_is_owner?(obj) call have an issue. To prove it, modify the code to:
def require_owner
# LineItem becomes #line_item
obj = instance_variable_get("##{controller_name.singularize.camelize.underscore}")
if current_user_is_owner?(obj)
return true
else
render_error_message("You must be the #{controller_name.singularize.camelize} owner to access this page", root_url)
return false
end
end
You might want to paste the current_user_is_owner? method.
Hopefully it helps.

Resources