After installing and setting up devise for user login I went ahead and added the rails_admin gem. Now if I go too localhost:3000/admin I can access the admin dashboard but How do I add admin authentication so you would need to enter admin credentials to access the admin dashboard?
I would recommend you to use an authorization gem called cancancan (is the updated version of cancan) it's super easy to use and it will let you to give certain permissions to different kind of users. If you don't know nothing about this gem i will recommend you to see this railscasts that will teach you how to use it properly.
So after you installed the cancancan gem in the ability.rb file you just need to do something like this to limit the admin access
models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user && 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
And don't forget to tell to the rails_admin gem that you are using cancancan to validate the authorization
config/initializers/rails_admin.rb
RailsAdmin.config do |config|
## == Cancan ==
config.authorize_with :cancan
end
To user the "user.admin?" method you must create it into the user model, but it will only work if you have a role model that has_many users and users belongs_to role otherwise you will need other way to verify the role, so it will be something like this
models/role.rb
has_many :users
models/user.rb
belongs_to :role
def admin?
role_id == 0 # If you have id == 0 for admin
end
Also i will recommend you to use a role model or enum to manage the different roles with ease.
I hope it helps :D
In your routes.rb
authenticate :admin do
mount RailsAdmin::Engine => 'rails_admin', as: :rails_admin
end
Provide your user with an admin role such that you can test, e.g. user.admin?.
Configure rails_admin to inherit from your application controller and to catch any access other than a signed in admin user.
#config/initializers/rails_admin.rb
RailsAdmin.config do |config|
...
config.parent_controller = 'ApplicationController'
config.authenticate_with do
# current_user = User.find_by_id(session[:user_id])
raise InvalidAdminAccess, 'Access denied.' unless current_user.try(:admin?)
end
end
Define the InvalidAdminAccess error class in
# app/errors/invalid_admin_access.rb
class InvalidAdminAccess < StandardError
end
Catch the error in your application controller
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
...
rescue_from InvalidAdminAccess do |exception|
respond_to do |format|
format.json { head :forbidden, content_type: 'text/html' }
format.html { redirect_to main_app.root_url, alert: exception.message }
format.js { head :forbidden, content_type: 'text/html' }
end
end
end
In this way, when there is incorrect access, rails_admin triggers an exception that is caught and handled by the application controller.
Related
I followed CanCanCan's configuration instructions for Rails Admin. I get the error message below:
CanCan::AccessDenied in RailsAdmin::MainController#dashboard
You are not authorized to access this page.
Extracted source (around line #180):
178 if cannot?(action, subject, *args)
179 message ||= unauthorized_message(action, subject)
180 raise AccessDenied.new(message, action, subject, args)
181 end
182 subject
183 end
ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, :all
can :manage, Article, user_id: user.id
return unless user.admin_role?
can :access, :rails_admin
can :read, :dashboard
can :manage, :all
end
end
rails_admin.rb:
RailsAdmin.config do |config|
## == CancanCan ==
config.authorize_with :cancancan
config.actions do
dashboard # mandatory
index # mandatory
new
export
bulk_delete
show
edit
delete
show_in_app
end
end
Gemfile includes these:
gem 'cancancan'
gem 'rails_admin', '~> 2.0', '>= 2.0.2'
Permissions seem to be working OK everywhere else though. There are 2 possible roles for a user: admin or user. On my admin account, I'm recognized as an admin and can do things users cannot. For example:
app/views/articles/index.html.erb:
<% if can? :update, article %><td><%= link_to 'Edit', edit_article_path(article) %></td> <% end %>
The "Edit" option only shows up for the user who created the article OR an admin. This works as expected.
In config/initializers/rails_admin.rb, removing config.authorize_with :cancancan and adding the code below seemed to solve the problem. Users with the admin_role can visit /admin, but others cannot. I'm still not sure why cancancan was not playing nicely, however this is now working as expected.
rails_admin.rb:
#config.authorize_with :cancancan
config.parent_controller = "::ApplicationController"
config.authorize_with do
if !current_user || !current_user.admin_role?
redirect_to(main_app.root_path, alert: "You are not permitted to view this page")
end
end
Thanks to philtr
As a note I am using Rails 6.0.2.2; Rails Admin 2.0.2; Cancancan 3.1.0; and I am NOT using Clearance.
Everytime i try to connect to my backoffice with an admin, I get this You are not authorized to access this page. even tho everything is setup to be supposed to accept admins. I've checked and all my employees are admins so it shouldn't be doing this!
I've tried to restrain basic users rights, I've tried accessing the page with every account but I always get this error, even tho all the employees are admins!
ability.rb
class Ability
include CanCan::Ability
def initialize(employee)
employee ||= Employee.new # guest employee (not logged in)
if employee.admin?
can :access, :rails_admin # only allow admin employees to access Rails Admin
can :read, :dashboard
can :manage, :all
authorize!(:dashboard, #employee)
end
end
end
employees_controller.rb
class EmployeesController < ApplicationController
def employee_params
params.require(:employee).permit(:email, :encrypted_password, :password_confirmation, :role)
end
end
accessible.rb
module Accessible
extend ActiveSupport::Concern
included do
before_action :check_employee
end
protected
def check_employee
if current_employee.admin
flash.clear
redirect_to(rails_admin.dashboard_path) && return
elsif current_employee
flash.clear
redirect_to(new_employee_session_path) && return
end
end
end
rails_admin.rb
RailsAdmin.config do |config|
config.parent_controller = "::ApplicationController"
config.authorize_with do |controller|
if current_employee.admin?
redirect_to main_app.new_account_session_path, flash: {error: 'Please Login to Continue..'}
elsif !current_employee.admin?
redirect_to main_app.root_path, flash: {error: 'You are not Admin'}
end
end
## == CancanCan ==
config.authorize_with :cancancan
config.actions do
dashboard # mandatory
index # mandatory
new
export
bulk_delete
show
edit
delete
show_in_app
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users, path: 'users'
devise_for :employees, path: 'employee'
namespace :user do
resources :users
end
namespace :employee do
resources :employees
end
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
root 'index#Index'
get '/Index', to: 'index#Index'
get '/ResidentialServices', to: 'pages#ResidentialServices'
get '/CorporateServices', to: 'pages#CorporateServices'
get '/Quotes', to: 'pages#Quotes'
get '/Awards', to: 'pages#Awards'
end
add admin to employees migration
class AddAdminToEmployees < ActiveRecord::Migration[5.2]
def change
add_column :employees, :admin, :boolean, default: false
end
end
employee (you can see that I've set my admin to true :
Employee.create!(lastName: 'xxx', firstName: 'xxx', title: 'Comm Rep', email: 'xxx#xx.xx', encrypted_password: BCrypt::Password.create('123456'), admin: 'true')
i expect admins to be able to access the backoffice
Shouldn't you pass variable instead of instance variable in ability.rb?
authorize!(:dashboard, employee)
instead of
authorize!(:dashboard, #employee)
cancancan expects a current_user not a current_employee
cancancan expects a current_user helper method, not a current_employee helper method - I'm guessing this is the helper method that you presently have, if you are using the devise gem.
You can check whether the employee you are passing into the ability method has been initialised using a debugger like byebug - my guess it that it has not been initialised at all. In short, you need to tell cancancan that you are using current_employee and not current_user
Here is a quick hack that should do that for you:
class ApplicationController < ActionController::Base
alias_method :current_user, :current_employee
end
I have Rails_admin installed with devise and I want to restrict the /admin dashboard to only admins. For the moment my code looks like :
config.authenticate_with do
warden.authenticate! scope: :user
end
config.current_user_method(&:current_user)
As you can see users can get in to the dashboard so I want only the users with a boolean true in the admin column of the user table to get access to the dashboard.
How would you suggest I do this ?
If you dont want to use cancan you can do this:
config.authorize_with do
redirect_to main_app.root_path unless current_user.try(:admin?)
end
I use this and it works fine.
I would recommend you to use an authorization gem called cancancan (is the updated version of cancan) it's super easy to use and it will let you to give certain permissions to different kind of users. If you don't know nothing about this gem i will recommend you to see this railscasts that will teach you how to use it properly.
So after you installed the cancancan gem in the ability.rb file you just need to do something like this to limit the admin access
models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user && 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
And don't forget to tell to the rails_admin gem that you are using cancancan to validate the authorization
config/initializers/rails_admin.rb
RailsAdmin.config do |config|
## == Cancan ==
config.authorize_with :cancan
end
To user the "user.admin?" method you must create it into the user model, but it will only work if you have a role model that has_many users and users belongs_to role otherwise you will need other way to verify the role, so it will be something like this
models/role.rb
has_many :users
models/user.rb
belongs_to :role
def admin?
role_id == 0 # If you have id == 0 for admin
end
Also i will recommend you to use a role model or enum to manage the different roles with ease.
I hope it helps :D
I am trying to test the user authentication for the Farm model, in this case for the :user role which has read access to all farms when being logged-in (as the guest user aka. anonymous has too).
# /models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
# Create guest user aka. anonymous (not logged in) when user is nil.
user ||= User.new
if user.has_role? :admin
can :manage, :all
else # guest user aka. anonymous
can :read, :all
# logged in user
if user.has_role? :user
can :create, Farm
can :manage, Farm, :user_id => user.id
end
end
end
end
...
# /controllers/api/v1/farms_controller.rb
class Api::V1::FarmsController < ActionController::Base
load_and_authorize_resource
rescue_from CanCan::AccessDenied do |exception|
redirect_to farms_path, alert: exception.message
end
respond_to :json
def index
# Next line might be redundant refering to the CanCan wiki. See below..
#farms = Farm.accessible_by(current_ability, :read)
respond_with(#farms)
end
end
...
# /spec/api/v1/farm_spec.rb
require "spec_helper"
describe "/api/v1/farms" do
let(:user) { create(:user) } # lets call this user1 in the discussion
let(:token) { user.authentication_token }
before do
user.add_role :user
create(:farm, user: user, name: "Testfarm")
create(:farm, name: "Access denied")
#ability = Ability.new(user)
end
context "farms viewable by this logged-in user" do
let(:url) { "/api/v1/farms" }
it "json" do
get "#{url}.json"
farms_json = Farm.accessible_by(#ability, :read).to_json
assert last_response.ok?
last_response.body.should eql(farms_json)
last_response.status.should eql(200)
farms = JSON.parse(last_response.body)
farms.any? do |farm|
farm["name"] == "Testfarm"
end.should be_true
farms.any? do |farm|
farm["name"] == "Access denied"
end.should be_true
end
end
end
The problem
When I inspect farms_json I can see it contains only the Testfarm. When I inspect the last_response I can see it contains both the Testfarm and Access denied. This is strange since I use the same accessible_by method both in the spec and the index action. The setup I use is described in the wiki of the CanCan gem entitled Fetching Records.
The useless workaround
When I add the user user to the farm Access denied, such as ...
create(:farm, user: user, name: "Access denied")
... then the test succeeds.
The questions
Why is the "Access denied" farm not returned although it can be read by any user (including guest users)?
Does get "#{url}.json" actually consider the status of the user? Is this all done by load_and_authorize_resource in the FarmsController?
The wiki mentions that #farms = Farm.accessible_by(current_ability, :read) can be left out since "this is done automatically by load_resource for the index action". Does this apply to my situation?
Experiments
I created another user "user2" and another farm "My little farm". I linked those to each other. This way the database in the example contains three farms alltogether:
Farm "Testfarm" associated to user1
Farm "Access denied" associated to no user
Farm "My little farm" associated to user2.
When I run Farm.accessible_by(Ability.new(user1), :read) I still only receive "Testfarm".
The answer to my question consists of multiple parts. I hope this clarifies the setup to everyone else who deals with a similar configuration.
1. Ability Precedence
First of all please mind that the order of ability rules does matter as described in Ability Precedence. After realizing this fact I came up with an updated set of ability rules.
# /models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
# Create guest user aka. anonymous (not logged-in) when user is nil.
user ||= User.new
if user.has_role? :admin
can :manage, :all
else
# logged in user
if user.has_role? :user
can :manage, Farm, :user_id => user.id
can :create, Farm
end
# guest user aka. anonymous
can :read, :all
end
end
end
2. FarmsContoller
Keep it simple in the index action. load_and_authorize_resource is your friend.
# /controllers/api/v1/farms_controller.rb
class Api::V1::FarmsController < ActionController::Base
load_and_authorize_resource
rescue_from CanCan::AccessDenied do |exception|
redirect_to farms_path, alert: exception.message
end
respond_to :json
def index
respond_with(#farms)
end
end
3. Get request with authentication token
Do not forget to pass the token when you request data from the farms controller.
# # /spec/api/v1/farm_spec.rb
get "#{url}.json", auth_token: :token
The token must be added in the User model as follows.
# app/models/user.rb
class User < ActiveRecord::Base
before_save :ensure_authentication_token
And the name of the method can be configured in the initializer of Devise.
# config/initializers/devise.rb
config.token_authentication_key = :auth_token
So I've implemented the rails admin gem, and even with the cancan gem, I can't figure out a way to password protect localhost:3000/admin
Could someone give me a step by step guide for doing this? I can't really find a view or controller for the admin panel, so I'm not sure how to password protect it.
This page describes how you should be using Cancan : https://github.com/sferik/rails_admin/wiki/CanCan
# in config/initializers/rails_admin.rb
RailsAdmin.config do |config|
config.authorize_with :cancan
end
Their ability.rb example may be a bit more than you need this is mine :
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
end
end
end
You must then give the admin role to a user.
you can do this at the rails console as so :
user = User.find(1) #find user with ID 1
user.add_role :admin #assign role
user.has_role? :admin #should evaluate to True