Class: Clickwrap::Capture

Inherits:
Object
  • Object
show all
Defined in:
lib/clickwrap/capture.rb,
lib/clickwrap/capture/event_builder.rb,
lib/clickwrap/capture/presentation_verifier.rb

Overview

Turns a verified submission into an evidence event, and — when the caller asks for it — commits that event in the same database transaction as the action it authorizes.

That last part is the whole point of the gem, so it is worth being exact about what is promised.

For a same-database protected action, capture_and! joins the caller's transaction. If the evidence write fails, the block's work rolls back with it. If the block raises, the event rolls back with it. Neither side can commit alone, and there is no rescue-and-continue path anywhere in here: an account that exists without the evidence that authorized it is precisely the failure this class exists to make impossible.

For anything crossing a system boundary — a payment provider, an identity service, a remote signature — none of that applies, and pretending otherwise would be worse than useless. Use Clickwrap.authorize_external_action! and its outbox instead. This class will not claim atomicity it cannot deliver.

Defined Under Namespace

Classes: EventBuilder, PresentationVerifier

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(policy:, actor: nil, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil, prospective_actor: nil, registration_flow_id: nil, represented_party_creation_flow_id: nil, consume_one_time_authorizations: true, record_protected_outcome: true, reason: nil, event_type: "capture", root_event_id: nil, predecessor_event_id: nil, statement_action_overrides: {}) ⇒ Capture

Returns a new instance of Capture.



35
36
37
38
39
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
# File 'lib/clickwrap/capture.rb', line 35

def initialize(policy:, actor: nil, subject: nil, tenant: nil, http_request: nil,
               submission: nil, answers: nil, locale: nil, capture_channel: nil,
               acting_for: nil, authentication_context: nil, attribution_method: nil,
               idempotency_key: nil, prospective_actor: nil,
               registration_flow_id: nil,
               represented_party_creation_flow_id: nil,
               consume_one_time_authorizations: true,
               record_protected_outcome: true,
               reason: nil, event_type: "capture", root_event_id: nil,
               predecessor_event_id: nil, statement_action_overrides: {})
  @policy = policy
  @actor = actor
  @prospective_actor = prospective_actor
  @subject = subject
  @tenant = tenant
  @http_request = http_request
  @submission = submission
  @explicit_answers = answers
  @locale = locale
  @explicit_capture_channel = capture_channel&.to_s
  @capture_channel = @explicit_capture_channel
  @acting_for = acting_for
  @authentication_context = authentication_context
  @attribution_method = attribution_method
  @explicit_idempotency_key = idempotency_key
  @registration_flow_id = registration_flow_id
  @represented_party_creation_flow_id = represented_party_creation_flow_id
  @creating_represented_party = false
  @consume_one_time_authorizations = consume_one_time_authorizations
  @record_protected_outcome = record_protected_outcome == true
  @reason = reason
  @event_type = event_type.to_s
  @root_event_id = root_event_id
  @predecessor_event_id = predecessor_event_id
  @statement_action_overrides = statement_action_overrides.to_h.transform_keys(&:to_s)
end

Instance Attribute Details

#actorObject (readonly)

Returns the value of attribute actor.



72
73
74
# File 'lib/clickwrap/capture.rb', line 72

def actor
  @actor
end

#capture_channelObject (readonly)

Returns the value of attribute capture_channel.



72
73
74
# File 'lib/clickwrap/capture.rb', line 72

def capture_channel
  @capture_channel
end

#http_requestObject (readonly)

Returns the value of attribute http_request.



72
73
74
# File 'lib/clickwrap/capture.rb', line 72

def http_request
  @http_request
end

#policyObject (readonly)

Returns the value of attribute policy.



72
73
74
# File 'lib/clickwrap/capture.rb', line 72

def policy
  @policy
end

#subjectObject (readonly)

Returns the value of attribute subject.



72
73
74
# File 'lib/clickwrap/capture.rb', line 72

def subject
  @subject
end

#submissionObject (readonly)

Returns the value of attribute submission.



72
73
74
# File 'lib/clickwrap/capture.rb', line 72

def submission
  @submission
end

#tenantObject (readonly)

Returns the value of attribute tenant.



72
73
74
# File 'lib/clickwrap/capture.rb', line 72

def tenant
  @tenant
end

Instance Method Details

#capture!Object

Records evidence with no protected action attached.



75
76
77
# File 'lib/clickwrap/capture.rb', line 75

def capture!
  perform(protected_action: false) { |_pending| nil }
end

#capture_and!(&block) ⇒ Object

Records evidence and runs the protected action inside the same transaction. The block receives a read-only PendingReceipt whose stable event_id the domain row can reference; export and verification are unavailable on it until commit, because until commit there is nothing to export.

Raises:

  • (ArgumentError)


84
85
86
87
88
# File 'lib/clickwrap/capture.rb', line 84

def capture_and!(&block)
  raise ArgumentError, "capture_and! needs a block containing the protected action" unless block

  perform(protected_action: true, &block)
end

#create_represented_party!(&block) ⇒ Object

Creates a new record of the type named by represented_party: and binds the block's persisted result to the evidence before either can commit. Presentation records honestly that membership authority was not yet verifiable; after the block saves the record and creates its authority relationship, the adapter rereads that relationship inside this same transaction and the finalized event is rebound to the persisted represented party.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/clickwrap/capture.rb', line 122

def create_represented_party!(&block)
  unless block
    raise ArgumentError,
          "create_represented_party! needs a block that persists the represented party"
  end
  unless @acting_for.respond_to?(:new_record?) && @acting_for.new_record?
    raise RepresentedPartyCreationFailed,
          "create_represented_party! needs the exact new `represented_party:` record " \
          "that was used to render the presentation."
  end

  @prospective_represented_party_class = @acting_for.class
  @creating_represented_party = true

  perform(protected_action: true) do |pending|
    result = block.call(pending)

    unless result.respond_to?(:persisted?) && result.persisted?
      raise RepresentedPartyCreationFailed,
            "The represented-party creation block must return the persisted represented party. " \
            "Save it and its authority relationship inside the block, then return that record."
    end
    unless result.instance_of?(@prospective_represented_party_class)
      raise RepresentedPartyCreationFailed,
            "The represented-party creation block returned #{result.class.name}, but the " \
            "presentation was bound to #{@verified_manifest.represented_party_type}. Return a " \
            "persisted record of the presented type."
    end

    @acting_for = result

    result
  end
end

#register!(&block) ⇒ Object

Signup. At first render there is no persisted actor, so the presentation bound itself to a short-lived registration flow instead of to a fictional authenticated user. Here the account is created and its stable reference bound to the evidence, both inside one transaction, and the receipt records account_registration attribution rather than claiming a session that did not exist.

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/clickwrap/capture.rb', line 96

def register!(&block)
  raise ArgumentError, "register! needs a block that persists the account" unless block

  @attribution_method = "account_registration"

  perform(protected_action: true) do |pending|
    result = block.call(pending)

    unless @prospective_actor&.persisted?
      raise RegistrationFailed,
            "The registration block did not persist the prospective actor. Use `save!`, or " \
            "raise when validation fails, so Clickwrap can roll the evidence back with it."
    end

    rebind_actor_after_registration!(pending)
    result
  end
end