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
#