Class: Clickwrap::Generators::HardeningGenerator
- Inherits:
-
Rails::Generators::Base
- Object
- Rails::Generators::Base
- Clickwrap::Generators::HardeningGenerator
- Includes:
- ActiveRecord::Generators::Migration
- Defined in:
- lib/generators/clickwrap/hardening_generator.rb
Overview
rails generate clickwrap:hardening --database — the opt-in database tier.
Clickwrap's models refuse ordinary update and destroy calls. This
generator adds a narrower database control for paths that bypass model
callbacks: direct SQL, delete, delete_all, update_column, and
update_all.
What that is worth is bounded, and the bound is the point: it rejects unsupported mutation paths within the documented database threat model. It does not make rows impossible to change, and it does nothing at all against anyone holding database superuser rights, direct file access, or the ability to drop the triggers — which, in most Rails applications, is the same credential that runs migrations. Real assurance against that comes from separately verified mechanisms: chained history, event digests published outside the primary database, and provider timestamps.
It is opt-in because it is a production decision with real consequences for anyone who clears tables with DELETE.
Class Method Summary collapse
- .next_migration_number(dir) ⇒ Object
-
.render_event_write_sets(write_sets = Clickwrap::Event::DATABASE_HARDENING_WRITE_SETS) ⇒ Object
A generated migration must remain runnable after the application later upgrades Clickwrap, so it snapshots the write sets at generation time instead of consulting the then-current gem while
db:migrateruns.
Instance Method Summary collapse
- #create_migration_file ⇒ Object
- #display_post_install_message ⇒ Object
- #explain_adapter_support ⇒ Object
- #require_explicit_opt_in! ⇒ Object
Class Method Details
.next_migration_number(dir) ⇒ Object
35 36 37 |
# File 'lib/generators/clickwrap/hardening_generator.rb', line 35 def self.next_migration_number(dir) ActiveRecord::Generators::Base.next_migration_number(dir) end |
.render_event_write_sets(write_sets = Clickwrap::Event::DATABASE_HARDENING_WRITE_SETS) ⇒ Object
A generated migration must remain runnable after the application later
upgrades Clickwrap, so it snapshots the write sets at generation time
instead of consulting the then-current gem while db:migrate runs.
The Event model is the sole source of truth; this formatter only turns
that frozen contract into readable, self-contained migration code.
44 45 46 47 48 49 |
# File 'lib/generators/clickwrap/hardening_generator.rb', line 44 def self.render_event_write_sets(write_sets = Clickwrap::Event::DATABASE_HARDENING_WRITE_SETS) write_sets.map do |name, columns| wrapped = columns.each_slice(5).map { |slice| " #{slice.join(" ")}" }.join("\n") %( #{name.inspect} => %w[\n#{wrapped}\n ]) end.join(",\n") end |
Instance Method Details
#create_migration_file ⇒ Object
69 70 71 72 |
# File 'lib/generators/clickwrap/hardening_generator.rb', line 69 def create_migration_file migration_template "clickwrap_hardening.rb.erb", File.join(db_migrate_path, "clickwrap_database_hardening.rb") end |
#display_post_install_message ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/generators/clickwrap/hardening_generator.rb', line 83 def say "\n☑️ The database hardening migration has been created.", :green say "\nBefore you run it:" say " 1. Read it. Its comments say exactly which transitions it accepts and rejects." say " 2. Check how your test suite clears tables. Transactional tests are fine —" say " a rollback is not a DELETE. But fixtures, and any cleaner using the" say " deletion strategy, run `DELETE FROM …`, and these protections reject" say " blanket deletion of finalized events and their evidence children." say " 3. Run 'rails db:migrate'." say "\nThe migration is reversible: `rails db:rollback` removes the triggers and" say "functions it created and leaves your data alone." say "\nWhat this tier claims, in full: it rejects unsupported mutation paths within" say "the documented database threat model. Nothing more. A local digest is still a" say "local digest, and your server's clock is still your server's clock.\n" end |
#explain_adapter_support ⇒ Object
74 75 76 77 78 79 80 81 |
# File 'lib/generators/clickwrap/hardening_generator.rb', line 74 def explain_adapter_support case adapter_family when :postgresql then explain_postgresql when :sqlite then explain_sqlite when :mysql then explain_mysql else explain_unknown_adapter end end |
#require_explicit_opt_in! ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/generators/clickwrap/hardening_generator.rb', line 51 def require_explicit_opt_in! return if [:database] raise Thor::Error, <<~MSG ❌ Nothing was generated, on purpose. Database hardening changes what your database will accept, in every environment the migration runs in, so it is never applied as a side effect of installing the gem. Ask for it explicitly: rails generate clickwrap:hardening --database Read what it does and does not do first — the generated migration says so at the top, and the honest summary is: it rejects unsupported mutation paths, and it stops nobody with superuser rights. MSG end |