authorize_resource not preventing access when it should? - ruby-on-rails

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

Related

cancancan authorize_resource not working as expected

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

How do I make certain pages of my ruby on rails application inaccessible to one of my STI based Devise User model types?

I want one pages of my ruby on rails web application inaccessible to one of my STI model types. I have two models typeA and typeB inheriting from User. I have used the column type in the User table to implement STI. I am using Devise gem for User sessions. I want one webpage 'http://localhost:3000/rate' inaccessible to my typeA User. Whenever an User logs in who is of the type 'typeA', he does not have the option of seeing the link 'Rate'. But I also do not want him to be able to access that page by the link 'http://localhost:3000/rate'. If he tries to access it through that link, I want to sign him out and make him log in again.
I managed this by using a piece of code in my Controller with the specific method for 'rate'.
def rate
if current_user.type == "typeA"
sign_out(current_user)
redirect_to new_user_session_path
else
#Code for User of typeB
end
end
This is working but I wanted to know if this can be done in a better way using before_filter :authenticate_user! or something else
Right now my before_filter part looks like this
before_filter :authenticate_user!, except: [:index, :show]
Is there any way I can make a change to the upper code to achieve that functionality.
P.S: Maybe this can be done better if I had used roles or other gems like CanCan/Pundit but I do not have much time left to submit my project, so I do not want to get into all that right now.
you can add another before_filter on the controller you want to restrict the access just to confirm your STI user type without overiding devise's authenticate_user! filter.
application_controller.rb
class ApplicationController < ActionController::Base
def confirm_user_type(user_type)
redirect_to new_user_session_path unless current_user.is_a?(user_type)
end
end
pages_controller.rb
class PagesController < ApplicationController
# must be authenticated to access
before_filter :authenticate_user!
# must be user of TypeA to access
before_filter { |c| c.confirm_user_type(TypeA) }
def rate
...
end
end
Then, you can use the same filter before_filter { |c| c.confirm_user_type(TypeB) } for STI user type: 'TypeB'
Try this:
class ApplicationController
before_action :authenticate_user!
def authorize_user!
if current_user.type == "typeA"
sign_out(current_user)
redirect_to new_user_session_path
end
end
end
with your controller:
class SomeController < ApplicationController
before_action :authorize_user!, except: [:index, :show]
def top_secret
...
end
end
I believe if a before_action (the new name for before_filter) renders or redirects, the action won't be processed.

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)

How to stop devise users from deleting other users posts?

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.

Ruby - Allowed/restrict Url access

I'm trying to set up something that allows users to go to certain urls only under certain circumstances. Right now I have a setEvent/:id url that sets a property on users to an event_id, then redirects the user to the event url. The user can access a url like .../whatever/event/1 where 1 needs to equal the event_id, and if it doesn't it redirects the user.
However, this doesn't stop someone from just typing .../whatever/setEvent/:id into their address bar to get access to the page.
The proper way to do this is with a before action in your controllers. Here is an example from one of my apps where a user who is not logged in will always be redirected to the new_session URL.
class ApplicationController < ActionController::Base
helper_method :current_user, :logged_in?, :herd_user
def herd_user
redirect_to new_session_url unless logged_in?
end
... other medthods...
end
and
class StaticPagesController < ApplicationController
before_action :herd_user
def index
end
end
without bringing in more gems you can just do a before_action
before_action :enforce_tenancy, except: [:index]
before_action :allow_only_admin, only: [:index]
private
def enforce_tenancy
render unauthorized unless event.user_id == current_user.id
end
def allow_only_admin
render no_way_sucka_path unless current_user.admin?
end
I had a similar problem and this might not be the best way to handle it but in the action for that page you can check the current url and check the property then redirect to the one they can access if they go to an incorrect url.
Something kind of like:
url_id = request.fullpath.sub('/whatever/event/', '')
redirect_to user_page_path(user.id) unless (current_user.event_id.to_s == url_id)
Sorry if the code isn't great I tried to write it based off of the info you gave.
Edit* Make sure to do this before getting any info for the page from your database or it will be less efficient.

Resources