models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role? :admin
can :manage, :all
else
can :read, :all
can :create, Comment
can :update, Comment do |comment|
comment.try(:user) == user || user.role?(:moderator)
end
if user.role?(:author)
can :create, Article
can :update, Article do |article|
article.try(:user) == user
end
end
end
end
end
In Railscasts there are methods user.role? :admin & if user.role?(:author).I dont get it. Do i need to create a method in model to make it work?
I'm storing roles in Users table as a role column.
Yes, you need to write this yourself. However, the CanCan project has a wiki page describing how to do this.
The first line of the wiki says:
"CanCan is decoupled from how you
implement roles in the User model, but
how might one set up basic role-based
authorization?"
Note that I disagree almost completely with the example on that page that uses a role_mask, but the page is still good and should give you some ideas.
There is also the Separate Role Model example, which I personally like better. It depends on where you want to store your role information.
If you're looking for a gem that is plug-and-play, then check out Declarative Authorization.
Raynb says that he developed Cancan because DA was overkill for some projects here: http://railscasts.com/episodes/192-authorization-with-cancan?autoplay=true
Related
Having trouble figuring out how to set up my different roles with cancancan abilities. I have a model "Business" which has many users with a role of either :owner, :manager or :employee.
Im trying to make it first that if they don't belong_to that business they can't see anything for that business. And second I want to limit functionality based on which role they have.
I guess I could do this within the views by using if statements and only showing them the things they have access to, but wondering if there is a better way with cancan
inside your ability.rb
class Ability
include CanCan::Ability
def initialize(user)
alias_action :create, :read, :update, :destroy, :to => :crud
if user
if user.role == "manager"
can :crud, Business, :id => user.business_id
# this will cek whether user can access business instance (id)
elsif user.role == "owner"
can :manage, :all
end
end
end
end
inside your controller you can do checking with 2 ways
step 1: with load_and_authorize_resource, this will automatically check all 7 rails method
class BookingsController < ApplicationController
load_and_authorize_resource
# this before filter will automatically check between users and resource
# rails method here
def show
end
end
step 2: check manually with authorize inside each method
def show
#business = Business.find(params[:id])
authorize! :read, #business
end
Definitely read through cancan's wiki as I'm not 100% on this, but I think the solution will look something like this:
def initialize(user)
user ||= User.new
if user.has_role?(:owner)
can :read, Business, Business.all do |business|
business.id == user.business_id
end
elsif user.has_role?(:whatever)
# etc
end
end
Then just check authorize! in the controller in the normal cancan way. As for showing them appropriate functionality in views, you can either do a bunch of if statements in the view any maybe try to use partials to make it all look palatable, or check in the controller and render different views based on role, but yeah, there's gotta be if statements somewhere.
The best way is to always use "incremental" permissions. Consider that cancancan starts already with the assumption that your users have no right on Business, so you can give them "incremental" permissions based on their role.
An example would be:
# give read access if they have any role related to the business
can :read, Business, users: { id: user.id }
# give write access if they are manager
can [:edit, :update], Business, business_users: { role: 'manager', user: { id: user.id } }
# give destroy permissions to owners
can [:destroy], Business, business_users: { role: 'owner', user: { id: user.id } }
I am using the CanCan Gem and would like to assign a User two roles which conflict. I need a User to have two roles, one is the x role and the other is the y role. Role x allows users to create posts but not create articles. Role y allows users to create articles but not create posts.
My ability.rb file is like the following:
if user.role?(x)
can :manage, Post
cannot :manage, Article
end
if user.role?(y)
cannot :manage, Post
can :manage, Article
end
Currently if I assign a User both roles, the permissions for role Y will override the permissions for role x. I would like to allow Admins to stack roles so that if I want to add roles x and y to a User, that User will be able to manage Posts and Articles. So if the role has a can and another role has a cannot, the can permission overrides.
Shouldn't
if user.role?(x)
can :manage, Post
end
if user.role?(y)
can :manage, Article
end
be enough?
cannot just removes previously granted permissions. But if you do not grant them, then you do not need to remove them.
See also https://github.com/CanCanCommunity/cancancan/wiki/Ability-Precedence
Here is a running example:
require 'cancan'
class Ability
include CanCan::Ability
attr_reader :user
def initialize(user)
#user = user
if user.role?(:editor)
can :edit, Post
end
if user.role?(:reader)
can :read, Post
end
end
end
class Post
end
class User
attr_reader :name
def initialize(name, *roles)
#name = name
#roles = roles
end
def role?(role)
#roles.include?(role)
end
end
admin = User.new('admin', :reader, :editor)
user = User.new('user', :reader)
editor = User.new('editor', :editor)
admin_ability = Ability.new(admin)
user_ability = Ability.new(user)
editor_ability = Ability.new(editor)
def output(ability, permission)
puts "#{ability.user.name} can #{permission} Post: #{ability.can?(permission, Post)}"
end
output(admin_ability, :edit)
output(user_ability, :edit)
output(editor_ability, :edit)
puts "--"
output(admin_ability, :read)
output(user_ability, :read)
output(editor_ability, :read)
The default is to NOT HAVE a permission. So if you only work in "additive" mode where you add permissions if a user has roles, you will never need to remove one of them.
:manage is the same as [:create, :edit, :update, :new, :destroy, :index, :show] and is only there for convenience. If you only want to add 6 of those permissions, you can add all 7 and then remove 1. But other than that I don't think you'll need cannot for your example.
The accepted answer is now 5 years old, and this functionality appears to be in CanCanCan as expected by the OP.
It did not work as expected in version 1.15, but does work in version 3.3, so you may need to upgrade your cancancan gem.
Does exists gem, that implements following things:
User can has many roles
Each role has group of permissions
Each permission just boolean
Roles and permissions stored in database (most important)
For example, I want for something like this:
if current_user.can?(:read_all, :list_of_orders)
...
end
And can? method should search, does any of roles allows it.
Following Zippie question, I suggest you going with CanCan but you can also use along with it for authentication. See the following links which you will find useful Rails 3 Authentication and Authorization with Devise and CanCan (Part 1) & Rails 3 Authentication and Authorization with Devise and CanCan (Part 2).
You could potentially have something like this in your ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
# raise user.role?(:administrator).inspect
if user.role? :administrator
can :manage, :all
can :manage, User
elsif user.role? :employee
can :read, :all
end
end
end
So that in your views you can do something like
You can also use the can? in your controller to create your necessary filters as well.
See devise for user authentication and you can define your roles there:
https://github.com/plataformatec/devise
or even better, combine it easily with cancan:
https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
You can try cancan gem, It is powerful
https://github.com/ryanb/cancan
for example:
<% if can? :update, #article %>
<%= link_to "Edit", edit_article_path(#article) %>
<% end %>
you can Defining Abilities in a ability.rb
can :manage, Article # user can perform any action on the article
can :read, :all # user can read any object
can :manage, :all # user can perform any action on any object
I've got an app that uses Devise, CanCan and Rolify to deal with authentication and authorization. But I don't think I'm using these gems to the full extent. Right now the only thing in my ability class is this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
else
can :read, :all
end
end
end
I found a security hole where an authenticated user is able to look at other user profiles. I fixed it by changing some code in the user controller.
def show
#user = current_user.has_role?(:admin) ? User.find(params[:id]) : current_user
end
Is this the best way to deal with this hole? Is there a best practice or a rails convention that addresses this in a different way?
From the doc:
can :read, ModelName, :user_id => user.id
Please, give me any idea about a little problem: in my Rails 3 app I need current_user always be NOT nil. I'm using Devise and CanCan for auth system and I thinking about how to implement "guest" user. For roles like "admin" it works fine ( if current_user.is? :admin), but if user is not logged in I wish to check not user_signed_in?, but the same way (if current_user.is? :guest) for example.
I think I need to put somthing creating current_user object ib before_filters of Application_Controller, but I dont' know how to create this global thing right way.
Thanks for any answers!
You could try it like this:
if current_user.try(:is?, :admin)
So it won't matter if current_user is nil
If you are using 'CanCan' you should be adding functionality like you mentioned into you Abilities class. For example:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :manage, :all if user.is? :admin
can :read, :all if user.is? :guest
end
end
For more information take a look through the Wiki or watch the screen cast:
https://github.com/ryanb/cancan/wiki
http://railscasts.com/episodes/192-authorization-with-cancan