Class: Clickwrap::RemediationToken

Inherits:
Object
  • Object
show all
Defined in:
lib/clickwrap/remediation_token.rb

Overview

A short-lived, signed handoff from a host gate to the standalone capture screen. It binds the exact actor, tenant, policy, subject, represented party, subject fingerprint, and local return path. The browser transports this context; it never chooses it.

Defined Under Namespace

Classes: Context

Constant Summary collapse

PURPOSE =
"clickwrap/remediation"
SCHEMA =
"clickwrap.remediation.v1"

Class Method Summary collapse

Class Method Details

.issue(policy:, actor:, tenant: nil, subject: nil, represented_party: nil, return_to: nil, issued_at: nil) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/clickwrap/remediation_token.rb', line 16

def issue(policy:, actor:, tenant: nil, subject: nil, represented_party: nil,
          return_to: nil, issued_at: nil)
  issued_at ||= Clickwrap.now
  expires_at = issued_at + Clickwrap.config.remediation_token_valid_for

  attributes = {
    "schema" => SCHEMA,
    "policy" => policy.key,
    "actor_reference" => Reference.actor(actor),
    "tenant_reference" => Reference.tenant(tenant),
    "subject" => record_binding(subject, "subject", expires_at: expires_at),
    "subject_fingerprint" => SubjectFingerprint.for(policy, subject),
    "represented_party" => record_binding(
      represented_party, "represented_party", expires_at: expires_at
    ),
    "return_to" => return_to,
    "issued_at" => Receipt.format_time(issued_at),
    "expires_at" => Receipt.format_time(expires_at),
    "nonce" => SecureRandom.uuid
  }.compact

  verifier.generate(attributes, purpose: PURPOSE, expires_at: expires_at)
end

.reset_verifier!Object



82
# File 'lib/clickwrap/remediation_token.rb', line 82

def reset_verifier! = @verifier = nil

.resolve!(token, policy:, actor:) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/clickwrap/remediation_token.rb', line 40

def resolve!(token, policy:, actor:)
  attributes = verifier.verified(token.to_s, purpose: PURPOSE)
  raise RemediationInvalid, "The remediation token is missing, expired, or invalid." unless attributes

  unless attributes["schema"] == SCHEMA && attributes["policy"] == policy.key
    raise RemediationInvalid, "The remediation token belongs to a different policy."
  end

  unless secure_equal?(attributes["actor_reference"], Reference.actor(actor))
    raise RemediationInvalid, "The remediation token belongs to a different actor."
  end

  # The tenant is CARRIED, not compared: the gate resolved it server-side
  # and signed it, and the engine's own routes have no ambient tenant to
  # compare against — a comparison here permanently 404'd every
  # remediation issued from a tenant-scoped page. The signature is the
  # authority; the resolved context hands the tenant back to
  # presentation and capture exactly like the subject.

  subject = resolve_record(attributes["subject"], "subject")
  represented_party = resolve_record(attributes["represented_party"], "represented_party")
  expected_fingerprint = SubjectFingerprint.for(policy, subject)

  unless secure_equal?(attributes["subject_fingerprint"], expected_fingerprint)
    raise RemediationInvalid,
          "The remediation subject changed after this route was issued. Return to the " \
          "blocked action and start again against its current state."
  end

  Context.new(
    policy: policy,
    actor_reference: attributes["actor_reference"],
    tenant_reference: attributes["tenant_reference"],
    subject: subject,
    represented_party: represented_party,
    return_to: attributes["return_to"],
    attributes: attributes.freeze
  )
rescue ActiveSupport::MessageVerifier::InvalidSignature
  raise RemediationInvalid, "The remediation token is missing, expired, or invalid."
end