123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::Encryption::EncryptedAttributeType

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::ImmutableString - Inherited

::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).

::ActiveRecord::Type::Text - Inherited

::ActiveModel::Type::String - Inherited

::ActiveModel::Type::ImmutableString - Inherited

::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, 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 118

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

#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 150

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

#decrypt(value) (private)

[ GitHub ]

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

def decrypt(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 146

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 132

def encrypt(value)
  with_context do
    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 142

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 138

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 110

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 126

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 122

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

#try_to_deserialize_with_previous_encrypted_types(value) (private)

[ GitHub ]

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

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