Class: HasHelpers::UserRestrictions

Inherits:
Object
  • Object
show all
Includes:
CanCan::Ability, Concerns::RestrictionsParseable
Defined in:
lib/has_helpers/utils/user_restrictions.rb

Overview

Resolves and queries the resource-level restrictions of a user, and exposes them through the CanCanCan interface (can?/cannot?).

Uses a restrictions-based model (negative permissions): the restrictions_hash on the user's primary user_organization defines what the user CANNOT do. An empty restrictions hash means the user is unrestricted (can :manage, :all).

This class includes CanCan::Ability and mirrors frameworkhq's Ability: a single catch-all can block delegates each check to the negative restricted? query, and results are memoized per (subject, action) within the instance (see memoized_permissions). Build one instance per request and reuse it — the authorize lambda and host ::Ability subclasses do exactly this.

The hash is keyed by application name and may hold restrictions for several host apps at once (e.g. AgenciesHQ, ReadyGOP, Reports, HQAdmin). Each host passes its own keys via app_keys (looked up first), or relies on case-insensitive matching against application / the PsqlHq client_app variable.

Payload shape (populated by hqadmin during the login sync; may arrive multi-encoded as JSON text — see RestrictionsParseable):

{
"AppName" => {
  "Client" => {
    "restriction_operations" => ["View", "Create"], # operations the user CANNOT do
    "resource_type"          => "Base",
    "alias_name"             => "Customer",          # optional: alternate resource name
    "nested_resources"       => [...]                # optional: NOT consumed here
  }
}
}

Usage:

restrictions = HasHelpers::UserRestrictions.new(user, app_keys: ["MyApp"])
restrictions.empty?                            # => true if unrestricted
restrictions.can?(:read, "Client")             # => false if View/Read restricted
restrictions.restricted?(:read, "Client")      # => true if View/Read restricted
restrictions.restricted_resource_names         # => ["Client", ...] (read-restricted)

Constant Summary collapse

READ_OPERATIONS =

Operations that block reading a resource.

%w[View Read].freeze
OPERATION_MAP =

Maps CanCanCan-style action names to the restriction operation names stored in the DB. Read-like actions match both "View" and "Read".

{
  "read" => READ_OPERATIONS,
  "view" => READ_OPERATIONS,
  "create" => ["Create"].freeze,
  "update" => ["Update"].freeze,
  "destroy" => ["Delete"].freeze,
  "delete" => ["Delete"].freeze,
  "export" => ["Export"].freeze,
  "merge" => ["Merge"].freeze
}.freeze
ACTION_ALIASES =

UI/action aliases (the standard frameworkhq set) mapped to the canonical action that OPERATION_MAP understands. CanCanCan passes the ORIGINAL action to a catch-all can block (not the one expanded by alias_action), so the block normalizes through this map before delegating to restricted?. The alias_action declarations in ability_aliases! mirror this and cover the rest of the CanCanCan API (e.g. cannot?). Keeping both in sync is intentional — do not remove either.

{
  cards: :read,
  download: :read,
  favorite: :read,
  copy: :create,
  delete: :destroy,
  edit: :update,
  email: :export,
  re_run: :update
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user, application: nil, app_keys: []) ⇒ UserRestrictions

Returns a new instance of UserRestrictions.



87
88
89
90
91
92
93
94
# File 'lib/has_helpers/utils/user_restrictions.rb', line 87

def initialize(user, application: nil, app_keys: [])
  @user = user
  @application = application
  @app_keys = Array(app_keys)

  ability_aliases!
  authorize_restrictions!
end

Instance Attribute Details

#app_keysObject (readonly)

Returns the value of attribute app_keys.



85
86
87
# File 'lib/has_helpers/utils/user_restrictions.rb', line 85

def app_keys
  @app_keys
end

#applicationObject (readonly)

Returns the value of attribute application.



85
86
87
# File 'lib/has_helpers/utils/user_restrictions.rb', line 85

def application
  @application
end

#userObject (readonly)

Returns the value of attribute user.



85
86
87
# File 'lib/has_helpers/utils/user_restrictions.rb', line 85

def user
  @user
end

Instance Method Details

#empty?Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/has_helpers/utils/user_restrictions.rb', line 103

def empty?
  restrictions.blank?
end

#restricted?(action, resource) ⇒ Boolean

Returns true if the given action is restricted for the resource. action: CanCanCan-style (:read, :create, ...); resource: String, Class, or an object responding to :klass (e.g. HasReports::Resource).

Returns:

  • (Boolean)


110
111
112
113
114
115
116
# File 'lib/has_helpers/utils/user_restrictions.rb', line 110

def restricted?(action, resource)
  resource_info = find_resource_info(resource)
  return false if resource_info.blank?

  operations = Array(resource_info["restriction_operations"]).map(&:to_s)
  (normalize_operations(action) & operations).any?
end

#restricted_resource_names(operations: READ_OPERATIONS) ⇒ Object

Names of the resources restricted for any of the given operations.



119
120
121
122
123
124
# File 'lib/has_helpers/utils/user_restrictions.rb', line 119

def restricted_resource_names(operations: READ_OPERATIONS)
  ops = Array(operations).map(&:to_s)
  restrictions.select do |_name, info|
    info.is_a?(Hash) && (Array(info["restriction_operations"]).map(&:to_s) & ops).any?
  end.keys
end

#restrictionsObject

Restrictions hash for the resolved application, keyed by resource name.



97
98
99
100
101
# File 'lib/has_helpers/utils/user_restrictions.rb', line 97

def restrictions
  return @restrictions if defined?(@restrictions)

  @restrictions = restrictions_for_app
end