rails_admin with cancan not catching access denied exception for redirect - devise

I am using rails 5, rails_admin, devise and cancancan.
Everything works correctly, but when there is access denied, it shows a 'You are not authorized to access this page' error screen.
I want to redirect to root_path, I've been searching and I only found that I have to write in app/controllers/application_controller.rb this code:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path, :alert => exception.message
end
end
And I did, but I am still in the error message when not authorised. It does not redirect.
I think the rest of the code must be ok, because it works, but not redirecting to anywhere.
#config/initializers/rails_admin.rb
config.authorize_with :cancan
config.current_user_method(&:current_user)
.
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin
can :access, :rails_admin # only allow admin users to access Rails Admin
can :dashboard
can :manage, :all
else
can :read, :all # allow everyone to read everything
end
end
end
I saw more people asking the same, but all of them are without answers. I found one with 3 answers, but I don't understand the accepted solution because it really do not explain any solution:
Cancan + Devise rescue_from not catching exception

It looks like ApplicationController isn't actually a parent of RailsAdmin::MainController by default. So, when RailsAdmin::MainController throws the CanCan::AccessDenied exception, it never actually touches ApplicationController, and the rescue block never kicks in.
You can explicitly declare ApplicationController as the parent for RailsAdmin::MainController in the rails_admin.rb config block with
config.parent_controller = 'ApplicationController'

You can also achieve this by extending the rails_admin controller. This is monkey patching, but can be useful if you don't want to set the parent controller to ApplicationController due to a particular reason.
Add following to config/initializers/rails_admin_cancan.rb file.
require 'rails_admin/main_controller'
module RailsAdmin
class MainController < RailsAdmin::ApplicationController
rescue_from CanCan::AccessDenied do |exception|
flash[:alert] = 'Access denied.'
redirect_to main_app.root_path
end
end
end

Related

How to apply abilities to a non-restful controller in cancan

I'm new to rails and for the life of me I don't "get" cancan.
I've read this tutorial but can't figure out how to apply instructions to my situation.
In the cancan wiki there is:
an AdminController
a roll_logs action
In the ability class is says:
can :roll, :logs if user.admin?
I don't get what the :roll and :logs symbols have to do with the controller and the action?
All I want to do is say, if a user is an admin, give them access to the AdminController actions, otherwise don't, is this possible?
Yes this is possible.
The statement
can :roll, :logs if user.admmin?
means that when calling authorize! :roll, :logs an unauthorized exception gets thrown if the user isn't an admin.
So it doesn't have anything to do with a controller or an action, untill you make it so.
If you have a logs_controller for example with an action roll you could do something like this.
class LogsController < ApplicationController
def roll
authorize! :roll, :logs
# Rest of the roll functionality.
end
So in your example, you want to give users who are admin permission to access all admin controller actions.
You can achieve this like this.
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can(:manage, :admin) if user.admin?
end
end
admin_controller.rb
class AdminController < ApplicationController
authorize_resource :class => false
def foo
end
def bar
end
end
This will make sure that only admins can access the foo and bar actions of the admin_controller.
The :class => false statement means that you are not authorizing a resource, which is what we want since you are not for example authorizing a certain post or comment. You are just authorizing actions on a controller.

Applying cancan to custom actions

I'm working on a polling app and use Devise and Cancan for authentication and authorization. I have a User model generated by Devise and a Poll model. A poll belongs to a user. My problem is that I have a custom action in my Polls controller and I can't get Cancan to work with that custom action. This is what I tried to do in my code:
config/routes.rb:
match 'users/:user_id/polls' => 'polls#show_user'
Ability.rb:
def initialize(user)
user ||= User.new
if user.is? :admin
can :manage, :all
else # default
can :read, :all
can :manage, Poll, :user_id => user.id
can :show_user, Poll, :user_id => user.id
end # if else admin
end
polls_controller.rb:
class PollsController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource :except => :show_user
def show_user
authorize! :show_user, user
#polls = Poll.find_all_by_user_id(params[:user_id])
render "index"
end
<...>
end
The idea is that a user's polls can be viewed only when the owner of the poll is signed in. However, with this code, when a poll's owner is signed in, that user gets kicked out of that page with a message that says authorization failed. If I remove the line authorize! :show_user, user, then a user who's signed in can view all other user's polls (the authorization doesn't work at all).
Can anyone see what I might be missing? Thanks in advance!
In abiltity.rb, you're verb/noun combination is :show_user and Poll, but in your controller you're using :show_user and user--you would need to use a Poll instead.
If, instead you want to allow the user to view all their own Polls, you might go with something like:
ability.rb:
can :show_polls_for, User, :id => user.id
polls_controller.rb:
def show_user
authorize! :show_polls_for, user
...
end

Ruby on Rails – CanCan ability to only let an admin view published blog posts

tl;dr
I use CanCan for authorization in a single-author blog. I want non-admin users to not be able to view unpublished posts. The following does not do the trick:
can :read, Post do |post|
post.published_at && post.published_at <= Time.zone.now
end
Why doesn't it work, and what can I do to make it work?
Thanks. ;-)
The long version
Hello World,
I have a single-user blogging application and use CanCan for authorization purposes. I want administrators (user.admin? # => true) to be able to do whatever they wish with everything (they are administrators after all…). I also want regular users (both those who are logged in, but does not have the admin role, and those who are not logged in) to be able to view blog posts that have been published. I do not want them to see those that are not published.
Blog posts (of the model Post) each have an attribute called published_at (which is a DateTime and nil by default). Needless to say: when published_at is nil, the post is not published, otherwise it is published at the set date and time.
I have the following in my Ability class:
class Ability
include CanCan::Ability
def initialize user
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, Post do |post|
post.published_at && post.published_at <= Time.zone.now
end
end
end
end
However, this does not seem to work as I intend it to. I have read on the CanCan wiki that this might not always work. However, I believe it should work in my case here, as I do have an instance of the Post model called #post in my PostsController#show action:
class PostsController < ApplicationController
authorize_resource
respond_to :html, :json
# other actions omitted ...
def show
#post = Post.find params[:id]
respond_with #post
end
# other actions omitted ...
end
Even with this code I am able to visit the blog post through the show action and view. I have also tried removing the authorize_resource call from the PostsController, realizing it might override some abilities or something, but it didn't help.
I have figured out a temporary solution, although I find it ugly and really want to utilize the CanCan abilities. My ugly temporary solution checks internally in the PostsController#show if the user has access to view the resource:
def show
#post = Post.find params[:id]
unless #post.published_at
raise CanCan::AccessDenied unless current_user && current_user.admin?
end
respond_with #post
end
As I said, this works. But I don't really want to go with this solution, as I believe there's a better way of doing this as a CanCan ability.
I'd much appreciate an explanation of why my approach does not work as well as a good solution to the problem. Thanks in advance. :-)
At the point where authorize_resource is being called (before_filter) you don't have a post object to authorize.
Assuming CanCan 1.6 or later, try this..
In your Post model
class Post < ActiveRecord::Base
scope :published, lambda { where('published_at IS NOT NULL AND published_at <= ?', Time.zone.now) }
# the rest of your model code
end
In your Ability model
class Ability
include CanCan::Ability
def initialize user
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, Post, Post.published do |post|
post.published_at && post.published_at <= Time.zone.now
end
end
end
end
In your controller
class PostsController < ApplicationController
load_and_authorize_resource
respond_to :html, :json
# other actions omitted ...
def show
respond_with #post
end
end

Devise - Run checks after user logged in, redirect if error

I'm working on an e-commerce application. When a user logs into my app, I want to make a check to my external subscription handler and make sure that their subscription is still active and not expired/failed/whatever.
I successfully figured out how to use a Warden callback in my initializers/devise.rb to perform a check on the model after login. However, if there is a problem, I want to log them out again and redirect to a certain page that tells them what to do next.
Here is what I have. I know I can't use redirect_to from the callback. Given that, what is the best way to do what I'm trying to do?
Warden::Manager.after_authentication do |user, auth, opts|
begin
user.check_active_subscription # this works, and will raise one of several exceptions if something is goofy
rescue
redirect_to "/account/expired" # obviously this won't work, but see what I'm trying to do?
end
end
Just let the callback raise the exception and rescue from it in your controller. E.g.:
Warden::Manager.after_authentication do |user, auth, opts|
user.check_active_subscription
end
class SessionsController < ApplicationController
def create
# Authenticate
rescue SubscriptionExpiredException
# Logout
redirect_to "/account/expired"
end
end
You could also use rescue_from in your ApplicationController like this:
class ApplicationController
rescue_from SubscriptionExpiredException, :with => :deny_access
def deny_access
redirect_to "/account/expired"
end
end

Rails & Devise: How to authenticate specific user?

I'm using Devise for the first time with rails, and I'm having trouble with one thing:
I used the provided authenticate_user! method in my user's controller to restrict access to pages like so:
before_filter :authenticate_user!, :only => [:edit, :show, :update, :create, :destroy]
But this allows any signed in user to access any other users :edit action, which I want to restrict to only that user. How would I do that?
In your edit method, you need to do a check to see if the user owns the record:
def edit
#record = Record.find(params[:id])
if #record.user == current_user
#record.update_attributes(params[:record])
else
redirect_to root_path
end
end
You should look into Authorization such as CanCan. Or alternatively create a new method like so:
# Create an admin boolean column for your user table.
def authenticate_admin!
authenticate_user! and current_user.admin?
end