Files
dunlop-core/app/services/dunlop/ability.rb
T

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