Setting CanCan ability.rs model - ruby-on-rails

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

Related

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.

How to make API Controller Actions readonly

I want to design an API in Rails that requires actions like Create, Update and Delete to be readonly for certain controllers, and open to the public for others (eg, comments on an article should be open but editing that article should require API authentication)
I know how to do the authentication part, what I don't know how to do is the "read only" part or the "you have permission to create a comment but not delete it" part.
Does any one have any resources, tips, tricks or github repositories that do this or something similar to this?
You are needing to do authorization. Look at Pundit for a scalable solution https://github.com/elabs/pundit
I had an app for a while that only needed a little bit of control as there were only a few methods on 2 controllers that were limited. For those i just created a before_filter and method to control the authorization.
The code below would allow everyone to do index and only allow users with a role attribute that has a value of "admin" to do any other action in the controller. You can also opt to raise an unauthorized error or raise an error message instead of redirecting. There are articles (probably books) written on the security side of the house for whether you should give users notice if they are not authorized to do something (which means they can infer that there is something there that someone can do at the uri)
SomeController < ApplicationController
before_filter check_authorized, except [:index]
def index
....stuff that everyone can do
end
def delete
....stuff only admin can do
end
private
def check_authorized
redirect_to root_path unless current_user.admin?
end
end
Of course you will need devise or a current_user method and a method on user that checks admin
class User < ActiveRecord::Base
def admin?
if self.role == "admin"
true
else
false
end
end
end

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.

Understanding CanCan Ability and Users

I have Rails Admin with CanCan support in my rails app. I'm confused on one issue though. How does CanCan know what user is signed in? For example, my users can have different roles and through CanCan I assign roles for certain access into each table. When I go to localhost:3000/admin, I receive the error
CanCan::AccessDenied in RailsAdmin::MainController#dashboard
My Ability.rb file
def initialize(user)
if user and user.role.eql? :super_admin
can :manage, :all # allow superadmins to do anything
elsif user and user.role.eql? :admin
can :manage, [Location, School] # allow admins to manage only Locations and Schools
end
end
So what do I do so that user's have the ability to sign in into Rails Admin? Do I have to manually create it?
By default, CanCan will use whatever is returned by current_user. If you are using Devise within a namespace though (admin for example) then Devise actually will use current_admin_user instead. You can either create a current_user method in your ApplicationController (or some other base controller) that returns current_admin_user or overwrite the current_ability method to instantiate the Ability with current_admin_user instead.
(this is all assuming your Devise is using a namespace. By default Devise will use current_user)
You need to have a current_user method available in your controller. Assuming you have that, if you aren't signed you won't have access to a current user, so you'll need to assign a user in your ability file if it doesn't exist. In your initialize method, add user ||= User.new to make the assignment if a user doesn't already exist.
def initialize(user)
user ||= User.new
if user and user.role.eql? :super_admin
can :manage, :all # allow superadmins to do anything
elsif user and user.role.eql? :admin
can :manage, [Location, School] # allow admins to manage only Locations and Schools
end
end

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