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: