tektite-software/authoreyes

View on GitHub
lib/authoreyes/authorization/attribute.rb

Summary

Maintainability
D
1 day
Test Coverage
module Authoreyes
  module Authorization
    class Attribute
      # attr_conditions_hash of form
      # { :object_attribute => [operator, value_block], ... }
      # { :object_attribute => { :attr => ... } }
      def initialize(conditions_hash)
        @conditions_hash = conditions_hash
      end

      def initialize_copy(_from)
        @conditions_hash = deep_hash_clone(@conditions_hash)
      end

      def validate?(attr_validator, object = nil, hash = nil)
        object ||= attr_validator.object
        return false unless object

        if Authorization.is_a_association_proxy?(object) &&
           object.respond_to?(:empty?)
          return false if object.empty?
          object.each do |member|
            return true if validate?(attr_validator, member, hash)
          end
          return false
        end

        (hash || @conditions_hash).all? do |attr, value|
          attr_value = object_attribute_value(object, attr)
          if value.is_a?(Hash)
            if attr_value.is_a?(Enumerable)
              attr_value.any? do |inner_value|
                validate?(attr_validator, inner_value, value)
              end
            elsif attr_value.nil?
              raise NilAttributeValueError, "Attribute #{attr.inspect} is nil in #{object.inspect}."
            else
              validate?(attr_validator, attr_value, value)
            end
          elsif value.is_a?(Array) && value.length == 2 && value.first.is_a?(Symbol)
            evaluated = if value[1].is_a?(Proc)
                          attr_validator.evaluate(value[1])
                        else
                          value[1]
                        end
            case value[0]
            when :is
              attr_value == evaluated
            when :is_not
              attr_value != evaluated
            when :contains
              begin
                attr_value.include?(evaluated)
              rescue NoMethodError => e
                raise AuthorizationUsageError, 'Operator contains requires a ' \
                                               "subclass of Enumerable as attribute value, got: #{attr_value.inspect} " \
                                               "contains #{evaluated.inspect}: #{e}"
              end
            when :does_not_contain
              begin
                !attr_value.include?(evaluated)
              rescue NoMethodError => e
                raise AuthorizationUsageError, 'Operator does_not_contain requires a ' \
                                               "subclass of Enumerable as attribute value, got: #{attr_value.inspect} " \
                                               "does_not_contain #{evaluated.inspect}: #{e}"
              end
            when :intersects_with
              begin
                !(evaluated.to_set & attr_value.to_set).empty?
              rescue NoMethodError => e
                raise AuthorizationUsageError, 'Operator intersects_with requires ' \
                                               "subclasses of Enumerable, got: #{attr_value.inspect} " \
                                               "intersects_with #{evaluated.inspect}: #{e}"
              end
            when :is_in
              begin
                evaluated.include?(attr_value)
              rescue NoMethodError => e
                raise AuthorizationUsageError, 'Operator is_in requires a ' \
                                               "subclass of Enumerable as value, got: #{attr_value.inspect} " \
                                               "is_in #{evaluated.inspect}: #{e}"
              end
            when :is_not_in
              begin
                !evaluated.include?(attr_value)
              rescue NoMethodError => e
                raise AuthorizationUsageError, 'Operator is_not_in requires a ' \
                                               "subclass of Enumerable as value, got: #{attr_value.inspect} " \
                                               "is_not_in #{evaluated.inspect}: #{e}"
              end
            when :lt
              attr_value && attr_value < evaluated
            when :lte
              attr_value && attr_value <= evaluated
            when :gt
              attr_value && attr_value > evaluated
            when :gte
              attr_value && attr_value >= evaluated
            else
              raise AuthorizationError, "Unknown operator #{value[0]}"
            end
          else
            raise AuthorizationError, 'Wrong conditions hash format'
          end
        end
      end

      # resolves all the values in condition_hash
      def obligation(attr_validator, hash = nil)
        hash = (hash || @conditions_hash).clone
        hash.each do |attr, value|
          if value.is_a?(Hash)
            hash[attr] = obligation(attr_validator, value)
          elsif value.is_a?(Array) && value.length == 2
            hash[attr] = [value[0], attr_validator.evaluate(value[1])]
          else
            raise AuthorizationError, 'Wrong conditions hash format'
          end
        end
        hash
      end

      def to_long_s(hash = nil)
        if hash
          hash.inject({}) do |memo, key_val|
            key, val = key_val
            memo[key] = case val
                        when Array then "#{val[0]} { #{val[1].respond_to?(:to_ruby) ? val[1].to_ruby.gsub(/^proc \{\n?(.*)\n?\}$/m, '\1') : '...'} }"
                        when Hash then to_long_s(val)
                        end
            memo
          end
        else
          "if_attribute #{to_long_s(@conditions_hash).inspect}"
        end
      end

      protected

      def object_attribute_value(object, attr)
        object.send(attr)
      rescue ArgumentError, NoMethodError => e
        raise AuthorizationUsageError, "Error occurred while validating attribute ##{attr} on #{object.inspect}: #{e}.\n" \
                                       "Please check your authorization rules and ensure the attribute is correctly spelled and \n" \
                                       'corresponds to a method on the model you are authorizing for.'
      end

      def deep_hash_clone(hash)
        hash.inject({}) do |memo, (key, val)|
          memo[key] = case val
                      when Hash
                        deep_hash_clone(val)
                      when NilClass, Symbol
                        val
                      else
                        val.clone
                      end
          memo
        end
      end
    end
  end
end