Class: CurrentScope::PermissionCatalog

Inherits:
Object
  • Object
show all
Defined in:
lib/current_scope/permission_catalog.rb

Overview

Derives the permission set from the host's routes: one permission per controller#action pair. There is no table to maintain — add a controller and its actions appear in the grid on next boot/reload.

Instance Method Summary collapse

Instance Method Details

#bypass_actionObject

The ONE parse of config.sod_bypass_permission's action segment — the grid view and the ungated task read it here rather than re-splitting the string themselves (a looser split("#").last silently turns a malformed "reports#" into "reports" and mislabels the break-glass cell; #79 review).

split("#", -1) keeps the trailing empty field, so a malformed "reports#" yields "" and is caught here rather than silently becoming "reports" and injecting "reports#reports". Blank raises instead of skipping: the host turned break-glass ON, so a permission nobody can hold means the veto can never be lifted and the feature is inert — an undiagnosable deny, which is exactly what this engine promises not to do. (A boot-time check for this config belongs with #40.)



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/current_scope/permission_catalog.rb', line 42

def bypass_action
  segments = CurrentScope.config.sod_bypass_permission.to_s.split("#", -1)
  # Exactly a bare action or one controller#action. More hashes would pass
  # a last-segment check while the resolver reads the ORIGINAL full string —
  # the catalog would inject a key nobody can be granted under, and the
  # veto could never be lifted. (#79 review)
  if segments.empty? || segments.size > 2 || segments.any?(&:blank?)
    raise ConfigurationError,
          "config.allow_sod_bypass is on, but config.sod_bypass_permission " \
          "(#{CurrentScope.config.sod_bypass_permission.inspect}) is not a bare action or a " \
          "single controller#action. Name the permission the record's initiator must hold " \
          "to break glass (the default is \"bypass_sod\"), or set config.allow_sod_bypass = false."
  end

  segments.last
end

#groupedObject

{ "reports" => ["approve", "create", ...], ... } for the role-editor grid.



16
17
18
19
# File 'lib/current_scope/permission_catalog.rb', line 16

def grouped
  keys.group_by { |key| key.split("#").first }
      .transform_values { |ks| ks.map { |k| k.split("#").last } }
end

#include?(key) ⇒ Boolean

Hot: the Guard asks this on EVERY gated request, and a role save asks it once per staged key. Set lookup, not Array#include? — the array is sorted for display, and scanning it linearly made every request pay for the size of the host's route table. Memoized alongside keys and dropped with it on reset!.

Returns:

  • (Boolean)


26
27
28
# File 'lib/current_scope/permission_catalog.rb', line 26

def include?(key)
  key_set.include?(key)
end

#keysObject



11
12
13
# File 'lib/current_scope/permission_catalog.rb', line 11

def keys
  @keys ||= derive
end

#routed?(key) ⇒ Boolean

Is this key derived from a real route (as opposed to the injected break-glass key)? The distinction matters to anything making an inertness claim: a ROUTED action named like the bypass permission is an ordinary action and gets no exemption, while the injected key is live on any row. (#79 review)

Returns:

  • (Boolean)


64
65
66
67
# File 'lib/current_scope/permission_catalog.rb', line 64

def routed?(key)
  keys # derive memoizes @routed_key_set as a side effect
  @routed_key_set.include?(key)
end