clickwrap 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +90 -0
  3. data/CHANGELOG.md +612 -0
  4. data/README.md +830 -1204
  5. data/SECURITY.md +33 -0
  6. data/app/assets/stylesheets/clickwrap.css +241 -0
  7. data/app/controllers/clickwrap/application_controller.rb +79 -0
  8. data/app/controllers/clickwrap/captures_controller.rb +145 -0
  9. data/app/controllers/clickwrap/document_versions_controller.rb +71 -0
  10. data/app/controllers/clickwrap/receipts_controller.rb +115 -0
  11. data/app/controllers/clickwrap/withdrawals_controller.rb +60 -0
  12. data/app/helpers/clickwrap/engine_helper.rb +97 -0
  13. data/app/views/clickwrap/captures/show.html.erb +34 -0
  14. data/app/views/clickwrap/receipts/index.html.erb +38 -0
  15. data/app/views/clickwrap/receipts/show.html.erb +91 -0
  16. data/app/views/clickwrap/shared/_error_summary.html.erb +39 -0
  17. data/app/views/clickwrap/shared/_fields.html.erb +100 -0
  18. data/app/views/clickwrap/shared/_statement.html.erb +105 -0
  19. data/app/views/clickwrap/withdrawals/new.html.erb +30 -0
  20. data/config/locales/en.yml +160 -0
  21. data/config/locales/es.yml +138 -0
  22. data/config/routes.rb +41 -0
  23. data/exe/clickwrap +374 -0
  24. data/guides/README.md +30 -0
  25. data/guides/accessibility.md +249 -0
  26. data/guides/consent-and-lifecycle.md +299 -0
  27. data/guides/integrating.md +610 -0
  28. data/guides/integrity.md +212 -0
  29. data/guides/migrating.md +335 -0
  30. data/guides/naming.md +320 -0
  31. data/guides/organizations.md +320 -0
  32. data/guides/receipts-and-verification.md +415 -0
  33. data/guides/request-evidence.md +512 -0
  34. data/guides/retention-and-legal-holds.md +438 -0
  35. data/lib/clickwrap/actor_proxy.rb +147 -0
  36. data/lib/clickwrap/anonymous_actor.rb +47 -0
  37. data/lib/clickwrap/authority.rb +174 -0
  38. data/lib/clickwrap/canonical_json.rb +216 -0
  39. data/lib/clickwrap/capture/event_builder.rb +220 -0
  40. data/lib/clickwrap/capture/presentation_verifier.rb +521 -0
  41. data/lib/clickwrap/capture.rb +650 -0
  42. data/lib/clickwrap/configuration.rb +1129 -0
  43. data/lib/clickwrap/controller_helpers.rb +758 -0
  44. data/lib/clickwrap/current_state.rb +282 -0
  45. data/lib/clickwrap/digest.rb +125 -0
  46. data/lib/clickwrap/doctor.rb +418 -0
  47. data/lib/clickwrap/document_definition.rb +255 -0
  48. data/lib/clickwrap/document_renderer.rb +83 -0
  49. data/lib/clickwrap/document_renderers/markdown.rb +175 -0
  50. data/lib/clickwrap/document_renderers/markdown_rails.rb +126 -0
  51. data/lib/clickwrap/dsl/policy_builder.rb +462 -0
  52. data/lib/clickwrap/dsl/retention_builder.rb +89 -0
  53. data/lib/clickwrap/durable_commit_callback.rb +37 -0
  54. data/lib/clickwrap/engine.rb +184 -0
  55. data/lib/clickwrap/errors.rb +181 -0
  56. data/lib/clickwrap/form_builder_extensions.rb +341 -0
  57. data/lib/clickwrap/front_matter.rb +67 -0
  58. data/lib/clickwrap/identifier.rb +112 -0
  59. data/lib/clickwrap/import/external_receipt.rb +241 -0
  60. data/lib/clickwrap/import/fine_print.rb +290 -0
  61. data/lib/clickwrap/import/legacy.rb +450 -0
  62. data/lib/clickwrap/integrations/organizations_authority.rb +81 -0
  63. data/lib/clickwrap/integrity/anchor.rb +130 -0
  64. data/lib/clickwrap/integrity/attestation_reconciler.rb +114 -0
  65. data/lib/clickwrap/integrity/attestor.rb +221 -0
  66. data/lib/clickwrap/integrity/chain.rb +313 -0
  67. data/lib/clickwrap/integrity/timestamp.rb +143 -0
  68. data/lib/clickwrap/ip_geolocation/location.rb +112 -0
  69. data/lib/clickwrap/ip_geolocation/null_resolver.rb +35 -0
  70. data/lib/clickwrap/ip_geolocation/resolver.rb +97 -0
  71. data/lib/clickwrap/ip_geolocation/static_resolver.rb +107 -0
  72. data/lib/clickwrap/ip_geolocation/trackdown_resolver.rb +330 -0
  73. data/lib/clickwrap/ip_geolocation.rb +16 -0
  74. data/lib/clickwrap/lifecycle.rb +534 -0
  75. data/lib/clickwrap/linter.rb +382 -0
  76. data/lib/clickwrap/localized_text.rb +101 -0
  77. data/lib/clickwrap/macros.rb +203 -0
  78. data/lib/clickwrap/models/application_record.rb +20 -0
  79. data/lib/clickwrap/models/chain_head.rb +79 -0
  80. data/lib/clickwrap/models/concerns/has_clickwraps.rb +55 -0
  81. data/lib/clickwrap/models/disposition_plan.rb +208 -0
  82. data/lib/clickwrap/models/document.rb +46 -0
  83. data/lib/clickwrap/models/document_version.rb +163 -0
  84. data/lib/clickwrap/models/event.rb +743 -0
  85. data/lib/clickwrap/models/event_document.rb +79 -0
  86. data/lib/clickwrap/models/event_statement.rb +92 -0
  87. data/lib/clickwrap/models/external_action.rb +150 -0
  88. data/lib/clickwrap/models/integrity_attestation.rb +90 -0
  89. data/lib/clickwrap/models/legal_hold.rb +81 -0
  90. data/lib/clickwrap/models/policy_revision.rb +115 -0
  91. data/lib/clickwrap/models/presentation.rb +59 -0
  92. data/lib/clickwrap/models/receipt_access.rb +53 -0
  93. data/lib/clickwrap/models/recording_sequence.rb +21 -0
  94. data/lib/clickwrap/models/request_evidence.rb +378 -0
  95. data/lib/clickwrap/models/statement_identity_lock.rb +38 -0
  96. data/lib/clickwrap/models/statement_state.rb +130 -0
  97. data/lib/clickwrap/pending_receipt.rb +177 -0
  98. data/lib/clickwrap/policy.rb +283 -0
  99. data/lib/clickwrap/presentation_manifest.rb +210 -0
  100. data/lib/clickwrap/presenter.rb +716 -0
  101. data/lib/clickwrap/privacy.rb +419 -0
  102. data/lib/clickwrap/protected_outcome.rb +120 -0
  103. data/lib/clickwrap/receipt.rb +606 -0
  104. data/lib/clickwrap/receipt_html.rb +235 -0
  105. data/lib/clickwrap/receipt_verifier.rb +978 -0
  106. data/lib/clickwrap/reference.rb +44 -0
  107. data/lib/clickwrap/registration.rb +236 -0
  108. data/lib/clickwrap/registry.rb +54 -0
  109. data/lib/clickwrap/remediation_token.rb +155 -0
  110. data/lib/clickwrap/request_evidence_extractor.rb +590 -0
  111. data/lib/clickwrap/request_evidence_policy.rb +261 -0
  112. data/lib/clickwrap/retention/applier.rb +231 -0
  113. data/lib/clickwrap/retention/disposition.rb +221 -0
  114. data/lib/clickwrap/retention/planner.rb +502 -0
  115. data/lib/clickwrap/retention_class.rb +97 -0
  116. data/lib/clickwrap/reviewed_text.rb +28 -0
  117. data/lib/clickwrap/schema_requirements.rb +196 -0
  118. data/lib/clickwrap/services/authorize_external_action.rb +149 -0
  119. data/lib/clickwrap/services/load_policies.rb +69 -0
  120. data/lib/clickwrap/services/publish_documents.rb +251 -0
  121. data/lib/clickwrap/services/validate_policy_references.rb +166 -0
  122. data/lib/clickwrap/statement.rb +248 -0
  123. data/lib/clickwrap/subject_fingerprint.rb +28 -0
  124. data/lib/clickwrap/submission.rb +169 -0
  125. data/lib/clickwrap/system_actor.rb +31 -0
  126. data/lib/clickwrap/test_helpers.rb +676 -0
  127. data/lib/clickwrap/testing.rb +211 -0
  128. data/lib/clickwrap/trusted_proxy_configuration.rb +92 -0
  129. data/lib/clickwrap/verification.rb +504 -0
  130. data/lib/clickwrap/version.rb +12 -1
  131. data/lib/clickwrap/view_helpers.rb +190 -0
  132. data/lib/clickwrap/vocabulary.rb +294 -0
  133. data/lib/clickwrap.rb +497 -7
  134. data/lib/generators/clickwrap/document_generator.rb +164 -0
  135. data/lib/generators/clickwrap/hardening_generator.rb +177 -0
  136. data/lib/generators/clickwrap/install_generator.rb +1287 -0
  137. data/lib/generators/clickwrap/link_generator.rb +56 -0
  138. data/lib/generators/clickwrap/policy_generator.rb +118 -0
  139. data/lib/generators/clickwrap/templates/clickwrap_hardening.rb.erb +256 -0
  140. data/lib/generators/clickwrap/templates/clickwrap_policies.rb.erb +192 -0
  141. data/lib/generators/clickwrap/templates/create_clickwrap_external_action_tables.rb.erb +128 -0
  142. data/lib/generators/clickwrap/templates/create_clickwrap_integrity_tables.rb.erb +157 -0
  143. data/lib/generators/clickwrap/templates/create_clickwrap_presentation_tables.rb.erb +160 -0
  144. data/lib/generators/clickwrap/templates/create_clickwrap_request_evidence_tables.rb.erb +180 -0
  145. data/lib/generators/clickwrap/templates/create_clickwrap_retention_tables.rb.erb +174 -0
  146. data/lib/generators/clickwrap/templates/create_clickwrap_tables.rb.erb +568 -0
  147. data/lib/generators/clickwrap/templates/initializer.rb.erb +439 -0
  148. data/lib/generators/clickwrap/templates/link_clickwrap_event_migration.rb.erb +12 -0
  149. data/lib/generators/clickwrap/templates/policy.rb.erb +31 -0
  150. data/lib/generators/clickwrap/templates/policy_test.rb.erb +56 -0
  151. data/lib/generators/clickwrap/templates/privacy.md.erb +58 -0
  152. data/lib/generators/clickwrap/templates/terms.md.erb +49 -0
  153. data/lib/generators/clickwrap/upgrade_generator.rb +50 -0
  154. data/lib/generators/clickwrap/views_generator.rb +101 -0
  155. data/lib/tasks/clickwrap.rake +569 -0
  156. metadata +311 -16
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+ require "rails/generators/active_record"
5
+
6
+ module Clickwrap
7
+ module Generators
8
+ # `rails generate clickwrap:hardening --database` — the opt-in database tier.
9
+ #
10
+ # Clickwrap's models refuse ordinary `update` and `destroy` calls. This
11
+ # generator adds a narrower database control for paths that bypass model
12
+ # callbacks: direct SQL, `delete`, `delete_all`, `update_column`, and
13
+ # `update_all`.
14
+ #
15
+ # What that is worth is bounded, and the bound is the point: it rejects
16
+ # unsupported mutation paths within the documented database threat model. It
17
+ # does not make rows impossible to change, and it does nothing at all against
18
+ # anyone holding database superuser rights, direct file access, or the
19
+ # ability to drop the triggers — which, in most Rails applications, is the
20
+ # same credential that runs migrations. Real assurance against that comes
21
+ # from separately verified mechanisms: chained history, event digests
22
+ # published outside the primary database, and provider timestamps.
23
+ #
24
+ # It is opt-in because it is a production decision with real consequences for
25
+ # anyone who clears tables with DELETE.
26
+ class HardeningGenerator < Rails::Generators::Base
27
+ include ActiveRecord::Generators::Migration
28
+
29
+ source_root File.expand_path("templates", __dir__)
30
+ desc "Add opt-in database update/delete protection for clickwrap evidence tables"
31
+
32
+ class_option :database, type: :boolean, default: false,
33
+ desc: "Required. Generate the adapter-specific database protection migration."
34
+
35
+ def self.next_migration_number(dir)
36
+ ActiveRecord::Generators::Base.next_migration_number(dir)
37
+ end
38
+
39
+ # A generated migration must remain runnable after the application later
40
+ # upgrades Clickwrap, so it snapshots the write sets at generation time
41
+ # instead of consulting the then-current gem while `db:migrate` runs.
42
+ # The Event model is the sole source of truth; this formatter only turns
43
+ # that frozen contract into readable, self-contained migration code.
44
+ def self.render_event_write_sets(write_sets = Clickwrap::Event::DATABASE_HARDENING_WRITE_SETS)
45
+ write_sets.map do |name, columns|
46
+ wrapped = columns.each_slice(5).map { |slice| " #{slice.join(" ")}" }.join("\n")
47
+ %( #{name.inspect} => %w[\n#{wrapped}\n ])
48
+ end.join(",\n")
49
+ end
50
+
51
+ def require_explicit_opt_in!
52
+ return if options[:database]
53
+
54
+ raise Thor::Error, <<~MSG
55
+ ❌ Nothing was generated, on purpose.
56
+
57
+ Database hardening changes what your database will accept, in every
58
+ environment the migration runs in, so it is never applied as a side
59
+ effect of installing the gem. Ask for it explicitly:
60
+
61
+ rails generate clickwrap:hardening --database
62
+
63
+ Read what it does and does not do first — the generated migration says
64
+ so at the top, and the honest summary is: it rejects unsupported
65
+ mutation paths, and it stops nobody with superuser rights.
66
+ MSG
67
+ end
68
+
69
+ def create_migration_file
70
+ migration_template "clickwrap_hardening.rb.erb",
71
+ File.join(db_migrate_path, "clickwrap_database_hardening.rb")
72
+ end
73
+
74
+ def explain_adapter_support
75
+ case adapter_family
76
+ when :postgresql then explain_postgresql
77
+ when :sqlite then explain_sqlite
78
+ when :mysql then explain_mysql
79
+ else explain_unknown_adapter
80
+ end
81
+ end
82
+
83
+ def display_post_install_message
84
+ say "\n☑️ The database hardening migration has been created.", :green
85
+ say "\nBefore you run it:"
86
+ say " 1. Read it. Its comments say exactly which transitions it accepts and rejects."
87
+ say " 2. Check how your test suite clears tables. Transactional tests are fine —"
88
+ say " a rollback is not a DELETE. But fixtures, and any cleaner using the"
89
+ say " deletion strategy, run `DELETE FROM …`, and these protections reject"
90
+ say " blanket deletion of finalized events and their evidence children."
91
+ say " 3. Run 'rails db:migrate'."
92
+ say "\nThe migration is reversible: `rails db:rollback` removes the triggers and"
93
+ say "functions it created and leaves your data alone."
94
+ say "\nWhat this tier claims, in full: it rejects unsupported mutation paths within"
95
+ say "the documented database threat model. Nothing more. A local digest is still a"
96
+ say "local digest, and your server's clock is still your server's clock.\n"
97
+ end
98
+
99
+ private
100
+
101
+ def event_write_sets_for_migration
102
+ self.class.render_event_write_sets
103
+ end
104
+
105
+ def migration_version
106
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
107
+ end
108
+
109
+ def database_adapter
110
+ return @database_adapter if defined?(@database_adapter)
111
+
112
+ @database_adapter = ActiveRecord::Base.connection_db_config.adapter.to_s.downcase
113
+ rescue StandardError
114
+ @database_adapter = nil
115
+ end
116
+
117
+ def adapter_family
118
+ case database_adapter
119
+ when /postgres|postgis/ then :postgresql
120
+ when /sqlite/ then :sqlite
121
+ when /mysql|trilogy/ then :mysql
122
+ end
123
+ end
124
+
125
+ def explain_postgresql
126
+ say "\n PostgreSQL detected. The migration installs row-level triggers that:", :green
127
+ say " • allow one in-transaction finalization of a new event;"
128
+ say " • allow only pointer nullification, one annex link, recorded legal-hold"
129
+ say " changes, and a fully documented core disposition after finalization;"
130
+ say " • reject every DELETE from clickwrap_events; and"
131
+ say " • reject child UPDATEs and allow child DELETEs only while the parent"
132
+ say " carries a valid linked core-disposition event."
133
+ say "\n Optional personal request evidence is deliberately NOT protected: it has"
134
+ say " to stay deletable on its retention schedule, and disposition appends its"
135
+ say " own event recording that it happened."
136
+ end
137
+
138
+ def explain_sqlite
139
+ say "\n⚠️ SQLite detected. The migration will run and do nothing.", :yellow
140
+ say "\n This is not an oversight, and writing SQLite triggers here would be"
141
+ say " theatre. SQLite has no users, no roles, and no privileges: the database is"
142
+ say " a file, and any process that can open it for writing can drop a trigger as"
143
+ say " easily as it can update a row. A protection that the thing it protects"
144
+ say " against can remove in one statement is worth stating honestly rather than"
145
+ say " installing."
146
+ say "\n What still holds on SQLite: the models refuse update and destroy, the"
147
+ say " schema has no `updated_at` on events to tempt anyone, every receipt is"
148
+ say " digest-verified, and `bin/rails clickwrap:verify` detects bytes that no"
149
+ say " longer match. What does not hold: anything about a writer with file access."
150
+ say "\n A separate mechanism can add assurance only when it publishes the exact"
151
+ say " event digest outside this database and independently verifies that record."
152
+ end
153
+
154
+ def explain_mysql
155
+ say "\n⚠️ MySQL detected. The migration will run and do nothing.", :yellow
156
+ say "\n MySQL triggers could raise on UPDATE and DELETE, so the honest reason is"
157
+ say " narrower than SQLite's: what MySQL cannot do is protect the triggers"
158
+ say " themselves from the account that installs them. Your application user runs"
159
+ say " migrations, so it holds TRIGGER (and usually DROP), and a protection that"
160
+ say " the protected account can remove is a comment, not a control."
161
+ say "\n The control that does work on MySQL is privilege separation, and it lives"
162
+ say " outside this gem: a migration role that owns the schema, and a runtime role"
163
+ say " with reviewed grants or stored procedures for Clickwrap's named write paths."
164
+ say " Disposition needs conditional UPDATE and DELETE behavior, so a blanket"
165
+ say " SELECT/INSERT-only grant is not a drop-in replacement. That is a database-"
166
+ say " administration decision with operational consequences, so this generator"
167
+ say " explains the boundary instead of guessing at your deployment."
168
+ end
169
+
170
+ def explain_unknown_adapter
171
+ say "\n⚠️ Adapter #{database_adapter || "unknown"} is outside the tested set.", :yellow
172
+ say " The migration only applies protections on PostgreSQL; on anything else it"
173
+ say " runs and does nothing rather than executing DDL nobody has tested."
174
+ end
175
+ end
176
+ end
177
+ end