Class: Authorization::Reader::AuthorizationRulesReader

Inherits:
Object
  • Object
show all
Defined in:
lib/declarative_authorization/reader.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAuthorizationRulesReader

:nodoc:



193
194
195
196
197
198
199
200
201
202
203
# File 'lib/declarative_authorization/reader.rb', line 193

def initialize # :nodoc:
  @current_role = nil
  @current_rule = nil
  @roles = []
  @omnipotent_roles = []
  # higher_role => [lower_roles]
  @role_hierarchy = {}
  @role_titles = {}
  @role_descriptions = {}
  @auth_rules = AuthorizationRuleSet.new
end

Instance Attribute Details

#auth_rulesObject (readonly)

Returns the value of attribute auth_rules.



190
191
192
# File 'lib/declarative_authorization/reader.rb', line 190

def auth_rules
  @auth_rules
end

#omnipotent_rolesObject (readonly)

Returns the value of attribute omnipotent_roles.



190
191
192
# File 'lib/declarative_authorization/reader.rb', line 190

def omnipotent_roles
  @omnipotent_roles
end

#role_descriptionsObject (readonly)

Returns the value of attribute role_descriptions.



190
191
192
# File 'lib/declarative_authorization/reader.rb', line 190

def role_descriptions
  @role_descriptions
end

#role_hierarchyObject (readonly)

Returns the value of attribute role_hierarchy.



190
191
192
# File 'lib/declarative_authorization/reader.rb', line 190

def role_hierarchy
  @role_hierarchy
end

#role_titlesObject (readonly)

Returns the value of attribute role_titles.



190
191
192
# File 'lib/declarative_authorization/reader.rb', line 190

def role_titles
  @role_titles
end

#rolesObject (readonly)

Returns the value of attribute roles.



190
191
192
# File 'lib/declarative_authorization/reader.rb', line 190

def roles
  @roles
end

Instance Method Details

#append_role(role, options = {}) ⇒ Object

:nodoc:



212
213
214
215
216
# File 'lib/declarative_authorization/reader.rb', line 212

def append_role(role, options = {}) # :nodoc:
  @roles << role unless @role_titles.key? role
  @role_titles[role] = options[:title] if options[:title]
  @role_descriptions[role] = options[:description] if options[:description]
end

#contains(&block) ⇒ Object

In an if_attribute statement, contains says that the value has to be part of the collection specified by the if_attribute attribute. For information on the block argument, see if_attribute.



472
473
474
# File 'lib/declarative_authorization/reader.rb', line 472

def contains(&block)
  [:contains, block]
end

#description(text) ⇒ Object

Sets a description for the current role. E.g.

role :admin
  description "To be assigned to administrative personnel"
  has_permission_on ...
end

Raises:



314
315
316
317
# File 'lib/declarative_authorization/reader.rb', line 314

def description(text)
  raise DSLError, "description only allowed in role blocks" if @current_role.nil?
  role_descriptions[@current_role] = text
end

#does_not_contain(&block) ⇒ Object

The negation of contains. Currently, query rewriting is disabled for does_not_contain.



478
479
480
# File 'lib/declarative_authorization/reader.rb', line 478

def does_not_contain(&block)
  [:does_not_contain, block]
end

#gt(&block) ⇒ Object

Greater than



513
514
515
# File 'lib/declarative_authorization/reader.rb', line 513

def gt(&block)
  [:gt, block]
end

#gte(&block) ⇒ Object

Greater than or equal to



518
519
520
# File 'lib/declarative_authorization/reader.rb', line 518

def gte(&block)
  [:gte, block]
end

#has_omnipotenceObject

Removes any permission checks for the current role.

role :admin
  has_omnipotence
end

Raises:



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

def has_omnipotence
  raise DSLError, "has_omnipotence only allowed in role blocks" if @current_role.nil?
  @omnipotent_roles << @current_role
end

#has_permission_on(*args) ⇒ Object

Allows the definition of privileges to be allowed for the current role, either in a has_permission_on block or directly in one call.

role :admin
  has_permission_on :employees, :to => :read
  has_permission_on [:employees, :orders], :to => :read
  has_permission_on :employees do
    to :create
    if_attribute ...
  end
  has_permission_on :employees, :to => :delete do
    if_attribute ...
  end
end

The block form allows to describe restrictions on the permissions using if_attribute. Multiple has_permission_on statements are OR’ed when evaluating the permissions. Also, multiple if_attribute statements in one block are OR’ed if no :join_by option is given (see below). To AND conditions, either set :join_by to :and or place them in one if_attribute statement.

Available options

:to

A symbol or an array of symbols representing the privileges that should be granted in this statement.

:join_by

Join operator to logically connect the constraint statements inside of the has_permission_on block. May be :and or :or. Defaults to :or.

Raises:



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/declarative_authorization/reader.rb', line 276

def has_permission_on(*args)
  options = args.extract_options!
  context = args.flatten
  
  raise DSLError, "has_permission_on only allowed in role blocks" if @current_role.nil?
  options = {:to => [], :join_by => :or}.merge(options)
  
  privs = options[:to] 
  privs = [privs] unless privs.is_a?(Array)
  raise DSLError, "has_permission_on either needs a block or :to option" if !block_given? and privs.empty?

  file, line = file_and_line_number_from_call_stack
  rule = AuthorizationRule.new(@current_role, privs.map(&:to_sym), context, options[:join_by],
             :source_file => file, :source_line => line)
  @auth_rules << rule
  if block_given?
    @current_rule = rule
    yield
    raise DSLError, "has_permission_on block content specifies no privileges" if rule.privileges.empty?
    # TODO ensure?
    @current_rule = nil
  end
end

#id_in_scope(&block) ⇒ Object



522
523
524
# File 'lib/declarative_authorization/reader.rb', line 522

def id_in_scope(&block)
  [:id_in_scope, block]
end

#if_attribute(attr_conditions_hash) ⇒ Object

In a has_permission_on block, if_attribute specifies conditions of dynamic parameters that have to be met for the user to meet the privileges in this block. Conditions are evaluated on the context object. Thus, the following allows CRUD for branch admins only on employees that belong to the same branch as the current user.

role :branch_admin
  has_permission_on :employees do
    to :create, :read, :update, :delete
    if_attribute :branch => is { user.branch }
  end
end

In this case, is is the operator for evaluating the condition. Another operator is contains for collections. In the block supplied to the operator, user specifies the current user for whom the condition is evaluated.

Conditions may be nested:

role :company_admin
  has_permission_on :employees do
    to :create, :read, :update, :delete
    if_attribute :branch => { :company => is {user.branch.company} }
  end
end

has_many and has_many through associations may also be nested. Then, at least one item in the association needs to fulfill the subsequent condition:

if_attribute :company => { :branches => { :manager => { :last_name => is { user.last_name } } }

Beware of possible performance issues when using has_many associations in permitted_to? checks. For

permitted_to? :read, object

a check like

object.company.branches.any? { |branch| branch.manager ... }

will be executed. with_permission_to scopes construct efficient SQL joins, though.

Multiple attributes in one :if_attribute statement are AND’ed. Multiple if_attribute statements are OR’ed if the join operator for the has_permission_on block isn’t explicitly set. Thus, the following would require the current user either to be of the same branch AND the employee to be “changeable_by_coworker”. OR the current user has to be the employee in question.

has_permission_on :employees, :to => :manage do
  if_attribute :branch => is {user.branch}, :changeable_by_coworker => true
  if_attribute :id => is {user.id}
end

The join operator for if_attribute rules can explicitly set to AND, though. See has_permission_on for details.

Arrays and fixed values may be used directly as hash values:

if_attribute :id   => 1
if_attribute :type => "special"
if_attribute :id   => [1,2]

Raises:



396
397
398
399
400
# File 'lib/declarative_authorization/reader.rb', line 396

def if_attribute(attr_conditions_hash)
  raise DSLError, "if_attribute only in has_permission blocks" if @current_rule.nil?
  parse_attribute_conditions_hash!(attr_conditions_hash)
  @current_rule.append_attribute Attribute.new(attr_conditions_hash)
end

#if_permitted_to(privilege, attr_or_hash = nil, options = {}) ⇒ Object

if_permitted_to allows the has_permission_on block to depend on permissions on associated objects. By using it, the authorization rules may be a lot DRYer. E.g.:

role :branch_manager
  has_permission_on :branches, :to => :manage do
    if_attribute :employees => contains { user }
  end
  has_permission_on :employees, :to => :read do
    if_permitted_to :read, :branch
    # instead of
    # if_attribute :branch => { :employees => contains { user } }
  end
end

if_permitted_to associations may be nested as well:

if_permitted_to :read, :branch => :company

You can even use has_many associations as target. Then, it is checked if the current user has the required privilege on any of the target objects.

if_permitted_to :read, :branch => :employees

Beware of performance issues with permission checks. In the current implementation, all employees are checked until the first permitted is found. with_permissions_to, on the other hand, constructs more efficient SQL instead.

To check permissions based on the current object, the attribute has to be left out:

has_permission_on :branches, :to => :manage do
  if_attribute :employees => contains { user }
end
has_permission_on :branches, :to => :paint_green do
  if_permitted_to :update
end

Normally, one would merge those rules into one. Dividing makes sense if additional if_attribute are used in the second rule or those rules are applied to different roles.

Options:

:context

When using with_permissions_to, the target context of the if_permitted_to statement is inferred from the last reflections target class. Still, you may override this algorithm by setting the context explicitly.

if_permitted_to :read, :home_branch, :context => :branches
if_permitted_to :read, :branch => :main_company, :context => :companies

Raises:



448
449
450
451
452
453
454
455
# File 'lib/declarative_authorization/reader.rb', line 448

def if_permitted_to(privilege, attr_or_hash = nil, options = {})
  raise DSLError, "if_permitted_to only in has_permission blocks" if @current_rule.nil?
  options[:context] ||= attr_or_hash.delete(:context) if attr_or_hash.is_a?(Hash)
  # only :context option in attr_or_hash:
  attr_or_hash = nil if attr_or_hash.is_a?(Hash) and attr_or_hash.empty?
  @current_rule.append_attribute AttributeWithPermission.new(privilege,
      attr_or_hash, options[:context])
end

#includes(*roles) ⇒ Object

Roles may inherit all the rights from subroles. The given roles become subroles of the current block’s role.

role :admin do
  includes :user
  has_permission_on :employees, :to => [:update, :create]
end
role :user do
  has_permission_on :employees, :to => :read
end

Raises:



242
243
244
245
246
# File 'lib/declarative_authorization/reader.rb', line 242

def includes(*roles)
  raise DSLError, "includes only in role blocks" if @current_role.nil?
  @role_hierarchy[@current_role] ||= []
  @role_hierarchy[@current_role] += roles.flatten
end

#initialize_copy(from) ⇒ Object

:nodoc:



205
206
207
208
209
210
# File 'lib/declarative_authorization/reader.rb', line 205

def initialize_copy(from) # :nodoc:
  [:roles, :role_hierarchy, :auth_rules,
      :role_descriptions, :role_titles, :omnipotent_roles].each do |attribute|
    instance_variable_set(:"@#{attribute}", from.send(attribute).clone)
  end
end

#intersects_with(&block) ⇒ Object

In an if_attribute statement, intersects_with requires that at least one of the values has to be part of the collection specified by the if_attribute attribute. The value block needs to evaluate to an Enumerable. For information on the block argument, see if_attribute.



486
487
488
# File 'lib/declarative_authorization/reader.rb', line 486

def intersects_with(&block)
  [:intersects_with, block]
end

#is(&block) ⇒ Object

In an if_attribute statement, is says that the value has to be met exactly by the if_attribute attribute. For information on the block argument, see if_attribute.



460
461
462
# File 'lib/declarative_authorization/reader.rb', line 460

def is(&block)
  [:is, block]
end

#is_in(&block) ⇒ Object

In an if_attribute statement, is_in says that the value has to contain the attribute value. For information on the block argument, see if_attribute.



493
494
495
# File 'lib/declarative_authorization/reader.rb', line 493

def is_in(&block)
  [:is_in, block]
end

#is_not(&block) ⇒ Object

The negation of is.



465
466
467
# File 'lib/declarative_authorization/reader.rb', line 465

def is_not(&block)
  [:is_not, block]
end

#is_not_in(&block) ⇒ Object

The negation of is_in.



498
499
500
# File 'lib/declarative_authorization/reader.rb', line 498

def is_not_in(&block)
  [:is_not_in, block]
end

#lt(&block) ⇒ Object

Less than



503
504
505
# File 'lib/declarative_authorization/reader.rb', line 503

def lt(&block)
  [:lt, block]
end

#lte(&block) ⇒ Object

Less than or equal to



508
509
510
# File 'lib/declarative_authorization/reader.rb', line 508

def lte(&block)
  [:lte, block]
end

#role(role, options = {}) ⇒ Object

Defines the authorization rules for the given role in the following block.

role :admin do
  has_permissions_on ...
end


224
225
226
227
228
229
230
# File 'lib/declarative_authorization/reader.rb', line 224

def role(role, options = {})
  append_role role, options
  @current_role = role
  yield
ensure
  @current_role = nil
end

#title(text) ⇒ Object

Sets a human-readable title for the current role. E.g.

role :admin
  title "Administrator"
  has_permission_on ...
end

Raises:



324
325
326
327
# File 'lib/declarative_authorization/reader.rb', line 324

def title(text)
  raise DSLError, "title only allowed in role blocks" if @current_role.nil?
  role_titles[@current_role] = text
end

#to(*privs) ⇒ Object

Used in a has_permission_on block, to may be used to specify privileges to be assigned to the current role under the conditions specified in the current block.

role :admin
  has_permission_on :employees do
    to :create, :read, :update, :delete
  end
end

Raises:



337
338
339
340
# File 'lib/declarative_authorization/reader.rb', line 337

def to(*privs)
  raise DSLError, "to only allowed in has_permission_on blocks" if @current_rule.nil?
  @current_rule.append_privileges(privs.flatten)
end