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.

Constant Summary collapse

@@can_expressions =
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



139
140
141
# File 'lib/trust/permissions.rb', line 139

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.



113
114
115
# File 'lib/trust/permissions.rb', line 113

def action
  @action
end

#klassObject (readonly)

Returns the value of attribute klass.



113
114
115
# File 'lib/trust/permissions.rb', line 113

def klass
  @klass
end

#parentObject (readonly)

Returns the value of attribute parent.



113
114
115
# File 'lib/trust/permissions.rb', line 113

def parent
  @parent
end

#subjectObject (readonly)

Returns the value of attribute subject.



113
114
115
# File 'lib/trust/permissions.rb', line 113

def subject
  @subject
end

#userObject (readonly)

Returns the value of attribute user.



113
114
115
# File 'lib/trust/permissions.rb', line 113

def user
  @user
end

Class Method Details

.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.



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/trust/permissions.rb', line 269

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)


321
322
323
324
325
326
327
328
329
330
331
# File 'lib/trust/permissions.rb', line 321

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

.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.



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
# File 'lib/trust/permissions.rb', line 208

def role(*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|
    self.permissions[role] ||= []
    if perms[:cannot] && perms[:cannot].size > 0
      perms[:cannot].each do |p|
        self.permissions[role].delete_if { |perm| perm[0] == p  }
      end
    end
    if perms[:can] && perms[:can].size > 0
      self.permissions[role] += perms[:can]
    end
  end
end

Instance Method Details

#authorized?Boolean

Returns true if the user is authorized to perform the action

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/trust/permissions.rb', line 144

def authorized?
  authorized = nil
  user && user.role_symbols.each do |role|
    (permissions[role] || {}).each do |act, opt|
      if act == action
        break if (authorized = opt.any? ? eval_expr(opt) : true)
      end
    end
    break if authorized
  end
  authorized
end