module Walruz # # One of the cores of the framework, it's purpuse is to encapsulate # some authorization logic with a given actor and subject. # It's main method `authorized?(actor, subject)` verifies that # an actor is actually authorized to manage the subject. # class Policy extend Walruz::Utils # @private # :nodoc: def self.inherited(child) @policies ||= {} unless child.policy_label.nil? @policies[child.policy_label] = child end end # # See +Walruz.policies+. # def self.policies @policies || {} end # Creates a new policy class based on an existing one, this method is used # when you want to reuse a Policy class for a subject association # # See the "Policy Combinators" section on the README for more info # def self.for_subject(key, &block) # :nodoc: clazz = Class.new(Walruz::Policy) do extend Walruz::Utils::PolicyCompositionHelper # :nodoc: def authorized?(actor, subject) 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 # Params: # - actor: The actor who checks if it is authorized # # Returns: (subject -> [Bool, Hash]) # # Example: # subjects.filter(&PolicyXYZ.with_actor(some_user)) # 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 # # Example: # class FriendEditProfilePolicy # depends_on FriendPolicy # # def authorized?(actor, subject) # 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 # @private # :nodoc: def self.policy_dependencies=(dependencies) @_policy_dependencies = dependencies end # Utility for depends_on macro # @private # :nodoc: def self.policy_dependencies @_policy_dependencies end # Utility for depends_on macro # @private # :nodoc: def self.return_policy if policy_dependencies.nil? self else all(*policy_dependencies) end end # Utility method (copied from ActiveSupport) # @private # :nodoc: def self.underscore(camel_cased_word) 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 # Params: # - actor: The object who checks if it is authorized # - subject: The object that is going to be accesed # # Returns: [Bool, Hash] # 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. # # Returns: # 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 ||= (self.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) @policy_label = label end # @private # :nodoc: def safe_authorized?(actor, subject) result = Array(authorized?(actor, subject)) result[1] ||= {} result[1][self.class.policy_keyword] = result[0] unless self.class.policy_keyword.nil? result end # @private # :nodoc: def set_params(params) @params = params self end def params @params ||= {} end end end