lib/trust/permissions.rb in trust-0.8.3 vs lib/trust/permissions.rb in trust-1.4.2

- old
+ new

@@ -107,20 +107,29 @@ # 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. # class Permissions + class SubjectInaccessible < StandardError; end + include InheritableAttribute - attr_reader :user, :action, :klass, :subject, :parent + attr_reader :user, :action, :klass, :parent + attr_accessor :subject inheritable_attr :permissions + inheritable_attr :member_permissions + inheritable_attr :entity_required + inheritable_attr :entity_attributes class_attribute :action_aliases, :instance_writer => false, :instance_reader => false self.permissions = {} + self.member_permissions = {} + self.entity_required = nil # for require + self.entity_attributes = [] # for permit self.action_aliases = { - read: [:index, :show], - create: [:create, :new], - update: [:update, :edit], - manage: [:index, :show, :create, :new, :update, :edit, :destroy] + # read: [:index, :show], + # create: [:create, :new], + # update: [:update, :edit], + # manage: [:index, :show, :create, :new, :update, :edit, :destroy] } @@can_expressions = 0 # Initializes the permission object # @@ -138,29 +147,81 @@ # def initialize(user, action, klass, subject, parent) @user, @action, @klass, @subject, @parent = user, action, klass, subject, parent end - # Returns true if the user is authorized to perform the action + # Returns params_handler if the user is authorized to perform the action + # + # The handler contains information used by the resource on retrieing parametes later 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 + 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 - authorized + params_handler end - - protected + + def preload + @preload = true + params_handler = authorized? || {} + @preload = false + params_handler + end + + # 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 + def members_role() + {} + end + + # Returns subject if subject is an instance, otherwise parent + # + def subject_or_parent + (@subject.nil? || subject.is_a?(Class)) ? parent : subject + end + + def subject + raise SubjectInaccessible, 'You cannot access subject when declaring require or permit for new_actions. You may test with :preload?' if @preload + @subject + end + + # 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 + def preload? + @preload + end + + private def eval_expr(options) #:nodoc: - options.collect do |oper, expr| + params_handler = {} + found = options.collect do |oper, expr| res = case expr - when Symbol then send(expr) + when Symbol + [:if, :unless].include?(oper) ? send(expr) : expr when Proc if expr.lambda? instance_exec &expr else instance_eval &expr @@ -170,17 +231,89 @@ end case oper when :if then res when :unless then !res + when :require + params_handler[:require] = res + true + when :permit + params_handler[:permit] = Array.wrap(res) + true else raise UnsupportedCondition, expr.inspect end end.all? + found && params_handler end - + + def permission_by_role + auth = nil + trace 'authorize_by_role?', 0, "#{user.try(:name)}" + user.role_symbols.any? do |role| + trace 'authorize_by_role?', 1, "#{role}" + if p = permissions[role] + trace 'authorize_by_role?', 2, "permissions: #{p.inspect}" + auth = authorization(p) + end + end + auth + end + + # Checks is a member is authorized + # You will need to implement members_role in permissions yourself + def permission_by_member_role + m = members_role + trace 'authorize_by_member_role?', 0, "#{user.try(:name)}:#{m}" + p = member_permissions[m] + trace 'authorize_by_role?', 1, "permissions: #{p.inspect}" + p && authorization(p) + end + + def authorization(permissions = {}) + auth = nil + permissions.any? do |act, opt| + auth = (opt.any? ? eval_expr(opt) : {}) if act == action + end + trace( 'authorization', 2, "got permission!") if auth + auth + end + + # sets default values for params_handler if keys does not exist. + # note: if keys exists, they can be nil, and they will not be set to default + def params_handler_default(params_handler) + params_handler[:require] = (self.class.entity_required || route_key(@klass)) unless params_handler.has_key?(:require) + params_handler[:permit] = self.class.entity_attributes unless params_handler.has_key?(:permit) + params_handler + end + + def route_key(klass) + klass.name.to_s.underscore.tr('/','_').to_sym + end + + def trace(method, indent = 0, msg = nil) + return unless Trust.log_level == :trace + Rails.logger.debug "#{self.class.name}.#{method}: #{"\t" * indent}#{msg}" + end + class << self + # Assign default requirement for whitelisting paremeters + # + # See {ActionController::Parameters.require} for how this works in Rails + # + def require(entity) + self.entity_required = entity + end + + # Assign default permissions for whitelisting paremeter attributes + # + # See {ActionController::Parameters.permit} for how this works in Rails + # + def permit(*attrs) + self.entity_attributes = attrs.dup + end + # 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 @@ -204,10 +337,30 @@ # end # # The above permits admin and accountant to read accounts. # def role(*roles, &block) + self.permissions = _role(self.permissions, *roles, &block) + end + alias :roles :role + + # 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 {Trust::Permissions.role} for definition + # See {Trust::Permissions.members_role} for how to implement this method + # + def member_role(*roles, &block) + self.member_permissions = _role(self.member_permissions, *roles, &block) + end + alias :member_roles :member_role + + def _role(existing_permissions, *roles, &block) if block_given? if @@can_expressions > 0 @@can_expressions = 0 raise RoleAssigmnentMissing end @@ -226,21 +379,21 @@ 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] ||= [] + existing_permissions[role] ||= [] if perms[:cannot] && perms[:cannot].size > 0 perms[:cannot].each do |p| - self.permissions[role].delete_if { |perm| perm[0] == p } + existing_permissions[role].delete_if { |perm| perm[0] == p } end end if perms[:can] && perms[:can].size > 0 - self.permissions[role] += perms[:can] + existing_permissions[role] += perms[:can] end end + existing_permissions end - alias :roles :role # Defines permissions # # === Arguments #