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,
#transforms_query_predicates?

Returns true if the type customizes how attributes and values are rendered in hash-based where predicates.

#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,
#query_attribute

Returns the ::Arel node used for the attribute side of a where predicate.

#query_value

Returns the ::Arel node used for the value side of a where predicate.

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

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 134

def serialize_with_oldest?
  return @serialize_with_oldest if defined?(@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 64

def 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 86

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 176

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 188

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 114

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 96

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 172

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 160

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 150

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 168

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 164

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

#freeze

[ GitHub ]

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

def freeze
  serialize_with_oldest?
  previous_types_without_clean_text
  previous_types
  clean_text_scheme

  super
end

#handle_deserialize_error(error, value) (private)

[ GitHub ]

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

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 78

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 ||= {
    support_unencrypted_data? => build_previous_types_for(previous_schemes_including_clean_text),
  }

  @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 82

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 144

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 140

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 180

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 118

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