This DRYs up setting up encrypted columns for a model. You just need to specify the column or columns to be encrypted, and the plugin takes care of setting up the column_encryption plugin. Other advantages: * Model.redacted_columns is now an attr_reader, and does not need to check for encrypted columns every time it is called. * Model#before_destroy can look at Model.encrypted_columns (also an attr_reader), so it is simplified as well. Simplify the method while here by using hash key omission, and make sure the constant it uses is frozen.
155 lines
3.3 KiB
Ruby
155 lines
3.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module ResourceMethods
|
|
def self.configure(model, etc_type: false, redacted_columns: nil, encrypted_columns: nil)
|
|
model.instance_exec do
|
|
extend UbidTypeEtcMethods if etc_type
|
|
@ubid_format = /\A#{ubid_type}[a-z0-9]{24}\z/
|
|
@encrypted_columns = Array(encrypted_columns).freeze
|
|
@redacted_columns = (Array(redacted_columns) + @encrypted_columns).freeze
|
|
|
|
unless @encrypted_columns.empty?
|
|
plugin :column_encryption do |enc|
|
|
@encrypted_columns.each do |col|
|
|
enc.column col
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module UbidTypeEtcMethods
|
|
def ubid_type
|
|
UBID::TYPE_ETC
|
|
end
|
|
end
|
|
|
|
module InstanceMethods
|
|
def freeze
|
|
ubid
|
|
super
|
|
end
|
|
|
|
def ubid
|
|
@ubid ||= UBID.from_uuidish(id).to_s.downcase
|
|
end
|
|
|
|
def to_s
|
|
inspect_prefix
|
|
end
|
|
|
|
def inspect_pk
|
|
ubid if id
|
|
end
|
|
|
|
def before_validation
|
|
set_uuid
|
|
super
|
|
end
|
|
|
|
def set_uuid
|
|
self.id ||= self.class.generate_uuid if new?
|
|
self
|
|
end
|
|
|
|
def validate
|
|
super
|
|
|
|
if self.class.ubid_format.match?(values[:name])
|
|
errors.add(:name, "cannot be exactly 26 numbers/lowercase characters starting with #{self.class.ubid_type} to avoid overlap with id format")
|
|
end
|
|
end
|
|
|
|
INSPECT_CONVERTERS = {
|
|
"uuid" => lambda { |v| UBID.from_uuidish(v).to_s },
|
|
"cidr" => :to_s.to_proc,
|
|
"inet" => :to_s.to_proc,
|
|
"timestamp with time zone" => lambda { |v| v.strftime("%F %T") }
|
|
}.freeze
|
|
def inspect_values
|
|
inspect_values = {}
|
|
sch = db_schema
|
|
@values.except(*self.class.redacted_columns).each do |k, v|
|
|
next if k == :id
|
|
|
|
inspect_values[k] = if v
|
|
if (converter = INSPECT_CONVERTERS[sch[k][:db_type]])
|
|
converter.call(v)
|
|
else
|
|
v
|
|
end
|
|
else
|
|
v
|
|
end
|
|
end
|
|
inspect_values.inspect
|
|
end
|
|
|
|
NON_ARCHIVED_MODELS = ["ArchivedRecord", "Semaphore"].freeze
|
|
def before_destroy
|
|
model_name = self.class.name
|
|
unless NON_ARCHIVED_MODELS.include?(model_name)
|
|
model_values = values.merge(model_name:)
|
|
|
|
self.class.encrypted_columns.each do |key|
|
|
model_values.delete(key)
|
|
end
|
|
|
|
ArchivedRecord.create(model_name:, model_values:)
|
|
end
|
|
|
|
super
|
|
end
|
|
end
|
|
|
|
module ClassMethods
|
|
attr_reader :ubid_format
|
|
|
|
attr_reader :encrypted_columns
|
|
|
|
attr_reader :redacted_columns
|
|
|
|
# Adapted from sequel/model/inflections.rb's underscore, to convert
|
|
# class names into symbols
|
|
def self.uppercase_underscore(s)
|
|
s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').tr("-", "_").upcase
|
|
end
|
|
|
|
def [](arg)
|
|
if arg.is_a?(UBID)
|
|
super(arg.to_uuid)
|
|
elsif arg.is_a?(String) && arg.bytesize == 26
|
|
begin
|
|
ubid = UBID.parse(arg)
|
|
rescue UBIDParseError
|
|
super
|
|
else
|
|
super(ubid.to_uuid)
|
|
end
|
|
else
|
|
super
|
|
end
|
|
end
|
|
|
|
def ubid_type
|
|
UBID.const_get("TYPE_#{ClassMethods.uppercase_underscore(name)}")
|
|
end
|
|
|
|
def generate_ubid
|
|
UBID.generate(ubid_type)
|
|
end
|
|
|
|
def generate_uuid
|
|
generate_ubid.to_uuid
|
|
end
|
|
|
|
def new_with_id(...)
|
|
new(...).set_uuid
|
|
end
|
|
|
|
def create_with_id(...)
|
|
create(...)
|
|
end
|
|
end
|
|
end
|