creating dynamic roles using cancancan gem - ruby-on-rails

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.

Related

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

Paper_Trail and CanCan: Rails_Admin doesn't show versions

I have configured Paper_Trail and CanCan in a Rails app. Rails_Admin shows me the tables versions and version_associations nicely.
In another app (which in fact is a fork of the previous one) I'd like Rails_Admin to show those tables, too, but it doesn't.
In both applications I am logged in as a user with role admin, and the ability for both apps looks like this:
class Ability
include CanCan::Ability
def initialize(current_user)
alias_action :create, :read, :update, :destroy, to: :crud
can :read, User
if current_user.nil?
can :create, User
else
can :update, User do |user|
user == current_user # Update himself
end
can :crud, Boilerplate # This is the only line that the 2nd app adds to the ability
if current_user.has_role?(:admin)
can :access, :rails_admin
can :dashboard
can :crud, :all
end
cannot :destroy, User do |user|
user == current_user
end
end
end
end
Aside from the line can :crud, Boilerplate, all is the same. The migrations are the also the same, so I have the needed tables etc.
I have created a dump of each app's current_ability: diff on diffchecker.com. What I can see is that the ability of the user which sees the Paper_Clip stuff in Rails_Admin has a lot of #expanded_actions, while the other one has not. Where could this come from? Both users definitely do have the :admin role.
The problem was that I didn't use the newest paper_trail version (version 4.0.0.rc).

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

use cancan and devise in my application

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!

Cancan 2.0, how to restrict user to update certain attributes on User model

There's a similar question already answered but seems to be a little outdated since it doesn't cover the new CanCan 2.0 version. I need to prevent certain fields (in this case :active and :limited) from being updated by the user and be able to only be edited by an admin.
# ability.rb
if user.persisted?
cannot :update, :users, [:active, :limited]
elsif user.admin?
can :access, :all
end
However this code is not preventing a user from editing those fields.
I also added the enable_authorization to a new class RegistrationsController < Devise::RegistrationsController and devise_for :users, path: 'users', controller: 'registrations' on routes but that doesn't seem to make it either.
I think it should be User not :users?
# ability.rb
...
cannot :update, User, [:active, :limited]

Resources