lib/trust/permissions.rb in trust-0.7.0 vs lib/trust/permissions.rb in trust-0.8.0
- old
+ new
@@ -32,24 +32,24 @@
# ...
# end
# ...
# end
#
- # The above is the minimum required definitions that must exist in you file. <tt>Default</tt> will be used if no classes
- # match the permissions requested, so the <tt>Default</tt> class definition is mandatory.
+ # 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:
- # * <tt>Project</tt> should have a matching class <tt>Permissions::Project</tt>
- # * <tt>Account</tt> should have a matching class <tt>Permissions::Account</tt>
- # * <tt>Account:Credit</tt> may have a matching class <tt>Permissions::Account::Credit</tt>, but if its inheriting from
- # <tt>Account</tt> and no special handling is necessary, it is not necessary to create the permissions class.
+ # * +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
#
@@ -64,30 +64,30 @@
# end
# end
#
# == Action aliases
#
- # You can define aliases for actions. You do this by setting the <tt>action_aliases</tt> attribute on Trust::Permissions class
+ # 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 <tt>can?</tt> method you must refer to
+ # 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:
- # * <tt>user</tt> - the user currently logged in
- # * <tt>action</tt> - the action request from the controller such as :edit, or the action tested from helper or
- # from the object itself when using <tt>ActiveRecord::can?</tt> is being used.
- # * <tt>subject</tt> - the object that is being tested for permissions. This may be a an existing object, a new object
+ # * +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.
- # * <tt>parent</tt> - the parent object if in a nested route, specified by +belongs_to+ in the controller.
- # * <tt>klass</tt> - the class of involed in the request. It can be a base class or the real class, depending on
+ # * +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.
@@ -126,15 +126,15 @@
#
# calling the +authorized?+ method on the instance later will test for the authorization.
#
# == Parameters:
#
- # <tt>user</tt> - user object, must respond to role_symbols
- # <tt>action</tt> - action, such as :create, :show, etc. Should not be an alias
- # <tt>klass</tt> - the class of the subject.
- # <tt>subject</tt> - the subject tested for authorization
- # <tt>parent</tt> - the parent object, normally declared through belongs_to
+ # +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 {Trust::Authorization} for more details
#
def initialize(user, action, klass, subject, parent)
@user, @action, @klass, @subject, @parent = user, action, klass, subject, parent
@@ -179,11 +179,11 @@
end
class << self
# Assign permissions to one or more roles.
#
- # You may call role or roles, they are the same function like <tt>role :admin</tt> or <tt>roles :admin, :accountant</tt>
+ # 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
@@ -209,38 +209,52 @@
if block_given?
if @@can_expressions > 0
@@can_expressions = 0
raise RoleAssigmnentMissing
end
- @perms = []
+ @perms = {:can => [], :cannot => []}
@in_role_block = true
yield
@in_role_block = false
- perms = @perms
+ perms = @perms
else
if @@can_expressions > 1
@@can_expressions = 0
raise RoleAssigmnentMissing
end
- options = roles.extract_options!
- raise ArgumentError, "Must have a block or a can expression" unless perms = options[:can]
+ 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] ||= []
- self.permissions[role] += perms
+ 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
alias :roles :role
# Defines permissions
#
# === Arguments
#
# action - can be an alias or an actions of some kind
- # options - :if/:unless :symbol or proc that will be called to evaluate an expression
+ # 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
@@ -252,15 +266,68 @@
#
# 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.
def can(*args)
options = args.extract_options!
+ enforce = options.delete(:enforce)
p = expand_aliases(args).collect { |action| [action, options] }
if @in_role_block
- @perms += p
+ @perms[:can] += p
+ if enforce
+ @perms[:cannot] = expand_aliases(args).collect { |action| action }
+ end
else
@@can_expressions += 1
- return {:can => p }
+ perms = {:can => p }
+ if enforce
+ perms[:cannot] = expand_aliases(args).collect { |action| action }
+ end
+ return perms
+ end
+ end
+
+ # 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
+ #
+ 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
private
def expand_aliases(actions) #:nodoc: