Cancan how to authorize collection model in one controller? - ruby-on-rails

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.

Related

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

Rails 4: Profile for users

I have a Rails 4 app with a User model and it has_one :client where Client is the model where I store information about the client. I want my users to be able to view a profile page with their client information but I'm not sure which action to use or how to route to it.
I want to be able to view a particular client using the show action:
def show
#user = User.find(params[:id])
#client = #user.client
end
...but I also want a route for "my profile" which would rely on current_user and not finding a user by their ID. In my mind this is also a show action and I'm trying to avoid adding custom methods and would rather find the "railsy" way to do this.
def show
#user = current_user
#client = #user.client
end
I know this is sorta philosophical, but if someone could explain the proper way to achieve this I would appreciate it.
Actually, I am not sure what you are trying to do. If you want to add a specific page about showing current_user profile. Why not adding a new method?
Like this
def profile
#client = current_user.client
end
Or maybe you can validate the params[:id] in show action
Like this
def show
#user = params[:id].present? ? User.find(params[:id]) : current_user
#client = #user.client
end

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

How to secure user show page alongside user admin functions when using devise

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.

Rails Security: redirect if not current_user

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

Resources