Class: Authorization::Engine

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/declarative_authorization/authorization.rb

Overview

Authorization::Engine implements the reference monitor. It may be used for querying the permission and retrieving obligations under which a certain privilege is granted for the current user.

Defined Under Namespace

Classes: AttributeValidator

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(reader = nil) ⇒ Engine

If reader is not given, a new one is created with the default authorization configuration of AUTH_DSL_FILES. If given, may be either a Reader object or a path to a configuration file.



112
113
114
115
# File 'lib/declarative_authorization/authorization.rb', line 112

def initialize(reader = nil)
  #@auth_rules = AuthorizationRuleSet.new reader.auth_rules_reader.auth_rules
  @reader = Reader::DSLReader.factory(reader || AUTH_DSL_FILES)
end

Instance Attribute Details

#readerObject (readonly)

Returns the value of attribute reader.



103
104
105
# File 'lib/declarative_authorization/authorization.rb', line 103

def reader
  @reader
end

Class Method Details

.development_reload?Boolean

Returns:

  • (Boolean)


331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/declarative_authorization/authorization.rb', line 331

def self.development_reload?
  if Rails.env.development?
    mod_time = AUTH_DSL_FILES.map do |m|
                begin
                  File.mtime(m)
                rescue
                  Time.at(0)
                end
              end.flatten.max
    @@auth_dsl_last_modified ||= mod_time
    if mod_time > @@auth_dsl_last_modified
      @@auth_dsl_last_modified = mod_time
      return true
    end
  end
end

.instance(dsl_file = nil) ⇒ Object

Returns an instance of Engine, which is created if there isn’t one yet. If dsl_file is given, it is passed on to Engine.new and a new instance is always created.



351
352
353
354
355
356
357
# File 'lib/declarative_authorization/authorization.rb', line 351

def self.instance(dsl_file = nil)
  if dsl_file or development_reload?
    @@instance = new(dsl_file)
  else
    @@instance ||= new
  end
end

Instance Method Details

#description_for(role) ⇒ Object

Returns the description for the given role. The description may be specified with the authorization rules. Returns nil if none was given.



297
298
299
# File 'lib/declarative_authorization/authorization.rb', line 297

def description_for(role)
  role_descriptions[role]
end

#initialize_copy(from) ⇒ Object

:nodoc:



117
118
119
# File 'lib/declarative_authorization/authorization.rb', line 117

def initialize_copy(from) # :nodoc:
  @reader = from.reader.clone
end

#obligations(privilege, options = {}) ⇒ Object

Returns the obligations to be met by the current user for the given privilege as an array of obligation hashes in form of

[{:object_attribute => obligation_value, ...}, ...]

where obligation_value is either (recursively) another obligation hash or a value spec, such as

[operator, literal_value]

The obligation hashes in the array should be OR’ed, conditions inside the hashes AND’ed.

Example

{:branch => {:company => [:is, 24]}, :active => [:is, true]}

Options

:context

See permit!

:user

See permit!



280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/declarative_authorization/authorization.rb', line 280

def obligations(privilege, options = {})
  options = {:context => nil}.merge(options)
  user, roles, privileges = user_roles_privleges_from_options(privilege, options)

  permit!(privilege, :skip_attribute_test => true, :user => user, :context => options[:context])

  return [] if roles.is_a?(Hash) && !(roles.keys & omnipotent_roles).empty?

  attr_validator = AttributeValidator.new(self, user, nil, privilege, options[:context])
  matching_auth_rules(roles, privileges, options[:context]).collect do |rule|
    rule.obligations(attr_validator)
  end.flatten
end

#permit!(privilege, options = {}) ⇒ Object

Returns true if privilege is met by the current user. Raises AuthorizationError otherwise. privilege may be given with or without context. In the latter case, the :context option is required.

Options:

:context

The context part of the privilege. Defaults either to the tableized class_name of the given :object, if given. That is, :users for :object of type User. Raises AuthorizationUsageError if context is missing and not to be inferred.

:object

An context object to test attribute checks against.

:skip_attribute_test

Skips those attribute checks in the authorization rules. Defaults to false.

:user

The user to check the authorization for. Defaults to Authorization#current_user.

:bang

Should NotAuthorized exceptions be raised Defaults to true.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/declarative_authorization/authorization.rb', line 170

def permit!(privilege, options = {})
  return true if Authorization.ignore_access_control
  options = {
    :object => nil,
    :skip_attribute_test => false,
    :context => nil,
    :bang => true
  }.merge(options)

  # Make sure we're handling all privileges as symbols.
  privilege = privilege.is_a?( Array ) ?
              privilege.flatten.collect { |priv| priv.to_sym } :
              privilege.to_sym

  #
  # If the object responds to :proxy_reflection, we're probably working with
  # an association proxy.  Use 'new' to leverage ActiveRecord's builder
  # functionality to obtain an object against which we can check permissions.
  #
  # Example: permit!( :edit, :object => user.posts )
  #
  if Authorization.is_a_association_proxy?(options[:object]) && options[:object].respond_to?(:new)
    options[:object] = options[:object].where(nil).new
  end

  options[:context] ||= options[:object] && (
    options[:object].class.respond_to?(:decl_auth_context) ?
        options[:object].class.decl_auth_context :
        options[:object].class.name.tableize.to_sym
  ) rescue NoMethodError

  user, roles, privileges = user_roles_privleges_from_options(privilege, options)

  callback = Rails.application.config.try(:ae_declarative_authorization_permit_callback)
  callback.call(controller: options[:controller], privilege: privilege) if callback && options.include?(:controller)

  return true if roles.is_a?(Hash) && !(roles.keys & omnipotent_roles).empty?

  # find a authorization rule that matches for at least one of the roles and
  # at least one of the given privileges
  attr_validator = AttributeValidator.new(self, user, options[:object], privilege, options[:context])
  rules = matching_auth_rules(roles, privileges, options[:context])

  # Test each rule in turn to see whether any one of them is satisfied.
  rules.each do |rule|
    return true if rule.validate?(attr_validator, options[:skip_attribute_test])
  end

  if options[:bang]
    # Call authorization_denied_callback if configured
    if Authorization.config.authorization_denied_callback
      action = if options[:controller]&.respond_to?(:action_name)
                 options[:controller].action_name
               elsif options[:controller]&.respond_to?(:route) # Grape API
                 options[:controller].route&.request_method
               end

      referer_url = options[:controller]&.respond_to?(:request) ? options[:controller].request&.referer : nil
      referer_path = referer_url ? (URI.parse(referer_url).path rescue nil) : nil

      Authorization.config.authorization_denied_callback.call(
        {
          action: action,
          path: options[:controller]&.respond_to?(:request) ? options[:controller].request&.path : nil,
          context: options[:context].to_s,
          attribute_check_denial: !rules.empty?,
          referer: referer_path
        }
      )
    end

    if rules.empty?
      raise NotAuthorized, "No matching rules found for #{privilege} for User with id #{user.try(:id)} " +
        "(roles #{roles.inspect}, privileges #{privileges.inspect}, " +
        "context #{options[:context].inspect})."
    else
      raise AttributeAuthorizationError, "#{privilege} not allowed for User with id #{user.try(:id)} on #{(options[:object] || options[:context]).inspect}."
    end
  else
    false
  end
end

#permit?(privilege, options = {}) ⇒ Boolean

Calls permit! but doesn’t raise authorization errors. If no exception is raised, permit? returns true and yields to the optional block.

Returns:

  • (Boolean)


255
256
257
258
259
260
261
262
# File 'lib/declarative_authorization/authorization.rb', line 255

def permit?(privilege, options = {}) # :yields:
  if permit!(privilege, options.merge(:bang=> false))
    yield if block_given?
    true
  else
    false
  end
end

#rev_priv_hierarchyObject

ctx] => [priv, …]



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/declarative_authorization/authorization.rb', line 122

def rev_priv_hierarchy
  if @rev_priv_hierarchy.nil?
    @rev_priv_hierarchy = {}
    privilege_hierarchy.each do |key, value|
      value.each do |val|
        @rev_priv_hierarchy[val] ||= []
        @rev_priv_hierarchy[val] << key
      end
    end
  end
  @rev_priv_hierarchy
end

#rev_role_hierarchyObject

ctx] => [priv, …]



136
137
138
139
140
141
142
143
144
145
146
# File 'lib/declarative_authorization/authorization.rb', line 136

def rev_role_hierarchy
  if @rev_role_hierarchy.nil?
    @rev_role_hierarchy = {}
    role_hierarchy.each do |higher_role, lower_roles|
      lower_roles.each do |role|
        (@rev_role_hierarchy[role] ||= []) << higher_role
      end
    end
  end
  @rev_role_hierarchy
end

#roles_for(user) ⇒ Object

Returns the role symbols of the given user.



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/declarative_authorization/authorization.rb', line 309

def roles_for(user)
  user ||= Authorization.current_user
  raise AuthorizationUsageError, "User object doesn't respond to roles (#{user.try(:id)})" \
    if !user.respond_to?(:role_symbols) and !user.respond_to?(:roles)

  Rails.logger.info("The use of user.roles is deprecated.  Please add a method " +
      "role_symbols to your User model.") if defined?(Rails) and Rails.respond_to?(:logger) and !user.respond_to?(:role_symbols)

  roles = user.respond_to?(:role_symbols) ? user.role_symbols : user.roles

  raise AuthorizationUsageError, "User.#{user.respond_to?(:role_symbols) ? 'role_symbols' : 'roles'} " +
    "doesn't return an Array of Symbols (#{roles.inspect})" \
        if !roles.is_a?(Array) or (!roles.empty? and !roles[0].is_a?(Symbol))

  (roles.empty? ? [Authorization.default_role] : roles)
end

#roles_with_hierarchy_for(user) ⇒ Object

Returns the role symbols and inherritted role symbols for the given user



327
328
329
# File 'lib/declarative_authorization/authorization.rb', line 327

def roles_with_hierarchy_for(user)
  flatten_roles(roles_for(user))
end

#title_for(role) ⇒ Object

Returns the title for the given role. The title may be specified with the authorization rules. Returns nil if none was given.



304
305
306
# File 'lib/declarative_authorization/authorization.rb', line 304

def title_for(role)
  role_titles[role]
end