Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

file 174 lines (152 sloc) 6.648 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
module CanCan
  # This class is used internally and should only be called through Ability.
  # it holds the information about a "can" call made on Ability and provides
  # helpful methods to determine permission checking and conditions hash generation.
  class Rule # :nodoc:
    attr_reader :base_behavior, :subjects, :actions, :conditions
    attr_writer :expanded_actions, :expanded_subjects

    # The first argument when initializing is the base_behavior which is a true/false
    # value. True for "can" and false for "cannot". The next two arguments are the action
    # and subject respectively (such as :read, @project). The third argument is a hash
    # of conditions and the last one is the block passed to the "can" call.
    def initialize(base_behavior, action = nil, subject = nil, *extra_args, &block)
      @match_all = action.nil? && subject.nil?
      @base_behavior = base_behavior
      @actions = [action].flatten
      @subjects = [subject].flatten
      @attributes = [extra_args.shift].flatten if extra_args.first.kind_of?(Symbol) || extra_args.first.kind_of?(Array) && extra_args.first.first.kind_of?(Symbol)
      raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if extra_args.first.kind_of?(Hash) && !block.nil?
      @conditions = extra_args.first || {}
      @block = block
    end

    # Matches the subject, action, and given attribute. Conditions are not checked here.
    def relevant?(action, subject, attribute)
      subject = subject.values.first if subject.class == Hash
      @match_all || (matches_action?(action) && matches_subject?(subject) && matches_attribute?(attribute))
    end

    # Matches the block or conditions hash
    def matches_conditions?(action, subject, attribute)
      if @match_all
        call_block_with_all(action, subject, attribute)
      elsif @block && subject_object?(subject)
        @block.arity == 1 ? @block.call(subject) : @block.call(subject, attribute)
      elsif @conditions.kind_of?(Hash) && subject.class == Hash
        nested_subject_matches_conditions?(subject)
      elsif @conditions.kind_of?(Hash) && subject_object?(subject)
        matches_conditions_hash?(subject)
      else
        # Don't stop at "cannot" definitions when there are conditions.
        @conditions.empty? ? true : @base_behavior
      end
    end

    def only_block?
      !conditions? && !@block.nil?
    end

    def only_raw_sql?
      @block.nil? && conditions? && !@conditions.kind_of?(Hash)
    end

    def attributes?
      @attributes.present?
    end

    def conditions?
      @conditions.present?
    end

    def instance_conditions?
      @block || conditions?
    end

    def associations_hash(conditions = @conditions)
      hash = {}
      conditions.map do |name, value|
        hash[name] = associations_hash(value) if value.kind_of? Hash
      end if conditions.kind_of? Hash
      hash
    end

    def attributes_from_conditions
      attributes = {}
      @conditions.each do |key, value|
        attributes[key] = value unless [Array, Range, Hash].include? value.class
      end if @conditions.kind_of? Hash
      attributes
    end

    def specificity
      specificity = 1
      specificity += 1 if attributes? || conditions?
      specificity += 2 unless base_behavior
      specificity
    end

    private

    def subject_object?(subject)
      # klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
      # klass == Class || klass == Module
      !subject.kind_of?(Symbol) && !subject.kind_of?(String)
    end

    def matches_action?(action)
      @expanded_actions.include?(:access) || @expanded_actions.include?(action.to_sym)
    end

    def matches_subject?(subject)
      subject = subject_name(subject) if subject_object? subject
      @expanded_subjects.include?(:all) || @expanded_subjects.include?(subject.to_sym) || @expanded_subjects.include?(subject) # || matches_subject_class?(subject)
    end

    def matches_attribute?(attribute)
      # don't consider attributes in a cannot clause when not matching - this can probably be refactored
      if !@base_behavior && @attributes && attribute.nil?
        false
      else
        @attributes.nil? || attribute.nil? || @attributes.include?(attribute.to_sym)
      end
    end

    # TODO deperecate this
    def matches_subject_class?(subject)
      @expanded_subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
    end

    # Checks if the given subject matches the given conditions hash.
    # This behavior can be overriden by a model adapter by defining two class methods:
    # override_matching_for_conditions?(subject, conditions) and
    # matches_conditions_hash?(subject, conditions)
    def matches_conditions_hash?(subject, conditions = @conditions)
      if conditions.empty?
        true
      else
        if model_adapter(subject).override_conditions_hash_matching? subject, conditions
          model_adapter(subject).matches_conditions_hash? subject, conditions
        else
          conditions.all? do |name, value|
            if model_adapter(subject).override_condition_matching? subject, name, value
              model_adapter(subject).matches_condition? subject, name, value
            else
              attribute = subject.send(name)
              if value.kind_of?(Hash)
                if attribute.kind_of? Array
                  attribute.any? { |element| matches_conditions_hash? element, value }
                else
                  attribute && matches_conditions_hash?(attribute, value)
                end
              elsif value.kind_of?(Array) || value.kind_of?(Range)
                value.include? attribute
              else
                attribute == value
              end
            end
          end
        end
      end
    end

    def nested_subject_matches_conditions?(subject_hash)
      parent, child = subject_hash.shift
      matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
    end

    def call_block_with_all(action, subject, attribute)
      if subject_object? subject
        @block.call(action, subject_name(subject), subject, attribute)
      else
        @block.call(action, subject, nil, attribute)
      end
    end

    def subject_name(subject)
      subject.class.to_s.underscore.pluralize.to_sym
    end

    def model_adapter(subject)
      ModelAdapters::AbstractAdapter.adapter_class(subject_object?(subject) ? subject.class : subject)
    end
  end
end
Something went wrong with that request. Please try again.