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)
Related
I have the following:
class ConversationsController < ApplicationController
before_action :set_conversation_show, only: [:show]
authorize_resource
def show
end
private
def set_conversation_show
#conversation = Conversation.where("sender_id = ?", current_user.id).find_by_id(params.fetch(:id))
end
end
My understanding is that:
Since a model instance (#conversation) is set in a before_action, authorize_resource should simply check to see if the current_user has access to that resource, and if not, deny access.
However, access is allowed....
When I change authorize_resource to load_and_authorize_resource, then access is denied as it should be.
Note: authorize_resource is the same as load_and_authorize_resource except it doesn't assume what what model instance should be loaded, but instead relies on there being one provided via a before_action. (more here).
I prefer to use authorize_resource for that very reason - because it doesn't automatically assume the model instance it is to check permissions against.
So the question is, why isn't the above code working as expected and denying access?
For reference
The relevant ability.rb:
can [:show], [Conversation] do |conversation|
conversation.sender_id == user.id
end
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 devise user ( that I call a Provider in my app) and I am trying to write a custom authentication method to prevent the Providers from deleting each other's posts (called Procedures in my app). Right now I have the correct_user method in my procedures controller.
def correct_user
#provider = #procedure.provider.find(params[:id])
redirect_to(provider_path(current_provider)) unless current_provider?(#provider)
end
I call it with the following before filter, also in my procedures controller:
before_filter :correct_user, :except => [:index, :show]
And I get the following error when trying to edit a procedure, even the provider's own procedure:
NoMethodError (undefined method `provider' for nil:NilClass)
app/controllers/procedures_controller.rb:8:in `correct_user'
Parameters: {"id"=>"523"}
From the looks of this error, the correct_user method is finding the procedure id instead of the provider id. How can I fix this? Thanks
Authentication is about making sure that the user is who he says is. Devise is an authorization library. The only access control it provides is that you can make actions off limits for unknown users.
Authorization is making rules about who gets to do what. Popular libraries include Pundit & CanCanCan.
Even without a lib you could write a simple authorization rule like this:
class Provider < ActiveRecord::Base
class NotAuthorized < StandardError; end
end
class ApplicationController < ActionController::Base
rescue_from Provider::NotAuthorized, with: :deny_access
private
def deny_access
render 'some_view', status: 403
end
end
class ProceduresController < ApplicationController
before_action :find_procedure, only: [:show, :edit, :update, :destroy]
before_action :authorize_resource!, except: [:new, :index, :show]
# DELETE /procedures/:id
def destroy
# This line never gets run if the user is not authorized.
#procedure.destroy
end
private
def find_procedure
#procedure = Procedure.find(params[:id])
end
def authorize_resource!
unless current_provider == #procedure.provider
raise Provider::NotAuthorized and return false
end
end
end
Notice that in the authorize_resource! method you compare the user id of the record that you are authorizing against the user id from the session.
If you used the id from the params you're leaving yourself wide open to a spoofing attack where a user pretends to be someone else by passing another user's id in the params.
However, I would not recommend that you write an authorization solution from scratch unless you really know what you are doing.
The error message tell you this:
Your variable #procedure is nil at the time that the method correct_user is called.
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 working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end