Skip to content

Instantly share code, notes, and snippets.

@ryansch
Last active November 3, 2022 20:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryansch/575cb0fc8d175a4b626dab16160f359c to your computer and use it in GitHub Desktop.
Save ryansch/575cb0fc8d175a4b626dab16160f359c to your computer and use it in GitHub Desktop.
attr_encryption -> Active Record Encryption

This is designed to work with https://github.com/PagerTree/attr_encrypted/tree/rails-7-0-support as attr_encrypted normally can't coexist with Rails 7.

It will allow you to add encrypts and migrate_encryption_for to your models, migrate, and finally remove everything but the encrypts lines.

You probably also want to set

config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new

in application.rb.

class ExampleModel < ApplicationRecord
include Migrations::MigrateEncryption::DSL
attr_encrypted :shared_secret, key: Figaro.env.example_model_shared_secret_key!.byteslice(0,32)
encrypts :shared_secret
migrate_encryption_for :shared_secret
end
module Migrations
class MigrateEncryption
module DSL
extend ActiveSupport::Concern
included do
class_attribute :migrate_encrypted_attributes
end
class_methods do
def migrate_encryption_for(*names)
self.migrate_encrypted_attributes ||= Set.new
names.each do |name|
migrate_encrypted_attribute(name)
end
end
private
def migrate_encrypted_attribute(name)
migrate_encrypted_attributes << name.to_sym
alias_method :"original_#{name}=", :"#{name}="
define_method "#{name}=" do |value|
send("original_#{name}=", value)
send("ar_enc_#{name}=", value)
end
define_method "ar_enc_#{name}=" do |value|
scheme = self.class.send(:scheme_for)
type = ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme)
write_attribute(name, type.cast(value))
end
define_method "ar_enc_#{name}" do
scheme = self.class.send(:scheme_for)
type = ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme)
type.deserialize(read_attribute_before_type_cast(name))
end
end
end
def migrate_encryption!
return if self.class.migrate_encrypted_attributes.empty?
self.class.migrate_encrypted_attributes.each do |name|
send("ar_enc_#{name}=", send(name))
end
save!
end
end
# This isn't strictly required but it's what we use.
include Metaractor
# This is a _very_ simple approach to this data migration. If you have a lot of data
# to work through, you're going to need to get fancy.
def call
[
# List of models here.
].each do |klass|
puts "Starting #{klass}"
klass.find_each.with_index do |model, index|
id = model.id
if index % 10 == 0
puts id
end
model.migrate_encryption!
rescue
puts "Error! Failed on id #{id}"
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment