Class: Clickwrap::Presenter

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

Overview

Builds a presentation: resolves the policy's documents and copy for one locale, records the exact call to action, and signs the result into a short-lived token.

This is the one place that turns a policy into something a person can see, and everything that renders — the form-builder helper, the reference views, the engine's standalone screen, a JSON API response, a fully custom design system — goes through it. There is deliberately no second path: a shortcut that skipped the manifest would produce evidence that looks identical and proves considerably less.

Defined Under Namespace

Classes: Combined, Document, Fragment, Result, Statement

Constant Summary collapse

COMPOSABLE_KINDS =

The only two kinds a composed sentence may absorb. A consent must stay unbundled to be separately withdrawable and separately provable, and a declaration, attestation, or authorization is a specific assertion someone should have to read on its own line rather than find folded into a sentence about something else.

%w[agreement acknowledgment].freeze
DOCUMENTS_PLACEHOLDER =

Where a sentence fragment puts the documents it is about.

"%{documents}"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(policy:, actor: nil, subject: nil, tenant: nil, locale: nil, submit_button_text: nil, capture_channel: :web_browser, registration_flow_id: nil, prospective_actor: nil, acting_for: nil, represented_party_creation_flow_id: nil, authentication_context: nil, document_version_path_with: nil, default_document_version_path_with: nil, combined: true) ⇒ Presenter

Returns a new instance of Presenter.

Raises:

  • (ArgumentError)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/clickwrap/presenter.rb', line 110

def initialize(policy:, actor: nil, subject: nil, tenant: nil, locale: nil,
               submit_button_text: nil, capture_channel: :web_browser,
               registration_flow_id: nil, prospective_actor: nil, acting_for: nil,
               represented_party_creation_flow_id: nil,
               authentication_context: nil,
               document_version_path_with: nil,
               default_document_version_path_with: nil,
               combined: true)
  @policy = policy
  @actor = actor
  @prospective_actor = prospective_actor
  @subject = subject
  @tenant = tenant
  @locale = (locale || default_locale).to_s
  @submit_button_text = submit_button_text
  @capture_channel = capture_channel.to_s
  @registration_flow_id = registration_flow_id
  @acting_for = acting_for
  @represented_party_creation_flow_id = represented_party_creation_flow_id
  @authentication_context = (authentication_context || {}).to_h.symbolize_keys
  @document_version_path_with = document_version_path_with
  # Wired by `form.clickwrap` and `present_clickwrap` from the render
  # context, never by a host: it is the engine fallback with the render's
  # own Hotwire Native treatment attached, and it is asked LAST, after a
  # document's declared `link:` has had its say.
  @default_document_version_path_with = default_document_version_path_with
  @combined = combined != false

  return unless @document_version_path_with && !@document_version_path_with.respond_to?(:call)

  raise ArgumentError,
        "document_version_path_with must respond to call(version), so Clickwrap can bind " \
        "the exact immutable link target into the presentation."
end

Instance Attribute Details

#actorObject (readonly)

Returns the value of attribute actor.



145
146
147
# File 'lib/clickwrap/presenter.rb', line 145

def actor
  @actor
end

#capture_channelObject (readonly)

Returns the value of attribute capture_channel.



145
146
147
# File 'lib/clickwrap/presenter.rb', line 145

def capture_channel
  @capture_channel
end

#localeObject (readonly)

Returns the value of attribute locale.



145
146
147
# File 'lib/clickwrap/presenter.rb', line 145

def locale
  @locale
end

#policyObject (readonly)

Returns the value of attribute policy.



145
146
147
# File 'lib/clickwrap/presenter.rb', line 145

def policy
  @policy
end

#subjectObject (readonly)

Returns the value of attribute subject.



145
146
147
# File 'lib/clickwrap/presenter.rb', line 145

def subject
  @subject
end

#tenantObject (readonly)

Returns the value of attribute tenant.



145
146
147
# File 'lib/clickwrap/presenter.rb', line 145

def tenant
  @tenant
end

Instance Method Details

#actor_referenceObject



213
214
215
216
217
# File 'lib/clickwrap/presenter.rb', line 213

def actor_reference
  return nil if actor.nil?

  Reference.actor(actor)
end

#presentObject



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/clickwrap/presenter.rb', line 147

def present
  policy.validate_tenant!(tenant)
  validate_actor_binding!
  validate_subject_binding!
  validate_represented_party_binding!
  validate_channel!
  validate_locale!
  authority_at_presentation = authority_at_presentation_snapshot

  revision = PolicyRevision.freeze_for(policy)
  resolved = policy.statements.map { |statement| resolve_statement(statement) }
  combined = compose(resolved)

  manifest = PresentationManifest.build(
    policy: policy,
    revision_digest: revision.revision_digest,
    statements: resolved.map { |statement| manifest_fragment(statement) },
    submit_button_text: @submit_button_text,
    locale: locale,
    actor_reference: actor_reference,
    actor_type: actor&.class&.name,
    tenant_key: tenant_key,
    subject_key: subject_key,
    subject_fingerprint: subject_fingerprint,
    registration_flow_id: @registration_flow_id,
    prospective_actor_type: @prospective_actor&.class&.name,
    represented_party_reference: represented_party_reference,
    represented_party_type: @acting_for&.class&.name,
    authority_rule: policy.authority_rule&.to_snapshot,
    represented_party_creation_flow_id: @represented_party_creation_flow_id,
    represented_party_will_be_created_by_protected_action:
      prospective_represented_party?,
    authority_at_presentation: authority_at_presentation,
    combined_control: combined_control_fragment(combined),
    capture_channel: capture_channel
  )

  persist_presentation(manifest, revision) if policy.persist_presentations?

  Result.new(
    policy: policy,
    manifest: manifest,
    token: manifest.to_token,
    statements: resolved,
    combined: combined,
    submit_button_text: @submit_button_text,
    locale: locale,
    actor: actor,
    subject: subject,
    represented_party: @acting_for,
    tenant_key: tenant_key
  )
end

#represented_party_referenceObject



219
220
221
222
223
224
# File 'lib/clickwrap/presenter.rb', line 219

def represented_party_reference
  return nil if @acting_for.nil?
  return "represented_party_creation/#{@represented_party_creation_flow_id}" if prospective_represented_party?

  Reference.represented_party(@acting_for)
end

#subject_fingerprintObject

The fingerprint of whatever the policy says identifies this subject. It is how an authorization for one withdrawal stops being usable for a different one, and how a declaration about one set of orders stops covering a changed set.



205
206
207
# File 'lib/clickwrap/presenter.rb', line 205

def subject_fingerprint
  SubjectFingerprint.for(policy, subject)
end

#subject_keyObject



209
# File 'lib/clickwrap/presenter.rb', line 209

def subject_key = StatementState.subject_key_for(subject)

#tenant_keyObject



211
# File 'lib/clickwrap/presenter.rb', line 211

def tenant_key = Reference.tenant(tenant)