123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::Encryption::EncryptedAttributeType

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: ActiveModel::Type::Value
Defined in: activerecord/lib/active_record/encryption/encrypted_attribute_type.rb

Overview

An ::ActiveModel::Type::Value that encrypts/decrypts strings of text.

This is the central piece that connects the encryption system with encrypts declarations in the model classes. Whenever you declare an attribute as encrypted, it configures an EncryptedAttributeType for that attribute.

Class Method Summary

::ActiveModel::Type::Value - Inherited

.new

Initializes a type with three basic configuration settings: precision, limit, and scale.

Instance Attribute Summary

::ActiveModel::Type::Helpers::Mutable - Included

::ActiveModel::Type::Value - Inherited

#limit, #precision, #scale,
#binary?

These predicates are not documented, as I need to look further into their use, and see if they can be removed entirely.

#mutable?, #serialized?

Instance Method Summary

::ActiveModel::Type::Helpers::Mutable - Included

#cast,
#changed_in_place?

raw_old_value will be the _before_type_cast version of the value (likely a string).

::ActiveModel::Type::Value - Inherited

#==, #as_json, #assert_valid_value,
#cast

::ActiveRecord::Type casts a value from user input (e.g. from a setter).

#changed?

Determines whether a value has changed for dirty checking.

#changed_in_place?

Determines whether the mutable value has been modified since it was read.

#deserialize

Converts a value from database input to the appropriate ruby type.

#eql?
#hash,
#serializable?

Returns true if this type can convert value to a type that is usable by the database.

#serialize

Casts a value from the ruby type to a type that the database knows how to understand.

#type

Returns the unique type name as a ::Symbol.

#cast_value

Convenience method for types which do not need separate type casting behavior for user and database inputs.

#force_equality?, #map,
#type_cast_for_schema

::ActiveRecord::Type casts a value for schema dumping.

#value_constructed_by_mass_assignment?

::ActiveModel::Type::SerializeCastValue - Included

Constructor Details

.new(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil) ⇒ EncryptedAttributeType

Options

  • :scheme - A Scheme with the encryption properties for this attribute.

  • :cast_type - A type that will be used to serialize (before encrypting) and deserialize (after decrypting). ActiveModel::Type::String by default.

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 23

def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil)
  super()
  @scheme = scheme
  @cast_type = cast_type
  @previous_type = previous_type
  @default = default
end

Instance Attribute Details

#accessor (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 16

delegate :accessor, :type, to: :cast_type

#cast_type (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 13

attr_reader :scheme, :cast_type

#key_provider (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 15

delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme

#previous_schemes (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 15

delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme

#previous_type?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 80

def previous_type?
  @previous_type
end

#scheme (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 13

attr_reader :scheme, :cast_type

#serialize_with_oldest?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 122

def serialize_with_oldest?
  @serialize_with_oldest ||= fixed? && previous_types_without_clean_text.present?
end

#support_unencrypted_data?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 61

def support_unencrypted_data?
  ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
end

#type (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 16

delegate :accessor, :type, to: :cast_type

#with_context (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 15

delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme

Instance Method Details

#build_previous_types_for(schemes) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 74

def build_previous_types_for(schemes)
  schemes.collect do |scheme|
    EncryptedAttributeType.new(scheme: scheme, previous_type: true)
  end
end

#cast(value)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 31

def cast(value)
  cast_type.cast(value)
end

#changed_in_place?(raw_old_value, new_value) ⇒ Boolean

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 51

def changed_in_place?(raw_old_value, new_value)
  old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
  old_value != new_value
end

#clean_text_scheme (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 162

def clean_text_scheme
  @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
end

#database_type_to_text(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 174

def database_type_to_text(value)
  if value && cast_type.binary?
    binary_cast_type = cast_type.serialized? ? cast_type.subtype : cast_type
    binary_cast_type.deserialize(value)
  else
    value
  end
end

#decrypt(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 102

def decrypt(value)
  text_to_database_type decrypt_as_text(database_type_to_text(value))
end

#decrypt_as_text(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 84

def decrypt_as_text(value)
  with_context do
    unless value.nil?
      if @default && @default == value
        value
      else
        encryptor.decrypt(value, **decryption_options)
      end
    end
  end
rescue ActiveRecord::Encryption::Errors::Base => error
  if previous_types_without_clean_text.blank?
    handle_deserialize_error(error, value)
  else
    try_to_deserialize_with_previous_encrypted_types(value)
  end
end

#decryption_options (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 158

def decryption_options
  { key_provider: key_provider }.compact
end

#deserialize(value)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 35

def deserialize(value)
  cast_type.deserialize decrypt(value)
end

#deterministic?Boolean

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 15

delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme

#downcase?Boolean

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 15

delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme

#encrypt(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 146

def encrypt(value)
  text_to_database_type encrypt_as_text(value)
end

#encrypt_as_text(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 136

def encrypt_as_text(value)
  with_context do
    if encryptor.binary? && !cast_type.binary?
      raise Errors::Encoding, "Binary encoded data can only be stored in binary columns"
    end

    encryptor.encrypt(value, **encryption_options)
  end
end

#encrypted?(value) ⇒ Boolean

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 47

def encrypted?(value)
  with_context { encryptor.encrypted? value }
end

#encryption_options (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 154

def encryption_options
  { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
end

#encryptor (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 150

def encryptor
  ActiveRecord::Encryption.encryptor
end

#fixed?Boolean

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 15

delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme

#handle_deserialize_error(error, value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 114

def handle_deserialize_error(error, value)
  if error.is_a?(Errors::Decryption) && support_unencrypted_data?
    value
  else
    raise error
  end
end

#previous_schemes_including_clean_text (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 66

def previous_schemes_including_clean_text
  previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
end

#previous_types

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 56

def previous_types # :nodoc:
  @previous_types ||= {} # Memoizing on support_unencrypted_data so that we can tweak it during tests
  @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
end

#previous_types_without_clean_text (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 70

def previous_types_without_clean_text
  @previous_types_without_clean_text ||= build_previous_types_for(previous_schemes)
end

#serialize(value)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 39

def serialize(value)
  if serialize_with_oldest?
    serialize_with_oldest(value)
  else
    serialize_with_current(value)
  end
end

#serialize_with_current(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 130

def serialize_with_current(value)
  casted_value = cast_type.serialize(value)
  casted_value = casted_value&.downcase if downcase?
  encrypt(casted_value.to_s) unless casted_value.nil?
end

#serialize_with_oldest(value) (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 126

def serialize_with_oldest(value)
  previous_types.first.serialize(value)
end

#text_to_database_type(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 166

def text_to_database_type(value)
  if value && cast_type.binary?
    ActiveModel::Type::Binary::Data.new(value)
  else
    value
  end
end

#try_to_deserialize_with_previous_encrypted_types(value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/encryption/encrypted_attribute_type.rb', line 106

def try_to_deserialize_with_previous_encrypted_types(value)
  previous_types.each.with_index do |type, index|
    break type.deserialize(value)
  rescue ActiveRecord::Encryption::Errors::Base => error
    handle_deserialize_error(error, value) if index == previous_types.length - 1
  end
end