How can I restrict User role assignment using CanCan? - ruby-on-rails

Using a combination of a few tutorials and screencasts to set up Rails and CanCan to work with Devise etc. I've established User, Role, and the UserRole joining model for the has_many :through connection. And I only want some users to be able to assign certain roles, or create new accounts with those roles, etc.
Based on the documentation here, I feel like this should be pretty straightforward. What I have is essentially this:
if user
if user.role? 'admin'
can :manage, User
elsif user.role? 'submanager'
can :manage, User, roles: { name: ['basic', 'premium'] }
end
else
can :create, User, roles: { name: ['basic'] }
end
And then I'd simply test it in the form with:
- if can? :create, User, roles: { name: 'admin' }
= check_box 'user[role_ids]', 'admin'
Or, at least, that's the gist. And trust me I've tried every combination of singulars, plurals, role_ids, role, role: [ ]. So I don't think it's something as simple as that. No matter what I do, the can? check passes. Every time. Without fail.
I've found a lot of stuff out there on CanCan but I can't seem to find this specific thing. Is it because I'm trying to misuse CanCan, and I should be doing this some other way? Or … how do I do this?

I don't think this should be done in CanCan, as the control granularity of CanCan is on action. So if you really wanna to use CanCan to fulfill your intention, I think you should make the ”role change“ as a method call instead of a form submission.
the controller file
class UserController
def basic
user.change_role_to_basic
end
def premium
user.change_role_to_premium
end
end
the ability file
can :basic, User do
end
can :premium, User do
end

Related

manage roles in rails

I want create roles in my project. Each user can be: admin, registered or demo. Each role see different things.
How can I do that? What is the best gem to do roles?
This is a example in 'bad programming" of what I want:
def index
if current_user.role[:name] == 'admin'
#installations = Installation.all
elsif current_user.role[:name] == 'registered'
#installations = current_user.installations
elsif current_user.role[:name] == 'demo'
#installations = current_user.installations.first
else
end
end
Some gems that might be interesting for you :
rolify
role_model
If you decide to implement it yourself, then within some page you might want to change the content, for that you might want to do something like this :
Add a role to the user model using a migration :
class AddRoleToUsers < ActiveRecord::Migration
def change
add_column :users, :role, :string, default: :demo
end
end
Then in your app you can use it as follows:
def index
case current_user.role
when :admin
#installations = Installation.all
when :registered
#installations = current_user.installations
else
#installations = current_user.installations.first
end
end
You can also simply create a boolean admin for instance.
What you might want to do also is create some methods in your model so that you can call current_user.admin? or current_user.registered? . You can do that by doing (if you chose to use a string to store the role):
class User < ActiveRecord::Base
def admin?
self.role == "admin"
end
def registered?
self.role == "registered"
end
end
One advantage I see of having a role stored in a string is that if you have 5 roles for instance then you do not have 4 booleans (as when you store admin in a boolean) but only one string. On the long run you might want to store actually a role_id instead of a string and have a separate role model.
An excellent alternative pointed out by Jorge de Los Santos (another answer) is to use enum :
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
It is an excellent alternative because it will automagically add the methods described above such as current_user.admin? without hard coding them.
With your roles, you might want to do some authorization (admins can have access to specific pages, demo users are restricted to only a subset of pages, etc.). For this, you can use the gem called cancancan. You can look at this railscast to learn more about it. Also, you can have some infos here : How to use cancancan? .
There are plenty of solutions available to you.
Starting by gems:
https://github.com/RolifyCommunity/rolify
https://github.com/martinrehfeld/role_model
By using Devise architecture (in case you use it):
https://github.com/plataformatec/devise/wiki/How-To:-Add-a-default-role-to-a-User
By using enums in rails 4:
class AddRolesToUser < ActiveRecord::Migration
#add_column 'role', :integer, default: 0 to the users table
end
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
That will enable role methods.
user = User.find(1)
user.role #:demo
user.admin? #false
user.registered? #false
And consequently:
if user.admin?
#somethig
elsif user.registered?
#another something
else
#another another something.
And last but not least, what you are searching is not the manage roles solution, is the manage permissions solutions:
https://github.com/ryanb/cancan
Add a boolean, :admin to your User model.
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, deafult: false
end
end
Create a method for a registered user to separate them from demo users, such as verifying their email, providing a home address and phone number, filling out a profile, etc. This is up to you though, first you need to decide how a registered and demo user should be different.
The CanCan gem adds authorization to your project, and is especially useful if you want to implement multiple roles with differing abilities. When used with an authentication system like devise, you get a full suite of capability for your site.
You're in full control of what roles you want to define and what abilities they have. CanCan manages tracking, assignment, and querying of roles, and then gets out of your way to let you build what you need.
You can find the CanCan gem in Github: https://github.com/ryanb/cancan
It's simple to use, and the documentation is straightforward and easy to follow.

How to define specific option for database values

Let's say I have model User.
I want to #user to be 3 types : admin, regular and pro.
If I create column type:String, of course I can pass string, when creating new #user, 'admin'/'regular'/'pro'.
And then each time I operate with #user check something like
if #user.type == 'admin'
for my purposes.
But I feel that this is not how it is made by professional developers. (Am I right here?)
I want User model to have column which can only contain 3 specific values and not just any string.
Use an enum
The column should be in integer,with a default value (probably 0 in the following example). And in your model define it as:
enum type: { regular: 0, pro: 1, admin: 2 }
for example.
Now you automatically have these methods:
#user.regular?
#user.pro?
#user.admin?
Also, you can call #user.type and you will get a nice string representation:
> #user.type
=> "admin"
You can also query with a symbol like:
User.where(type: [:pro, :regular])
or
User.where.not(type: :admin)
etc.
This approach also works perfectly well with the CanCan gem.
If you want to assign roles in your application , you can simply use the Role-Based specific gems like Cancan and Rollify etc. The link to which is here. I will explain below how easy it would be to assign roles to users and yes how the professional developers do it.
In your User model , there will be a method as :
ROLES = %w[admin regular pro]
def role?(base_role)
ROLES.index(base_role.to_s) <= ROLES.index(role)
end
Now in your UsersController you can check whether :
if user.role? :admin
can :manage, Post
end
if user.role? :regular
can :manage, ForumThread
end
if user.role? :pro
can :manage, Forum
end
It's that easy ..!!!.
I hope this helped ..!!! :)

CanCan Ability Model

I have a Rails 4 app that i wrote a CanCan Ability model for based on my model setup in my app. While testing it works sometimes and then it doesn't. Im not sure what the problem is. There is nothing in the logs that would point to a problem. I also don't think that it is that clean anyway. Any suggestions on how to improve this Ability model? It seems very redundant and confusing.
if user
##
can :manage, User do |u|
case u.account
## If user is a Developer allow it to only be managed by itself (user)
when Developer
u == user
## If user is a Organization allow it to only be managed by it's founders
when Organization
u.account.founders.exists?(user)
else
false
end
end
can :manage, App do |a|
case a.appable
## If app belongs to a Developer allow it to only be managed by it owner (developer)
when Developer
a.appable.id == user.id
## If app belongs to a Organization allow it to only be managed by its organizations' founders
when Organization
a.appable.founders.exists?(user)
else
false
end
end
##
end
As for cleaning it up, you could consider instead organizing the logic by user role. This way you can easily look at a given role and understand all the abilities that they do and do not have access to.
Something like this:
if user.developer?
can :manage User, id: user.id
can :manage App, App.appable_by(user)
end
NOTE: you may need to write some custom finders like App.appable_by(user) in order to test the rule
I don't fully follow what you are trying to do with Organization / founders. If this is a role like user.founder? Then treat it like mentioned above, but with a different authorization rule.

Set Up Admin Users to Edit Users, Articles, Collaborators

I am trying to solve an issue I'm having with setting up admin on my site. Here is a simple version of my routes.
resources :users
resources :articles
resources :collaborators
end
resources :admin
Admin users are very similar to Collaborators, because they can edit and create Articles, but Admin has the ability to also c.r.u.d. Users, Collaborators, and Articles for every User. Collaborators can only c.r.u.d. Articles that are associated with the User they have been assigned to.
The part where this gets a little bit confusing is how to set up the controller and views that Admin uses for CRUD operations. As of now the way I am trying to implement it is to create separate CRUD views within admin/. The problem with this is that the functionality is so simimlar to a collaborator, I feel like it could be DRYer somehow.
Anybody know the most basic way to implement something like this? Thanks!
Update: I'd like to update this to say that it's super easy to google admin tools for rails. I'm more wondering how to implement this without an admin tool, or if that sounds like a bad idea, why?
The answer I came up with is to create Collaborators with an admin field true / false. I also set up CanCan and it's working pretty nicely.
Here is my CanCan ability.rb
def initialize(user)
if user.admin?
can :manage, :all
else
can :manage, Article, :id => user.article_id
cannot :index, Article
end
end
I just used "user" because it's more readable and shorter than collaborator and basically a user...
Try ActiveAdmin (http://activeadmin.info/). I think it is the most simplest way to build administrator backend for your application.

Authorization gem for a multi-tenancy application?

Are there are any authorization gems/examples for multi-tenancy applications?
I looked at CanCan and CanTango, but I wasn't able to find what I am looking for.
My app has Account, User, Relationship model. The relationship model has a relationship_type column which determines authorization level. Its value can be owner, moderator, editor, perhaps more in the future. Users can own/moderate many accounts, and an account can have many owners/moderators.
All the examples I found describe a single tenant application, whereas my app's authorization has to be scoped through the current account being viewed. A user can be a guest on one account and an owner of another, for example.
I'm starting to think my Relationship model is bad design and might have drawbacks, but I'm not sure what is a better alternative.
CanCan can indeed handle this because it lets you define arbitrary ruls. Your ability file might look like this:
def initialize(user)
can :read, BlogPost do |blog_post|
blog_post.account == user.account and user.relationship.in?([:owner, :moderator])
end
end
Try declarative_authorization, the authorization rules are defined in a single Ruby file using a DSL and you can define advanced rules based on objects' attributes and on the user role of course.
For example you can say something like this
role :moderator
has_permission_on :accounts do
to :manage
if_attribute :moderators contains {user}
end
end
declarative_authorization offer several methods you can use in models/controllers/views, for example in the account view you can use something like:
<% permitted_to? :update, #account do %>
<%= link_to 'Edit account', edit_account_path(#account) %>
<% end %>
You can have a look at the documentation and at the RailsCasts episode.

Resources