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
Related
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
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
I am using cancan with rails 4. My user model has many datings and a dating has many reply. In the user controller's show action, I want to authorize all of them:
#user = User.find params[:id]
authorize! :read, #user
#datings = #user.first_page_datings
authorize! :read, #datings
# How to authorize replies of all the datings here?
Question is how to authorize replies of all the datings here?
Instead of trying to authorize all the child objects, use #dating = #user.datings.accessible_by(current_ability) to load the permitted ones.
I'm using devise and have let admins manage users with a Manage::UsersController.
This is locked down using cancan:
# models/ability.rb
def initialize(user)
if user admin?
can :manage, User
end
end
Normal users can have nothing to do with User other than through devise, so everything looks secure.
Now I want to give users a 'show' page for their own account information (rather than customising the devise 'edit' page). The advice (1,2,3) seems to be to add a users_controller with a show method.
I tried to give non-admins the ability to read only their own information with:
if user admin?
can :manage, User
else
can :read, User, :id => user.id # edited based on #Edwards's answer
end
However this doesn't seem to restrict access to Manage::UsersController#index, so it means that everybody can see a list of all users.
What's the simplest way to achieve this? I can see two options, (but I'm not sure either is right):
1) Prevent user access to Manage::UsersController#index
def index
#users = User.all
authorize! :manage, User # feels hackish because this is 'read' action
end
2) Over-write devise controller
Per this answer over-writing a devise controller could be a good approach, but the question is which controller (the registrations controller?) and how. One of my concerns with going this route is that the information I want to display relates to the User object but not devise specifically (i.e. user plan information etc.). I'm also worried about getting bogged down when trying to test this.
What do you recommend?
In your ability.rb you have
can :read, User, :user_id => user.id
The User model won't have a user_id - you want the logged in user to be able to see their own account - that is it has the same id as the current_user. Also, :read is an alias for [:index, :show], you only want :show. So,
can :show, User, :id => user.id
should do the job.
I would keep your registration and authentication as Devise controllers; then, create your own User controller that is not a devise controller.
In your own controller, let's call it a ProfilesController, you could only show the specific actions for the one profile (the current_user)
routes
resource :profile
profiles controller
class ProfilesController
respond_to :html
def show
#user = current_user
end
def edit
#user = current_user
end
def update
#user = current_user
#user.update_attributes(params[:user])
respond_with #user
end
end
Since it's always only editing YOU, it restricts the ability to edit or see others.
I've been using authlogic and it works really well. One thing I've noticed is that as a hacker I could easily type in this address:
localhost::3000/users/a_user_that_is_not_me/edit
Since the form in the edit form is for #user which is set to current_user and requires an authenticity token, even if I tried to put in details for the other user I end up changing my own account instead of the other users.
That's nice and good, but I'd like it so that these hackers get redirected before they even see the form.
I tried this in the users_controller:
def edit
if admin?
#user = params[:user]
elsif User.find_by_username(params[:id]) != current_user
#user = current_user
#not_user = User.find_by_username(params[:id])
redirect_to user_path(#not_user)
else
#user = current_user
end
end
The redirect works if I type in an address with another user's name but I get a 404 error when trying to access the edit page for the current user.
Any ideas why this doesn't work?
If you're going to be doing this kind of thing a lot, check out an authorization plugin like authorization-san.
Authorization differs from authentication in that authentication is logging in, but authorization pertains to the authenticated (or un-authenticated) user's rights to perform actions.
With authentication-san, you could define this rule with this piece of code in your controller:
# this assumes you've got some way to set #user to the user you're looking up,
# e.g. in a before_filter
allow_access(:authenticated, :only => [:edit, :update]) { current_user == #user }
It looks like you are assigning #user to a string if the current user is an admin. This is simpler (less typo-prone):
def edit
u = User.find_by_username!(params[:id])
if admin? or current_user.username == params[:id]
#user = u
else
redirect_to user_path(u)
end
end
Also, don't you want to use find_by_username! (with bang on end) so that a 404 page is rendered when the user is not found? I'm not sure how you're getting the 404 page now...