Pundit, Devise - Authorization with multiple devise models - ruby-on-rails

Setting up authorization for two separate Devise models in a Rails application. Only the current signed in medical_student should be able to edit or delete their profile. Other medical_students should be able to view other medical_students and regular users should be able to view their profile as well.
Here is my code:
Policy
class MedicalStudentProfilePolicy
attr_reader :medical_student, :medical_student_profile
def initialize(medical_student, medical_student_profile)
#medical_student = medical_student
#medical_student_profile = medical_student_profile
end
def edit?
#medical_student_profile.medical_student_id == medical_student
end
def destroy?
#medical_student_profile.medical_student_id == medical_student
end
end
Pundit User
def pundit_user
if medical_student_signed_in?
#medical_student = current_medical_student
elsif user_signed_in?
#medical_student = MedicalStudent.find params[:medical_student_id]
end
end
Edit
def edit
authenticate_medical_student!
authorize #medical_student_profile, :edit?
end
View
- if policy(#medical_student_profile).edit?
This works when logged in as a user, however current medical students are unable to edit their profiles. Any ideas?

Related

How can I prevent user access?

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

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 %>

Mutiple Objects authorization using pundit in one action

In the code below i want authorize team and user.
By authorizing the team , i want to make sure the current_user is the admin of team
Second authorize is to make sure that the user being removed is not the admin(user) of team.
I assume you have basic knowledge of Pundit, a rails gem.
Is there a better way for doing the same.?How this code can be improved?
def remove_user
team = Team.find(params[:id])
user = User.find(params[:user_id])
authorize team
authorize user
....
end
In the policy class you wrote add method to implement the logic
eg:
class PostPolicy
-------
other methods and declaration
-------
def initialize(current_user, user)
#current_user = current_user
#user = user
end
def remove_user?
#current_user.admin? and #current_user != #user
end
end

devise overrule devise SessionController broke login

I have extended the devise controller for sessions. To add some extra functionality when a user logs in. Now upon login, IF no username or password are entered I get error :
SessionsController#create
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
I searched last week for up to 2 hours how to fix this anyone can help me out on this one? Would be highly appreciated
How to correctly add my custom functionality but still preserve the Devise create action?
class SessionsController < Devise::SessionsController
def create
#user = User.where(:id => current_user.id).first
#moderated = Asset.where(:attachable_id => current_user.id, :moderated => true).first
if #user.sign_in_count.to_i == 1
def after_sign_in_path_for(resource)
"/welcome/basics"
end
else
if #moderated.nil?
unless #user.has_photo?
def after_sign_in_path_for(resource)
"/home/no_photo"
end
end
else
def after_sign_in_path_for(resource)
"/home/moderated"
end
end
end
end
end
If your additional functionality consists of redirecting the user to a different page at his first login i would suggest defining after_sign_in_path_for in the application controller as suggested by the wiki.
class ApplicationController < ActionController::Base
private
def after_sign_in_path_for(resource)
if current_user.sign_in_count == 1
"/welcome/basics"
else
"/other/path"
end
end
end
Please note that this only works if User is the only resource that can sign into your application. Otherwise you would have to differentiate in this method as well via the resource parameter.

Gradual engagement, persistent guest user with Devise

I'm trying to set up gradual engagement in my utility app which people can use without registering e.g. notepad.cc and jsfiddle.net and I plan to create a guest user (with Devise) for the user when he 'writes' to the app.
I found this guide on the Devise wiki https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user which shows how to create a guest user for the duration of the browser session. What I want is for the user to continue using the same guest account in subsequent visits, until he signs up, maybe when I introduce subscription plans for more features.
How can I modify what's in the guide to make this possible?
Code in the guide linked above:
# file: app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
# if user is logged in, return current_user, else return guest_user
def current_or_guest_user
if current_user
if session[:guest_user_id]
logging_in
guest_user.destroy
session[:guest_user_id] = nil
end
current_user
else
guest_user
end
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user
User.find(session[:guest_user_id].nil? ? session[:guest_user_id] = create_guest_user.id : session[:guest_user_id])
end
# called (once) when the user logs in, insert any code your application needs
# to hand off from guest_user to current_user.
def logging_in
end
private
def create_guest_user
u = User.create(:name => "guest", :email => "guest_#{Time.now.to_i}#{rand(99)}#email_address.com")
u.save(false)
u
end
end
And using it in the controller:
#thing.user = current_or_guest_user
#thing.save
After some yak-shaving I've managed to get it to work. Here's the working code:
class ApplicationController < ActionController::Base
protect_from_forgery
# if user is logged in, return current_user, else return guest_user
def current_or_guest_user
if current_user
if cookies[:uuid]
logging_in # Look at this method to see how handing over works
guest_user.destroy # Stuff have been handed over. Guest isn't needed anymore.
cookies.delete :uuid # The cookie is also irrelevant now
end
current_user
else
guest_user
end
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user
User.find_by_lazy_id(cookies[:uuid].nil? ? create_guest_user.lazy_id : cookies[:uuid])
end
# called (once) when the user logs in, insert any code your application needs
# to hand off from guest_user to current_user.
def logging_in
# What should be done here is take all that belongs to user with lazy_id matching current_user's uuid cookie... then associate them with current_user
end
private
def create_guest_user
uuid = rand(36**64).to_s(36)
temp_email = "guest_#{uuid}#email_address.com"
u = User.create(:email => temp_email, :lazy_id => uuid)
u.save(:validate => false)
cookies[:uuid] = { :value => uuid, :path => '/', :expires => 5.years.from_now }
u
end
end
I will accept another answer if you can show me a better way to do this.
The above solution works great.
Don't forget to setuphelper_method :current_or_guest_user to make the method accessible in views. Took me some time to figure out.

Resources