How to allow ONLY Admins to create new users? - ruby-on-rails

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

Related

Admin Tree in Rails HRM

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.

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?

How to show post only to logged in user in Ruby on Rails?

Okay guys, I am fairly new to rails. I have successfully created a rails app that stores login information for you. I used devise for the user management and installed cancan but no idea how to use it.
Anyways,
Right now, not matter if you are logged in or not, the site shows you all the "post" or "entrees" that have been entered by any user. I need a way to restrict this to only show post that were made by the user that is currently logged in.
I have found through research that I need do something here:
class FtpLoginsController < ApplicationController
before_action :set_ftp_login, only: [:show, :edit, :update, :destroy]
# GET /ftp_logins
# GET /ftp_logins.json
def index
#ftp_logins = FtpLogin.all
end
# GET /ftp_logins/1
# GET /ftp_logins/1.json
def show
end
# GET /ftp_logins/new
def new
#ftp_login = FtpLogin.new
end
# GET /ftp_logins/1/edit
def edit
end
# POST /ftp_logins
# POST /ftp_logins.json
def create
#ftp_login = FtpLogin.new(ftp_login_params)
respond_to do |format|
if #ftp_login.save
format.html { redirect_to #ftp_login, notice: 'Ftp login was successfully created.' }
format.json { render action: 'show', status: :created, location: #ftp_login }
else
format.html { render action: 'new' }
format.json { render json: #ftp_login.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /ftp_logins/1
# PATCH/PUT /ftp_logins/1.json
def update
respond_to do |format|
if #ftp_login.update(ftp_login_params)
format.html { redirect_to #ftp_login, notice: 'Ftp login was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #ftp_login.errors, status: :unprocessable_entity }
end
end
end
# DELETE /ftp_logins/1
# DELETE /ftp_logins/1.json
def destroy
#ftp_login.destroy
respond_to do |format|
format.html { redirect_to ftp_logins_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_ftp_login
#ftp_login = FtpLogin.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def ftp_login_params
params.require(:ftp_login).permit(:client_name, :website_name, :ftp_login, :ftp_password, :notes)
end
end
If someone could please send me in the right direction here that would be fantastic!
Thanks in advance.
for that you first have to make ensure that user is logged in before it goes to your action . so you need a before filter for that . authenticate_user! is a method given by devise . so if a user is not logged in he will redirect to the sign in page automatically
before_filter :authenticate_user!, only: [:posts, :entries]
for collecting the posts of a specific user
#posts = current_user.posts
or if it is coming for show a particular post you can do
#post = current_user.posts.where(id: params[:id])
You can use before_filter :authenticate_user!, only: [:posts, :entries] to restrict only the logged in user to view these actions.
To restrict users to view only posts created by them, you can create your own filter like
def check_user
redircet_to :back, notice: "Restricted area!" if current_user.posts.include?(#post)
end

return redirect_to in private controller method

Preface: I'm using devise for authentication.
I'm trying to catch unauthorized users from being able to see, edit, or update another user's information. My biggest concern is a user modifying the form in the DOM to another user's ID, filling out the form, and clicking update. I've read specifically on SO that something like below should work, but it doesn't. A post on SO recommended moving the validate_current_user method into the public realm, but that didn't work either.
Is there something obvious I'm doing wrong? Or is there a better approach to what I'm trying to do, either using devise or something else?
My UsersController looks like this:
class UsersController < ApplicationController
before_filter :authenticate_admin!, :only => [:new, :create, :destroy]
before_filter :redirect_guests
def index
redirect_to current_user unless current_user.try(:admin?)
if params[:approved] == "false"
#users = User.find_all_by_approved(false)
else
#users = User.all
end
end
def show
#user = User.find(params[:id])
validate_current_user
#user
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
validate_current_user
#user
end
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to #user, :notice => 'User was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
def update
#user = User.find(params[:id])
validate_current_user
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, :notice => 'User was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
private
def redirect_guests
redirect_to new_user_session_path if current_user.nil?
end
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
return redirect_to(current_user)
end
end
end
The authenticate_admin! method looks like this:
def authenticate_admin!
return redirect_to new_user_session_path if current_user.nil?
unless current_user.try(:admin?)
flash[:error] = "Unauthorized access!"
redirect_to root_path
end
end
EDIT -- What do you mean "it doesn't work?"
To help clarify, I get this error when I try to "hack" another user's account:
Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most
once per action. Also note that neither redirect nor render terminate
execution of the action, so if you want to exit an action after
redirecting, you need to do something like "redirect_to(...) and
return".
If I put the method code inline in the individual controller actions, they do work. But, I don't want to do that because it isn't DRY.
I should also specify I've tried:
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
redirect_to(current_user) and return
end
end
If you think about it, return in the private method just exits the method and passes control back to the controller - it doesn't quit the action. If you want to quit the action you have to return again
For example, you could have something like this:
class PostsController < ApplicationController
def show
return if redirect_guest_posts(params[:guest], params[:id])
...
end
private
def redirect_guest_post(author_is_guest, post_id)
redirect_to special_guest_post_path(post_id) if author_is_guest
end
end
If params[:guest] is present and not false, the private method returns something truthy and the #show action quits. If the condition fails then it returns nil, and the action continues.
You are trying and you want to authorize users before every action. I would suggest you to use standard gems like CanCan or declarative_authorization.
Going ahead with this approach you might end up reinventing the wheel.
In case you decide on using cancan, all you have to do is add permissions in the ability.rb file(generated by rails cancan:install)
can [:read,:write,:destroy], :role => "admin"
And in the controller just add load_and_authorize_resource (cancan filter). It will check if the user has permissions for the current action. If the user doesnt have persmissions, then it will throw a 403 forbidden expection, which can be caught in the ApplicationController and handled appropriately.
Try,
before_filter :redirect_guests, :except => [:new, :create, :destroy]
should work.
This is because you are using redirect twice, in authenticate_admin! and redirect_guests for new, create and destroy actions.
"Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action."
That's the reason of the error. In show method, if you are neither the owner of this account nor the admin, you are facing two actions: redirect_to and render
My suggestion is to put all of the redirect logic into before_filter

before_filter with exception for current user

I have this current before filter in my users_controller
before_filter :authenticate_usergroup, :except => [:edit, :update, :show]
It works great for just allowing a user to edit their information but not being able to see all the users, but i'v found that by just changing their 'id' in the url they could also edit other users information.
How can i only allow :edit, :show and :update functions to the current user for their own record. i have this method in my application_controller to be able to access the current user
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
#update from users_controller.rb
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
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
I did it this way:
in #show action in the controller:
if current_user.id == Certificate.find(params[:id]).user_id
#certificate = Certificate.find(params[:id])
else
redirect_to certificates_url, notice: 'You can only view your own certificates'
end
I know its not using a 'before_filter', but it seems to have the same effect of blocking users from simply typing in different urls to view other users information.
if you "scope" that only the current_user can access their information, then it doesn't matter.
so in show and edit
#user = current_user
in update action something like
current_user.update_attributes(params[:user])
should do the trick.

Resources