Class: Trust::Permissions

Inherits:
Object
  • Object
show all
Includes:
InheritableAttribute
Defined in:
lib/trust/permissions.rb

Overview

Trust Permissions

Permissions should be specified in a separate file in you app/model directory. The file could look like this:

module Permissions
  class Default < Trust::Permissions
    ...
  end
  ...
end

The above is the minimum required definitions that must exist in you file. Default will be used if no classes match the permissions requested, so the Default class definition is mandatory.

If you want to separate the permissions into separate files that is ok. Then you shoud place these files in the /app/model/permissions directory.

Defining permisions

The basic rules is to define classes in the Permissions module that matches your models. Here are some examples:

  • Project should have a matching class Permissions::Project

  • Account should have a matching class Permissions::Account

  • Account:Credit may have a matching class Permissions::Account::Credit, but if its inheriting from Account and no special handling is necessary, it is not necessary to create the permissions class.

Using inheritance

Inheritance is also fully supported, but should generally follow your own inheritance model

module Permissions
  class Account < Default
    role :admin, :accountant do 
      ...
    end
  end
  class Account::Credit < Account
    ...
  end
end

Action aliases

You can define aliases for actions. You do this by setting the action_aliases attribute on Trust::Permissions class Example:

Trust::Permissions.action_aliases = {
   read: [:index, :show],
   create: [:create, :new]
   }

Keep in mind that all permissions are expanded upon declaration, so when using the can? method you must refer to the actual action and not the alias. The alias will never give a positive permission.

Accessors

Accessors that can be used when testing permissions:

  • user - the user currently logged in

  • action - the action request from the controller such as :edit, or the action tested from helper or from the object itself when using ActiveRecord::can? is being used.

  • subject - the object that is being tested for permissions. This may be a an existing object, a new object (such as for :create and :new action), or nil if no object has been instantiated.

  • parent - the parent object if in a nested route, specified by belongs_to in the controller.

  • klass - the class of involed in the request. It can be a base class or the real class, depending on your controller design.

Defining your own accessors or instance methods

You can easily define your own accessors in the classes. These can be helpful when declaring permissions.

Example:

class Account < Trust::Permissions
  role :admin, :accountant do
    can :update, :unless => :closed?
  end
  def closed? 
    subject.closed?
  end
end

In the above example closed is testing on the subject to see if it is closed. The permission is referring to this method when evaluated. Keep in mind that you must refer to the subject, as you do not access the inctance of the object directly.

Defined Under Namespace

Classes: SubjectInaccessible

Constant Summary collapse

@@can_expressions =

read: [:index, :show], create: [:create, :new], update: [:update, :edit], manage: [:index, :show, :create, :new, :update, :edit, :destroy]

0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from InheritableAttribute

deep_copy

Constructor Details

#initialize(user, action, klass, subject, parent) ⇒ Permissions

Initializes the permission object

calling the authorized? method on the instance later will test for the authorization.

Parameters:

+user+ - user object, must respond to role_symbols
+action+ - action, such as :create, :show, etc. Should not be an alias
+klass+ - the class of the subject.
+subject+ - the subject tested for authorization
+parent+ - the parent object, normally declared through belongs_to

See Authorization for more details



148
149
150
# File 'lib/trust/permissions.rb', line 148

def initialize(user, action, klass, subject, parent)
  @user, @action, @klass, @subject, @parent = user, action, klass, subject, parent
end

Instance Attribute Details

#actionObject (readonly)

Returns the value of attribute action.



115
116
117
# File 'lib/trust/permissions.rb', line 115

def action
  @action
end

#klassObject (readonly)

Returns the value of attribute klass.



115
116
117
# File 'lib/trust/permissions.rb', line 115

def klass
  @klass
end

#parentObject (readonly)

Returns the value of attribute parent.



115
116
117
# File 'lib/trust/permissions.rb', line 115

def parent
  @parent
end

#subjectObject

Returns the value of attribute subject.



116
117
118
# File 'lib/trust/permissions.rb', line 116

def subject
  @subject
end

#userObject (readonly)

Returns the value of attribute user.



115
116
117
# File 'lib/trust/permissions.rb', line 115

def user
  @user
end

Class Method Details

._role(existing_permissions, *roles, &block) ⇒ Object



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/trust/permissions.rb', line 361

def _role(existing_permissions, *roles, &block)
  if block_given?
    if @@can_expressions > 0
      @@can_expressions = 0
      raise RoleAssigmnentMissing 
    end
    @perms = {:can => [], :cannot => []}
    @in_role_block = true
    yield
    @in_role_block = false
    perms = @perms
  else
    if @@can_expressions > 1
      @@can_expressions = 0
      raise RoleAssigmnentMissing 
    end
    perms = roles.extract_options!
    unless perms.size >= 1 && (perms[:can] || perms[:cannot])
      raise ArgumentError, "Must have a block or a can or a cannot expression: #{perms.inspect}"
    end
    @@can_expressions = 0
  end
  roles.flatten.each do |role|
    existing_permissions[role] ||= []
    if perms[:cannot] && perms[:cannot].size > 0
      perms[:cannot].each do |p|
        existing_permissions[role].delete_if { |perm| perm[0] == p  }
      end
    end
    if perms[:can] && perms[:can].size > 0
      existing_permissions[role] += perms[:can]
    end
  end
  existing_permissions
end

.can(*args) ⇒ Object

Defines permissions

Arguments

action - can be an alias or an actions of some kind
options - control the behavior of the permission

Options

+:if/:unless+ - :symbol or proc that will be called to evaluate an expression
+enforce+ - set to true to enforce the permission, delete any previous grants given from parent classes. Most meaningful in
            combination with +:if+ and +:unless+ options

Example

module Permissions
  class Account < Trust::Permissions
    role :admin, :accountant do 
      can :read
      can :update, :unless => :closed?
    end
  end
end

The above permits admin and accountant to read accounts, but can update only if the account is not closed. In the example above a method is used to test data on the actual record when testing for permissions.



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/trust/permissions.rb', line 422

def can(*args)
  options = args.extract_options!
  enforce = options.delete(:enforce)
  p = expand_aliases(args).collect { |action| [action, options] }
  if @in_role_block
    @perms[:can] += p
    if enforce
      @perms[:cannot] = expand_aliases(args).collect { |action| action }
    end
  else
    @@can_expressions += 1
    perms = {:can => p }
    if enforce
      perms[:cannot] = expand_aliases(args).collect { |action| action }
    end
    return perms
  end
end

.cannot(*args) ⇒ Object

Revokes permissions.

Revokes any previous permissions given in parent classes. This cannot be used with conditions. See also :enforce option for can

can has presedence over cannot. In practice this means that in a block; cannot statements are processed before can, and any previously permissions granted are deleted. Another way to say this is; if you have cannot :destroy and can :destroy, then all inheritied destroys will first be deleted, and then the can destroy will be granted.

Arguments

action - actions to be revoked permissions for. Cannot be aliases

Example

module Permissions
  class Account < Trust::Permissions
    role :admin, :accountant do 
      can :read
      can :read
      can :update, :destroy, :unless => :closed?
    end
  end

  class Account::Credit < Account
     role :accountant do
       cannot :destroy    # revoke permission to destroy
     end
  end
end

Raises:

  • (ArgumentError)


474
475
476
477
478
479
480
481
482
483
484
# File 'lib/trust/permissions.rb', line 474

def cannot(*args)
  options = args.extract_options!
  raise ArgumentError, "No options (#{options.inspect}) are allowed for cannot. It is just meaning less" if options.size > 0
  p = expand_aliases(args).collect { |action| action }
  if @in_role_block
    @perms[:cannot] += p
  else
    @@can_expressions += 1
    return {:cannot => p }
  end
end

.member_role(*roles, &block) ⇒ Object Also known as: member_roles

Assign permissions to one or more roles on a member role.

You may call member_role or member_roles, they are the same function like

+member_role :scrum_master+ or +member_roles :scrum_master, :product_owner+

When using this feature, your permission class must respond to members_rols, and return only one role

See role for definition See members_role for how to implement this method



356
357
358
# File 'lib/trust/permissions.rb', line 356

def member_role(*roles, &block)
  self.member_permissions = _role(self.member_permissions, *roles, &block)
end

.permit(*attrs) ⇒ Object

Assign default permissions for whitelisting paremeter attributes

See ActionController::Parameters.permit for how this works in Rails



311
312
313
# File 'lib/trust/permissions.rb', line 311

def permit(*attrs)
  self.entity_attributes = attrs.dup
end

.require(entity) ⇒ Object

Assign default requirement for whitelisting paremeters

See ActionController::Parameters.require for how this works in Rails



303
304
305
# File 'lib/trust/permissions.rb', line 303

def require(entity)
  self.entity_required = entity
end

.role(*roles, &block) ⇒ Object Also known as: roles

Assign permissions to one or more roles.

You may call role or roles, they are the same function like role :admin or roles :admin, :accountant

There are two ways to call role, with or without block. If you want to set multiple permissions with different conditons then you should use a block.

module Permissions
  class Account < Trust::Permissions
    role :admin, can(:manage, :audit)
  end
end

The above assigns the manage and audit permissions to admin.

module Permissions
  class Account < Trust::Permissions
    role :admin, :accountant do 
      can :read
      can :update
    end
  end
end

The above permits admin and accountant to read accounts.



341
342
343
# File 'lib/trust/permissions.rb', line 341

def role(*roles, &block)
  self.permissions = _role(self.permissions, *roles, &block)
end

Instance Method Details

#authorized?Boolean

Returns params_handler if the user is authorized to perform the action

The handler contains information used by the resource on retrieing parametes later

Returns:

  • (Boolean)


155
156
157
158
159
160
161
# File 'lib/trust/permissions.rb', line 155

def authorized?
  trace 'authorized?', 0, "@user: #{@user.inspect}, @action: #{@action.inspect}, @klass: #{@klass.inspect}, @subject: #{@subject.inspect}, @parent: #{@parent.inspect}"
  if params_handler = (user && (permission_by_role || permission_by_member_role))
    params_handler = params_handler_default(params_handler)
  end
  params_handler
end

#members_roleObject

Implement this in your permissions class if using membership roles

One example is that you have teams or projects that have members with role and you want to Authorize against that role instead of any of the roles associated with the user directly

Example:

class Sprint < Trust::Permissions
  member_role :scrum_master, can(:update)
  def members_role()
    @members_role ||= subject.memberships.where(user_id: user.id).first.role_symbol
  end


182
183
184
# File 'lib/trust/permissions.rb', line 182

def members_role()
  {}
end

#preloadObject



163
164
165
166
167
168
# File 'lib/trust/permissions.rb', line 163

def preload
  @preload = true
  params_handler = authorized? || {}
  @preload = false
  params_handler
end

#preload?Boolean

returns true if permissions are currently being preloaded In new_actions, the framework must load require and permit in order to set permitted variables before the authorization can be evaluated. At that time, the subject is not accessible by permissions. It is not mandatory to use this, but you may test on this in yor permissions file if necessary.

Example:

module Permissions
  class Account < Trust::Permissions
    role :admin, :accountant do 
      can :create, :new, require: :account, permit: [:number, :amount, :comment], if: :preload?
      can :create, :new, require: :account, permit: [:number, :amount, :comment], if: :valid_amount?, unless: :preload?
    end
  end
end

Returns:

  • (Boolean)


212
213
214
# File 'lib/trust/permissions.rb', line 212

def preload?
  @preload
end

#subject_or_parentObject

Returns subject if subject is an instance, otherwise parent



188
189
190
# File 'lib/trust/permissions.rb', line 188

def subject_or_parent
  (@subject.nil? || subject.is_a?(Class)) ? parent : subject
end