Class: Clickwrap::Configuration

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

Overview

The single configuration object the host populates in config/initializers/clickwrap.rb via Clickwrap.configure do |config| ... end.

Design rules:

- Runtime adapters and class names are read at the point of use. Policy
semantics — including merged request-evidence defaults — are copied into
the immutable compiled policy revision at boot, so changing this object
cannot mutate a form that was already rendered.
- Class names are stored as strings and constantized lazily, so the
initializer works no matter when the app loads (the User model may not
exist yet at boot).
- Validating setters normalize their input and raise a plain-English
ConfigurationError on a bad value — failing at the assignment line
rather than at 3am with a NoMethodError. `validate!` runs once more at
the end of `configure` for the cross-field checks.
- Every hook defaults to a no-op, so the gem works untouched and a host
wires hooks only as needed.
- Nothing here collects personal data by default. Every `record_*` flag
starts false. Turning one on takes one line and nothing else; the
purpose and the disposal answer have honest gem-supplied defaults, and
a host who wants reviewed ones writes them.

One setting deserves its own note: there is deliberately no gdpr_compliant_mode, maximum_evidence, full_evidence, or legal_proof. Those names hide what they collect and pretend to make a legal determination, which is exactly the thing this gem exists not to do. record_request_evidence_by_default is the opposite kind of switch: it says out loud what it records (an IP address, a browser user agent, and a coarse country/region/city estimate), it enables nothing finer, and it claims nothing about the law.

Constant Summary collapse

DOCUMENT_STORES =
%i[database active_storage resolver].freeze
DIGEST_ALGORITHMS =
%i[sha256 sha384 sha512].freeze
%i[external_browser same_screen].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



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
144
145
146
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/clickwrap/configuration.rb', line 114

def initialize
  # Identity. "User" is the overwhelmingly common case; the host overrides
  # it if their actor model is "Account", "Member", or something else.
  @actor_class_name = "User"
  @current_actor_method_name = :current_user
  @parent_controller_class_name = "ApplicationController"
  @find_current_tenant_with = ->(_controller) {}

  # How an actor is referenced in evidence. Prefer a host override, then a
  # GlobalID when available, then a stable class/id string in minimal Rails.
  # The resulting string survives row deletion instead of becoming a
  # cascading foreign-key loss.
  @identify_actor_with = ->(actor) { default_actor_reference(actor) }

  # Actor snapshots contain only what the host names. Clickwrap never
  # serializes a whole user into evidence: a receipt should carry the
  # fields someone reviewed and chose, not every column that happened to
  # exist on the day it was written.
  @snapshot_actor_with = ->(_actor) { {} }

  # An honest default: when the controller can name a current actor, the
  # request ran under an application-authenticated session; when it cannot
  # — a signup form, a public capture screen, a controller with no
  # authentication at all — nothing is claimed. The temptation this guards
  # against is describing every request as authenticated merely because it
  # passed through ApplicationController. Deliberately gentler than the
  # capture path's actor resolution: describing authentication is context,
  # not identity, so a missing actor method here means "nothing to
  # describe", never an error.
  configuration = self
  @describe_authentication_with = lambda do |controller|
    method_name = configuration.current_actor_method_name
    actor = controller.respond_to?(method_name, true) ? controller.send(method_name) : nil
    actor ? { method: :authenticated_session } : {}
  end

  # Documents and policies.
  @store_document_contents_in = :database
  @document_renderer = DocumentRenderer.new
  @document_resolver = nil
  @document_link_html_options_with = ->(_document) { { target: "_blank", rel: "noopener" } }
  @hotwire_native_document_links = nil
  @policy_paths = ["config/clickwrap.rb", "config/clickwrap/*.rb"]

  # Publishing rides `db:prepare`, so the deploy step everyone forgets
  # does not exist: by the time the server takes traffic, every declared
  # document version has an immutable snapshot. Idempotent — an
  # already-published version is left untouched — and a publish refusal
  # (a reused label over changed bytes) fails the deploy loudly, which is
  # strictly better than signups failing quietly later.
  @publish_documents_after_database_preparation = true

  # A required legal statement with no translation is not presentable. Fail
  # rather than show a raw I18n key, a blank, or an unexpected language.
  @raise_on_missing_translation = true

  # nil means "decide from the environment": on in development and test,
  # off everywhere else. `true` and `false` answer for a host that
  # disagrees with either half — a linter nobody can turn off is a warning
  # people learn to scroll past.
  @lint_presentations = nil

  # Presentation manifests are short-lived by design: they bind a render to
  # a submit, and a token that stayed valid for days would weaken exactly
  # the substitution check it exists to make.
  @presentation_valid_for = 2.hours
  @remediation_token_valid_for = 2.hours

  # Integrity. The baseline detects accidental or ordinary mutation of the
  # verified bytes. Chains, anchors, and third-party timestamps are separate,
  # explicitly enabled tiers, and each one claims only what it supplies.
  @digest_canonical_receipts_with = :sha256
  @chain_event_history_with = nil
  @anchor_event_history_with = nil
  @timestamp_receipts_with = nil
  @application_version = -> {}
  @template_version = -> {}

  # Authorization. Actors can read their own receipts; anything wider is
  # the host's decision, and unredacted request evidence needs a reason.
  @authorize_receipt_access_with = ->(_controller, _receipt) { false }
  @authorize_unredacted_request_evidence_access_with = ->(_controller, _receipt, _because) { false }
  @verify_actor_can_act_for_represented_party_with = lambda do |actor:, represented_party:, policy:,
                                                                authentication_context:, tenant:|
    AuthorityDecision.new(authorized: false)
  end
  @authorize_clickwrap_remediation_subject_with = lambda do |actor:, subject:, policy:, controller:|
    subject.nil?
  end
  @authorize_clickwrap_remediation_represented_party_with =
    lambda do |actor:, represented_party:, policy:, controller:|
      represented_party.nil?
    end
  @remediation_subject_authorization_configured = false
  @remediation_represented_party_authorization_configured = false
  @represented_party_authority_adapters = {
    "organizations_membership" => Integrations::OrganizationsAuthority.new
  }

  # Request evidence. Every one of these is false, and that is the whole
  # point. Recording an IP address is a decision with consequences; the
  # library will not make it silently on a host's behalf.
  @record_ip_address_by_default = false
  @record_browser_user_agent_by_default = false
  Vocabulary::IP_GEOLOCATION_DATA_FIELDS.each do |field|
    instance_variable_set(:"@record_ip_geolocation_#{field}_by_default", false)
  end

  @reason_for_recording_ip_addresses_by_default = nil
  @reason_for_recording_browser_user_agents_by_default = nil
  @reason_for_recording_ip_geolocation_by_default = nil
  @legal_basis_reference_for_recording_ip_addresses_by_default = nil
  @legal_basis_reference_for_recording_browser_user_agents_by_default = nil
  @legal_basis_reference_for_recording_ip_geolocation_by_default = nil
  @review_default_request_evidence_configuration_on = nil

  @encrypt_recorded_ip_addresses = true
  @encrypt_recorded_browser_user_agents = true
  @encrypt_recorded_ip_geolocation = true

  # nil means "every policy that enables the field must supply its own
  # rule" — or, for by-default recording, that the host has said
  # `keep_recorded_..._indefinitely!(because: "…")` out loud. Keeping
  # forever is never silent: it is either the per-policy retention class's
  # explicit business, or a named, reasoned sentence in the initializer.
  @delete_recorded_ip_addresses_after = nil
  @delete_recorded_browser_user_agents_after = nil
  @delete_recorded_ip_geolocation_after = nil
  @keep_recorded_request_evidence_indefinitely = {}

  # Rails' request.remote_ip is the conventional reader. The host remains
  # responsible for configuring and testing trusted proxies correctly:
  # https://api.rubyonrails.org/classes/ActionDispatch/RemoteIp.html
  @read_ip_address_from_http_request_with = ->(http_request) { http_request.remote_ip }
  @read_browser_user_agent_from_http_request_with = ->(http_request) { http_request.user_agent }
  @trusted_proxy_configuration_digest = nil

  @ip_geolocation_resolver = nil
  @ip_geolocation_resolvers = {}
  @automatically_adopted_ip_geolocation_resolver = nil
  @considered_automatic_ip_geolocation_resolver = false
  @fail_capture_when_ip_geolocation_is_unavailable = false

  # The keyed annex digest carries a key ID so a host can rotate keys
  # without making old annexes unverifiable. The default derives one key
  # from Rails' key generator and names it by a non-secret fingerprint.
  # Production hosts with explicit key rotation can replace both settings
  # with a credentials-backed keyring.
  @current_request_evidence_binding_key_id = nil
  @find_request_evidence_binding_key_with = lambda do |requested_key_id|
    key = default_request_evidence_binding_key
    expected_id = default_request_evidence_binding_key_id(key)
    key if key && Digest.secure_compare?(requested_key_id.to_s, expected_id.to_s)
  end

  # Hooks. These run only after required evidence and domain state have
  # committed, and a failure here is reported but can never undo the
  # committed action.
  @after_event_is_committed = ->(_event) {}
  @report_after_commit_failure_with = ->(_error, _event) {}

  # Host-registered retention calculations, keyed by the name a retention
  # class refers to.
  @retention_time_calculators = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *arguments, **options) ⇒ Object



1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
# File 'lib/clickwrap/configuration.rb', line 1260

def method_missing(name, *arguments, **options, &)
  if name.to_s.end_with?("=")
    setting = name.to_s.delete_suffix("=")
    raise ConfigurationError,
          "Clickwrap has no initializer setting named `config.#{setting}`. Check the " \
          "spelling; unknown settings are refused so a typo can never look configured."
  end

  super
end

Instance Attribute Details

#actor_class_nameObject

The readers are grouped by section on purpose, and kept that way even though rubocop would happily collapse them into one line. The initializer this class backs is the main thing a host reads about Clickwrap, and the shape of these groups is the shape of that file.

--- Identity -------------------------------------------------------------



46
47
48
# File 'lib/clickwrap/configuration.rb', line 46

def actor_class_name
  @actor_class_name
end

#after_event_is_committedObject

--- Hooks ----------------------------------------------------------------



112
113
114
# File 'lib/clickwrap/configuration.rb', line 112

def after_event_is_committed
  @after_event_is_committed
end

#anchor_event_history_withObject

Returns the value of attribute anchor_event_history_with.



68
69
70
# File 'lib/clickwrap/configuration.rb', line 68

def anchor_event_history_with
  @anchor_event_history_with
end

#application_versionObject

Returns the value of attribute application_version.



69
70
71
# File 'lib/clickwrap/configuration.rb', line 69

def application_version
  @application_version
end

#authorize_clickwrap_remediation_represented_party_withObject

Returns the value of attribute authorize_clickwrap_remediation_represented_party_with.



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

def authorize_clickwrap_remediation_represented_party_with
  @authorize_clickwrap_remediation_represented_party_with
end

#authorize_clickwrap_remediation_subject_withObject

Returns the value of attribute authorize_clickwrap_remediation_subject_with.



74
75
76
# File 'lib/clickwrap/configuration.rb', line 74

def authorize_clickwrap_remediation_subject_with
  @authorize_clickwrap_remediation_subject_with
end

#authorize_receipt_access_withObject

--- Authorization --------------------------------------------------------



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

def authorize_receipt_access_with
  @authorize_receipt_access_with
end

#authorize_unredacted_request_evidence_access_withObject

--- Authorization --------------------------------------------------------



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

def authorize_unredacted_request_evidence_access_with
  @authorize_unredacted_request_evidence_access_with
end

#chain_event_history_withObject

--- Integrity ------------------------------------------------------------



67
68
69
# File 'lib/clickwrap/configuration.rb', line 67

def chain_event_history_with
  @chain_event_history_with
end

#current_actor_method_nameObject

The readers are grouped by section on purpose, and kept that way even though rubocop would happily collapse them into one line. The initializer this class backs is the main thing a host reads about Clickwrap, and the shape of these groups is the shape of that file.

--- Identity -------------------------------------------------------------



46
47
48
# File 'lib/clickwrap/configuration.rb', line 46

def current_actor_method_name
  @current_actor_method_name
end

#delete_recorded_browser_user_agents_afterObject

Returns the value of attribute delete_recorded_browser_user_agents_after.



104
105
106
# File 'lib/clickwrap/configuration.rb', line 104

def delete_recorded_browser_user_agents_after
  @delete_recorded_browser_user_agents_after
end

#delete_recorded_ip_addresses_afterObject

Returns the value of attribute delete_recorded_ip_addresses_after.



104
105
106
# File 'lib/clickwrap/configuration.rb', line 104

def delete_recorded_ip_addresses_after
  @delete_recorded_ip_addresses_after
end

#delete_recorded_ip_geolocation_afterObject

Returns the value of attribute delete_recorded_ip_geolocation_after.



104
105
106
# File 'lib/clickwrap/configuration.rb', line 104

def delete_recorded_ip_geolocation_after
  @delete_recorded_ip_geolocation_after
end

#describe_authentication_withObject

Returns the value of attribute describe_authentication_with.



48
49
50
# File 'lib/clickwrap/configuration.rb', line 48

def describe_authentication_with
  @describe_authentication_with
end

#digest_canonical_receipts_withObject

--- Integrity ------------------------------------------------------------



67
68
69
# File 'lib/clickwrap/configuration.rb', line 67

def digest_canonical_receipts_with
  @digest_canonical_receipts_with
end

Returns the value of attribute document_link_html_options_with.



52
53
54
# File 'lib/clickwrap/configuration.rb', line 52

def document_link_html_options_with
  @document_link_html_options_with
end

#document_rendererObject

--- Documents and policies -----------------------------------------------



51
52
53
# File 'lib/clickwrap/configuration.rb', line 51

def document_renderer
  @document_renderer
end

#document_resolverObject

--- Documents and policies -----------------------------------------------



51
52
53
# File 'lib/clickwrap/configuration.rb', line 51

def document_resolver
  @document_resolver
end

#encrypt_recorded_browser_user_agentsObject

Returns the value of attribute encrypt_recorded_browser_user_agents.



102
103
104
# File 'lib/clickwrap/configuration.rb', line 102

def encrypt_recorded_browser_user_agents
  @encrypt_recorded_browser_user_agents
end

#encrypt_recorded_ip_addressesObject

Returns the value of attribute encrypt_recorded_ip_addresses.



102
103
104
# File 'lib/clickwrap/configuration.rb', line 102

def encrypt_recorded_ip_addresses
  @encrypt_recorded_ip_addresses
end

#encrypt_recorded_ip_geolocationObject

Returns the value of attribute encrypt_recorded_ip_geolocation.



102
103
104
# File 'lib/clickwrap/configuration.rb', line 102

def encrypt_recorded_ip_geolocation
  @encrypt_recorded_ip_geolocation
end

#fail_capture_when_ip_geolocation_is_unavailableObject

Returns the value of attribute fail_capture_when_ip_geolocation_is_unavailable.



107
108
109
# File 'lib/clickwrap/configuration.rb', line 107

def fail_capture_when_ip_geolocation_is_unavailable
  @fail_capture_when_ip_geolocation_is_unavailable
end

#find_current_tenant_withObject

Returns the value of attribute find_current_tenant_with.



47
48
49
# File 'lib/clickwrap/configuration.rb', line 47

def find_current_tenant_with
  @find_current_tenant_with
end

#find_request_evidence_binding_key_withObject

Returns the value of attribute find_request_evidence_binding_key_with.



109
110
111
# File 'lib/clickwrap/configuration.rb', line 109

def find_request_evidence_binding_key_with
  @find_request_evidence_binding_key_with
end

Returns the value of attribute hotwire_native_document_links.



53
54
55
# File 'lib/clickwrap/configuration.rb', line 53

def hotwire_native_document_links
  @hotwire_native_document_links
end

#identify_actor_withObject

Returns the value of attribute identify_actor_with.



47
48
49
# File 'lib/clickwrap/configuration.rb', line 47

def identify_actor_with
  @identify_actor_with
end

#ip_geolocation_resolverObject

Returns the value of attribute ip_geolocation_resolver.



107
108
109
# File 'lib/clickwrap/configuration.rb', line 107

def ip_geolocation_resolver
  @ip_geolocation_resolver
end

--- Request evidence: why, how long, and how it is protected -------------



94
95
96
# File 'lib/clickwrap/configuration.rb', line 94

def legal_basis_reference_for_recording_browser_user_agents_by_default
  @legal_basis_reference_for_recording_browser_user_agents_by_default
end

--- Request evidence: why, how long, and how it is protected -------------



94
95
96
# File 'lib/clickwrap/configuration.rb', line 94

def legal_basis_reference_for_recording_ip_addresses_by_default
  @legal_basis_reference_for_recording_ip_addresses_by_default
end

--- Request evidence: why, how long, and how it is protected -------------



94
95
96
# File 'lib/clickwrap/configuration.rb', line 94

def legal_basis_reference_for_recording_ip_geolocation_by_default
  @legal_basis_reference_for_recording_ip_geolocation_by_default
end

#lint_presentationsObject

--- Development aids -----------------------------------------------------



60
61
62
# File 'lib/clickwrap/configuration.rb', line 60

def lint_presentations
  @lint_presentations
end

#parent_controller_class_nameObject

The readers are grouped by section on purpose, and kept that way even though rubocop would happily collapse them into one line. The initializer this class backs is the main thing a host reads about Clickwrap, and the shape of these groups is the shape of that file.

--- Identity -------------------------------------------------------------



46
47
48
# File 'lib/clickwrap/configuration.rb', line 46

def parent_controller_class_name
  @parent_controller_class_name
end

#policy_pathsObject

Returns the value of attribute policy_paths.



55
56
57
# File 'lib/clickwrap/configuration.rb', line 55

def policy_paths
  @policy_paths
end

#presentation_valid_forObject

--- Presentation ---------------------------------------------------------



63
64
65
# File 'lib/clickwrap/configuration.rb', line 63

def presentation_valid_for
  @presentation_valid_for
end

#publish_documents_after_database_preparationObject

Returns the value of attribute publish_documents_after_database_preparation.



54
55
56
# File 'lib/clickwrap/configuration.rb', line 54

def publish_documents_after_database_preparation
  @publish_documents_after_database_preparation
end

#raise_on_missing_translationObject

Returns the value of attribute raise_on_missing_translation.



57
58
59
# File 'lib/clickwrap/configuration.rb', line 57

def raise_on_missing_translation
  @raise_on_missing_translation
end

#read_browser_user_agent_from_http_request_withObject

Returns the value of attribute read_browser_user_agent_from_http_request_with.



106
107
108
# File 'lib/clickwrap/configuration.rb', line 106

def read_browser_user_agent_from_http_request_with
  @read_browser_user_agent_from_http_request_with
end

#read_ip_address_from_http_request_withObject

Returns the value of attribute read_ip_address_from_http_request_with.



106
107
108
# File 'lib/clickwrap/configuration.rb', line 106

def read_ip_address_from_http_request_with
  @read_ip_address_from_http_request_with
end

#reason_for_recording_browser_user_agents_by_defaultObject

--- Request evidence: why, how long, and how it is protected -------------



94
95
96
# File 'lib/clickwrap/configuration.rb', line 94

def reason_for_recording_browser_user_agents_by_default
  @reason_for_recording_browser_user_agents_by_default
end

#reason_for_recording_ip_addresses_by_defaultObject

--- Request evidence: why, how long, and how it is protected -------------



94
95
96
# File 'lib/clickwrap/configuration.rb', line 94

def reason_for_recording_ip_addresses_by_default
  @reason_for_recording_ip_addresses_by_default
end

#reason_for_recording_ip_geolocation_by_defaultObject

--- Request evidence: why, how long, and how it is protected -------------



94
95
96
# File 'lib/clickwrap/configuration.rb', line 94

def reason_for_recording_ip_geolocation_by_default
  @reason_for_recording_ip_geolocation_by_default
end

#reason_for_storing_request_evidence_unencryptedObject (readonly)

Returns the value of attribute reason_for_storing_request_evidence_unencrypted.



108
109
110
# File 'lib/clickwrap/configuration.rb', line 108

def reason_for_storing_request_evidence_unencrypted
  @reason_for_storing_request_evidence_unencrypted
end

#record_browser_user_agent_by_defaultObject

--- Request evidence: what is recorded by default ------------------------

One reader per IP-geolocation field, spelled out rather than generated, because each is a separate decision about what to keep about someone's network context and each should be greppable by its own name.



82
83
84
# File 'lib/clickwrap/configuration.rb', line 82

def record_browser_user_agent_by_default
  @record_browser_user_agent_by_default
end

#record_ip_address_by_defaultObject

--- Request evidence: what is recorded by default ------------------------

One reader per IP-geolocation field, spelled out rather than generated, because each is a separate decision about what to keep about someone's network context and each should be greppable by its own name.



82
83
84
# File 'lib/clickwrap/configuration.rb', line 82

def record_ip_address_by_default
  @record_ip_address_by_default
end

#record_ip_geolocation_accuracy_radius_in_kilometers_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_accuracy_radius_in_kilometers_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_accuracy_radius_in_kilometers_by_default
  @record_ip_geolocation_accuracy_radius_in_kilometers_by_default
end

#record_ip_geolocation_city_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_city_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_city_by_default
  @record_ip_geolocation_city_by_default
end

#record_ip_geolocation_continent_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_continent_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_continent_by_default
  @record_ip_geolocation_continent_by_default
end

#record_ip_geolocation_country_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_country_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_country_by_default
  @record_ip_geolocation_country_by_default
end

#record_ip_geolocation_latitude_and_longitude_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_latitude_and_longitude_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_latitude_and_longitude_by_default
  @record_ip_geolocation_latitude_and_longitude_by_default
end

#record_ip_geolocation_metro_code_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_metro_code_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_metro_code_by_default
  @record_ip_geolocation_metro_code_by_default
end

#record_ip_geolocation_postal_code_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_postal_code_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_postal_code_by_default
  @record_ip_geolocation_postal_code_by_default
end

#record_ip_geolocation_region_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_region_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_region_by_default
  @record_ip_geolocation_region_by_default
end

#record_ip_geolocation_timezone_by_defaultObject (readonly)

Returns the value of attribute record_ip_geolocation_timezone_by_default.



83
84
85
# File 'lib/clickwrap/configuration.rb', line 83

def record_ip_geolocation_timezone_by_default
  @record_ip_geolocation_timezone_by_default
end

#remediation_token_valid_forObject

Returns the value of attribute remediation_token_valid_for.



64
65
66
# File 'lib/clickwrap/configuration.rb', line 64

def remediation_token_valid_for
  @remediation_token_valid_for
end

#report_after_commit_failure_withObject

--- Hooks ----------------------------------------------------------------



112
113
114
# File 'lib/clickwrap/configuration.rb', line 112

def report_after_commit_failure_with
  @report_after_commit_failure_with
end

#review_default_request_evidence_configuration_onObject

--- Request evidence: why, how long, and how it is protected -------------



94
95
96
# File 'lib/clickwrap/configuration.rb', line 94

def review_default_request_evidence_configuration_on
  @review_default_request_evidence_configuration_on
end

#snapshot_actor_withObject

Returns the value of attribute snapshot_actor_with.



47
48
49
# File 'lib/clickwrap/configuration.rb', line 47

def snapshot_actor_with
  @snapshot_actor_with
end

#store_document_contents_inObject

--- Documents and policies -----------------------------------------------



51
52
53
# File 'lib/clickwrap/configuration.rb', line 51

def store_document_contents_in
  @store_document_contents_in
end

#template_versionObject

Returns the value of attribute template_version.



69
70
71
# File 'lib/clickwrap/configuration.rb', line 69

def template_version
  @template_version
end

#timestamp_receipts_withObject

Returns the value of attribute timestamp_receipts_with.



68
69
70
# File 'lib/clickwrap/configuration.rb', line 68

def timestamp_receipts_with
  @timestamp_receipts_with
end

#trusted_proxy_configuration_digestObject

Returns the value of attribute trusted_proxy_configuration_digest.



108
109
110
# File 'lib/clickwrap/configuration.rb', line 108

def trusted_proxy_configuration_digest
  @trusted_proxy_configuration_digest
end

#verify_actor_can_act_for_represented_party_withObject

Returns the value of attribute verify_actor_can_act_for_represented_party_with.



73
74
75
# File 'lib/clickwrap/configuration.rb', line 73

def verify_actor_can_act_for_represented_party_with
  @verify_actor_can_act_for_represented_party_with
end

Instance Method Details

#actor_classObject

The constantized actor class, resolved lazily on EVERY use. Lazy on purpose: the initializer that sets config.actor_class_name = "User" runs before the User model is necessarily loaded.

Not memoized, deliberately. This used to cache the resolved Class object and re-resolve only when the class NAME changed — which is never, since the name is set once in an initializer. In a Rails app with reloading, the class behind that name is replaced on every code change: Zeitwerk unloads the old User and defines a new one, same name, different object. The cache then held a class no living record was an instance of, and actor.is_a?(config.actor_class) failed for a perfectly ordinary User.

What that looked like from the outside was worse than the bug: the ConfigurationError names the offending class and the configured class, and both printed "User". The message read as a contradiction, sent people to an initializer that was correct, and came back after every edit — the first signup of a dev session worked and the second did not.

constantize after the first load is a const_get, and this is called once per capture rather than in a loop, so there was nothing to save. parent_controller_class below has always resolved this way.



331
332
333
# File 'lib/clickwrap/configuration.rb', line 331

def actor_class
  actor_class_name.constantize
end

#application_default_ip_geolocation_resolverObject

What a policy gets when it does not name a resolver: the host's own, or — when they never set one and their bundle already carries trackdown 0.4 or newer — the official adapter for it. That is the whole "trackdown plus Cloudflare just works" path, and it is deliberately not a silent collection decision: nothing calls this until a policy has already enabled an IP-geolocation field.

An installed-but-too-old trackdown is NOT hidden here. The adapter's own sentence about upgrading is more useful than pretending the gem is missing.



795
796
797
# File 'lib/clickwrap/configuration.rb', line 795

def application_default_ip_geolocation_resolver
  @ip_geolocation_resolver || automatically_adopted_ip_geolocation_resolver
end

#calculate_retention_time_for(name, &block) ⇒ Object

Registers a host calculation for an event-based retention rule.

config.calculate_retention_time_for :regulated_evidence_retention_ends do |event|
[event.recorded_at_by_server + 5.years,
 event.subject_liquidated_at&.+(3.years)].compact.max
end

Returning nil is a legitimate answer: it means the triggering host event has not happened yet, so the record is not due for disposition and Clickwrap reports it as unresolved rather than inventing a date.

Raises:



874
875
876
877
878
879
880
881
882
883
884
885
886
# File 'lib/clickwrap/configuration.rb', line 874

def calculate_retention_time_for(name, &block)
  raise ConfigurationError, "calculate_retention_time_for needs a block" unless block

  key = ensure_present_symbol(name, "retention calculation name")
  if @retention_time_calculators.key?(key)
    raise ConfigurationError,
          "A retention calculation named #{key.inspect} is already registered. Use one " \
          "stable name per calculation; Clickwrap will not silently replace a deletion " \
          "deadline because initializer order changed."
  end

  @retention_time_calculators[key] = block
end

#current_request_evidence_binding_key_idObject



823
824
825
826
# File 'lib/clickwrap/configuration.rb', line 823

def current_request_evidence_binding_key_id
  @current_request_evidence_binding_key_id ||
    default_request_evidence_binding_key_id(default_request_evidence_binding_key)
end

#current_request_evidence_binding_key_id=(value) ⇒ Object

Plain-English key-rotation API. The ID is evidence and must stay stable; the callback returns key bytes for current OR historical IDs.

config.current_request_evidence_binding_key_id = "request-evidence-2026-01"
config.find_request_evidence_binding_key_with = ->(key_id) { keyring[key_id] }


817
818
819
820
821
# File 'lib/clickwrap/configuration.rb', line 817

def current_request_evidence_binding_key_id=(value)
  @current_request_evidence_binding_key_id = ensure_present_string(
    value, "current_request_evidence_binding_key_id"
  )
end

#deliberately_store_request_evidence_unencrypted!(because: nil) ⇒ Object

The named escape hatch for turning encryption off. The ceremony is the method: you cannot reach encrypt_recorded_* = false without writing a line that says out loud what you are doing, and that line is what a reviewer finds in a diff. Since 0.3.1 the because: is optional — the gem records its own sentence when you do not write one — because the host's privacy policy owns the why, and demanding it twice never stopped anybody who had already typed this method name.

Encryption itself is unchanged: on by default, for all three categories.



1252
1253
1254
1255
1256
# File 'lib/clickwrap/configuration.rb', line 1252

def deliberately_store_request_evidence_unencrypted!(because: nil)
  @deliberately_storing_request_evidence_unencrypted = true
  @reason_for_storing_request_evidence_unencrypted =
    because.presence || Vocabulary::DEFAULT_REASON_FOR_STORING_REQUEST_EVIDENCE_UNENCRYPTED
end

#enabled_default_ip_geolocation_fieldsObject



943
944
945
946
947
# File 'lib/clickwrap/configuration.rb', line 943

def enabled_default_ip_geolocation_fields
  Vocabulary::IP_GEOLOCATION_DATA_FIELDS.select do |field|
    public_send(:"record_ip_geolocation_#{field}_by_default")
  end
end

#hotwire_native_canonical_hostObject

The canonical host, resolved and validated at use time so a host that is only knowable after boot (application config, credentials) can be a callable. Trailing slashes are trimmed because the engine path this prefixes always begins with one.



502
503
504
505
506
507
508
509
# File 'lib/clickwrap/configuration.rb', line 502

def hotwire_native_canonical_host
  configured = hotwire_native_document_links&.fetch(:canonical_host, nil)
  return nil if configured.nil?

  resolved = configured.respond_to?(:call) ? configured.call.to_s : configured.to_s
  validate_hotwire_native_canonical_host!(resolved)
  resolved.chomp("/")
end

The mode this render should use, resolved at use time so a callable can answer per request or per screen. context is the controller handling the request; both the href and the link attributes are resolved from the same one, so the two halves of a document link cannot disagree.



482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/clickwrap/configuration.rb', line 482

def hotwire_native_document_link_mode(context = nil)
  configured = hotwire_native_document_links&.fetch(:open_in, nil)
  return configured unless configured.respond_to?(:call)

  resolved = (configured.arity.zero? ? configured.call : configured.call(context))&.to_sym

  unless HOTWIRE_NATIVE_DOCUMENT_LINK_MODES.include?(resolved)
    raise ConfigurationError,
          "hotwire_native_document_links `open_in:` answered #{resolved.inspect}. A callable " \
          "there must answer #{HOTWIRE_NATIVE_DOCUMENT_LINK_MODES.map(&:inspect).join(" or ")} " \
          "on every request — there is no third way for a document link to open."
  end

  resolved
end

#ip_geolocation_resolver_for(name = nil) ⇒ Object



779
780
781
782
783
# File 'lib/clickwrap/configuration.rb', line 779

def ip_geolocation_resolver_for(name = nil)
  return application_default_ip_geolocation_resolver if name.blank? || name.to_s == "application_default"

  @ip_geolocation_resolvers[name.to_s]
end

#ip_geolocation_resolver_in_forceObject

The resolver actually in force, for anything that only wants to describe the configuration (the privacy inventory, clickwrap:doctor). Unlike the reader above it never adopts one as a side effect of being asked.



802
803
804
# File 'lib/clickwrap/configuration.rb', line 802

def ip_geolocation_resolver_in_force
  @ip_geolocation_resolver || @automatically_adopted_ip_geolocation_resolver
end

#ip_geolocation_resolver_namesObject



810
# File 'lib/clickwrap/configuration.rb', line 810

def ip_geolocation_resolver_names = @ip_geolocation_resolvers.keys.sort.freeze

#ip_geolocation_resolver_was_adopted_automatically?Boolean

Returns:

  • (Boolean)


806
807
808
# File 'lib/clickwrap/configuration.rb', line 806

def ip_geolocation_resolver_was_adopted_automatically?
  @ip_geolocation_resolver.nil? && !@automatically_adopted_ip_geolocation_resolver.nil?
end

#keep_recorded_browser_user_agents_indefinitely!(because: nil) ⇒ Object



1215
1216
1217
# File 'lib/clickwrap/configuration.rb', line 1215

def keep_recorded_browser_user_agents_indefinitely!(because: nil)
  declare_indefinite_request_evidence!(:browser_user_agent, because)
end

#keep_recorded_ip_addresses_indefinitely!(because: nil) ⇒ Object

Says out loud what an absent deletion clock already means: this category keeps pace with the evidence it corroborates. Request evidence exists to corroborate evidence that (since 0.2.0) keeps indefinitely by default, and a corroboration that expires before the thing it corroborates is a scheduled weakening of the record.

Saying it explicitly is worth doing — it puts the decision and its reason in the initializer where a reviewer finds them — but since 0.3.0 it is no longer the price of admission, and because: is optional. What a host writes is kept as their own words; what they leave out gets Clickwrap's.



1211
1212
1213
# File 'lib/clickwrap/configuration.rb', line 1211

def keep_recorded_ip_addresses_indefinitely!(because: nil)
  declare_indefinite_request_evidence!(:ip_address, because)
end

#keep_recorded_ip_geolocation_indefinitely!(because: nil) ⇒ Object



1219
1220
1221
# File 'lib/clickwrap/configuration.rb', line 1219

def keep_recorded_ip_geolocation_indefinitely!(because: nil)
  declare_indefinite_request_evidence!(:ip_geolocation, because)
end

#keeps_recorded_request_evidence_indefinitely?(category) ⇒ Boolean

Returns:

  • (Boolean)


1223
1224
1225
# File 'lib/clickwrap/configuration.rb', line 1223

def keeps_recorded_request_evidence_indefinitely?(category)
  @keep_recorded_request_evidence_indefinitely.key?(category.to_sym)
end

#parent_controller_classObject



335
# File 'lib/clickwrap/configuration.rb', line 335

def parent_controller_class = parent_controller_class_name.constantize

#reason_for_keeping_recorded_request_evidence_indefinitely(category) ⇒ Object



1239
1240
1241
# File 'lib/clickwrap/configuration.rb', line 1239

def reason_for_keeping_recorded_request_evidence_indefinitely(category)
  @keep_recorded_request_evidence_indefinitely[category.to_sym]
end

#reason_for_recording_by_default(category) ⇒ Object

The purpose that will actually be recorded for a category enabled application-wide, and which of the two wrote it. The inventory reports both, so a reviewer can tell a sentence their team signed off on from the one the gem supplied.



1231
1232
1233
# File 'lib/clickwrap/configuration.rb', line 1231

def reason_for_recording_by_default(category)
  host_reason_for_recording_by_default(category) || Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE
end

#reason_for_recording_by_default_source(category) ⇒ Object



1235
1236
1237
# File 'lib/clickwrap/configuration.rb', line 1235

def reason_for_recording_by_default_source(category)
  host_reason_for_recording_by_default(category) ? "host" : "gem_default"
end

#record_request_evidence_by_defaultObject

Reads back what the switch describes rather than a remembered assignment: true when all three coarse fields are on, however they were turned on.



670
671
672
673
674
675
# File 'lib/clickwrap/configuration.rb', line 670

def record_request_evidence_by_default
  record_ip_address_by_default && record_browser_user_agent_by_default &&
    Vocabulary::COARSE_IP_GEOLOCATION_DATA_FIELDS.all? do |field|
      public_send(:"record_ip_geolocation_#{field}_by_default")
    end
end

#record_request_evidence_by_default=(value) ⇒ Object

The one switch.

config.record_request_evidence_by_default = true

Records, on every policy: the IP address the request arrived from, the browser user agent it sent, and a coarse country/region/city estimate for that address. Nothing finer — a postal code, coordinates, a timezone, a metro code — and nothing else at all. The purpose and the disposal answer have honest gem defaults (see Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE and the keep-indefinitely posture below), so this line is genuinely the whole first step.

It is a fan-out setter, not a mode: it writes the individual record_*_by_default flags, which means it composes with them in reading order. Write the switch first and a narrower flag after it to carve one field back out —

config.record_request_evidence_by_default = true
config.record_browser_user_agent_by_default = false

— and any policy can still override all of it with record_ip_address, do_not_record_ip_address, and their siblings. Setting it to false turns the same three fields off and leaves the finer geolocation fields alone, because it never turned those on.



658
659
660
661
662
663
664
665
666
# File 'lib/clickwrap/configuration.rb', line 658

def record_request_evidence_by_default=(value)
  enabled = ensure_boolean(value, "record_request_evidence_by_default")

  @record_ip_address_by_default = enabled
  @record_browser_user_agent_by_default = enabled
  Vocabulary::COARSE_IP_GEOLOCATION_DATA_FIELDS.each do |field|
    instance_variable_set(:"@record_ip_geolocation_#{field}_by_default", enabled)
  end
end

#records_any_request_evidence_by_default?Boolean

A convenience the initializer template and clickwrap:doctor both use.

Returns:

  • (Boolean)


937
938
939
940
941
# File 'lib/clickwrap/configuration.rb', line 937

def records_any_request_evidence_by_default?
  record_ip_address_by_default ||
    record_browser_user_agent_by_default ||
    enabled_default_ip_geolocation_fields.any?
end

#register_ip_geolocation_resolver(name, resolver) ⇒ Object

Register more than one resolver and let each server-owned policy select one by name with record_ip_geolocation ..., using: :maxmind.



764
765
766
767
768
769
770
771
772
773
774
775
776
777
# File 'lib/clickwrap/configuration.rb', line 764

def register_ip_geolocation_resolver(name, resolver)
  key = ensure_present_symbol(name, "IP-geolocation resolver name").to_s
  if key == "application_default" || @ip_geolocation_resolvers.key?(key)
    raise ConfigurationError,
          "An IP-geolocation resolver named #{key.inspect} is already reserved or registered. " \
          "Choose one stable, unique name; Clickwrap will not silently replace a resolver " \
          "because initializer order changed."
  end

  @ip_geolocation_resolvers[key] = ensure_ip_geolocation_resolver(
    resolver,
    "IP-geolocation resolver #{key}"
  )
end

#register_represented_party_authority(name, adapter) ⇒ Object

Register a named, server-side authority adapter. Policies refer to the name from their compiled revision; the browser never submits it.

config.register_represented_party_authority :company_directory, MyAdapter.new

The adapter receives actor:, represented_party:, authority_rule:, tenant:, and authentication_context:, and returns Clickwrap::AuthorityDecision.



611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/clickwrap/configuration.rb', line 611

def register_represented_party_authority(name, adapter)
  key = ensure_present_symbol(name, "represented-party authority name").to_s
  if @represented_party_authority_adapters.key?(key)
    raise ConfigurationError,
          "A represented-party authority adapter named #{key.inspect} is already registered. " \
          "Use one stable name per adapter; Clickwrap will not silently replace an " \
          "authorization decision because initializer order changed."
  end

  @represented_party_authority_adapters[key] =
    ensure_adapter(adapter, "represented-party authority #{key}", :verify)
end

#remediation_represented_party_authorization_configured?Boolean

Returns:

  • (Boolean)


600
601
602
# File 'lib/clickwrap/configuration.rb', line 600

def remediation_represented_party_authorization_configured?
  @remediation_represented_party_authorization_configured
end

#remediation_subject_authorization_configured?Boolean

Returns:

  • (Boolean)


598
# File 'lib/clickwrap/configuration.rb', line 598

def remediation_subject_authorization_configured? = @remediation_subject_authorization_configured

#replace_retention_time_calculation_for(name, because:, &block) ⇒ Object

Deliberate replacement for tests, staged migrations, or a host that is intentionally changing an existing calculation. The separate verb and required reason keep this from becoming last-initializer-wins behavior.



891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
# File 'lib/clickwrap/configuration.rb', line 891

def replace_retention_time_calculation_for(name, because:, &block)
  key = ensure_present_symbol(name, "retention calculation name")
  unless @retention_time_calculators.key?(key)
    raise ConfigurationError,
          "No retention calculation named #{key.inspect} exists to replace. Register it " \
          "first with `calculate_retention_time_for`."
  end
  unless block
    raise ConfigurationError,
          "replace_retention_time_calculation_for needs a block with the new calculation."
  end
  if because.to_s.strip.empty?
    raise ConfigurationError,
          "Replacing retention calculation #{key.inspect} needs a `because:` explaining " \
          "the reviewed change."
  end

  @retention_time_calculators[key] = block
end

#represented_party_authority_adapter(name) ⇒ Object



624
625
626
# File 'lib/clickwrap/configuration.rb', line 624

def represented_party_authority_adapter(name)
  @represented_party_authority_adapters[name.to_s]
end

#represented_party_authority_adapter_namesObject



628
629
630
# File 'lib/clickwrap/configuration.rb', line 628

def represented_party_authority_adapter_names
  @represented_party_authority_adapters.keys.sort.freeze
end

#request_evidence_binding_key_for(key_id) ⇒ Object



833
834
835
836
837
838
839
840
841
842
843
844
845
# File 'lib/clickwrap/configuration.rb', line 833

def request_evidence_binding_key_for(key_id)
  key = find_request_evidence_binding_key_with.call(key_id)
  return nil if key.nil?

  bytes = key.to_s.b
  if bytes.bytesize < 32
    raise ConfigurationError,
          "find_request_evidence_binding_key_with returned only #{bytes.bytesize} bytes for " \
          "#{key_id.inspect}. Request-evidence binding keys must be at least 32 bytes."
  end

  bytes
end

#resolve_retention_time(name, event) ⇒ Object



913
914
915
916
917
918
919
920
921
922
923
924
# File 'lib/clickwrap/configuration.rb', line 913

def resolve_retention_time(name, event)
  calculator = @retention_time_calculators[name.to_sym]

  unless calculator
    raise ConfigurationError,
          "No retention calculation is registered for #{name.inspect}. A retention class " \
          "asked for it with `retain_..._until #{name.inspect}`. Register it with " \
          "`config.calculate_retention_time_for #{name.inspect} do |event| ... end`."
  end

  calculator.call(event)
end

#resolved_application_versionObject



567
# File 'lib/clickwrap/configuration.rb', line 567

def resolved_application_version = application_version.call

#resolved_template_versionObject



568
# File 'lib/clickwrap/configuration.rb', line 568

def resolved_template_version = template_version.call

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


1271
1272
1273
# File 'lib/clickwrap/configuration.rb', line 1271

def respond_to_missing?(name, include_private = false)
  super
end

#retention_time_calculator_namesObject



911
# File 'lib/clickwrap/configuration.rb', line 911

def retention_time_calculator_names = @retention_time_calculators.keys

#storing_request_evidence_unencrypted?Boolean

Returns:

  • (Boolean)


1258
# File 'lib/clickwrap/configuration.rb', line 1258

def storing_request_evidence_unencrypted? = @deliberately_storing_request_evidence_unencrypted == true

#validate!Object

Run at the end of Clickwrap.configure. The per-setter checks already caught the typos; these are the things that need the whole block resolved.



930
931
932
933
934
# File 'lib/clickwrap/configuration.rb', line 930

def validate!
  validate_request_evidence_defaults!
  validate_ip_geolocation_resolver!
  true
end