I am learning to use Pundit for authorization. But the way I see it is authorization for resources not pages. I want a user to be redirected to a unauthorized page if he/she is not authorized to visit the page using pundit.
For e.g.
class OnlyAdminCanVisitController < ApplicationController
before_filter :admin_authenticate
Stops a non-admin role user.
Also, I want to take care of made up scenarios like following(Considering there are 4 roles as Admin,Manager,Employee,Outsider. The design below is obiviously bad)
class AdminManagerCanVisitController < ApplicationController
before_filter :admin_or_manager_authenticate
class AdminEmployeeCanVisitController < ApplicationController
before_filter :admin_or_employee_authenticate
class AdminOutsiderCanVisitController < ApplicationController
before_filter :admin_or_outsider_authenticate
class AdminManagerEmployeeCanVisitController < ApplicationController
before_filter :admin_or_manager_employee_authenticate
I have 4 roles and would like to write pundit policies for these controllers which allows any combination of authorizations.
Let me know if pundit is designed to tackle this issue.
Thanks
There is not much difference between pages and resources actually. So you can solve your problem by rescuing a denied Authorization from your application_controller.rb :
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
protected
def admin_authenticate
authorize current_user, :admin?
end
private
def user_not_authorized(exception)
# Redirect to whatever page you want if not authorized
end
end
You then need to define your policy. I generally create an admin? method in application_policy.rb (https://github.com/elabs/pundit#policies) so it is spread on my other policies as well :
class ApplicationPolicy
def admin?
# Logic to ensure the user is an admin
end
end
class UserPolicy < ApplicationPolicy
end
Then in your other controllers, just do as you did :
class OnlyAdminCanVisitController < ApplicationController
before_action :admin_authenticate
end
Related
I am using existing rails application, where we currently use devise for authentication and Pundit for authorization. My requirement is to skip all policies scope authorization for all action in this existing rails application. How Can I do this?
I have tried below code but not worked:-
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery with: :exception
before_action :authenticate_user!, :set_default_timezone#, :modify_search_params
before_action :set_current_user
skip_after_action :verify_policy_scoped
#.....
end
Thanks in advance :)
Pundit provides skip_authorization to skip his authorization. Executing it before all actions of the controller will make it work for your requirement.
class ApplicationController < ActionController::Base
# ...
before_action :skip_all_authorization
private
def skip_all_authorization
skip_authorization
end
# ...
end
You need to skip both action authorization with an object (which are called by using authorize(object) and with a policy scope (called with policy_scope).
You can skip the hooks on your base class:
skip_after_action :verify_policy_scoped
skip_after_action :verify_authorized
Or just add another hook to skip them on your controller (my preferred approach)
after_action :skip_all_authorization
private
def skip_all_authorization
skip_policy_scope
skip_authorization
end
But by the way, you shouldn't need this unless you're ensuring the policy is called by adding the appropriate hooks.
I'm using Devise + ominauth2 to allow users to sign in via different services. If they sign in they get a slightly better experience but if not life still goes on. However I don't see a way with Devise to have a page that allows both authenticated and unauthenticated access.
For example:
class SomeController < ApplicationController
before_action :authenticate_user!
def some_action
end
end
In the above controller, some_action is only accessible if the user authenticates. But what I actually want is something more like:
class SomeController < ApplicationController
def some_action
loadSessionFromCookieIfExists
end
end
Then, I can use user_signed_in? in my views. But this doesn't work because authenticate_user! will redirect them elsewhere. Unless there is an authenticate_if_possible method?
So what I have now, which is really gross is:
class SomeController < ApplicationController
before_action :authenticate_user!
skip_before_action :authenticate_user!, :only=>[:no_auth]
def action
do_stuff
return
end
def no_auth
do_stuff
return
end
private
def do_stuff
render :template => "action"
end
end
I should add, that omniauth2 doesn't support rememberable out of the gate so it's possible this is all supposed to work just fine and the problem is with omniauth2.
I'm using the CanCan gem in my Rails app and want to check if the current request is a protected resource in my application.
So for example I have the following:
class AdminController < ApplicationController
load_and_authorize_resource
end
How can I check if the request is protected by CanCan?
I can access the controller and action via params. I can't use the standard can? and cannot? helpers as they will check if the current_user has permission rather than if the action itself has a protection on it.
So for example:
class ApplicationController < ActionController::Base
before_action :check_protected
def check_protected
if can? params[:action].to_sym, Object.const_get(params[:controller].classify)
# resource is protected
else
# resource is not protected
end
end
end
^^ This won't work because it will always say false when no current_user or if the user doesn't have permission. I need to check if the resource itself is protected with CanCan.
If I had these examples:
class PostsController < AdminController
def index
end
end
class HomeController < ApplicationController
def index
end
end
The index for PostsController should be identifiable as protected, and the index for HomeController as unprotected.
CanCan uses CanCan::ControllerResource#skip? method to determine whether it should authorize resource or not. So I guess you may rely on it as follows:
def check_protected
if CanCan::ControllerResource.new(self).skip?(:authorize)
# resource is not protected
else
# resource is protected
end
end
I've tried it in my sandbox and it worked for me
So I am running into a problem where I have two separate devise models (Admin and User), and because I am using a multi tenancy gem called Milia I need to two application controllers (One for the Admin which is outside of the tenancy and one for the User which is inside the tenancy. The problem is I need to access one controller from both of those devise models, but I can only inherit that controller from one of the below application controllers. Is there a way around this where I can specify which user should use which application controller when hitting the controller for a particular resource? Or is there another way around this problem?
The two application controllers look like this
class AdminApplicationController < ActionController::Base
before_action :authenticate_admin!
end
class ApplicationController < ActionController::Base
before_action :authenticate_tenant!
end
Give you some inspiration :)
class ApplicationController < ActionController::Base
before_action :authenticate_them
def authenticate_them
authenticate_tenant!
end
end
class AdminApplicationController < ApplicationController
before_action :authenticate_them
def authenticate_them
authenticate_admin!
end
end
class AnotherController < ApplicationController
before_action :authenticate_them
def authenticate_them
if admin_signed_in? or tenant_signed_in?
#or whatever other authentication you want
end
end
end
I have an engine called Admin and a few controllers.
# admin/app/controllers/admin/application_controller.rb
module Admin
class ApplicationController < ActionController::Base
end
end
# admin/app/controllers/admin/foo_controller.rb
module Admin
class CardsController < ApplicationController
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
If I visit my main app and THEN /admin the CardsController inherits from ApplicationController NOT Admin::ApplicationController. If I first visit /admin and then the main app it works.
I guess that when we first visit the main app the constant ApplicationController is created, which is then found and used when visiting /admin instead of loading Admin::ApplicationController.
How can one avoid this issue?
You'll have to be specific about which class you are inheriting from:
module Admin
class CardsController < Admin::ApplicationController
# ...
end
end