Class: Shamu::Security::Policy

Inherits:
Object
  • Object
show all
Includes:
Roles
Defined in:
lib/shamu/security/policy.rb

Overview

...

Examples:

class UserPolicy < Shamu::Security::Policy

  role :admin, inherits: :manager
  role :manager
  role :user

  private

    def permissions
      alias_action :email, to: :contact

      permit :contact, UserEntity if in_role?( :manager )
      permit :email, UserEntity do |user|
        user.public_profile?
      end
    end
end

principal = Shamu::Security::Principal.new( user_id: user.id )
policy = UserPolicy.new(
  principal: principal,
  roles: roles_service.roles_for( principal )
  )

if policy.permit? :contact, user
  mail_to user
end

Direct Known Subclasses

ActiveRecordPolicy

Dependencies collapse

DSL collapse

Instance Method Summary collapse

Methods included from Roles

expand_roles, role, role_defined?, roles

Constructor Details

#initialize(principal: nil, roles: nil) ⇒ Policy

Returns a new instance of Policy.



53
54
55
56
# File 'lib/shamu/security/policy.rb', line 53

def initialize( principal: nil, roles: nil )
  @principal = principal || Principal.new
  @roles     = roles || []
end

Instance Attribute Details

#principalPrincipal

Returns principal holding user identity and access credentials.

Returns:

  • (Principal)

    principal holding user identity and access credentials.



45
46
47
# File 'lib/shamu/security/policy.rb', line 45

def principal
  @principal
end

#rolesArray<Roles>

Returns roles that have been granted to the #principal.

Returns:



49
50
51
# File 'lib/shamu/security/policy.rb', line 49

def roles
  @roles
end

Instance Method Details

#alias_action(*actions, to: fail)

This method returns an undefined value.

Add an action alias so that granting the alias will result in permits for any of the listed actions.

Examples:

alias_action :show, :list, to: :read
permit :read, :stuff

permit?( :show, :stuff )  # => :yes
permit?( :list, :stuff )  # => :yes
permit?( :read, :stuff )  # => :yes
permit?( :write, :stuff ) # => false

Parameters:

  • actions (Array<Symbol>)

    to alias.

  • to (Symbol) (defaults to: fail)

    the action that should permit all the listed aliases.



246
247
248
249
# File 'lib/shamu/security/policy.rb', line 246

def alias_action( *actions, to: fail ) # bug in rubocop chokes on trailing required keyword
  aliases[to] ||= []
  aliases[to] |= actions
end

#authorize!(action, resource, additional_context = nil) ⇒ resource

Authorize the given action on the given resource. If it is not permitted then an exception is raised.

Parameters:

  • action (Symbol)

    to perform.

  • resource (Object)

    the resource the action will be performed on.

  • additional_context (Object) (defaults to: nil)

    that the policy may consider.

Returns:

  • (resource)

Raises:



65
66
67
68
69
70
71
72
73
# File 'lib/shamu/security/policy.rb', line 65

def authorize!( action, resource, additional_context = nil )
  return resource if permit?( action, resource, additional_context ) == :yes

  fail Security::AccessDeniedError,
       action: action,
       resource: resource,
       additional_context: additional_context,
       principal: principal
end

#deny(*actions, resource) {|resource, additional_context| ... }

This method returns an undefined value.

Explicitly deny an action previously granted with #permit.

Parameters:

  • actions (Array<Symbol>)

    to be permitted.

  • resource (Object)

    to perform the action on or the Class of instances to permit the action on.

Yields:

  • (resource, additional_context)

Yield Parameters:

  • resource (Object)

    instance or Class offered to #permit? that the requested action is to be performed on.

  • additional_context (Object)

    offered to #permit?.

Yield Returns:

  • (Boolean)

    true to deny the action.



204
205
206
# File 'lib/shamu/security/policy.rb', line 204

def deny( *actions, resource, &block )
  add_rule( actions, resource, false, &block )
end

#in_role?(*roles) ⇒ Boolean

Returns true if the #principal has been granted one of the given roles.

Parameters:

  • roles (Array<Symbol>)

    to check.

Returns:

  • (Boolean)

    true if the #principal has been granted one of the given roles.



120
121
122
# File 'lib/shamu/security/policy.rb', line 120

def in_role?( *roles )
  ( principal_roles & roles ).any?
end

#permissions

This method returns an undefined value.

Hook to be overridden by a derived class to define the set of rules that #permit? should consider when evaluating the #principal's permissions on a resource.

Rules defined in the permissions block are evaluated in reverse order such that the last matching #permit or #deny will determine the permission.

If no rules match, the permission is denied.

Examples:

def permissions
  permit :read, UserEntity

  deny :read, UserEntity do |user|
    user.protected_account? && !in_role( :admin )
  end
end


158
159
160
# File 'lib/shamu/security/policy.rb', line 158

def permissions
  fail IncompleteSetupError, "Permissions have not been defined. Add a private `permissions` method to #{ self.class.name }" # rubocop:disable Metrics/LineLength
end

#permit(*actions, resource) {|resource, additional_context| ... }

This method returns an undefined value.

Permit one or more actions to be performed on a given resource.

When a block is provided the policy will yield to the block to allow for more complex or context aware policy checks. The block is not called if the resource offered to #permit? is a Class or Module.

Examples:

permit :read, UserEntity
permit :show, :dashboard
permit :update, UserEntity do |user|
  user.id == principal.user_id
end
permit :destroy, UserEntity do |user, additional_context|
  in_role?( :admin ) && additional_context[:custom_data] == :safe
end

Parameters:

  • actions (Array<Symbol>)

    to be permitted.

  • resource (Object)

    to perform the action on or the Class of instances to permit the action on.

Yields:

  • (resource, additional_context)

Yield Parameters:

  • resource (Object)

    instance or Class offered to #permit? that the requested action is to be performed on.

  • additional_context (Object)

    offered to #permit?.

Yield Returns:

  • (:yes, :maybe, false)

    see #permit?.



189
190
191
192
193
# File 'lib/shamu/security/policy.rb', line 189

def permit( *actions, resource, &block )
  result = @when_elevated ? :maybe : :yes

  add_rule( actions, resource, result, &block )
end

#permit?(action, resource, additional_context = nil) ⇒ :yes, ...

Determines if the given action may be performed on the given resource.

Parameters:

  • action (Symbol)

    to perform.

  • resource (Object)

    the resource the action will be performed on.

  • additional_context (Object) (defaults to: nil)

    that the policy may consider.

Returns:

  • (:yes, :maybe, false)

    a truthy value if permitted, otherwise false. The truthy value depends on the certainty of the policy. A value of :yes or true indicates the action is always permitted. A value of :maybe indicates the action is permitted but the user may need to present additional credentials such as logging on this session or entering a TFA code.



87
88
89
90
91
92
93
94
95
96
97
# File 'lib/shamu/security/policy.rb', line 87

def permit?( action, resource, additional_context = nil )
  fail_on_active_record_check( resource )

  rules.each do |rule|
    next unless rule.match?( action, resource, additional_context )

    return rule.result
  end

  false
end

#when_elevated(&block)

This method returns an undefined value.

Only #authorize! the permissions defined in the given block when the #principal has elevated this session by providing their credentials.

Permissions defined in the block will yield a :maybe result when queried via #permit? and will raise an AccessDeniedError when an #authorize! check is enforced.

This allows you to enable/disable UX in response to what a user should be capable of doing but wait to actually allow it until they have offered their credentials.



222
223
224
225
226
227
# File 'lib/shamu/security/policy.rb', line 222

def when_elevated( &block )
  current = @when_elevated
  @when_elevated = true
  yield
  @when_elevated = current
end