module DeclarativePolicy

DeclarativePolicy: A DSL based authorization framework

Default policy definition for nil values

Constants

CLASS_CACHE_IVAR
CLASS_CACHE_MUTEX
VERSION

Public Class Methods

class_for(subject) click to toggle source
# File lib/declarative_policy.rb, line 40
def class_for(subject)
  return configuration.nil_policy if subject.nil?
  return configuration.named_policy(subject) if subject.is_a?(Symbol)

  subject = find_delegate(subject)

  policy_class = class_for_class(subject.class)
  raise "no policy for #{subject.class.name}" if policy_class.nil?

  policy_class
end
configure(&block) click to toggle source
# File lib/declarative_policy.rb, line 52
def configure(&block)
  configuration.instance_eval(&block)

  nil
end
configure!(&block) click to toggle source

Reset configuration

# File lib/declarative_policy.rb, line 59
def configure!(&block)
  @configuration = DeclarativePolicy::Configuration.new
  configure(&block) if block
end
has_policy?(subject)
Alias for: policy?
policy?(subject) click to toggle source
# File lib/declarative_policy.rb, line 64
def policy?(subject)
  !class_for_class(subject.class).nil?
end
Also aliased as: has_policy?
policy_for(user, subject, opts = {}) click to toggle source
# File lib/declarative_policy.rb, line 27
def policy_for(user, subject, opts = {})
  cache = opts[:cache] || {}
  key = Cache.policy_key(user, subject)

  cache[key] ||=
    # to avoid deadlocks in multi-threaded environment when
    # autoloading is enabled, we allow concurrent loads,
    # https://gitlab.com/gitlab-org/gitlab-foss/issues/48263
    ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
      class_for(subject).new(user, subject, opts)
    end
end

Private Class Methods

class_for_class(subject_class) click to toggle source

This method is heavily cached because there are a lot of anonymous modules in play in a typical rails app, and name performs quite slowly for anonymous classes and modules.

See bugs.ruby-lang.org/issues/11119

if the above bug is resolved, this caching could likely be removed.

# File lib/declarative_policy.rb, line 82
def class_for_class(subject_class)
  unless subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)
    CLASS_CACHE_MUTEX.synchronize do
      # re-check in case of a race
      break if subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)

      policy_class = compute_class_for_class(subject_class)
      subject_class.instance_variable_set(CLASS_CACHE_IVAR, policy_class)
    end
  end

  subject_class.instance_variable_get(CLASS_CACHE_IVAR)
end
compute_class_for_class(subject_class) click to toggle source
# File lib/declarative_policy.rb, line 96
def compute_class_for_class(subject_class)
  return subject_class.declarative_policy_class.constantize if subject_class.respond_to?(:declarative_policy_class)

  subject_class.ancestors.each do |klass|
    name = klass.name
    klass = policy_class(name)

    return klass if klass
  end

  nil
end
configuration() click to toggle source
# File lib/declarative_policy.rb, line 71
def configuration
  @configuration ||= DeclarativePolicy::Configuration.new
end
find_delegate(subject) click to toggle source
# File lib/declarative_policy.rb, line 115
def find_delegate(subject)
  seen = Set.new

  while subject.respond_to?(:declarative_policy_delegate)
    raise ArgumentError, 'circular delegations' if seen.include?(subject.object_id)

    seen << subject.object_id
    subject = subject.declarative_policy_delegate
  end

  subject
end
policy_class(name) click to toggle source
# File lib/declarative_policy.rb, line 109
def policy_class(name)
  clazz = configuration.policy_class(name)

  clazz if clazz && clazz < Base
end