Alright, so I am tacking a Role Based Authentication and Multi-tenancy problem using Ruby on Rails. Up to now, I have been doing this manually as I add new functionality with items in controllers like this:
Role based authentication
[code]
class SomeController < ApplicationController
before filter :check_authorized
authorized_roles = ["platinum", "gold"]
def check_authorized
redirect_to some_path, :notice => "Come on…you know you can’t do that unless you upgrade!" unless authorized_roles.include? current_user.role
end
#other methods
end
[/code]
Multi-tenancy
[code]
class SomeController < ApplicationController
def show
@something = Somthing.find(params[:id])
if current_user.id != @something.user_id then
@something = nil
redirect_to some_path, :notice => "Stop trying to see other people’s stuff!!!!"
end
end
[/code]
This was fine and dandy for the first 10 months of my app because I don’t add a ton of models/controller actions and most of the functionality has been on our mining server. However, it’s getting kind of tedious, not to mention that I could easily misplace a filter and user would have access to anothers data, which would be not good. Also, it would be kind of nice to be able to see what everyone can do in one spot.
CanCan encourages users to have an Ability.rb class and to use a RESTful architecture. Lets assume that you have a silver, gold, platinum architecture. We will create RBAC and Multi-tenancy for Platinum users to edit a model Content that belongs to them.
For the user, we will keep this super simple and just add a column to the database that represents their role. So a call to User.role will return that users role. In our content model, their will be a user_id column.
User
has_many :contents
Content
belongs_to :user
Role Based Authentication
We will allow the platinum user to manage every action in the Content model. This means that they will be able to perform ANY action that is in the contents controller, keep this in mind as you add functionality if you stray from REST
[code]
Ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.role == :admin
can :manage, Content
end
end
end
[/code]
Code Enforced Multi-tenancy
For this we really just need to make one addition to the ability declaration since CanCan will take in a hash of conditions and we have given the Content a column that has the user_id of the owning user.
[code]
can :manage, Content
turns into
can :manage, Content, :user_id => current_user.id
[/code]
and Ta-da you now have role based abilities and the most primative form of Multi-tenancy available.
Hi Austin,
How would you go about adding extra logins for your Platinum members?
Let’s say the users of your app want to give their employees access to the account, but only certain portions. The boss logs in using the main email address for the account, but employee bob logs in using his own email, but he sees all of his boss’s data. Make sense?
Hey Patrick, sorry for the late reply. Have been heads down lately 🙂
I think my approach for this has changed over the years as I have switched to pundit but you could accomplish similar with CanCan(Can).
In your case, I would have multiple Roles inside of an organization and keep it the most simple you can to start. Owner and User. Then in your controller/service object that is responsible for handling requests you would merge in a scope when searching multiple things.
So more specific, say you have a bunch of items. In your ActiveRecord Item class create a couple of scopes.
– by_org -> (org)
– by_role -> (user)
– scoped -> (org, user) { use by_org and by_role here }
then in your index/item lookups you can do
Item.where(x).scoped(organization, user)
In pundit we call this a `scope` and when you apply the scope to all the requests, it prevents the users from being able to see what they should not see.
Then for delete and edit you would have to check any additional things that are beyond the scope. For instance, if a user cannot delete an owners account, you would handle that in a can_item_delete.
Does that help?