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.
- checksums.yaml +4 -4
- data/.rubocop.yml +90 -0
- data/CHANGELOG.md +612 -0
- data/README.md +830 -1204
- data/SECURITY.md +33 -0
- data/app/assets/stylesheets/clickwrap.css +241 -0
- data/app/controllers/clickwrap/application_controller.rb +79 -0
- data/app/controllers/clickwrap/captures_controller.rb +145 -0
- data/app/controllers/clickwrap/document_versions_controller.rb +71 -0
- data/app/controllers/clickwrap/receipts_controller.rb +115 -0
- data/app/controllers/clickwrap/withdrawals_controller.rb +60 -0
- data/app/helpers/clickwrap/engine_helper.rb +97 -0
- data/app/views/clickwrap/captures/show.html.erb +34 -0
- data/app/views/clickwrap/receipts/index.html.erb +38 -0
- data/app/views/clickwrap/receipts/show.html.erb +91 -0
- data/app/views/clickwrap/shared/_error_summary.html.erb +39 -0
- data/app/views/clickwrap/shared/_fields.html.erb +100 -0
- data/app/views/clickwrap/shared/_statement.html.erb +105 -0
- data/app/views/clickwrap/withdrawals/new.html.erb +30 -0
- data/config/locales/en.yml +160 -0
- data/config/locales/es.yml +138 -0
- data/config/routes.rb +41 -0
- data/exe/clickwrap +374 -0
- data/guides/README.md +30 -0
- data/guides/accessibility.md +249 -0
- data/guides/consent-and-lifecycle.md +299 -0
- data/guides/integrating.md +610 -0
- data/guides/integrity.md +212 -0
- data/guides/migrating.md +335 -0
- data/guides/naming.md +320 -0
- data/guides/organizations.md +320 -0
- data/guides/receipts-and-verification.md +415 -0
- data/guides/request-evidence.md +512 -0
- data/guides/retention-and-legal-holds.md +438 -0
- data/lib/clickwrap/actor_proxy.rb +147 -0
- data/lib/clickwrap/anonymous_actor.rb +47 -0
- data/lib/clickwrap/authority.rb +174 -0
- data/lib/clickwrap/canonical_json.rb +216 -0
- data/lib/clickwrap/capture/event_builder.rb +220 -0
- data/lib/clickwrap/capture/presentation_verifier.rb +521 -0
- data/lib/clickwrap/capture.rb +650 -0
- data/lib/clickwrap/configuration.rb +1129 -0
- data/lib/clickwrap/controller_helpers.rb +758 -0
- data/lib/clickwrap/current_state.rb +282 -0
- data/lib/clickwrap/digest.rb +125 -0
- data/lib/clickwrap/doctor.rb +418 -0
- data/lib/clickwrap/document_definition.rb +255 -0
- data/lib/clickwrap/document_renderer.rb +83 -0
- data/lib/clickwrap/document_renderers/markdown.rb +175 -0
- data/lib/clickwrap/document_renderers/markdown_rails.rb +126 -0
- data/lib/clickwrap/dsl/policy_builder.rb +462 -0
- data/lib/clickwrap/dsl/retention_builder.rb +89 -0
- data/lib/clickwrap/durable_commit_callback.rb +37 -0
- data/lib/clickwrap/engine.rb +184 -0
- data/lib/clickwrap/errors.rb +181 -0
- data/lib/clickwrap/form_builder_extensions.rb +341 -0
- data/lib/clickwrap/front_matter.rb +67 -0
- data/lib/clickwrap/identifier.rb +112 -0
- data/lib/clickwrap/import/external_receipt.rb +241 -0
- data/lib/clickwrap/import/fine_print.rb +290 -0
- data/lib/clickwrap/import/legacy.rb +450 -0
- data/lib/clickwrap/integrations/organizations_authority.rb +81 -0
- data/lib/clickwrap/integrity/anchor.rb +130 -0
- data/lib/clickwrap/integrity/attestation_reconciler.rb +114 -0
- data/lib/clickwrap/integrity/attestor.rb +221 -0
- data/lib/clickwrap/integrity/chain.rb +313 -0
- data/lib/clickwrap/integrity/timestamp.rb +143 -0
- data/lib/clickwrap/ip_geolocation/location.rb +112 -0
- data/lib/clickwrap/ip_geolocation/null_resolver.rb +35 -0
- data/lib/clickwrap/ip_geolocation/resolver.rb +97 -0
- data/lib/clickwrap/ip_geolocation/static_resolver.rb +107 -0
- data/lib/clickwrap/ip_geolocation/trackdown_resolver.rb +330 -0
- data/lib/clickwrap/ip_geolocation.rb +16 -0
- data/lib/clickwrap/lifecycle.rb +534 -0
- data/lib/clickwrap/linter.rb +382 -0
- data/lib/clickwrap/localized_text.rb +101 -0
- data/lib/clickwrap/macros.rb +203 -0
- data/lib/clickwrap/models/application_record.rb +20 -0
- data/lib/clickwrap/models/chain_head.rb +79 -0
- data/lib/clickwrap/models/concerns/has_clickwraps.rb +55 -0
- data/lib/clickwrap/models/disposition_plan.rb +208 -0
- data/lib/clickwrap/models/document.rb +46 -0
- data/lib/clickwrap/models/document_version.rb +163 -0
- data/lib/clickwrap/models/event.rb +743 -0
- data/lib/clickwrap/models/event_document.rb +79 -0
- data/lib/clickwrap/models/event_statement.rb +92 -0
- data/lib/clickwrap/models/external_action.rb +150 -0
- data/lib/clickwrap/models/integrity_attestation.rb +90 -0
- data/lib/clickwrap/models/legal_hold.rb +81 -0
- data/lib/clickwrap/models/policy_revision.rb +115 -0
- data/lib/clickwrap/models/presentation.rb +59 -0
- data/lib/clickwrap/models/receipt_access.rb +53 -0
- data/lib/clickwrap/models/recording_sequence.rb +21 -0
- data/lib/clickwrap/models/request_evidence.rb +378 -0
- data/lib/clickwrap/models/statement_identity_lock.rb +38 -0
- data/lib/clickwrap/models/statement_state.rb +130 -0
- data/lib/clickwrap/pending_receipt.rb +177 -0
- data/lib/clickwrap/policy.rb +283 -0
- data/lib/clickwrap/presentation_manifest.rb +210 -0
- data/lib/clickwrap/presenter.rb +716 -0
- data/lib/clickwrap/privacy.rb +419 -0
- data/lib/clickwrap/protected_outcome.rb +120 -0
- data/lib/clickwrap/receipt.rb +606 -0
- data/lib/clickwrap/receipt_html.rb +235 -0
- data/lib/clickwrap/receipt_verifier.rb +978 -0
- data/lib/clickwrap/reference.rb +44 -0
- data/lib/clickwrap/registration.rb +236 -0
- data/lib/clickwrap/registry.rb +54 -0
- data/lib/clickwrap/remediation_token.rb +155 -0
- data/lib/clickwrap/request_evidence_extractor.rb +590 -0
- data/lib/clickwrap/request_evidence_policy.rb +261 -0
- data/lib/clickwrap/retention/applier.rb +231 -0
- data/lib/clickwrap/retention/disposition.rb +221 -0
- data/lib/clickwrap/retention/planner.rb +502 -0
- data/lib/clickwrap/retention_class.rb +97 -0
- data/lib/clickwrap/reviewed_text.rb +28 -0
- data/lib/clickwrap/schema_requirements.rb +196 -0
- data/lib/clickwrap/services/authorize_external_action.rb +149 -0
- data/lib/clickwrap/services/load_policies.rb +69 -0
- data/lib/clickwrap/services/publish_documents.rb +251 -0
- data/lib/clickwrap/services/validate_policy_references.rb +166 -0
- data/lib/clickwrap/statement.rb +248 -0
- data/lib/clickwrap/subject_fingerprint.rb +28 -0
- data/lib/clickwrap/submission.rb +169 -0
- data/lib/clickwrap/system_actor.rb +31 -0
- data/lib/clickwrap/test_helpers.rb +676 -0
- data/lib/clickwrap/testing.rb +211 -0
- data/lib/clickwrap/trusted_proxy_configuration.rb +92 -0
- data/lib/clickwrap/verification.rb +504 -0
- data/lib/clickwrap/version.rb +12 -1
- data/lib/clickwrap/view_helpers.rb +190 -0
- data/lib/clickwrap/vocabulary.rb +294 -0
- data/lib/clickwrap.rb +497 -7
- data/lib/generators/clickwrap/document_generator.rb +164 -0
- data/lib/generators/clickwrap/hardening_generator.rb +177 -0
- data/lib/generators/clickwrap/install_generator.rb +1287 -0
- data/lib/generators/clickwrap/link_generator.rb +56 -0
- data/lib/generators/clickwrap/policy_generator.rb +118 -0
- data/lib/generators/clickwrap/templates/clickwrap_hardening.rb.erb +256 -0
- data/lib/generators/clickwrap/templates/clickwrap_policies.rb.erb +192 -0
- data/lib/generators/clickwrap/templates/create_clickwrap_external_action_tables.rb.erb +128 -0
- data/lib/generators/clickwrap/templates/create_clickwrap_integrity_tables.rb.erb +157 -0
- data/lib/generators/clickwrap/templates/create_clickwrap_presentation_tables.rb.erb +160 -0
- data/lib/generators/clickwrap/templates/create_clickwrap_request_evidence_tables.rb.erb +180 -0
- data/lib/generators/clickwrap/templates/create_clickwrap_retention_tables.rb.erb +174 -0
- data/lib/generators/clickwrap/templates/create_clickwrap_tables.rb.erb +568 -0
- data/lib/generators/clickwrap/templates/initializer.rb.erb +439 -0
- data/lib/generators/clickwrap/templates/link_clickwrap_event_migration.rb.erb +12 -0
- data/lib/generators/clickwrap/templates/policy.rb.erb +31 -0
- data/lib/generators/clickwrap/templates/policy_test.rb.erb +56 -0
- data/lib/generators/clickwrap/templates/privacy.md.erb +58 -0
- data/lib/generators/clickwrap/templates/terms.md.erb +49 -0
- data/lib/generators/clickwrap/upgrade_generator.rb +50 -0
- data/lib/generators/clickwrap/views_generator.rb +101 -0
- data/lib/tasks/clickwrap.rake +569 -0
- 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
|