I'm building a site-wide cancan auth system. Just to get very simple validation of my attempts, I'm initializing my test cases every time I try to hit a page:
# application_controller.rb
class ApplicationController < ActionController::Base
admin = Role.new(name: :admin, is_default: true)
admin.permissions << Permission.create(action: :manage, subject_class: :all)
admin.save
current_user=User.create!
# users automatically get associations to all roles with `is_default: true`
check_authorization
end
Only controllers with skip_authorization_check are letting me through, all others throw the AuthorizationNotPerformed error:
CanCan::AuthorizationNotPerformed in ExampleAuthEngin::PermissionsController#index
This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check.
What am I doing wrong?
Within the controller, you need to either call load_and_authorize_resource (at the class level), or within individual actions call authorize! :action, resource where :action and resource are the things to be tested. load_and_authorize_resource adds before_filters to the controller that instantiates an instance variable for the resource (where the method of instantiation is based on the action, and the resource to load is based on the controller name) and also adds a filter to authorize the loaded resource, authorizing on either :read, :create, :update, or :destroy, based on the action that's being hit.
You can read more about it here.
Related
I'm stuck with an implementation for a project I'm working on. Using rails 7 with devise, all users can sign in so I have the methods authenticate_user! and current_user, which, again, works fine.
I've introduced a Customer model. For now, I do not want the customer to register with password etc, I want them to click a link, sent to them from my application, then visit a page:
# Observer
class CustomerObserver < ActiveRecord::Observer
def after_create(customer)
secret_param = customer.to_sgid(expires_in: nil).to_s
url = ENV['HOST'] + "?csig=#{secret_param}"
# Send email with the url...
end
end
Once customer clicks that link, they should be taken to a special page and should have access to this controller only.
Been looking at GlobalID and not sure how to use the for: name in .to_sgid so that I could restrict the customer to access only the CustomersController and the show action.
In all controllers I have before_action :authenticate_user!. First thing came to mine is overriding that method. Feels wrong. How to have a customer access the CustomersController via the signed link and still be protected from unauthorize users?
I'm currently exploring this method where I could potentially have the customer model authenticatable? This means having two logins, one for user and other for customer. Would be nice to have one sign in path /sign_in/ for both user and customer. I feel this post is changing its direction.
How about some lazy check like this;
before_action :authenticate_user!, :if => :check_csig
# ...
private
def check_csig
# if csig param do not exists, call the authenticate_user!
true unless params[:csig].exists?
# if csig param exists and csig is valid do not call the authenticate_user!
false if check_csig
true
end
OR
before_action :authenticate_user!, :except => [:csgi]
before_action :check_csgi, :only => [:csgi]
# ...
private
def check_csig
#check if csgi is valid
end
My goal is to create a scope which can only be accessed by users with a certain role, in this case "editor". Or rather by admins and editors.
I'm using devise. Here's a part of my attempt:
scope "/editor" do
devise_for :editors
# ...other routes
end
When running it via "rails server":
/home/user123/.gem/ruby/2.4.1/gems/activesupport-5.1.3/lib/active_support/inflector/methods.rb:269:in `const_get': uninitialized constant Editor (NameError)
How should I fix it?
I think you're misunderstanding use of the scope argument. If you want to restrict access in a certain view you can do the following. Assume you have a video streaming service where a regular user can only access the index and show actions/views, yet an admin can see the rest (new edit etc...).
You make a concern (google DRY) at app/controllers/concerns/ and name it admin.rb
module IsAdmin
extend ActiveSupport::Concern
def admin?
return redirect_to videos_new, alert: "Access Denied" unless current_user.admin?
end
end
Now in your videos_controller.rb include the concern we just made and tell the controller in which views you want to use the admin? method.
Class VideosController < Applicationcontroller
include IsAdmin
before_action :admin?, only: [:new, :edit]
end
Motivation
I'm trying to use CanCan in an application where one of the controllers isn't associated with a resource - non RESTful controller. I want actions for that controller to be authorized conditionally dependant on other resources and session parameters.
Background
The CanCanCan documentation for Non RESTful controllers states that
you should not use the load_and_authorize_resource method since there
is no resource to load. Instead you can call authorize! in each action
separately.
Which shouldn't be an issue anyway as in theory I should be locking it down and ensuring that authorization happens on every action in my application by adding check_authorization to my ApplicationController. According to the CanCanCan documentation:
This will raise an exception if authorization is not performed in an
action.
Problem
The issue I'm having is that check_authorization doesn't appear to be locking down the actions for non RESTful controllers. Actions that don't declare authorize! are being executed without raising any AccessDenied exceptions.
Below is an MCVE that represents what I am doing. Am I doing something wrong or have I discovered a bug? What is the 'right' way to do this?
Implementation
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
check_authorization
def current_user
Object.new
end
protected
rescue_from CanCan::AccessDenied do |exception|
puts "CanCan::AccessDenied Exception thrown : message=#{exception.message}"
end
end
Within my controller I'm trying to manually authorize but only if certain conditions are met:
class FooController < ApplicationController
def new
authorize! :foo, :bar, unless: true
puts 'FooController#new'
end
def index
authorize! :foo, :bar, if: true
puts 'FooController#index'
end
def show
puts 'FooController#show'
end
end
And the Ability class that enables the :foo :bar authorization:
class Ability
include CanCan::Ability
def initialize(user)
puts 'CanCan::Ability#initialise'
can :foo, :bar
end
end
Output generated in Rails console:
2.3.0 :001 > app.get '/foo/new'
CanCan::Ability#initialise
FooController#new
2.3.0 :002 > app.get '/foo'
CanCan::Ability#initialise
FooController#index
2.3.0 :001 > app.get '/foo/1'
FooController#show
Note, that the show action doesn't even have the authorize! declaration, yet still allows execution without throwing an exception.
Also, what I don't understand is that Ability#initialize isn't called at all for the show action. To add to my confusion - the Ability#initialize method is called for both new and index - you would expect one of them to have the same behaviour as the show action, given that the new action shouldn't be invoking the authorize! method.
A smoother way to do it would be defining check_authorization in your ApplicationController, something like this:
def check_authorization
raise CanCan::AccessDenied.new('Some message', params[:action].to_sym, params[:controller].to_sym) if cannot? params[:action].to_sym, params[:controller].to_sym
end
Your rescue_from block would then do the rest.
And inside your controllers where you need to check authorization, rather than calling authorize! in the beginning of each action, you could actually prefer doing something like :
before_action: check_authorization
in the beginning, or using before_action with except or only, if you need to check authorization over selective actions, something like:
before_action: check_authorization, except: [:create, :update]
or
before_action: check_authorization, only: [:show, :update_permissions]
I don't know if it solves your problems completely, but it sure is a more readable way and works for both RESTful and non-RESTful controllers. Give it a try.
I have found a workaround to this issue.
In the scenario where, because of the conditional statements, you want the authorisation to fail within an action you need to explicitly authorize! an ability that you know won't be defined. For example;
condition ? (authorize! :foo, :bar) : (authorize! nil, nil)
This will then result in the AccessDenied exception being thrown for a non RESTful controller when the condition is false, which you can then handle as you would for other RESTful controllers where a resource hasn't been authorised.
However, this solution is not ideal - it feels very hacky and just doesn't feel like the CanCan way of doing things.
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'd like /something to only be accessible for logged in users, I have a current_user helper which returns a user id or nil if the current visitor is not logged in.
Where would be the best place to limit access to /something in the controller or can it be added as part of the routes?
You must add in controller :before_filter and create action for that.
:before_filter :authenticate
def authenticate
redirect_to(registration_path) unless current_user.nil?
end
Also you can use :only or :except filter options.
Or i did not understant question?
You should handle that in your controller. Routes decide where things go and then it is up to the controller to decide if you're allowed to go there.
You should have a general purpose authenticate method in your ApplicationController that checks if someone is logged in and redirects them to a login page if they're not. Then in your specific controller:
class SomethingController < ApplicationController
before_filter :authenticate
def handler
#...
end
end
You can skip authentication for a specific handling with the :except option:
before_filter :authenticate, :except => [ :this_one, :and_this_one ]
There are other options as well, see the filters section of the Action Controller Overview for details.