Admin Tree in Rails HRM - ruby-on-rails

I've been trying to develop a Human Resource Management system for my organisation and have created the basic infrastructure. I've now got to the point where I need to create admin authorisation. My organisation is very much tree-structured and from what I've grasped, what I want to do isn't really facilitated in some of the Admin gems such as Active Admin (although please correct me if I'm wrong!). I have considered using a parent_id field for each level below but am not sure how I would facilitate this in the app.
Effectively, I want the lowest level of employees to be able to view the majority of their personal data minus a few classes such as performance reports/notes and similar attributes, and to be able to edit basic details such as contact details to ensure they are up to date. Their line manager should then be able to view and all of the details for those employees they are in charge of. There are about four tiers to the organisation, although I would like to keep it expandable. There are also multiple line-managers at the second and third tier, which I believe is where the problem in the gems arises.
I have installed the Ancestry gem as I assume this will be the key to unravelling the dilemma but wondered if anyone had any bright ideas. My current employee.rb file is below, just not sure where to go from here.
class EmployeesController < ApplicationController
before_action :set_employee, only: [:show, :edit, :update, :destroy]
before_action :logged_in_employee, only: [:index, :show, :edit, :update, :destroy]
before_action :correct_employee, only: [:show, :edit, :update]
before_action :admin_employee, only: :destroy
# GET /employees
# GET /employees.json
def index
#employees = Employee.paginate(page: params[:page])
end
# GET /employees/1
# GET /employees/1.json
def show
end
# GET /employees/new
def new
#s3_direct_post = S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: 201, acl: :public_read)
#employee = Employee.new
end
# GET /employees/1/edit
def edit
#employee = Employee.find(params[:id])
end
# POST /employees
# POST /employees.json
def create
#employee = Employee.new(employee_params)
if #employee.save
#employee.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
# PATCH/PUT /employees/1
# PATCH/PUT /employees/1.json
def update
respond_to do |format|
if #employee.update(employee_params)
flash[:success] = "Profile updated"
format.html { redirect_to #employee, notice: 'Employee was successfully updated.' }
format.json { render :show, status: :ok, location: #employee }
else
format.html { render :edit }
format.json { render json: #employee.errors, status: :unprocessable_entity }
end
end
end
# DELETE /employees/1
# DELETE /employees/1.json
def destroy
Employee.find(params[:id]).destroy
flash[:success] = "Employee deleted"
respond_to do |format|
format.html { redirect_to employees_url, notice: 'Employee was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_employee
#employee = Employee.find(params[:id])
end
# Confirms a logged-in user.
def logged_in_employee
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_employee
#employee = Employee.find(params[:id])
redirect_to(root_url) unless current_employee?(#employee)
end
# Confirms an admin user.
def admin_employee
redirect_to(root_url) unless current_employee.admin?
end
# Never trust parameters from the scary internet, only allow the white list through.
def employee_params
params.require(:employee).permit(:avatar, :email, :first_name, :last_name, :service_no, :password, :date_of_birth, :gender, :service_start_date, :substantive_rank, :promotion_date, :passport_number, :passport_expiry, :passport_country_of_origin, :nationality, :national_insurance)
end
end

In ActiveAdmin the authorization happens when you access a resource (or collection). It basically uses the action name (eg index, show, edit, update, etc) and the model object or class depending on what is available to authorize the request. Without configuration ActiveAdmin does not provide an authorization solution. If you do not configure anything it will just let any user do anything.
ActiveAdmin's authorization method works really well with the cancan gem (or rather its successor the cancancan gem) but Pundit is also supported out of the box. Rolling your own adapter is pretty easy as well: you can use any gem with little effort.
For your specific problem:
The relationships between the employees and how you do authorization doesn't necessarily have to affect each other. A role based approach seems to cover your problem pretty well. For example you can create roles for the different hierarchy levels:
Employee (lowest level)
Line manager
Boss of line manager
Admin (superuser, can do anything)
A user can have multiple roles or roles can be inclusive (line manager can do everything that an employee can). From what I understand the latter is a better fit for your problem.
For a concrete case when someone wants to update a user's contact info (not necessarily her own) you have to check if the user's role permits the update operation and that the current user has access to the other user object. In case of an employee it has to be herself, but a line manager could update her subordinates' contact info as well.
Most authorization libraries provide a declarative way of expressing what different roles can do. I encourage you to take a look at the cancancan gem.

Related

How to allow ONLY Admins to create new users?

I'm using Devise and I want allow Only Admins to create new users. I've already reviewed This answer but it looks outdated. I've tried so many possible answer but nothing worked. I'm looking for a bit detailed answer as I'm still a newbie.
Admins are labeled with Boolean value in the users table I'm trying to keep things minimal.
You could achieve this numerous ways. What I have done in the past is a combination of showing\hiding the links in the views and checking the user in the controller. I've assumed you have a form with the user details that you will submit to the user controller.
I've included a controller from one app I worked on below.
The first thing I do is to check to see if the user is authenticated (we use google for this but if you have set up devise you won't need this and probably have your own authentication in place). Devise will have created the current_user object if you have logged in which should include your "role" attribute. In the standard user create you can check the current user.role and simply redirect if the current_user.role is not 1 (I assumed 1 means admin).
class UsersController < ApplicationController
# Set the user record before each action
before_action :set_user, only: [:show, :edit, :update, :destroy]
# User must authenticate to use all actions in the users controller
before_filter :authenticate_user!
def create
if current_user.role = 1 then
#user = User.new(user_params)
#user.password = Devise.friendly_token[0,20]
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render action: 'show', status: :created, location: #user }
else
format.html { render action: 'new' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
else
format.html { redirect_to #user, notice: 'You do not have sufficient rights to set up a new user.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:notice] = "User record does not exist"
redirect_to users_url
end
end
Well the simplest way to do that is to add a before_action to your users controller limit the create and edit and anyother action you want to specific criteria
before_action :create_user , only: [:edit , :update , :new, :correct_user]
and then you can define a create user private method
def create_user
#user=User.find(params[:id])
redirect_to #user unless #user.criteria == criteria
end
Hope this is what you are looking for. If not please comment with further details.
#
# In Users_Controller
#
before_action :check_user_role , only: [:edit , :update , :new, :create]
def check_user_role
redirect_to home_path if current_user.role != "Your Role"
end

Rails 4 with Pundit

I am trying to make an app in Rails 4.
I want to use Pundit for authorisations. I also use Devise for authentication and Rolify for role management.
I have a user model and am making my first policy, following along with this tutorial:
https://www.youtube.com/watch?v=qruGD_8ry7k
I have a users controller with:
class UsersController < ApplicationController
before_action :set_user, only: [:index, :show, :edit, :update, :destroy]
def index
if params[:approved] == "false"
#users = User.find_all_by_approved(false)
else
#users = User.all
end
end
# GET /users/:id.:format
def show
# authorize! :read, #user
end
# GET /users/:id/edit
def edit
# authorize! :update, #user
end
# PATCH/PUT /users/:id.:format
def update
# authorize! :update, #user
respond_to do |format|
if #user.update(user_params)
sign_in(#user == current_user ? #user : current_user, :bypass => true)
format.html { redirect_to #user, notice: 'Your profile 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
# GET/PATCH /users/:id/finish_signup
def finish_signup
# authorize! :update, #user
if request.patch? && params[:user] #&& params[:user][:email]
if #user.update(user_params)
#user.skip_reconfirmation!
sign_in(#user, :bypass => true)
redirect_to #user, notice: 'Your profile was successfully updated.'
else
#show_errors = true
end
end
end
# DELETE /users/:id.:format
def destroy
# authorize! :delete, #user
#user.destroy
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(policy(#user).permitted_attributes)
# accessible = [ :first_name, :last_name, :email ] # extend with your own params
# accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
# accessible << [:approved] if user.admin
# params.require(:user).permit(accessible)
end
end
And this is my first go at the User policy.
class UserPolicy < ApplicationPolicy
def initialize(current_user, user)
#current_user = current_user
#user = user
end
def index?
#current_user.admin?
end
def show?
#current_user.admin?
end
def edit?
#current_user.admin?
end
def update?
#current_user.admin?
end
def finish_signup?
#current_user = #user
end
def destroy?
return false if #current_user == #user
#current_user.admin?
end
private
def permitted_attributes
accessible = [ :first_name, :last_name, :email ] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
accessible << [:approved] if user.admin
params.require(:user).permit(accessible)
end
end
My questions are:
The tutorial shows something called attr_reader. I have started learning rails from rails 4 so I don't know what these words mean. I think it has something to do with the old way of whitelisting user params in the controller, so I think I don't need to include this in my user policy. Is that correct?
is it right that i have to initialise the user model the way I have above (or is that only the case in models other than user, since I'm initialising current_user, it might already get the user initialised?
is it necessary to move the strong params to the policy, or will this work if I leave them in the controller?
The tutorial shows something called attr_reader. I have started learning rails from rails 4 so I don't know what these words mean. I think it has something to do with the old way of whitelisting user params in the controller, so I think I don't need to include this in my user policy. Is that correct?
No, it is very important.
attr_reader creates instance variables and corresponding methods that return the value of each instance variable. - From Ruby Official Documentation
Basically if you do
class A
attr_reader :b
end
a = A.new
you can do a.b to access b instance variable. It is important because in every policies you might allow read access of instance variables. #current_user and #user is instance variable.
is it right that i have to initialise the user model the way I have above (or is that only the case in models other than user, since I'm initialising current_user, it might already get the user initialised?
You have to initialise it manually. Currently, the way you did it is correctly. Good.
is it necessary to move the strong params to the policy, or will this work if I leave them in the controller?
It is the matter of choice. It will work even if you kept it into controller. Move to policy only if you want to whitelist attributes in quite complex way.
Note: device , pundit and rolify gem works good but there share some of the same functionality so be careful and consistence what to do with what.
For example, You can use devise_for :users , :students , :teachers which will give 3 different links to login the respective resources. You can do lot of things with it. You can further authenticate the urls as per the resources with authenticate method. Check https://github.com/plataformatec/devise/wiki/How-To:-Define-resource-actions-that-require-authentication-using-routes.rb This sort of thing can also be done with pundit with policies and rolify with roles.

Devise Custom Controller not creating record in nested form

This was working, but I've made a change somewhere and now cannot get it to work. I have a nested form to create a company along with the first contact that uses Devise. The company record is being created but the contact (devise model) isn't being created. If I create the company and then add a contact to the company, the contact is created so I'm pretty sure the controller works. Need another set of eyes to spot my mistake.
Error:
RuntimeError in CompaniesController#create
Could not find a valid mapping for nil
CompaniesController (relevant methods):
class CompaniesController < ApplicationController
before_action :set_company, only: [:show, :edit, :update, :destroy]
before_action :authenticate_contact!, only: [:index, :show, :edit, :update, :destroy]
# GET /companies/new
def new
#company = Company.new
#company.contacts.build
end
# POST /companies
# POST /companies.json
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
sign_in(#company.contacts.first)
format.html { redirect_to #company, notice: 'Company was successfully created.' }
format.json { render action: 'show', status: :created, location: #company }
else
format.html { render action: 'new' }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def company_params
params.require(:company).permit(:name, :legal_entity, :KVK_number, :VAT_number, :KVKdoc, contacts_attributes:[:first_name, :last_name, :address, :phone, :email, :postcode, :password] )
end
end
Contacts Registration Controller:
class Contacts::RegistrationsController < Devise::RegistrationsController
# This is a custom controller that inherits from the standard Devise Registration Controller.
# This controller is used as the user is added to the existing account not it's own account.
# Skips the standard Devise authentication filter to allow for an active user session to create a user recorod.
skip_before_filter :require_no_authentication
# Custom method to ensure that the user on the page has an active session to ensure that the account is accessable.
before_filter :has_current_contact
# Create method is just a call to the standard Devise create method
def create
super
end
protected
# Stop the user sessisons from being switched to the new user
def sign_up(resource_name, resource)
true
end
# Test to see that the person on the page has an active session
def has_current_contact
if current_contact.nil?
redirect_to root_path, alert: 'You need to be logged in to do this!'
end
end
# Sets the permited params for the registration. Inherits the standard devise parameters defined in the
# application controller and merges the account id of the current user.
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up).merge(company_id: current_contact.company_id)
end
end
I can see that the code throws the error on the attempt to find a contact for the company and sign in since it can't find a contact given that none has been created, but can't figure out why it's not being created.
Help?

Implementing scopes in Pundit

I am using the Pundit gem (with Devise and Rolify) to restrict access to information based on logged-in user roles.
At this time I have three roles for my User model defined: Admin, Client Admin, and Customer Admin.
A User belongs_to a Customer.
Customer has_many Users.
I have successfully implemented a Pundit policy when indexing the Customer model. Admins and Client Admins can see all Customers. Customer Admin can only see their OWN record.
The problem lies when I am trying to restrict the show method of the Customer controller. Admins and Client Admins can see all Customers. However, the Customer Admin should only be able to see his own record. But as it stands the Customer Admin can input any id in the URL and see any Customer record.
I'm fuzzy on the scoping. It's my understanding that the Policy methods (i.e. index? and show?) are to restrict WHO can perform these actions and the Scoping methods restrict WHICH RECORDS can be obtained. I'm having trouble composing the correct scope for the above scenario.
Here's the Customer controller:
class CustomersController < ApplicationController
before_action :set_customer, only: [:show, :edit, :update, :destroy]
after_action :verify_authorized
# GET /customers
# GET /customers.json
def index
#customers = policy_scope(Customer)
authorize Customer
end
# GET /customers/1
# GET /customers/1.json
def show
authorize #customer
end
# GET /customers/new
def new
#customer = Customer.new
authorize #customer
end
# GET /customers/1/edit
def edit
authorize #customer
end
# POST /customers
# POST /customers.json
def create
#customer = Customer.new(customer_params)
authorize #customer
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render :show, status: :created, location: #customer }
else
format.html { render :new }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /customers/1
# PATCH/PUT /customers/1.json
def update
authorize #customer
respond_to do |format|
if #customer.update(customer_params)
format.html { redirect_to #customer, notice: 'Customer was successfully updated.' }
format.json { render :show, status: :ok, location: #customer }
else
format.html { render :edit }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
# DELETE /customers/1
# DELETE /customers/1.json
def destroy
authorize #customer
#customer.destroy
respond_to do |format|
format.html { redirect_to customers_url, notice: 'Customer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_customer
#customer = Customer.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def customer_params
params.require(:customer).permit(:name, :parent_customer_id, :customer_type, :active, :currency)
end
end
And here is the Customer policy:
class CustomerPolicy < ApplicationPolicy
def index?
# Admins, ClientAdmins, and CustomerAdmins can index customers (see Scope class for filters)
#user.has_role? :admin or #user.has_role? :client_admin or #user.has_role? :customer_admin
end
def show?
# Admins, ClientAdmins, and CustomerAdmins can see any customer details
#user.has_role? :admin or #user.has_role? :client_admin or #user.has_role? :customer_admin
end
def update?
# Only Admins and ClientAdmins can update customer details
#user.has_role? :admin or #user.has_role? :client_admin
end
def destroy?
#user.has_role? :admin or #user.has_role? :client_admin
end
class Scope < Struct.new(:user, :scope)
def resolve
if (user.has_role? :admin or user.has_role? :client_admin)
# Admins and ClientAdmins can see all Customers
scope.where(:parent_id => nil)
elsif user.has_role? :customer_admin
# Customer Admins can only see their own Customer
scope.where(:id => user.customer) # THIS DOES NOT APPEAR TO GET INVOKED BY THE SHOW METHOD OF THE CONTROLLER
end
end
def show?
# NOT SURE WHAT TO PUT IN HERE
end
end
end
Success!! Thanks to the headstart given to me by railscard, the trick was to modify the show? method in the Customer policy file like the following:
def show?
# Admins, ClientAdmins, and CustomerAdmins can see any customer details
# Students cannot see customer details
return true if user.has_role?(:admin) || user.has_role?(:client_admin)
return true if user.customer_id == #record.id && user.has_role?(:customer_admin)
false
end
Note that I had to use the #record instance variable, as that's what the Application policy class uses to refer to the record being passed in by the authorize method.
Thanks!!
To get Pundit's scoping working for the show action, Pundit's policy_scope helper (or policy_scope!) could be used, or you could just inherit show? from the generated ApplicationPolicy.
The index action is already using policy_scope correctly, we just need to do something similar for the show action. Here are some options:
Option 1: Modify the show action to
def show
# Also remove :show from the :only option where
# before_action :set_customer, only: ... is called.
#customer = policy_scope(Customer).find(params[:id])
authorize #customer
end
OR
Option 2: Modify set_customer to
def set_customer
#customer = policy_scope(Customer).find(params[:id])
end
OR
Option 3: Modify CustomerPolicy#show? to
def show?
# scope call here will return the
# result of CustomerPolicy::Scope#resolve
# This is the same implementation generated
# in the default ApplicationPolicy so you could
# just delete this method here and inherit instead.
scope.where(:id => record.id).exists?
end
Here's the code that generates the default ApplicationPolicy#show? method.
See Pundit's README section on Scopes for additional details.
I think you can safely delete the empty show? method you have in CustomerPolicy::Scope, I don't believe it will be called.
I think you don't need scope to restrict access for show action.
def show?
return true if user.has_role? :admin || user.has_role? :client_admin
return true if user.customer_id == customer.id && user.has_role? :customer_admin
false
end
Pundit scopes usually used to fetch a list of records which user have access to. In case of show method (or any other method in controller, where you call authorize) Pundit instantiates policy class with current user and given customer and then simply calls show? method to check user permissions, i.e. CustomerPolicy.new(current_user, #customer).show?

How to list only current user orders if not an admin (Ruby on Rails)?

I'm pretty new to rails and I'm just now developing my first rails app, so this might be a dumb question to some. I would like to let the current_user see only their own orders if they are not an admin. I was able to set only admins can see all orders, but I'm having a hard time enabling current user to see, list and delete only their own orders. My app has a :orders model that belongs_to :users and a :users model with has_many :orders.
This is how my orders_controller.rb look like:
class OrdersController < ApplicationController
before_action :set_order, only: [:show, :edit, :update, :destroy]
# GET /orders
# GET /orders.json
def index
authorize! :index, #user, :message => 'Not authorized as an administrator.'
#orders = Order.all
end
# GET /orders/1
# GET /orders/1.json
def show
end
# GET /orders/new
def new
#order = Order.new
end
# GET /orders/1/edit
def edit
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(order_params)
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render action: 'show', status: :created, location: #order }
else
format.html { render action: 'new' }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /orders/1
# PATCH/PUT /orders/1.json
def update
respond_to do |format|
if #order.update(order_params)
format.html { redirect_to #order, notice: 'Order was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# DELETE /orders/1
# DELETE /orders/1.json
def destroy
#order.destroy
respond_to do |format|
format.html { redirect_to orders_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
#order = Order.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:user_id, :drop_address)
end
end
My question is how do I allow only the current user to list and see all orders made by them only?
Thanks
There is gem named cancan for you.
Please read wiki page.
Need more help? let me know :)
define ability
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, Order, :user_id => user.id
end
end
end
from controller query by accessible_by
#orders = Order.accessible_by(current_ability)
you have to do it on two levels. In index you have to fetch orders for the current users so users can only see his orders. the second level is you make sure that the user may enter an order url that doesnt belong to him, so check for that in the other actions(edit,update,delete,show).
Or you can use declarative authorization gem. it is very helpful https://github.com/stffn/declarative_authorization
-hint: for naming conventions change belongs_to :users in order model to belongs_to :user (belongs_to is always singular)
This is how your controller should look like
#this is the filter called before each method to make sure the user is authorized to access the order
before_filter :authorize_user, :only => [:edit,:update,:delete,:show]
def index
authorize! :index, #user, :message => 'Not authorized as an administrator.'
#here fetch the orders of the current user only
#orders = current_user.orders
end
#and then goes all your methods here as normal
private
def authorize_user
unless current_user.eql?(#order.user)
flash[:notice]="You are not authorized to access this order"
redirect_to orders_path
end
end

Resources