use cancan and devise in my application - ruby-on-rails

I want to use cancan in order to limit the users that want to view some pages in my application.
so I try to do it by this tutorial:
http://www.roberthuberdeau.com/articles/9-Blog-tutorial-part-3
I have two roles: Admin and Worker, and I have two controllers: Tasksadmins and Workers.
I want to define the next thing:
1) Workers can manage and see all the things of the Workerscontroller.
2) Admins can manage and see all the things of the Tasksadminscontroller.
I'm not sure if I defined it correctly:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role? :Admin
can :manage, :tasksadmins
elsif user.role? :Worker
can :manage, :workers
end
end
end
the next thing that I think I don't need to implement is: "the def initialize user bit is for guest users." I force the users to sign_in with: before_filter :authenticate_user
the next thing is: start restricting access to the blog application based on user role:
I don't know what and where I should write.
in the example, he wrote:
authorize! :edit, #article
so I tried to write the next followings in the tasksadmins controller:
authorize! :edit, #tasksadmins
authorize! :new, #tasksadmins
authorize! :index, #tasksadmins
authorize! :create, #tasksadmins
authorize! :show, #tasksadmins
authorize! :destroy, #tasksadmins
but I got an error: undefined method 'authorize!' for TasksadminsController:Class
please help me, I'm in the end of the definition of cancan.

This article is pretty dated, I would suggest looking at the CanCan docs on the github page, particularly this one. This looks like what you want to do but as shown in the doc, you do not have to authorize every action individually. Also, this should help with your second issue with devise. If you are specifying the version for devise like as shown in the article I would highly recommend upgrading your gems if possible. Good luck!

Related

creating dynamic roles using cancancan gem

So I came across this problem at work that I need to make the roles dynamic. On the cancan gem they said that I must create predefined roles inside the User model like this
ROLES = %i[admin moderator author banned] but we removed it since we wanted to create it on the fly. we only have one role Admin and then this admin can create roles. Now here's are an awesome problem. On our ability.rb file we have this.
def initialize(user)
clear_aliased_actions
# override cancan default aliasing (we don't want to differentiate between read and index)
alias_action :delete, to: :destroy
alias_action :edit, to: :update
alias_action :new, to: :create
alias_action :new_action, to: :create
alias_action :show, to: :read
alias_action :index, :read, to: :display
alias_action :create, :update, :destroy, to: :modify
user ||= Spree.user_class.new
if user.respond_to?(:has_spree_role?) && user.has_spree_role?('admin')
can :manage, :all
else
user.models.pluck(:key).each do |m|
can :manage, m.constantize
end
end
so what I did is like this. Since can method takes 2 arguments, an action, and a Model, so I created a models table that has a many_to_many relationship with Role. my problem is that this code especially the else part, some of the model work some of it doesn't, another problem is. Let's say we create another model right a developer will have to go to the console to this. Model.create(name: 'Newmodel', key:'NamepsaceModelaname') this is hella weird. can you suggest another way of doing it? or a gem that can do a dynamic role.

How to setup cancancan abilities

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

cancan::access denied using devise and rolify

I am trying to use cancancan for authorization. When I am an authorized 'group_creator' user I am still denied access to groups/new.
Roles are functional and has_role? from rolify works. So I think the issue comes with cancancan or maybe devise.
Ability.rb
def initialize(user)
user ||= User.new # guest user (not logged in)
alias_action :create, :read, :update, :destroy, to: :crud
if user.has_role? :group_creator
can :create, Group
elsif user.has_role?(:creator, Group)
can :manage, Group, owner_id: user.id
elsif user.has_role?(:admin, Group)
can :crud, Group, :id => Group.with_role(:admin, user).pluck(:id)
else
can :read, Group
end
end
Relevant GroupsController.rb
class GroupsController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource
def index
#groups = Group.all # Can be deleted due to cancancan?
end
def new
#group = Group.new # Can also be deleted due to cancancan...
end
end
I have tried debugging in the console with:
user=User.last
user.has_role? :group_creator # returns true
group=Group.last
ability=Ability.new(user)
ability.can?(:create, Group) # returns false
ability.can?(:create, group) # returns false
Two things here.
The user needs :read AND :create ability to access new action. This solved this problem for me.
After fiddling with a fresh mind this morning some advice for others running across this that are new like me... Make sure to reboot server after altering ability.rb... (I am not sure if this was part of the issue yesterday but I thought I should mention it). Cancancan wiki has some explanation on how to utilize a separate permissions table for dynamic modification of abilities.

How do I setup my CanCanCan permissions correctly?

I am a little confused about how to configure CanCanCan properly.
For starters, do I have to add load_and_authorize_resource to every controller resource I want to restrict access to?
This is what I would like to do:
Admin can manage and access all controllers and actions
Editor can read all, manage :newsroom, and can manage all Posts
Member can read every Post and can create & update Posts (not edit/delete/anything else), cannot access the newsroom. The difference between an update & edit post in our business rules is that an update is creating a new post that is a child post of the current post. So it isn't an edit. Just a new record with an ancestry association.
Guest can read every Post, but cannot create Posts nor access the Newsroom.
This is what my ability.rb looks like:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
#Admin
if user.has_role? :admin
can :manage, :all
can :manage, :newsroom
# Editor
elsif user.has_role? :editor
can :read, :all
can :manage, :newsroom
can :manage, Post
#Member
elsif user.has_role? :member
can :read, :all
can :create, Post
can :status, Post
can :update, Post do |post|
post.try(:user) == user
end
#Guest
else
can :read, :all
can :create, Post
can :status, Post
end
end
end
In my routes.rb I have this:
authenticate :user, lambda { |u| u.has_role? :admin or :editor } do
get 'newsroom', to: 'newsroom#index', as: "newsroom"
get 'newsroom/published', to: 'newsroom#published'
get 'newsroom/unpublished', to: 'newsroom#unpublished'
end
What is happening though, is when I am logged in with a user that has not been assigned any roles (i.e. what I want to be a "Guest"), they can access the Newsroom.
When I try to edit a post with the role of :member, it gives me a "Not authorized to edit post" error (which is correct).
I just can't quite lockdown the Newsroom and I am not sure why.
You do not need to use load_and_authorize_resource in every controller. That is a convenience macro that does two things. First it assigns an instance variable with the record(s) assumed for the current controller and action. It then authorizes the resource. For some controller actions the first step might be wrong, so you want to load your resource and then authorize it manually. An example from the Railscasts episode about CanCan is like this:
def edit
#article = Article.find(params[:id])
unauthorized! if cannot? :edit, #article
end
You can also do it like in the example on the CanCan Wiki for authorizing controllers:
def show
#project = Project.find(params[:project])
authorize! :show, #project
end
Or you can just use authorize_resource and take care of loading it yourself. In the end, you must make sure that CanCan is used for authorization somehow (controller macro or in each action). Regarding your abilities, I think you want something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
#Admin
if user.has_role? :admin
can :manage, :all
# Editor
elsif user.has_role? :editor
can :read, :all
can :manage, :newsroom
can :manage, Post
#Member
elsif user.has_role? :member
can :read, :all
can :create, Post
can :status, Post
can :update, Post do |post|
post.try(:user) == user
end
#Guest
else
can :read, :all
cannot [:index, :published, :unpublished], :newsroom
end
end
end
And here's an example like how you might be able to authorize your newsroom:
class ToolsController < ApplicationController
authorize_resource :class => false
def show
# automatically calls authorize!(:show, :tool)
end
end
A last personal note about CanCan is that I wouldn't suggest it for new projects since it isn't actively maintained anymore and that I found it a bit counterintuitive when defining abilities. That said, CanCan is one of the most well-documented gems that I have worked with, especially the wiki has loads of examples and explanations.
can :read, :all
means user has permission to read all the resources of your app. It should be
can :read, Post
also add
cannot :manage, :newsroom
where you do not want access to newsroom. The order in which you specify permissions matters.
As others have already mentioned, 'load_and_authorize_resource' is optional. Only 'authorize resource' is needed to authorize all actions of a controller. If you skip these then you can 'authorize' individual controller actions.
Avoid using block for ability unless absolutely necessary. For instance if Post has a user_id in it then you could do
can :update, Post, user_id: user.id
Lastly, 'class => false' is used where you do not have a model backing your controller.
i.e you do not have a model called 'Newsroom' but you have a controller called 'NewsroomsController'.
For what it's worth, I had to setup my NewsroomController like this:
class NewsroomController < ApplicationController
authorize_resource :class => false
This is what the working version of my ability.rb looks like after I got it to work with the permissions I needed:
#Roles
#Admin
if user.has_role? :admin
can :manage, :all
# Editor
elsif user.has_role? :editor
can :manage, :newsroom
can :manage, Post
#Member
elsif user.has_role? :member
can [:read, :create, :status], Post
can :update, Post do |post|
post.try(:user) == user
end
#Guest
else
can [:read, :status], Post
end
For starters, do I have to add load_and_authorize_resource to every controller resource I want to restrict access to?
Yes.
What is happening though, is when I am logged in with a user that has
not been assigned any roles (i.e. what I want to be a "Guest"), they
can access the Newsroom.
From the guest role above:
...
#Guest
else
can :read, :all
can :create, Post
can :status, Post
end
This gives a guest read access to everything and the ability to create posts.
If you want your Guests to only be able to read posts it should be:
...
#Guest
else
can :read, Post
# can :status, Post # maybe you want this aswell
end

CanCan Auth issue

Good Afternoon All,
I am trying to sort out my user authentication and causing myself headaches.
I have a :role_type defined in User and my user has two roles, Employer or Developer, now I my user is currently developer and should be able to see jobs#index but it cannot and I get the default cancan message of unauthorized:
class JobsController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!
before_action :set_job, only: [:show, :edit, :update, :destroy]
and here is the Ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :create, :delete, :update, Job if user.role_type == "Employer"
can :read, Job if user.role_type == "Developer"
end
end
Thanks for the help.
As written in the comments: Make sure that you are not having a typo like "Employer" instead of "employer" :-)
I'm assuming the error occurs when the user.role_type == 'Employer'
Looking at your code it looks like the Employer is missing the :read action.
If Employer can manage all jobs (basic crud actions) you might want to consider giving him the :manage ability
Example:
can :manage, Job if user.role_type == "Employer"
can :read, Job if user.role_type == "Developer"
The :manage ability gives the user access to every action within the JobsController. See the following doc for clarification on the :manage ability: https://github.com/ryanb/cancan/wiki/defining-abilities#the-can-method
Otherwise you would need to add the :read action to Employer, as they are not a Developer and will not get the :read action

Resources