Mutiple Objects authorization using pundit in one action - ruby-on-rails

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

Related

Session in Action Mailer - how to pass it?

Let's say I have a website where people can get a free ebook if they will sign up for a newsletter - after they've done it, I will create a User model and I will show them Edit Form to add some extra details about them.
I don't want to force them to add a password or any other details on the first page because it would decrease conversions and I don't require the additional information either. Also, I don't want them to have forever access to the Edit page so I solved it by assigned a session to them and recognize it through it on the Edit page. This is my controller:
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
UserWorker.perform_in(5.minutes, 'new_user', user.id)
redirect to edit form...
end
end
def edit
#user = User.find(session[:user_id])
end
def update
#user = User.find(session[:user_id])
#user.update!(user_edit_params)
redirect_to user_thank_you_path
end
end
But if they won't add extra information within 10 mins, I will send them an email via ActiveMailer with a link to the Edit form and ask them to do so.
Th question is how could I identify the user through the session and show them the form - how could I do User.find(session[:user_id] via ActionMailer)? Is it actually a correct way or would you recommend a different approach?
One way could be to set a background job to run in 10 minutes.
Inside that job, you would check if they're still "unregistered". You deliver the email if they've yet to complete the registration.
Something like this;
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
RegistrationCompletionReminderWorker.perform_in(10.minutes, user.id)
# redirect to edit form...
end
end
end
class RegistrationCompletionReminderWorker
def perform(user_id)
user = User.find(user_id)
if user.password.nil? # or whatever your logic for registration completion is
UserMailer.registration_reminder(user_id).deliver_now
end
end
end

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

Pundit: Ensure current_user is user from params

Apparently, you can't access the params hash in a Pundit policy. It makes sense that they want to expose as little information to the policies as possible. But one use case I'm running into, which I would think would be quite common, is to check that the current_user is the user from the params.
So here's my new action in my controller:
class ReviewsController < ApplicationController
...
def new
#user = User.friendly.find(params[:user_id])
unless current_user.admin? || current_user.id == #user.id
flash[:alert] = 'Access denied.'
redirect_to root_url
end
#review = #user.reviews.build
end
...
end
So here, I'm saying to authorize if the user is an admin, or the current user is the same as the one in the URL. Otherwise, the user with the id of 2 could go to /users/1/reviews/new.
There doesn't seem to be any way to handle this in the policy, because I can't pass the params[:user_id] into the policy.
Is there a way to handle this authorization scheme from a Pundit policy, rather than handling auth logic in my controller?
Not sure if this question is out of date.
When pundit does the authorization in the controller, it will pass two objects. One is record and another is current_user. But you only need to provide the record when you call the authorize method, current_user will be passed automatically.
#authorize(record, query = nil) ⇒ true
In your case, when you call authorize(#user, :new?), in your policy, #user will be referenced as record, and current_user will be referenced as user.
Therefore, in your policy:
class UserPolicy < ApplicationPolicy
def new?
user.admin? || record == user
end
end
And you can check the policy in your controller:
class ReviewsController < ApplicationController
...
def new
#user = User.friendly.find(params[:user_id])
authorize(#user, :new?)
#review = #user.reviews.build
end
...
end

Pundit, Devise - Authorization with multiple devise models

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?

Authorize related object when using pundit gem

If I have user object and user has one basicinfo. In user show action I have:
def show
#user = User.find params[:id]
authorize #user
end
And in show.html.erb I must show user's basicinfo, such as:
User name is: <%= #user.basicinfo.name %>
In this case should I also authorize basicinfo in user show action?
def show
#user = User.find params[:id]
authorize #user
authorize #user.basicinfo, :show?
end
The authorization applies to the entire action.
If you want to filter out some elements in the view you can do so on an ad hoc basis, basically applying whatever attribute you are using in the xxxPolicy class (which is not provided above)
Handling user authorization is possibly too complicated via Pundit
def initialize(user, user)
I definitely do filtering in the views when it comes to user actions

Resources