Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
simple role-based authorization by current controller#action

##Problems with role-based authorizations

The CanCan way

e.g. gem 'access-granted' https://github.com/chaps-io/access-granted

role :admin, { is_admin: true } do
  can :destroy, Post
end

role :member do
  can :read, Post
  can :create, Post
end

####Authorizing controller actions

class PostsController
  def show
    @post = Post.find(params[:id])
    authorize! :read, @post
  end

  def create
    authorize! :create, Post
    # (...)
  end
end

we have to define authorize! for each controller action; in case of many controllers, it could be overwhelming, e.g. 42 controllers -> 312 methods to define authorize!


alternative approach needed!

like this:

  1. group controllers in directories (folders), like: admin/, super_admin/ ...
  2. define authorization rules based on User role methods: e.g. super_admin? admin? auditor? tech? (role methods returns boolean value)
  3. check for user authorization in controllers or in the ApplicationController
class AccessValidor
def initialize(params:, user:)
@params = params
@user_rules = user.assigned_rules_by_roles
end
# authorized_rules is array of authorization rules (selected by the controller and action)
def authorized_rules
@authorized_rules ||= Array.new.tap do |result|
# check for authorization rule if controller is in the controllers folder, e.g. "admin/"
result += rules_with_current_action(user_rules_by_controller_group)
# and those authorized by controller
result += rules_with_current_action(user_rules_by_controller)
end
end
private
def user_rules_by_controller_group
return [] if controller_group.blank?
@user_rules.select{ |a| (a[:controller_group] == :any) || (a[:controller_group] == controller_group) }
end
def user_rules_by_controller
return [] if controller.blank?
@user_rules.select{ |a| a[:controller] == controller }
end
def controller_group
controller[/\A\w*\//] # this is top folder name, e.g. "admin/", (nil otherwise)
end
def controller
@params["controller"] # e.g. "admin/devices", or "home", etc.
end
def action
@params["action"] # e.g. "index", "show", "custom_action", etc.
end
def rules_with_current_action(rules)
rules.select{ |a| (a[:actions] == :any) || a[:actions].include?(action) }
end
end
class ApplicationController < ActionController::Base
def authorize_user!
render_unauthenticated unless current_user
render_unauthorized unless authorized_for_action?
end
def authorized_for_action?
@access_validor ||= AccessValidor.new(params: params, user: current_user)
@access_validor.authorized_rules.any?
end
end
class User
def assigned_rules_by_roles
@assigned_rules_by_roles ||= AccessPolicy.new(self).assigned_rules_by_roles
end
AccessPolicy = Struct.new(:user) do
def assigned_rules_by_roles # set of authorization rules (assigned by user roles)
Set.new.tap do |set|
set << { controller: "sessions", actions: :any }
set << { controller: "enroll", actions: :any }
set << { controller: "home", actions: :any }
if @user.super_admin?
set << { controller_group: :any, actions: :any }
end
if @user.admin?
set << { controller_group: "admin/", actions: :any}
end
if @user.auditor?
set << { controller_group: "admin/", actions: %w(index show) }
end
if @user.tech?
set << { controller_group: "admin/", actions: %w(index show) }
set << { controller: "admin/settings", actions: %w(edit update) }
set << { controller: "admin/devices", actions: %w(push edit_note update_note) }
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.