module Walruz
#
# One of the cores of the framework, its purpose is to encapsulate an authorization logic
# with a given actor and subject. It's main method authorized?(actor, subject)
# verifies that an actor is authorized to manage the subject.
#
class Policy
extend Walruz::Utils
def self.inherited(child) # :nodoc:
@policies ||= {}
unless child.policy_label.nil?
@policies[child.policy_label] = child
end
end
def halt(msg="You are not authorized")
raise PolicyHalted.new(msg)
end
# @see Walruz.policies
def self.policies
@policies || {}
end
#
# Creates a new policy class based on an existing one, you may use this method
# when you want to reuse a Policy class for a subject's association.
#
# @param [Symbol] Name of the association that will be the subject of the policy.
# @return [Walruz::Policy] New generated policy that will pass the association as the subject of the original policy.
#
# @see http://github.com/noomii/walruz See the "Policy Combinators" section on the README for more info and examples.
#
def self.for_subject(key, &block)
clazz = Class.new(Walruz::Policy) do # :nodoc:
extend Walruz::Utils::PolicyCompositionHelper
def authorized?(actor, subject) # :nodoc:
params = self.class.params
new_subject = subject.send(params[:key])
result = self.class.policy.new.safe_authorized?(actor, new_subject)
params[:callback].call(result[0], result[1], actor, subject) if params[:callback]
result
end
end
clazz.policy = self
clazz.set_params(:key => key, :callback => block)
clazz
end
#
# Returns a Proc with a curried actor, making it easier to perform validations of a policy
# in an Array of subjects.
#
# @param [Walruz::Actor] The actor who checks if he is authorized.
# @return [Proc] A proc that receives as a parameter subject and returns what the authorized?
# method returns.
#
# @example
# subjects.select(&PolicyXYZ.with_actor(some_user))
#
# @see Walruz::CoreExt::Array#only_authorized_for
def self.with_actor(actor)
policy_instance = self.new
lambda do |subject|
policy_instance.safe_authorized?(actor, subject)[0]
end
end
#
# Stablish other Policy dependencies, so that they are executed
# before the current one, giving chances to receive the previous
# policies return parameters.
#
# @param [Array] Policies in wich this policy depends on.
# @return self
#
# @example
# class FriendEditProfilePolicy
# depends_on FriendPolicy
#
# def authorized?(actor, subject)
# # The FriendPolicy returns a hash with a key of :friend_relationship
# # this will be available via the params method.
# params[:friend_relationship].can_edit? # for friend metadata
# end
#
# end
#
def self.depends_on(*other_policies)
self.policy_dependencies = (other_policies << self)
end
# Utility for depends_on macro
def self.policy_dependencies=(dependencies) # :nodoc:
@_policy_dependencies = dependencies
end
# Utility for depends_on macro
def self.policy_dependencies # :nodoc:
@_policy_dependencies
end
# Utility for depends_on macro
def self.return_policy # :nodoc:
if policy_dependencies.nil?
self
else
all(*policy_dependencies)
end
end
# Utility method (from ActiveSupport)
def self.underscore(camel_cased_word) # :nodoc:
if camel_cased_word.empty?
camel_cased_word
else
camel_cased_word.to_s.split('::').last.
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end
#
# Verifies if the actor is authorized to interact with the subject
# @param [Walruz::Actor] The object who checks if it is authorized
# @param [Walruz::Subject] The object that is going to be accesed
# @return It may return a Boolean, or an Array with the first position being a boolean and the second
# being a Hash of parameters returned from the policy.
#
#
#
def authorized?(actor, subject)
raise NotImplementedError.new("You need to implement policy")
end
#
# Returns the identifier of the Policy that will be setted on the
# policy params hash once the authorization is executed.
#
# @return [Symbol] By default it will return a symbol with the name of the Policy class in underscore (unless the policy label
# was setted, in that case the policy label will be used) with an '?' appended.
#
def self.policy_keyword
if self.policy_label.nil?
nil
else
:"#{self.policy_label}?"
end
end
#
# Returns the label assigned to the policy
#
def self.policy_label
@policy_label ||= ((name.nil? || name.empty?) ? nil : :"#{self.underscore(self.name)}")
end
#
# Sets the identifier of the Policy for using on the `satisfies?` method
#
# Parameters:
# - label: Symbol that represents the policy
#
def self.set_policy_label(label)
Walruz.policies[label] = Walruz.policies.delete(self.policy_label)
@policy_label = label
end
def safe_authorized?(actor, subject) # :nodoc:
result = Array(authorized?(actor, subject))
result[1] ||= {}
result[1][self.class.policy_keyword] = result[0] unless self.class.policy_keyword.nil?
result
end
def set_params(params) # :nodoc:
@params = params
self
end
def params
@params ||= {}
end
end
end