167 lines
5.4 KiB
Ruby
167 lines
5.4 KiB
Ruby
module Dunlop::Ability
|
|
extend ActiveSupport::Concern
|
|
include CanCan::Ability
|
|
|
|
included do
|
|
attr_reader :user
|
|
@allowed_authorization_classes ||= []
|
|
@subject_aliases = {}
|
|
end
|
|
|
|
def self.has_permission_of_type(type, user)
|
|
return true if user.admin?
|
|
user.role_names.any?{|role| role =~ /^\w+-#{type}-/}
|
|
end
|
|
|
|
def self.any_permission_starts_with(expression, user)
|
|
return true if user.admin?
|
|
expression = "(#{expression.join('|')})" if expression.is_a?(Array)
|
|
user.role_names.any?{|role| role =~ /^\w+-\w+-#{expression}/ }
|
|
end
|
|
|
|
def initialize(user)
|
|
return unless user.present?
|
|
@user = user
|
|
setup_abilities
|
|
end
|
|
|
|
def has_permission_of_type(type)
|
|
::Dunlop::Ability.has_permission_of_type(type, user)
|
|
end
|
|
|
|
def any_permission_starts_with(expression)
|
|
::Dunlop::Ability.any_permission_starts_with(expression, user)
|
|
end
|
|
|
|
def setup_dunlop_abilities
|
|
if user.admin?
|
|
can :manage, :all
|
|
# Extra permissions, not assigned
|
|
# can :backtrace, Dunlop::LogEntry
|
|
# can :report, :index
|
|
# can :report, :system
|
|
else
|
|
setup_dunlop_class_based_abilities
|
|
setup_subject_aliases
|
|
can :do, :shit if rules.empty? # cancancan 1.15.0 bug, cannot handle empty rules
|
|
end
|
|
end
|
|
|
|
def user_roles
|
|
@user_roles ||= all_roles.select(&:user_role?)
|
|
end
|
|
|
|
def admin_roles
|
|
@admin_roles ||= all_roles.select(&:admin_role?)
|
|
end
|
|
|
|
def all_roles
|
|
return @all_roles if @all_roles
|
|
roles = []
|
|
user.role_names.each do |role_name|
|
|
roles.push get_role_for_role_name(role_name)
|
|
end
|
|
user.role_names_admin.each do |role_name|
|
|
roles.push get_role_for_role_name(role_name, role: ::Dunlop::Ability::AdminRole, verify_class: false)
|
|
end
|
|
@all_roles = roles.compact
|
|
end
|
|
|
|
def get_role_for_role_name(role_name, role: ::Dunlop::Ability::UserRole, verify_class: true)
|
|
return unless role_name =~ /\w+-\w+-[\w:\/-]+/
|
|
ability, type, class_name = role_name.split('-', 3)
|
|
case type
|
|
when 'app', 'adapter', 'engine'
|
|
type = 'class'
|
|
class_name = "#{class_name.camelize}::Engine"
|
|
when 'model'
|
|
type = 'class'
|
|
class_name = class_name.gsub('-', '_').camelize
|
|
end
|
|
return if verify_class and not self.class.allowed_authorization_classes.include?(class_name)
|
|
role.new(ability, type, class_name)
|
|
end
|
|
|
|
def all_class_roles
|
|
@all_class_roles ||= all_roles.select(&:class_role?)
|
|
end
|
|
|
|
def setup_dunlop_class_based_abilities
|
|
all_class_roles.each do |role|
|
|
can role.ability, role.target_class
|
|
end
|
|
|
|
# One usecase is to build a topmenu having the right menu structure based on the permissions. If there
|
|
# is a permission on class A::B::C::D then there will be no permission on the A class, but I still have
|
|
# to show the A toplevel menu, otherwise the user never arrives at the A::B::C::D class to perform it's
|
|
# action through the UI. The current solution is to set an :index ability on the top level class A, so
|
|
# in the _navigation_links.html.slim you can use
|
|
# can? :index, A
|
|
# or even:
|
|
# can? :index, A::B
|
|
# since a top level ability gives it to all subclasses (CanCanCan)
|
|
all_class_roles.map{|role| role.class_name.split('::').first }.uniq.each do |top_level_class_name|
|
|
can :index, top_level_class_name.constantize
|
|
end
|
|
end
|
|
|
|
# This method should be implemented in the application app/models/ability.rb to implement custom authorization logic
|
|
def setup_abilities
|
|
setup_dunlop_abilities
|
|
end
|
|
|
|
def alternative_subjects(subject)
|
|
result = super
|
|
if subject.is_a?(Class) and subject_alias = self.class.subject_aliases.find{ |alias_from, alias_target| subject.ancestors.include?(alias_from) }.try(:last) # target
|
|
result << subject_alias unless result.include?(subject_alias)
|
|
end
|
|
result
|
|
end
|
|
|
|
# Add the aliases to the rule's subjects
|
|
def setup_subject_aliases
|
|
rules.each do |rule|
|
|
self.class.subject_aliases.each do |alias_from, alias_target|
|
|
rule.subjects << alias_from if rule.subjects.include?(alias_target) and not rule.subjects.include?(alias_from)
|
|
end
|
|
end
|
|
end
|
|
|
|
module ClassMethods
|
|
|
|
def alias_subject(*subjects)
|
|
options = subjects.extract_options!
|
|
subject_to = options[:to] or raise "alias_subject has no: ', to: TakePermissionsFrom' statement"
|
|
subjects.each do |subject|
|
|
subject_aliases[subject] = subject_to # shape of hash is to quick find the subject, subject_to less important
|
|
end
|
|
end
|
|
|
|
# This method is a default implementation and meant to be overridden in de app/models/ability.rb class
|
|
# for custom behaviour
|
|
def allowed_authorization_classes
|
|
@allowed_authorization_classes
|
|
end
|
|
|
|
def add_allowed_authorization_classes(classes)
|
|
class_names = Array.wrap(classes).map{|c| c.is_a?(String) ? c : (c.try(:name) || c.to_s)}
|
|
@allowed_authorization_classes |= class_names
|
|
end
|
|
|
|
def subject_aliases
|
|
@subject_aliases
|
|
end
|
|
|
|
def add_dunlop_allowed_authorization_classes!
|
|
result = ['Dunlop::Engine']
|
|
if ::Dunlop.has_workflow?
|
|
result += ['WorkflowInstance', 'WorkflowInstanceBatch']
|
|
result += WorkflowInstance.possible_workflow_step_names.map{|name| name.to_s.classify }
|
|
end
|
|
result += SourceFile.classes.map(&:name) if ::Dunlop.has_source_files?
|
|
result += TargetFile.classes.map(&:name) if ::Dunlop.has_target_files?
|
|
@allowed_authorization_classes |= result
|
|
end
|
|
end
|
|
end
|