Module: LucidPolicy::Mixin

Defined in:
lib/isomorfeus_policy/lucid_policy/mixin.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/isomorfeus_policy/lucid_policy/mixin.rb', line 3

def self.included(base)
  base.instance_exec do
    if RUBY_ENGINE != 'opal'
      Isomorfeus.add_valid_policy_class(base) unless base == LucidPolicy::Base
    end

    def authorization_rules
      @authorization_rules ||= { rules: {}.dup, policies: {}.dup, others: :deny }.dup
    end

    def all
      :all
    end

    def allow(*classes_and_methods_and_options)
      _allow_or_deny(:allow, *classes_and_methods_and_options)
    end

    def deny(*classes_and_methods_and_options)
      _allow_or_deny(:deny, *classes_and_methods_and_options)
    end

    def others
      :others
    end

    def rule(*classes_and_methods, &block)
      _allow_or_deny(:rule, *classes_and_methods, &block)
    end

    def combine_with(policy_class, **options)
      authorization_rules[:policies] = { policy_class => options }
    end

    def _allow_or_deny(thing, *classes_methods_options, &block)
      rules = authorization_rules

      if %i[allow deny].include?(thing) && %i[others all].include?(classes_methods_options.first)
        rules[:others] = thing
        return
      end

      target_classes = []
      target_methods = []
      target_options = {}

      classes_methods_options.each do |class_method_option|
        if class_method_option.class == Hash
          target_options = class_method_option
        else
          class_or_method_s = class_method_option.to_s
          if class_method_option.class == Symbol && class_or_method_s[0].downcase == class_or_method_s[0]
            target_methods << class_method_option
          else
            class_method_option = class_or_method_s unless class_method_option.class == String
            target_classes << class_method_option
          end
        end
      end

      thing_or_block = block_given? ? block : thing

      target_classes.each do |target_class|
        target_class = target_class.split('>::').last if target_class.start_with?('#<')
        rules[:rules][target_class] = {} unless rules[:rules].key?(target_class)

        if target_methods.empty?
          rules[:rules][target_class][:rule] = thing_or_block
          rules[:rules][target_class][:options] = target_options unless target_options.empty?
        else
          rules[:rules][target_class][:rule] = :deny unless rules[:rules][target_class].key?(:rule)
          rules[:rules][target_class][:methods] = {} unless rules[:rules][target_class].key?(:methods)
          target_methods.each do |target_method|
            rules[:rules][target_class][:methods][target_method] = { rule: thing_or_block }
            rules[:rules][target_class][:methods][target_method][:options] = target_options unless target_options.empty?
          end
        end
      end
    end
  end

  attr_reader :reason

  def initialize(object, record_reason = nil)
    @object = object
    @reason = nil
    @record_reason = record_reason
    if @record_reason
      @class_name = self.class.name
      @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
    end
  end

  def authorized?(target_class, target_method = nil, props = nil)
    Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{self}: At least the class or class name must be given!") unless target_class

    target_class = target_class.to_s unless target_class.class == String
    target_class = target_class.split('>::').last if target_class.start_with?('#<')

    rules  =  self.class.authorization_rules

    result =  if rules[:rules].key?(target_class)
                if target_method && rules[:rules][target_class].key?(:methods) && rules[:rules][target_class][:methods].key?(target_method)
                  options = rules[:rules][target_class][:methods][target_method][:options]
                  rule = rules[:rules][target_class][:methods][target_method][:rule]
                  @reason = { policy_class: @class_name, class_name: target_class, method: target_method, rule: rule } if @record_reason
                else
                  options = rules[:rules][target_class][:options]
                  rule = rules[:rules][target_class][:rule]
                  @reason = { policy_class: @class_name, class_name: target_class, rule: rule } if @record_reason
                end

                if rule.class == Symbol || rule.class == String
                  if options
                    condition, method_result = _get_condition_and_result(options)
                    if @record_reason
                      @reason[:condition] = condition
                      @reason[:condition_result] = method_result
                    end
                    rule if (condition == :if && method_result == true) || (condition == :if_not && method_result == false)
                  else
                    rule
                  end
                else
                  props = LucidProps.new(props) unless props.class == LucidProps
                  policy_helper = LucidPolicy::Helper.new
                  policy_helper.instance_exec(@object, target_class, target_method, props, &rule)
                  r = policy_helper.result
                  @reason[:rule_result] = r if @record_reason
                  r
                end
              else
                r = rules[:others]
                @reason = { policy_class: @class_name, class_name: target_class, others: r } if @record_reason
                r
              end

    return true if result == :allow

    rules[:policies].each do |policy_class, options|
      combined_policy_result = nil
      if options.empty?
        policy_instance = policy_class.new(@object, @record_reason)
        combined_policy_result = policy_instance.authorized?(target_class, target_method, props)
        @reason = @reason = { policy_class: @class_name, combined: policy_instance.reason } if @record_reason
      else
        condition, method_result = _get_condition_and_result(options)
        if (condition == :if && method_result == true) || (condition == :if_not && method_result == false)
          policy_instance = policy_class.new(@object, @record_reason)
          combined_policy_result = policy_instance.authorized?(target_class, target_method, props)
          @reason = { policy_class: @class_name, combined: policy_instance.reason, condition: condition, condition_result: method_result } if @record_reason
        end
      end
      return true if combined_policy_result == true
    end

    result == :allow ? true : false
  end

  def authorized!(target_class, target_method = nil, props = nil)
    result = authorized?(target_class, target_method, props)
    reason_message = reason ? ", reason: #{reason}" : ''
    return true if result
    Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{@object}: not authorized to call #{target_class}#{}#{target_method} #{props} #{reason_message}!")
  end

  def _get_condition_and_result(options)
    condition = nil
    method_name_or_block = if options.key?(:if)
                             condition = :if
                             options[:if]
                           elsif options.key?(:if_not)
                             condition = :if_not
                             options[:if_not]
                           elsif options.key?(:unless)
                             condition = :if_not
                             options[:unless]
                           end
    method_result = if method_name_or_block && method_name_or_block.class == Symbol
                      @object.__send__(method_name_or_block)
                    else
                      props = LucidProps.new(props) unless props.class == LucidProps
                      method_name_or_block.call(@object, props)
                    end
    [condition, method_result]
  end
end

Instance Method Details

#_get_condition_and_result(options) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/isomorfeus_policy/lucid_policy/mixin.rb', line 169

def _get_condition_and_result(options)
  condition = nil
  method_name_or_block = if options.key?(:if)
                           condition = :if
                           options[:if]
                         elsif options.key?(:if_not)
                           condition = :if_not
                           options[:if_not]
                         elsif options.key?(:unless)
                           condition = :if_not
                           options[:unless]
                         end
  method_result = if method_name_or_block && method_name_or_block.class == Symbol
                    @object.__send__(method_name_or_block)
                  else
                    props = LucidProps.new(props) unless props.class == LucidProps
                    method_name_or_block.call(@object, props)
                  end
  [condition, method_result]
end

#authorized!(target_class, target_method = nil, props = nil) ⇒ Object



162
163
164
165
166
167
# File 'lib/isomorfeus_policy/lucid_policy/mixin.rb', line 162

def authorized!(target_class, target_method = nil, props = nil)
  result = authorized?(target_class, target_method, props)
  reason_message = reason ? ", reason: #{reason}" : ''
  return true if result
  Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{@object}: not authorized to call #{target_class}#{}#{target_method} #{props} #{reason_message}!")
end

#authorized?(target_class, target_method = nil, props = nil) ⇒ Boolean

Returns:

  • (Boolean)


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
# File 'lib/isomorfeus_policy/lucid_policy/mixin.rb', line 96

def authorized?(target_class, target_method = nil, props = nil)
  Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{self}: At least the class or class name must be given!") unless target_class

  target_class = target_class.to_s unless target_class.class == String
  target_class = target_class.split('>::').last if target_class.start_with?('#<')

  rules  =  self.class.authorization_rules

  result =  if rules[:rules].key?(target_class)
              if target_method && rules[:rules][target_class].key?(:methods) && rules[:rules][target_class][:methods].key?(target_method)
                options = rules[:rules][target_class][:methods][target_method][:options]
                rule = rules[:rules][target_class][:methods][target_method][:rule]
                @reason = { policy_class: @class_name, class_name: target_class, method: target_method, rule: rule } if @record_reason
              else
                options = rules[:rules][target_class][:options]
                rule = rules[:rules][target_class][:rule]
                @reason = { policy_class: @class_name, class_name: target_class, rule: rule } if @record_reason
              end

              if rule.class == Symbol || rule.class == String
                if options
                  condition, method_result = _get_condition_and_result(options)
                  if @record_reason
                    @reason[:condition] = condition
                    @reason[:condition_result] = method_result
                  end
                  rule if (condition == :if && method_result == true) || (condition == :if_not && method_result == false)
                else
                  rule
                end
              else
                props = LucidProps.new(props) unless props.class == LucidProps
                policy_helper = LucidPolicy::Helper.new
                policy_helper.instance_exec(@object, target_class, target_method, props, &rule)
                r = policy_helper.result
                @reason[:rule_result] = r if @record_reason
                r
              end
            else
              r = rules[:others]
              @reason = { policy_class: @class_name, class_name: target_class, others: r } if @record_reason
              r
            end

  return true if result == :allow

  rules[:policies].each do |policy_class, options|
    combined_policy_result = nil
    if options.empty?
      policy_instance = policy_class.new(@object, @record_reason)
      combined_policy_result = policy_instance.authorized?(target_class, target_method, props)
      @reason = @reason = { policy_class: @class_name, combined: policy_instance.reason } if @record_reason
    else
      condition, method_result = _get_condition_and_result(options)
      if (condition == :if && method_result == true) || (condition == :if_not && method_result == false)
        policy_instance = policy_class.new(@object, @record_reason)
        combined_policy_result = policy_instance.authorized?(target_class, target_method, props)
        @reason = { policy_class: @class_name, combined: policy_instance.reason, condition: condition, condition_result: method_result } if @record_reason
      end
    end
    return true if combined_policy_result == true
  end

  result == :allow ? true : false
end

#initialize(object, record_reason = nil) ⇒ Object



86
87
88
89
90
91
92
93
94
# File 'lib/isomorfeus_policy/lucid_policy/mixin.rb', line 86

def initialize(object, record_reason = nil)
  @object = object
  @reason = nil
  @record_reason = record_reason
  if @record_reason
    @class_name = self.class.name
    @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
  end
end