Module: EncryptedStore::ActiveRecord::Mixin

Defined in:
lib/encrypted_store/active_record/mixin.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.descendantsObject



14
15
16
17
# File 'lib/encrypted_store/active_record/mixin.rb', line 14

def descendants
  Rails.application.eager_load! if defined?(Rails) && Rails.application
  ::ActiveRecord::Base.descendants.select { |model| model < Mixin }
end

.descendants?Boolean

Returns:

  • (Boolean)


19
20
21
# File 'lib/encrypted_store/active_record/mixin.rb', line 19

def descendants?
  !descendants.empty?
end

.included(base) ⇒ Object



7
8
9
10
11
12
# File 'lib/encrypted_store/active_record/mixin.rb', line 7

def included(base)
  base.before_save(:_encrypted_store_save,
    if: :encrypted_attributes_changed?)

  base.extend(ClassMethods)
end

Instance Method Details

#_crypto_hashObject



72
73
74
# File 'lib/encrypted_store/active_record/mixin.rb', line 72

def _crypto_hash
  @_crypto_hash || _decrypt_encrypted_store
end

#_decrypt_encrypted_storeObject



76
77
78
# File 'lib/encrypted_store/active_record/mixin.rb', line 76

def _decrypt_encrypted_store
  @_crypto_hash = CryptoHash.decrypt(_decrypted_key, self.encrypted_store)
end

#_decrypted_keyObject



92
93
94
# File 'lib/encrypted_store/active_record/mixin.rb', line 92

def _decrypted_key
  EncryptedStore.retrieve_dek(EncryptionKey, _encryption_key_id)
end

#_encrypt_encrypted_storeObject



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/encrypted_store/active_record/mixin.rb', line 80

def _encrypt_encrypted_store
  iter_mag = EncryptedStore.config.iteration_magnitude? ?
             EncryptedStore.config.iteration_magnitude  :
             -1

  self.encrypted_store = _crypto_hash.encrypt(
    _decrypted_key,
    EncryptionKeySalt.generate_salt(_encryption_key_id),
    iter_mag
  )
end

#_encrypted_store_dataObject



64
65
66
# File 'lib/encrypted_store/active_record/mixin.rb', line 64

def _encrypted_store_data
  self.class._encrypted_store_data
end

#_encrypted_store_get(field) ⇒ Object



96
97
98
# File 'lib/encrypted_store/active_record/mixin.rb', line 96

def _encrypted_store_get(field)
  _crypto_hash[field]
end

#_encrypted_store_saveObject



113
114
115
116
117
# File 'lib/encrypted_store/active_record/mixin.rb', line 113

def _encrypted_store_save
  _crypto_hash # make sure we have a cached copy of the decrypted data.
  _encrypted_store_sync_key
  _encrypt_encrypted_store
end

#_encrypted_store_set(field, value) ⇒ Object



100
101
102
103
# File 'lib/encrypted_store/active_record/mixin.rb', line 100

def _encrypted_store_set(field, value)
  attribute_will_change!(field)
  _crypto_hash[field] = value
end

#_encrypted_store_sync_keyObject

Locks the record (although doesn’t reload the attributes) and updates the encryption_key_id if it has changed since the record was originally loaded.



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/encrypted_store/active_record/mixin.rb', line 123

def _encrypted_store_sync_key
  unless new_record?
    # Obtain a lock without overriding attribute values for this
    # instance. Here `record` will be an updated version of this instance.
    record = self.class.unscoped { self.class.lock.find(id) }

    if record && record.encryption_key_id
      self.encryption_key_id = record.encryption_key_id
    end
  end
end

#_encryption_key_idObject



68
69
70
# File 'lib/encrypted_store/active_record/mixin.rb', line 68

def _encryption_key_id
  self.encryption_key_id ||= EncryptionKey.primary_encryption_key.id
end

#encrypted_attributes_changed?Boolean

Checks if any of the encrypted attributes are in the list of changed attributes

Returns:

  • (Boolean)


108
109
110
111
# File 'lib/encrypted_store/active_record/mixin.rb', line 108

def encrypted_attributes_changed?
  !(changed.map(&:to_sym) & _encrypted_store_data[:encrypted_attributes])
    .empty?
end

#purge_encrypted_dataObject

Completely purges encrypted data for a record



57
58
59
60
61
62
# File 'lib/encrypted_store/active_record/mixin.rb', line 57

def purge_encrypted_data
  self.encrypted_store = nil
  self.encryption_key_id = nil
  @_crypto_hash = nil
  save!(validate: false)
end

#reencrypt(encryption_key) ⇒ Object

Instance Methods



44
45
46
47
48
49
50
51
52
53
# File 'lib/encrypted_store/active_record/mixin.rb', line 44

def reencrypt(encryption_key)
  with_lock do
    # Must decrypt any changes made to the encrypted data, before updating
    # the encryption_key_id.
    _decrypt_encrypted_store
    self.encryption_key_id = encryption_key.id
    _encrypt_encrypted_store
    save!(validate: false)
  end
end