How to secure user show page alongside user admin functions when using devise - ruby-on-rails

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.

Related

Rails: cancancan gem condition is not working

In my project, I have a user class working with Devise authentication. Each user have one role associated with. For exemple, a manager is the role #1.
Role is a class and there is an association beetween role and user based on role_id. So a manager will have a role_id of 1.
I installed cancancan gem to manage permission for each user. Only a manager can create user account. So, I wrote this in the ability class:
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role_id == 1
can :manage, :user
else
cannot :manage, :user
end
end
In my user controller, I have this:
def new
#user = User.new
authorize! :manage, #user
end
The problem is when I login with my manager and try to access to new user page, I receive the message from cancancan saying that I don't have the permission to access the page.
I don't know if it's my condition or when I'm trying to access to user.role_id or...
Thanks for your help.
See cancancan gem : https://github.com/CanCanCommunity/cancancan
Edit:
It's normal if my IDE says that it cannot find current_user?
Also, if you check on github, there is an example of how to use cancancan:
As you see, there is an #article after the read permission. So I don't think I need to put current_user instead of #user
Finally:
Problem was the :user that was not correct. When it's a model, you need to write a capital letter first for the name of the model without ":". So instead of :user, it should be User
current_user relies on a Rails session. Since this is your #new action on your Users Controller, you don't have an active session, thus no current_user.

How to restrict the access to models for different user roles in rails (cancan gem)?

I want 3 user levels as Admin ,Manager,Customer in my rails application.
So i've created 3 devise models as admin,manager and customer.
And in my application there are models and controllers for product,delivery,services.
And I want to set access levels to each models.
So Admin have access to all models, controllers
Manager have access to Product, Delivery
Customer have access to Services
how can i write the ability model to match these requirements
I've written it as follows.Don't know whether it's correct.
class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
elsif user.manager?
can :manage, product ,delivery
elsif user.customer?
can :manage, services
end
end
AND PLEASE HELP ME TO WRITE THE CODE FOR THE MODELS TO RESTRICT DIFFERENT USER ROLE ACCESS.
PLEASE BE KIND ENOUGH TO HELP ME!
I think the easiest way to tackle this would be in your controller. Let us say you had a book model with controller, and only wanted to allow admins to access this part. It's probably best to create a method in your bookings controller, and call it using the before action method:
class BookingsController < ApplicationController
before_action :check_admin
def check_admin
return unless admin_signed_in?
redirect_to root_path, error: 'You are not allowed to access this part of the site'
end
end
This will perform the check_admin method every time anything happens in the bookings controller, and will redirect to your root path unless an admin is signed in. Devise also comes with user_signed_in? and manager_signed_in? helpers if you've created models with those names.
You can tailor this more by deciding which controller actions will perform the action, for example
before_action :check_admin, only: [:edit, :create, :delete, :update, :new]
Would only perform the check before those controller actions, and allow anyone to access the index and show actions.

CanCan Nested Resource Controller Authorization

In my application, a user is allowed to assign another user as their "account manager" and the account manager would be allowed to modify all user info. I defined the following ability:
can :manage, User do |user|
user == current_user or user.account_manager == current_user
end
A user also has some nested resources (e.g: publications). I defined the following ability:
can :manage, Publication do |publication, user|
publication.user == current_user or user == current_user or user.account_manager == current_user
end
In the views I check using the following:
can? :update, #publication, #user_we_are_accessing
can? :create, Publication.new, #user_we_are_accessing.
Everything works just fine so far. My problem is with the controller. In my PublicationsController I added:
load_and_authorize_resource :user
load_and_authorize_resource :publication, :through => :user
However this always throws AccessDenied, because the check for publication is not passing the user object to the ability (trying to inspect the user object in the ability shows nil).
Any ideas how I can go about implementing this?
tl;dr: Using CanCan to authorize access to resources. User can assign another user as account manager. User has nested resources. Problem: nested resource is not accessible by account manager.
I solved this by doing the following:
1) replaced load_and_authorize_resource :publication with just loading the resource load_resource :publication
2) Added a before_filter :authorize after the load_resource call with the following implementaiton:
def authorize
raise CanCan::AccessDenied unless can? :manage, #publication, #user
end
This works, but I was hoping for a way that would solve this in the devise way, if there is such a thing. Thoughts and feedback are appreciated.

Setting CanCan ability.rs model

I successfully made login system with Devise and CanCan, and I have 3 types of users. Admin, internal and global users. I created Controllers and index actions: Admin, Cpanel, Report and State, and I want to restrict access to this controllers for some users.
Admin user should have privilegies to access: Reports(all), State (read), Admin (all)
Global user should have privilegies to access: Reports(only read), State(read), Cpanel(all)
Internal user should have privilegies to access: Reports(all), State (read)
And I tried to do this with following code in ability.rs:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role? :admin
can :manage, [Report, Admin]
can :read, State
elsif user.role? :global_user
can :read, [Report, State]
can :manage, Cpanel
elsif user.role? :internal_user
can :manage, Report
can :read, State
end
end
end
At this time I have only index actions in this controllers, and when I login to app with internal user I CAN access to /admin for example, and that is not behavior that I want. I want to restrict access to all controllers instead of controllers listed in ability.rb class.
Source code is here
If I were going to prevent access to an entire controller, I would make a before filter that redirects the user to an access denied page if he does not have the admin role. Might look something like:
def check_permissions
raise CanCan::AccessDenied unless #current_user.role?(:admin)
end
If I just wanted to prevent access to update and create, for example, I would do:
def update
raise CanCan::AccessDenied unless can?(:update,Thing)
...
end
def create
raise CanCan::AccessDenied unless can?(:create,Thing)
...
end
You can handle the CanCan::AccessDenied exception in your application controller:
rescue_from CanCan::AccessDenied do |exception|
flash[:error] = exception.message
redirect_to no_access_path
end
I have some pretty good posts about CanCan and Devise here and here
UPDATE
I use this method in my application controller to set my current user variable:
# Make the current user object available to views
#----------------------------------------------------------------------------
def get_user
#current_user = session[:current_user]
end
You need to add checks for the cancan authorization to your controllers.
This might be just adding a line like
authorize! :read, #state
to your state controller index action, and similarly for all the other index actions.
EDIT:
Sorry, in a state controller index action, you likely don't have #state, so the above wouldn't apply. Possibly something like
authorize! :read, State
There is also a load_and_authorize method that you can use to combine authorization for multiple actions in a controller and reduce your code. The load_and_authorize version is likely to look similar to
load_and_authorize_resource :state
and it should be before your actions.
You might want to look at this railscast on cancan authorization for a complete basic setup (in rails2).
I suspect to clear up other problems, we might need to see some more code. Try posting some of your controller code.
I haven't used this in rails3, but I assume most of it remains more or less similar.
I solved this problem with
def check_permissions
raise CanCan::AccessDenied unless current_user.role?(:admin)
end
but, note that I must to change #current_user to current_user (without #)
Thanks Tony

CanCan and sets of data

I've been struggling through this for some time and I've finally got to the point where it seems that CanCan doesn't allow you to authorize a collection of records. For example:
ads_controller.rb
def index
#ads = Ad.where("ads.published_at >= ?", 30.days.ago).order("ads.published_at DESC")
authorize! :read, #ads
end
ability.rb
def initialize(user)
user ||= User.new # Guest user
if user
if user.role? :admin # Logged in as admin
can :manage, :all
else # Logged in as general user
can :read, Ad
can :read_own, Ad, :user_id => user.id
can :create, Ad
end
else # Not logged in (Guest)
can :read, Ad
end
end
This results the unauthorised access message when trying to access the index action.
You are not authorized to access this page.
However, if you change the authorize call in the index action to check on the Ad class rather than the collection like so
def index
#ads = Ad.where("ads.published_at >= ?", 30.days.ago)
authorize! :read, Ad
end
... it works fine.
Any help in explaining this one would be greatly appreciated.
Thanks in advance.
ps. I was originally getting redirect loops when trying to work this out. It turns out there's a gotchya with the recommended rescue_from that you put in the application controller to give you nice error messages. If your root_path is set to the same place where your authorize! call is not true (or failing), you'll get a redirect loop. Comment out the rescue_from Learnt that one the hard way.
CanCan is not designed to be used like that. You can check whether a user has permissions on the model class (e.g. Ad) or a single instance (e.g. #ad).
I suggest you just use accessible_by to filter your collection:
#ads = Ad.where("ads.published_at >= ?", 30.days.ago).accessible_by(current_ability)
# #ads will be empty if none are accessible by current user
raise CanCan::AccessDenied if #ads.empty? # handle however you like
Another approach would be to define a custom permission based on the conditions you use to retrieve the collection:
# ability.rb
can :read_ads_from_past_month, Ad, ["ads.published_at >= ?", 30.days.ago]
# in your controller
def index
authorize! :read_ads_from_past_month, Ad
#ads = Ad.where("ads.published_at >= ?", 30.days.ago)
end
I solved this problem usings splats. In this code example, I am trying to authorize users on a collection of TimeOffRequests. They should be authorized if the User is an admin, a manager or the time off request belongs to them.
# time_off_requests_controller.rb
authorize! :read, *#time_off_requests
# Ability.rb
can :manage, TimeOffRequest do |*time_off_requests|
membership.has_any_role?(:admin, :manager) ||
time_off_requests.all? { |tor| membership.id == tor.employee_id }
end
I wrote about it in detail here if you're interested: http://zacstewart.com/2012/01/08/defining-abilities-for-collections-of-records-using-cancan.html

Resources