Module: LcpRuby::Authorization::PageGate

Defined in:
lib/lcp_ruby/authorization/page_gate.rb

Overview

Pure-function gate that decides whether a request to a routable page should be allowed, denied, or treated as misconfigured. Controller-state-free; called by ‘LcpRuby::PageAuthorization`.

Returns one of ‘:allow / :deny / :misconfigured_no_gate / :misconfigured_malformed`. The concern maps the two misconfigured-* symbols to `record_error` codes AUTH-002-runtime (no gate) vs AUTH-003 (malformed). `LcpRuby::ConditionError` propagates uncaught — concern’s rescue handles the dev/prod split + AUTH-010.

See docs/design/authorization_hardening.md § “authorize_page! decision table”.

Constant Summary collapse

PUBLIC_OPT_IN_ROLES =

Public-opt-in sentinel. Matches scalar (‘{ role: “any” }`) and single-element-array (`{ role: [“any”] }`) forms. Mixed arrays like `[“any”, “admin”]` fall through to literal role matching. NOTE: only PageGate honors `:any`; zone_resolution.rb:137-140 and parameter_definition.rb:90-95 treat `“any”` as a literal role name. Aligning those three sites is a follow-up.

[ "any" ].freeze

Class Method Summary collapse

Class Method Details

.evaluate(page, user:, record:) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/lcp_ruby/authorization/page_gate.rb', line 27

def evaluate(page, user:, record:)
  vw = page.visible_when

  if vw.nil?
    return :allow if page.any_zone_gated?
    return :allow unless page.standalone?
    return :misconfigured_no_gate
  end

  return :misconfigured_malformed unless Conditions::Validator.parseable?(vw)

  # Role-shortcut — PageGate handles `{ role: ... }` directly
  # because ConditionEvaluator.evaluate_any only recognizes
  # condition-object shapes (field/service/compound/collection).
  # `vw` is already stringify_deep'd; size==1 ensures extra keys
  # fall through to the evaluator instead of being ignored.
  if vw.size == 1 && vw.key?("role")
    required = Array(vw["role"]).map(&:to_s)
    return :allow if required == PUBLIC_OPT_IN_ROLES
    return :deny unless user
    return (LcpRuby.user_roles(user) & required).any? ? :allow : :deny
  end

  decision = ConditionEvaluator.evaluate_any(
    record, vw, context: { current_user: user }
  )
  decision ? :allow : :deny
end