I have used CanCan plugin to manage authorization on my rails app, but it’s still static authorization. I just create code like this:
def initialize(user) if user.admin? can :manage, :all else can :read, :all end end
My app is getting bigger. There are users with many user types. Every user type has different role / authorization. I’d like to create a feature that user admin can manage authorization for all user types. Admin can assign or remove the permission to each user types. So, what should I do to implement that feature?
To create dynamic authorization with CanCan, first we must prepare the database to store authorization data, said that “permissions” table. The structure of permissions table can look like this:
permissions: • id • object_type • action_name • description
Permission model should have some validations:
validates_presence_of "object_type", :action_name validates_uniqueness_of "object_type", :scope => [:action_name]
We should also have users, user_types, and user_type_permissions table. User belongs to user_type. UserType has many users. UserType has many permissions through user_type_permissions. Permission has many user_type through user_type_permissions.
And then on the ability model, we should put code like this :
class Ability include CanCan::Ability def initialize(user) if user.admin? can :manage, :all else user.user_type.permissions.each do |permission| can permission.action_name.to_sym, permission.object_type.constantize end end end end
Dynamic ability is done, the ability model read the authorization for all user types from database.
For the manage permission feature for admin, just create CRUD function as usual. However, don’t give the admin an authorization to modify data on the permissions table because it’s can make the application error if the data in the permissions table can’t be defined on source code. Just give the admin an authorization to modify user_type_permissions table. The structure of the table can look like this:
user_type_permissions: • id • user_type_id • permission_id
Then, let the program to create permission data automatically. We must do some modification on CanCan plugin for it. Please modify the authorize method on the /vendor/plugins/cancan/lib/cancan/ability.rb to be like this :
# See ControllerAdditions#authorize! for documentation. def authorize!(action, subject, *args) case action.to_s when "show", "index" then action_name = "read" when "create", "new" then action_name = "create" when "update", "edit" then action_name = "update" when "delete", "destroy" then action_name = "destroy" else action_name = action.to_s end # save data to permissions table Permission.create(:object_type => subject.to_s, :action_name => action_name) message = nil if args.last.kind_of?(Hash) && args.last.has_key?(:message) message = args.pop[:message] end if cannot?(action, subject, *args) message ||= unauthorized_message(action, subject) raise AccessDenied.new(message, action, subject) end end
By this code, the authorize data can automatically save to the permissions table everytime we access any method in the controller. For example, we have users controller with method index, show, create, and new. So everytime we access method index/show, authorize method will check index/show method and save it to permissions table as: object_type = User; action_name = read. Everytime we access method create/new, authorize method will check create/new method and save it to permissions table as: object_type = User; action_name = create. However, in order this function works, don’t forget to put “authorize_resource” in the controller :
class Admin::UsersController < Admin::ApplicationController authorize_resource ........ end
or call “authorize!” in the method :
def show authorize! :read_users, User ......... end