How can I prevent user access? - ruby-on-rails

I have a view that to access you must log in ,my users table I have a field called kind.
I am using gem devise
enum kind: {
admin: 0,
customer: 1
}
add this in application_controller
def user_can_access(resource)
if current_user
if resource.is_admin?
root_path
else
sign_out current_user
new_user_session_path
end
end
end
user.rb
def is_admin?
self.admin?
end
That is my attempt to solve it, but it is wrong since I want the user that I do not want to access the view to not login.
In my method, the user who does not have access logs in but then I log him out, which I think is wrong
How can I prevent client access?
note:I will only have a page for the administrator, the other users will not have a view to access.

You can check the cancancan gem for authorization features or you can write yours then include a before action to check if the current_user is authorized before rendering the view.
e.g
class MyPage < ApplicationController
before_action :is_authorized?
def show_my_page
#foo == bar
end
private
def is_authorized?
return true if current_user.kind == admin
redirect_to root_path, notice: "You are not allowed to access this page"
end
end

Related

Rails - Handle User roles using pundit

I have a table of users with enum user_type [Manager, Developer, QA]. Currently, I'm handling sign in using Devise and after login I'm using the following logic to display the appropriate webpage:
class HomeController < ApplicationController
before_action :authenticate_user!
def index
if current_user.manager?
redirect_to manager_path(current_user.id)
end
if current_user.developer?
redirect_to developer_path(current_user.id)
end
if current_user.quality_assurance?
redirect_to qa_path(current_user.id)
end
end
end
I want to use pundit gem to handle this. From the documentation, it transpired that this logic will be delegated to policies but I can't figure out how. Can somebody help me in implementing pundit in my project?
This is my users table:
I have created a user_policy but its mostly empty:
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
end
User model:
You want to use Pundit to authorize a user, as in check if that user should be allowed to visit a controller action. If the user is not authorized for a specific action it raises a Pundit::NotAuthorizedError
You can check if a user is allowed to perform an action in the pundit policy, in which you have access to record (the instance thats passed to authorize) and user. So assuming you have a Flat Model, where only the owner can edit the Flat you might do this:
# flats_policy.rb
def edit?
record.user == user
end
Now lets say you also want to allow admins to edit you might do this
# flats_policy.rb
def owner_or_admin?
record.user == user || user.admin # where admin is a boolean
end
def edit?
owner_or_admin?
end
and the controller:
# flats_controller.rb
def edit
#flat = Flat.find(params[:id])
authorize #flat
# other code here
end
Now the index action is the odd one out because you would essentially have to call authorize on each instance, so the way Pundit handles this is with the Scope:
# flats_policy.rb
class Scope < Scope
def resolve
scope.all
end
end
and a corresponding index action might look like:
def index
#flats = policy_scope(Flat) # note that we call the model here
end
So lets say a user can only see flats that he/she owns:
# flats_policy.rb
class Scope < Scope
def resolve
scope.where(user: user)
end
end
and if admins can see all flats:
# flats_policy.rb
class Scope < Scope
def resolve
if user.admin
scope.all
else
scope.where(user: user)
end
end
end
In any case if the user is not allowed to perform an action you can rescue from the error like so:
# application_controller
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_to(root_path)
end
I guess you could do some dirty redirecting here, as in send admins to an admins_root_path, users to a default_root_path and so on...
On a final note, since this post is already too long you can check a policy in the view like this:
<% if policy(restaurant).edit? %>
You can see me if you have edit rights
<% end %>

sRails 5 Devise after_sign_in_path

I have a Rails 5 app with Devise. Each user has a role_id where they are assigned a role upon creation. I'm trying to use the after_sign_in_path_for method that Devise gives to redirect to a specific page on login based on the role.
Below is what I have so far, but it doesn't work when trying to sign out a disabled user.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
case resource.role_id
when Role.find_by(name: "admin").id
root_path
when Role.find_by(name: "disabled").id
destroy_user_session_path
else
super
end
end
end
I'm able to sign in when I'm an admin user and it redirects. But if I try to sign in as a user whose role is disabled, it tries to tear down the session then raises an exception of No route matches [GET] "/users/sign_out". I know the method destroy_user_session_path expects a delete method but how can I pass this in the application controller?
What am I doing wrong here?
Update
I tried the sign_out(resource) as suggested in the first answer, and it raises an exception undefined methodto_model' for true:TrueClassin mymy_sessions_controller.rb` which I use to override the create method to set a login token and limit concurrent sessions. Here is the controller.
class MySessionsController < Devise::SessionsController
skip_before_action :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save(validate: false)
end
end
You can check roles inside MySessionsController#create and prevent logging if the role not valid instead of allowing user to login then logout
def create
unless current_user.role_id == Role.find_by(name: "disabled").id
super set_login_token
else
redirect_to new_user_session_path, alert: "You can't log in"
end
end
You can also use active_for_authentication? and inactive_message methods in user model to prevent him from login. in /app/models/user.rb:
def active_for_authentication?
super and self.role_id != Role.find_by(name: "disabled").id
end
def inactive_message
"You can't log in"
end
destroy_user_session_path is making [GET] "/users/sign_out" request.
You can use sign_out or reset_session function to delete session directly.
Hope this answer works for you.
Use devise's sign_out(resource) method instead of destroy_user_session_path. This method will destroy the user session.

How can I make specific user(not current_user) sign out on rails-devise website?

I want to know how I can make specific user(not current_user) sign out.
I saw this http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Controllers/SignInOut#sign_out-instance_method and maked this code.
def kick_admin
user = User.find params[:user_id]
user.admin = false
user.save
sign_out user #want to kick him.
end
But it does not make that user sign out but make me(current_user) signed out.
What is the right way to use the sign_out method?
I checked this answer(Sign out specific user with Devise in Rails) but it was not helpful.
One way you could do this is create a new attribute in the User table, call it force_sign_out.
def kick_admin
user = User.find params[:user_id]
user.update_attributes(admin: false, force_sign_out: true)
end
And have a before action in ApplicatonController so that if the user attempts any activity he's signed out
class ApplicationController < ActionController::Base
before_action :check_if_force_sign_out
def check_if_force_sign_out
return unless current_user.force_sign_out
current_user.update_attributes(force_sign_out: false) # reset for non-admin log in
sign_out
redirect_to root_path
end
end

Checking authorized flag in devise

I have a rails app where I have added a boolean field named 'authorized' to the user model. Basically, I want to lock the app down such that only authorized users can access the app. I am trying to do this in my application controller:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def authorized
redirect_to root_path, alert: "Not Authorized" if !current_user.authorized?
end
end
However, I am getting a redirect error when I do this as I have the root route set to a path where authentication is required.
I now I can do this check in the view or another controller, but I'd like to do it in the app controller as I want the entire app locked down.
You can override these methods in your user model.
def active_for_authentication?
super && approved?
end
def inactive_message
'my custom inactive message' unless approved?
super
end
I recommend you to change the authorized name by approved, I'm not sure but authorized sounds like an internal Devise method name.
Here was my solution...
In the controller where I wanted the 'authorized' authentication I added...
before_filter :authorized, :except => :not_authorized
...
def authorized
if !current_user.authorized?
redirect_to not_authorized_path
end
end
In my routes, I added..
get 'not_authorized' => 'my_controller#not_authorized'
...and added a simple app/views/my_controller/not_authorized.html.erb

Rails: Devise Login as another user issues

In my application, users have the ability to invite others to be 'Contributors' on their account. These contributors once they have logged in, are redirected to a dashboard that shows each account they can log in as.
The below is a controller that is used to allow Contributors and School Admins to log into Athlete accounts. When the original user logs into an account the app checks for the session[:original_user_id] variable to display a message banner across the top of the screen with a link so that the admin can log back into their account - This is what I am having issues with trying to figure out how to log the original user back in.
SignInAsController:
class SignInAsController < ApplicationController
before_filter :authenticate_user!
include SchoolAdmin::Athletes
def create
session[:original_user_id] = if (current_user.school_admin? || current_user.athlete_contributor?)
current_user.id
else
nil
end
user = User.find(params[:id])
if current_user.can_manage?(user)
sign_out(User.find(current_user.id))
handle_request(athlete)
redirect_to user_root_path
else
redirect_to :back, notice: "You do not have access to that account"
end
end
private
def handle_request(athlete)
sign_in(:user, athlete, { bypass: true })
end
end
UserModel can_manage? method:
class User < ActiveRecord::Base
#other methods
def can_manage?(user)
if athlete_contributor?
managed_athletes.include?(user)
elsif school_admin?
active_subscription.athletes.include?(user)
end
false
end
end
This is what worked for me. It allows an Admin user to login as another User.
class AdminController < ApplicationController
before_filter :authenticate_user!
def become
return unless current_user.is_an_admin?
sign_in(:user, User.find(params[:id]))
redirect_to root_path
end
end
You can also do sign_in(:user, User.find(params[:id]), { :bypass => true }) if you don't want to update last_sign_in_at and current_sign_in when admin becomes the user.
Devise has sessions & registrations controllers, which allow you to create & override various methods for devise
Sessions
Considering the logging-in process is about creating a session, I'd look at implementing my own method to destroy the current session & create a new one with the admin stats, like this:
#config/routes.rb
devise_for :users, :controllers => { :sessions => "sessions" }
devise_scope :user do
post "admin", :to => "devise/sessions#admin_switch"
end
#app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
#Switch to "admin" mode
def admin_switch
sign_out #method to destroy current user's session
sign_in #create new session -- needs more work
end
end
This is meant to demonstrate how you'd achieve your goal. If you want me to add some more code, I certainly will amend my post! Specifically, the sign_in command won't work (needs specific data passed to it, and uses Warden)
You can use Switch User gem which can work with:
:devise, :authlogic, :clearance, :restful_authentication or :sorcery
https://github.com/code-and-effect/effective_website/blob/develop/app/controllers/users/impersonations_controller.rb
this functionality “impersonating another user”. an admin can go onto Admin::Users#index -> Find a user, and impersonate them. That is, sign into their account.need a button that posts to this action
https://github.com/code-and-effect/effective_website/blob/develop/app/controllers/admin/users_controller.rb#L11
def impersonate
#user = User.find(params[:id])
authorize! :impersonate, #user
# Impersonate
session[:impersonation_user_id] = current_user.id
expire_data_after_sign_in!
warden.session_serializer.store(#user, Devise::Mapping.find_scope!(#user))
redirect_to(root_path)
end
before_action :authenticate_user!
skip_authorization_check only: [:destroy]
def destroy
#user = User.find(session[:impersonation_user_id])
# Reset impersonation
session[:impersonation_user_id] = nil
expire_data_after_sign_in!
warden.session_serializer.store(#user, Devise::Mapping.find_scope!(#user))
redirect_to(admin_users_path)
end
end
Which uses devise, to set the session[:impersonation_user_id] so later you know you are impersonating another use, and then this warden.session_serializer.store which signs you in as a new user.
If you close the tab. Reopen the tab. You will still be impersonating that user.
Put a partial in the site (haml) to display an alert to the user on every page when they’re impersonating
.bg-warning.d-print-none.text-center
You are logged in as <strong>#{current_user}</strong>.
= link_to 'click here', impersonate_path, 'data-method': :delete
to return to your original account.
https://github.com/code-and-effect/effective_website/blob/develop/app/views/layouts/_impersonate.html.haml

Resources