I have devise installed and configured on my rails 3 project and I want to make it so that only admins can create/edit users. How can I edit the devise controllers to accomplish this?
I suggest using CanCan for that.
First, you'll define abilities like :read, :create, :update and :destroy and assign them to user roles by using something like:
if user.admin?
can :manage, :all
end
Then, you'll check those abilities by checking whether the user has permissions to create/edit users by using something like if can? :create, User.
I've sorted this out previously. I remember it being a pain but it does work. It requires CanCan.
Given that an administrator is defined with an admin boolean on the User model:
user.rb:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
attr_accessor :current_password
attr_accessible :name, :password, :password_confirmation, :current_password, :email, :remember_me, :admin
end
class Ability
include CanCan::Ability
def initialize(user)
can :manage, :all if user.admin
end
end
users_controller.rb
def update
#user = User.find(params[:id])
params[:user].delete(:password) if params[:user][:password].blank?
params[:user].delete(:password_confirmation) if params[:user][:password].blank? and params[:user][:password_confirmation].blank?
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully updated "+#user.name
redirect_to users_path
else
render :action => 'edit'
end
end
routes.rb
devise_for :users, :path => "d"
devise_scope :user do
get '/sign_in' => 'devise/sessions#new'
get '/sign_out' => 'devise/sessions#destroy'
end
resources :users, :controller => "users"
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
after_filter :user_activity
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path
end
def admin?
self.admin == true
end
def authenticate_admin
redirect_to :new_user_session_path unless current_user && current_user.admin?
end
private
def user_activity
current_user.try :touch
end
end
application_helper.rb
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
That should do it.
If you need only to allow admins to create users you could write something like
class uUsersController < ApplicationController
def create
#validate if the current user is an admin
end
end
But the more standard, flexible way is to use a gem like cancan, which personally I would prefer :)
Related
I am a junior developer working on my first web application for a customer, an electronic commerce portal using Rails 4.2.4, Devise and a pins scaffolding.
Devise is working great and anyone can signup and login and CRUD a pin.
Problem: Users cannot be given access to CUD as the pins contain live customer products that are for sale.
I am therfore trying to implement pundit so that users are purely RESTRICTED to read only and CANNOT create, update or destroy pins, Only the business owner can do so in an Admin capacity.
Currently, I am getting the error "Pundit::NotDefinedError in PinsController#new
1.) How can I add myself as the admin(is this through a migration?)
2) How can I get Pundit working.
class PinsController < ApplicationController
before_action :set_pin, only: [:show, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def index
if params[:search].present? && !params[:search].nil?
#pins = Pin.where("description LIKE ?", "%#{params[:search]}%").paginate(:page => params[:page], :per_page => 15)
else
#pins = Pin.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 15)
end
end
def show
end
def new
#pin = current_user.pins.build
authorize #pins
end
def edit
end
def create
#pin = current_user.pins.build(pin_params)
if #pin.save
redirect_to #pin, notice: 'Pin was successfully created.'
else
render :new
end
end
def update
if #pin.update(pin_params)
redirect_to #pin, notice: 'Pin was successfully updated.'
else
render :edit
end
end
def destroy
#pin.destroy
redirect_to pins_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_pin
#pin = Pin.find_by(id: params[:id])
end
def correct_user
#pin = current_user.pins.find_by(id: params[:id])
redirect_to pins_path, notice: "Not authorized to edit this pin" if #pin.nil?
end
# Never trust parameters from the scary internet, only allow the white list through.
def pin_params
params.require(:pin).permit(:description, :image)
end
end
Blockquote
class ApplicationPolicy
attr_reader :user, :pin
def initialize(user, pin)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#user = user
#pin = pin
end
def index?
true
end
def show?
scope.where(:id => record.id).exists?
end
def create?
user.admin?
end
def new?
user.admin?
end
def update?
user.admin?
end
def edit?
update?
end
def destroy?
user.admin?
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
Blockquote
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
devise_parameter_sanitizer.for(:account_update) << :name
end
private
def user_not_authorized
flash[:warning] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
end
end
Blockquote
class Pin < ActiveRecord::Base
belongs_to :user
has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png"]
validates :image, presence: true
validates :description, presence: true
end
Blockquote
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :pins, dependent: :destroy
validates :name, presence: true
end
I have set up users with devise and each user can select a role. What I am trying to do is allow admins to be able to edit any user on the site if they have role admin. I currently have a UsersController setup like this:
class UsersController < ApplicationController
before_filter :authenticate_user!, only: [:index, :new, :edit, :update, :destroy]
skip_before_filter
def index
#users = User.order('created_at DESC').all
end
def show
#user = User.friendly.find(params[:id])
#users_authors = User.all_authors
end
# get authors index in here
def authors
end
def create
#user = User.create(user_params)
end
def edit
#user = User.find(params[:id])
end
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
if #user.destroy
redirect_to users_url, notice: "User deleted."
end
end
private
def user_params
params.require(:user).permit(:avatar, :email, :name, :biography, :role_id, :book_id, :username, :password, :password_confirmation)
end
end
This is trying to create a CRUD to edit users which works but I need to be able to populate the forms in the users/edit view wityh the correct selected users details. I my devise controller I have this setup:
class Admin::UsersController < Admin::BaseController
helper_method :sort_column, :sort_direction
before_filter :find_user, :only => [:edit, :update, :show, :destroy]
def index
#q = User.search(params[:q])
#users = find_users
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to admin_users_path, :notice => "Successfully created user."
else
render :new
end
end
def show
end
def edit
#user = User.find(params[:id])
end
def update
if #user.update_attributes(user_params)
redirect_to admin_users_path, :notice => "Successfully updated user."
else
render :edit
end
end
def destroy
#user.destroy
redirect_to admin_users_path, :notice => "User deleted."
end
protected
def find_user
#user = User.find(params[:id])
end
def find_users
search_relation = #q.result
#users = search_relation.order(sort_column + " " + sort_direction).references(:user).page params[:page]
end
def sort_column
User.column_names.include?(params[:sort]) ? params[:sort] : "created_at"
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "desc"
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:email,:username,:name,:biography,:role_id,:book_id,:role_name,:password,:password_confirmation,:encrypted_password,:reset_password_token,:reset_password_sent_at,:remember_created_at,:sign_in_count,:current_sign_in_at,:last_sign_in_at,:current_sign_in_ip,:last_sign_in_ip)
end
end
For clarity here is the user model:
class User < ActiveRecord::Base
belongs_to :role
has_many :books, dependent: :destroy
has_many :ideas, dependent: :destroy
accepts_nested_attributes_for :books
accepts_nested_attributes_for :ideas
def confirmation_required?
false
end
extend FriendlyId
friendly_id :username, use: [:slugged, :finders]
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_attached_file :avatar, styles: {
large: "600x450#",
medium: "250x250#",
small: "100x100#"
}, :default_url => "/images/:style/filler.png"
#validates_attachment_content_type :avatar, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
validates :avatar, :email, :username, :password, presence: true
def self.all_authors
User.select('users.id, users.username, users.role_id AS USER_ROLE')
.joins(:role).where(users: {role_id: '2'})
end
before_create :set_default_role
private
def set_default_role
self.role ||= Role.find_by_name('Admin')
end
end
In my routes I added a new route for users below the devise users resource as suggested on devise wiki like so:
devise_for :users, :path_prefix => 'my', :path_names => { :sign_up => "register" }
namespace :admin do
resources :users
end
Can anyone help with adding the ability of admins being able to edit all users here, I think its right but I cannot get the correct data into the forms in edit, it uses the current logged users details only.
A first draft for your ability.rb would be:
class Ability
include CanCan::Ability
def initialize(user)
# ...
if user.admin?
can :manage, User
end
# ...
end
end
And then in your user's controller remove the before_filter :find_user, :only => [:edit, :update, :show, :destroy] and related method, and use
load_and_authorize_resource :user
That would load the user from the URL and authorize! it using CanCan. You'll also need to handle the CanCan::AccessDenied exception for non-admin users visiting those pages, but that is another question that you can check in the CanCan docs.
When you visit admin_users_path routes you'll be able to CRUD them if you have the views ready and working.
After looking at the ability.rb. I have allowed the admins to manage
everything (that part works) but how do I allow the user to just, view
and edit their own Logg using cancan? At the moment the users cannot
view anything at all, not even their own created logg. But admins can do
everything.
class Logg < ActiveRecord::Base
has_and_belongs_to_many :user
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
ROLES = %w[admin moderator author banned]
has_and_belongs_to_many :logg
end
I have no User controller. I have the loggs controller:
class LoggsController < ApplicationController
before_action :set_logg, only: [:show, :edit, :update, :destroy]
load_and_authorize_resource
respond_to :html
def index
#loggs = Logg.all
respond_with(#loggs)
end
def show
respond_with(#logg)
end
def new
#logg = Logg.new
respond_with(#logg)
end
def edit
end
def create
#logg = Logg.new(logg_params)
#logg.save
respond_with(#logg)
end
def update
#logg.update(logg_params)
respond_with(#logg)
end
def destroy
#logg.destroy
respond_with(#logg)
end
private
def set_logg
#logg = Logg.find(params[:id])
end
def logg_params
params.require(:logg).permit(:name, :date, :time,
:whats_gone_well_this_week, :whats_not_gone_well_this_week,
:learnt_anything_new, :what_would_you_like_to_improve, :anything_else)
end
end
class Ability
include CanCan::Ability
def initialize(user)
if user.nil?
cannot :read, Logg
elsif user.admin?
can :manage, Logg
else
can :create, Logg, :user_id => user.id
can :update, Logg, :user_id => user.id
end
end
end
You need to add that that can read their Logg, like you have for create or update:
can :read, Logg, :user_id => user.id
def initialize(user)
if user.nil?
cannot :read, Logg
elsif user.admin?
can :manage, Logg
else
can :create, Logg, :user_id => user.id
can :update, Logg, :user_id => user.id
can :read, Logg, :user_id => user.id
end
end
But given all of those being their you probably want:
can :manage, Logg, :user_id => user.id rather than those three statements.
I created the app with the help of rails composer. Using devise for authentication and cancan for managing roles. So I have 3 roles by default: Admin, User and VIP. I deleted VIP, because I don't need it. Run rake db:seed to create a default admin. Then I'm coming to localhost and seeing the "First User" as admin. I logout and register(signup) a new user. Then, signing in again as admin. I see, that by deafault, this new user doesn't have any role. And also I see, that I can change it("Change role" - button). I push it and as admin can choose whether new user will be the second admin or just User. I choose, for example, User, push "change role" and have an "ArgumentError in UsersController#update wrong number of arguments (2 for 1)".
Sooo, I have two questions:
1. How to give my admin the ability to change roles without errors.
2. How to make new signing up users to have default role "User".
Thanks!
Ok, I managed to set the default role this way:
after_create :assign_reader_role
private
def assign_reader_role
self.add_role "user"
end
Here is my UserControlle:
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
authorize! :index, #user, :message => 'Not authorized as an administrator.'
#users = User.all
end
def show
#user = User.find(params[:id])
end
def update
authorize! :update, #user, :message => 'Not authorized as an administrator.'
user = User.find(params[:id])
if user.update_attributes(user_params)
redirect_to users_path, :notice => "User updated."
else
redirect_to users_path, :alert => "Unable to update user."
end
end
def destroy
authorize! :destroy, #user, :message => 'Not authorized as an administrator.'
user = User.find(params[:id])
unless user == current_user
user.destroy
redirect_to users_path, :notice => "User deleted."
else
redirect_to users_path, :notice => "Can't delete yourself."
end
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
Here is models.
User:
class User < ActiveRecord::Base
after_create :assign_reader_role
rolify
devise :database_authenticatable, :registerable,#:confirmable,
:recoverable, :rememberable, :trackable, :validatable
validates_presence_of :name
private
def assign_reader_role
self.add_role "user"
end
end
Role:
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource, :polymorphic => true
scopify
end
UserController I've already put! And where can I take params from the form?
I think you missed role_ids in permit
def user_params
params.require(:user).permit(:name, :email, :role_ids)
end
I have this custom controller in ActiveAdmin to allow to display buttons according to the user roles. I do this in the app/admin/invoices.rb file
controller do
load_and_authorize_resource :except => :index
def scoped_collection
end_of_association_chain.accessible_by(current_ability)
end
def action_methods
['index'] + (current_admin_user.role=="administrator" ? ['edit','update','new','create','destroy', 'show'] : ['show'])
end
end
If the user is not logged in I get this error...
NoMethodError in Admin::InvoicesController#index
undefined method `role' for nil:NilClass
How can I redirect to the login page admin_root_path instead? I also tested something like this...
def action_methods
if current_admin_user.nil?
redirect_to admin_root_path
elsif current_admin_user.role == "administrator"
['index', 'edit','update','new','create','destroy', 'show']
elsif current_admin_user.role == "customer"
['index']
else
end
end
and I get this error
AbstractController::ActionNotFound (AbstractController::ActionNotFound):
The AdminUser class adminuser.rb
class AdminUser < ActiveRecord::Base
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me,
:customer_id, :role
validates :customer_id, :presence => true, :if => :is_customer?
belongs_to :customer
after_create { |admin| admin.send_reset_password_instructions }
def password_required?
new_record? ? false : super
end
def is_customer?
self.role == 'customer'
end
before_create :set_new_user_as_customer
def set_new_user_as_customer
self.role = 'customer'
end
end
The Ability class ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= AdminUser.new
if user.role == "administrator"
can :manage, :all
elsif user.role == "customer"
cannot :create, :all
cannot :update, :all
cannot :destroy, :all
can :read, Shipment, :customer_id => user.customer_id
can :index, Invoice, :customer_id => user.customer_id
else
cannot :manage, :all
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
# Override build_footer method in ActiveAdmin::Views::Pages
require 'active_admin_views_pages_base.rb'
rescue_from CanCan::AccessDenied do |exception|
redirect_to admin_custom_dashboards_path, :alert => exception.message
end
def after_sign_in_path_for(resource_or_scope)
admin_custom_dashboards_path
end
def current_ability
#current_ability ||= Ability.new(current_admin_user)
end
end
/app/admin/invoices.rb
ActiveAdmin.register Invoice do
menu :if => proc{ can?(:manage, Invoice) }, :priority => 2
controller do
load_and_authorize_resource :except => :index
def scoped_collection
end_of_association_chain.accessible_by(current_ability)
end
def action_methods
['index'] + (current_admin_user.role=="administrator" ? ['edit','update','new','create','destroy', 'show'] : ['show'])
end
end
...
current_admin_user is an object of a ruby class.
Can you post the content of that class(that contains the role method?
I think, this object is not correctly initialized.
You are doing some mistake with if-else checking. Check this again carefully.
It looks like you need to make the current user object available to views by setting your current admin user variable. Currently current_admin_user is nil so it clearly isn't being defined. Try something like the following in your sessions controller, if you have one.
def get_user
current_admin_user = session[:current_user]
end
The expected result from action_methods is an array of action names, so when you try to return a redirect from that method you should expect an exception. You should be ensuring you have a logged-in user with a before filter (e.g. before_filter :authenticate_user!).
Another thing I would check (because I haven't used ActiveAdmin or abstract controllers in general) is to make sure that your AbstractController (controller do ... end) has access to the Devise controller methods - otherwise load_and_authorize_resource, authenticate_user!, etc. will fail.