module ActiveRecord::Encryption::EncryptableRecord
def add_length_validation_for_encrypted_columns
def add_length_validation_for_encrypted_columns encrypted_attributes&.each do |attribute_name| validate_column_size attribute_name end end
def build_decrypt_attribute_assignments
def build_decrypt_attribute_assignments Array(self.class.encrypted_attributes).collect do |attribute_name| type = type_for_attribute(attribute_name) encrypted_value = ciphertext_for(attribute_name) new_value = type.deserialize(encrypted_value) [attribute_name, new_value] end.to_h end
def build_encrypt_attribute_assignments
def build_encrypt_attribute_assignments Array(self.class.encrypted_attributes).index_with do |attribute_name| self[attribute_name] end end
def cant_modify_encrypted_attributes_when_frozen
def cant_modify_encrypted_attributes_when_frozen self.class&.encrypted_attributes.each do |attribute| errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute) end end
def ciphertext_for(attribute_name)
def ciphertext_for(attribute_name) read_attribute_before_type_cast(attribute_name) end
def decrypt
def decrypt decrypt_attributes if has_encrypted_attributes? end
def decrypt_attributes
def decrypt_attributes validate_encryption_allowed decrypt_attribute_assignments = build_decrypt_attribute_assignments ActiveRecord::Encryption.without_encryption { update_columns decrypt_attribute_assignments } end
def deterministic_encrypted_attributes
def deterministic_encrypted_attributes @deterministic_encrypted_attributes ||= encrypted_attributes&.find_all do |attribute_name| type_for_attribute(attribute_name).deterministic? end end
def encrypt
def encrypt encrypt_attributes if has_encrypted_attributes? end
def encrypt_attribute(name, attribute_scheme)
def encrypt_attribute(name, attribute_scheme) encrypted_attributes << name.to_sym attribute name do |cast_type| ActiveRecord::Encryption::EncryptedAttributeType.new scheme: attribute_scheme, cast_type: cast_type end preserve_original_encrypted(name) if attribute_scheme.ignore_case? ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name) end
def encrypt_attributes
def encrypt_attributes validate_encryption_allowed update_columns build_encrypt_attribute_assignments end
def encrypted_attribute?(attribute_name)
def encrypted_attribute?(attribute_name) ActiveRecord::Encryption.encryptor.encrypted? ciphertext_for(attribute_name) end
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
* :previous - List of previous encryption schemes. When provided, they will be used in order when trying to read
encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
* :context_properties - Additional properties that will override +Context+ settings when this attribute is
is true.
served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
designated column +original_
* :ignore_case - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
in preserving it.
effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
* :downcase - When true, it converts the encrypted content to downcase automatically. This allows to
data.
+deterministic: { fixed: false }+. That will make it use the newest encryption scheme for encrypting new
will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption
initialization vector based on the encrypted content. This means that the same content will generate
with the same key twice will generate different ciphertexts. When set to +true+, it will generate the
initialization vector for each encryption operation. This means that encrypting the same content
* :deterministic - By default, encryption is not deterministic. It will use a random
serves derivated keys. Both options can't be used at the same time.
* :key - A password to derive the key from. It's a shorthand for a +:key_provider+ that
+ActiveRecord::Encryption.key_provider+.
* :key_provider - A key provider to provide encryption and decryption keys. Defaults to
=== Options
Encrypts the +name+ attribute.
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties) self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, \ ignore_case: ignore_case, previous: previous, **context_properties names.each do |name| encrypt_attribute name, scheme end end
def global_previous_schemes_for(scheme)
def global_previous_schemes_for(scheme) ActiveRecord::Encryption.config.previous_schemes.collect do |previous_scheme| scheme.merge(previous_scheme) end end
def has_encrypted_attributes?
def has_encrypted_attributes? self.class.encrypted_attributes.present? end
def load_schema!
def load_schema! super add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size end
def override_accessors_to_preserve_original(name, original_attribute_name)
def override_accessors_to_preserve_original(name, original_attribute_name) include(Module.new do define_method name do if ((value = super()) && encrypted_attribute?(name)) || !ActiveRecord::Encryption.config.support_unencrypted_data send(original_attribute_name) else value end end define_method "#{name}=" do |value| self.send "#{original_attribute_name}=", value super(value) end end) end
def preserve_original_encrypted(name)
def preserve_original_encrypted(name) original_attribute_name = "#{ORIGINAL_ATTRIBUTE_PREFIX}#{name}".to_sym if !ActiveRecord::Encryption.config.support_unencrypted_data && !column_names.include?(original_attribute_name.to_s) raise Errors::Configuration, "To use :ignore_case for '#{name}' you must create an additional column named '#{original_attribute_name}'" end encrypts original_attribute_name override_accessors_to_preserve_original name, original_attribute_name end
def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties) ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme| scheme.previous_schemes = global_previous_schemes_for(scheme) + Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) } end end
def source_attribute_from_preserved_attribute(attribute_name)
def source_attribute_from_preserved_attribute(attribute_name) attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if /^#{ORIGINAL_ATTRIBUTE_PREFIX}/.match?(attribute_name) end
def validate_column_size(attribute_name)
def validate_column_size(attribute_name) if limit = columns_hash[attribute_name.to_s]&.limit validates_length_of attribute_name, maximum: limit end end
def validate_encryption_allowed
def validate_encryption_allowed raise ActiveRecord::Encryption::Errors::Configuration, "can't be modified because it is encrypted" if ActiveRecord::Encryption.context.frozen_encryption? end