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



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

def _crypto_hash
  @_crypto_hash || _decrypt_encrypted_store
end

#_decrypt_encrypted_storeObject



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

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

#_decrypted_keyObject



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

def _decrypted_key
  EncryptedStore.retrieve_dek(EncryptionKey, _encryption_key_id)
end

#_encrypt_encrypted_storeObject



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

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



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

def _encrypted_store_data
  self.class._encrypted_store_data
end

#_encrypted_store_get(field) ⇒ Object



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

def _encrypted_store_get(field)
  _crypto_hash[field]
end

#_encrypted_store_saveObject



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

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



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

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.



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

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



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

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)


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

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



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

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



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

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