activerecord 6.1.4.1 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +729 -1161
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +31 -9
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +24 -25
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader/association.rb +161 -49
  25. data/lib/active_record/associations/preloader/batch.rb +51 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +37 -11
  28. data/lib/active_record/associations/preloader.rb +46 -110
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +1 -1
  31. data/lib/active_record/associations.rb +76 -81
  32. data/lib/active_record/asynchronous_queries_tracker.rb +57 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +41 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +6 -9
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +3 -18
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -0
  48. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -558
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -7
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +3 -3
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +112 -66
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
  60. data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
  61. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
  62. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
  63. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  64. data/lib/active_record/connection_adapters/pool_config.rb +1 -3
  65. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  66. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  69. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  70. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  73. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  76. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +157 -100
  79. data/lib/active_record/connection_adapters/schema_cache.rb +26 -3
  80. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  83. data/lib/active_record/connection_adapters.rb +6 -5
  84. data/lib/active_record/connection_handling.rb +20 -38
  85. data/lib/active_record/core.rb +113 -112
  86. data/lib/active_record/database_configurations/database_config.rb +12 -0
  87. data/lib/active_record/database_configurations/hash_config.rb +27 -1
  88. data/lib/active_record/database_configurations/url_config.rb +2 -2
  89. data/lib/active_record/database_configurations.rb +18 -9
  90. data/lib/active_record/delegated_type.rb +33 -11
  91. data/lib/active_record/destroy_association_async_job.rb +1 -1
  92. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  93. data/lib/active_record/dynamic_matchers.rb +1 -1
  94. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  95. data/lib/active_record/encryption/cipher.rb +53 -0
  96. data/lib/active_record/encryption/config.rb +44 -0
  97. data/lib/active_record/encryption/configurable.rb +61 -0
  98. data/lib/active_record/encryption/context.rb +35 -0
  99. data/lib/active_record/encryption/contexts.rb +72 -0
  100. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  101. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  102. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  103. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  104. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  105. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  106. data/lib/active_record/encryption/encryptor.rb +155 -0
  107. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  108. data/lib/active_record/encryption/errors.rb +15 -0
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
  111. data/lib/active_record/encryption/key.rb +28 -0
  112. data/lib/active_record/encryption/key_generator.rb +42 -0
  113. data/lib/active_record/encryption/key_provider.rb +46 -0
  114. data/lib/active_record/encryption/message.rb +33 -0
  115. data/lib/active_record/encryption/message_serializer.rb +80 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  117. data/lib/active_record/encryption/properties.rb +76 -0
  118. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  119. data/lib/active_record/encryption/scheme.rb +99 -0
  120. data/lib/active_record/encryption.rb +55 -0
  121. data/lib/active_record/enum.rb +41 -41
  122. data/lib/active_record/errors.rb +66 -3
  123. data/lib/active_record/fixture_set/file.rb +15 -1
  124. data/lib/active_record/fixture_set/table_row.rb +40 -5
  125. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  126. data/lib/active_record/fixtures.rb +16 -11
  127. data/lib/active_record/future_result.rb +139 -0
  128. data/lib/active_record/gem_version.rb +4 -4
  129. data/lib/active_record/inheritance.rb +55 -17
  130. data/lib/active_record/insert_all.rb +34 -5
  131. data/lib/active_record/integration.rb +1 -1
  132. data/lib/active_record/internal_metadata.rb +3 -5
  133. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  134. data/lib/active_record/locking/optimistic.rb +10 -9
  135. data/lib/active_record/log_subscriber.rb +6 -2
  136. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  137. data/lib/active_record/middleware/database_selector.rb +8 -3
  138. data/lib/active_record/migration/command_recorder.rb +4 -4
  139. data/lib/active_record/migration/compatibility.rb +83 -1
  140. data/lib/active_record/migration/join_table.rb +1 -1
  141. data/lib/active_record/migration.rb +109 -79
  142. data/lib/active_record/model_schema.rb +46 -32
  143. data/lib/active_record/nested_attributes.rb +3 -3
  144. data/lib/active_record/no_touching.rb +2 -2
  145. data/lib/active_record/null_relation.rb +2 -6
  146. data/lib/active_record/persistence.rb +134 -45
  147. data/lib/active_record/query_cache.rb +2 -2
  148. data/lib/active_record/query_logs.rb +203 -0
  149. data/lib/active_record/querying.rb +15 -5
  150. data/lib/active_record/railtie.rb +117 -17
  151. data/lib/active_record/railties/controller_runtime.rb +1 -1
  152. data/lib/active_record/railties/databases.rake +80 -56
  153. data/lib/active_record/readonly_attributes.rb +11 -0
  154. data/lib/active_record/reflection.rb +45 -44
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  156. data/lib/active_record/relation/batches.rb +3 -3
  157. data/lib/active_record/relation/calculations.rb +41 -28
  158. data/lib/active_record/relation/delegation.rb +6 -6
  159. data/lib/active_record/relation/finder_methods.rb +32 -23
  160. data/lib/active_record/relation/merger.rb +20 -13
  161. data/lib/active_record/relation/predicate_builder.rb +1 -6
  162. data/lib/active_record/relation/query_attribute.rb +5 -11
  163. data/lib/active_record/relation/query_methods.rb +232 -49
  164. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  165. data/lib/active_record/relation/spawn_methods.rb +2 -2
  166. data/lib/active_record/relation/where_clause.rb +10 -6
  167. data/lib/active_record/relation.rb +166 -77
  168. data/lib/active_record/result.rb +17 -2
  169. data/lib/active_record/runtime_registry.rb +2 -4
  170. data/lib/active_record/sanitization.rb +11 -7
  171. data/lib/active_record/schema_dumper.rb +3 -3
  172. data/lib/active_record/schema_migration.rb +0 -4
  173. data/lib/active_record/scoping/default.rb +61 -12
  174. data/lib/active_record/scoping/named.rb +3 -11
  175. data/lib/active_record/scoping.rb +40 -22
  176. data/lib/active_record/serialization.rb +1 -1
  177. data/lib/active_record/signed_id.rb +1 -1
  178. data/lib/active_record/tasks/database_tasks.rb +107 -23
  179. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  180. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
  181. data/lib/active_record/test_databases.rb +1 -1
  182. data/lib/active_record/test_fixtures.rb +4 -4
  183. data/lib/active_record/timestamp.rb +3 -4
  184. data/lib/active_record/transactions.rb +9 -14
  185. data/lib/active_record/translation.rb +2 -2
  186. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  187. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  188. data/lib/active_record/type/internal/timezone.rb +2 -2
  189. data/lib/active_record/type/serialized.rb +1 -1
  190. data/lib/active_record/type/type_map.rb +17 -20
  191. data/lib/active_record/type.rb +1 -2
  192. data/lib/active_record/validations/associated.rb +1 -1
  193. data/lib/active_record.rb +170 -2
  194. data/lib/arel/attributes/attribute.rb +0 -8
  195. data/lib/arel/crud.rb +18 -22
  196. data/lib/arel/delete_manager.rb +2 -4
  197. data/lib/arel/insert_manager.rb +2 -3
  198. data/lib/arel/nodes/casted.rb +1 -1
  199. data/lib/arel/nodes/delete_statement.rb +8 -13
  200. data/lib/arel/nodes/insert_statement.rb +2 -2
  201. data/lib/arel/nodes/select_core.rb +2 -2
  202. data/lib/arel/nodes/select_statement.rb +2 -2
  203. data/lib/arel/nodes/update_statement.rb +3 -2
  204. data/lib/arel/predications.rb +1 -1
  205. data/lib/arel/select_manager.rb +10 -4
  206. data/lib/arel/table.rb +0 -1
  207. data/lib/arel/tree_manager.rb +0 -12
  208. data/lib/arel/update_manager.rb +2 -4
  209. data/lib/arel/visitors/dot.rb +80 -90
  210. data/lib/arel/visitors/mysql.rb +6 -1
  211. data/lib/arel/visitors/postgresql.rb +0 -10
  212. data/lib/arel/visitors/to_sql.rb +43 -2
  213. data/lib/arel.rb +1 -1
  214. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  215. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  216. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  217. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  218. metadata +55 -16
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module EncryptedFixtures
6
+ def initialize(fixture, model_class)
7
+ @clean_values = {}
8
+ encrypt_fixture_data(fixture, model_class)
9
+ process_preserved_original_columns(fixture, model_class)
10
+ super
11
+ end
12
+
13
+ private
14
+ def encrypt_fixture_data(fixture, model_class)
15
+ model_class&.encrypted_attributes&.each do |attribute_name|
16
+ if clean_value = fixture[attribute_name.to_s]
17
+ @clean_values[attribute_name.to_s] = clean_value
18
+
19
+ type = model_class.type_for_attribute(attribute_name)
20
+ encrypted_value = type.serialize(clean_value)
21
+ fixture[attribute_name.to_s] = encrypted_value
22
+ end
23
+ end
24
+ end
25
+
26
+ def process_preserved_original_columns(fixture, model_class)
27
+ model_class&.encrypted_attributes&.each do |attribute_name|
28
+ if source_attribute_name = model_class.source_attribute_from_preserved_attribute(attribute_name)
29
+ clean_value = @clean_values[source_attribute_name.to_s]
30
+ type = model_class.type_for_attribute(attribute_name)
31
+ encrypted_value = type.serialize(clean_value)
32
+ fixture[attribute_name.to_s] = encrypted_value
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An encryptor that can encrypt data but can't decrypt it.
6
+ class EncryptingOnlyEncryptor < Encryptor
7
+ def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
8
+ encrypted_text
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "zlib"
5
+ require "active_support/core_ext/numeric"
6
+
7
+ module ActiveRecord
8
+ module Encryption
9
+ # An encryptor exposes the encryption API that +ActiveRecord::Encryption::EncryptedAttributeType+
10
+ # uses for encrypting and decrypting attribute values.
11
+ #
12
+ # It interacts with a +KeyProvider+ for getting the keys, and delegate to
13
+ # +ActiveRecord::Encryption::Cipher+ the actual encryption algorithm.
14
+ class Encryptor
15
+ # Encrypts +clean_text+ and returns the encrypted result
16
+ #
17
+ # Internally, it will:
18
+ #
19
+ # 1. Create a new +ActiveRecord::Encryption::Message+
20
+ # 2. Compress and encrypt +clean_text+ as the message payload
21
+ # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
22
+ # by default)
23
+ # 4. Encode the result with Base 64
24
+ #
25
+ # === Options
26
+ #
27
+ # [:key_provider]
28
+ # Key provider to use for the encryption operation. It will default to
29
+ # +ActiveRecord::Encryption.key_provider+ when not provided
30
+ #
31
+ # [:cipher_options]
32
+ # +Cipher+-specific options that will be passed to the Cipher configured in
33
+ # +ActiveRecord::Encryption.cipher+
34
+ def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
35
+ clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
36
+
37
+ validate_payload_type(clear_text)
38
+ serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
39
+ end
40
+
41
+ # Decrypts a +clean_text+ and returns the result as clean text
42
+ #
43
+ # === Options
44
+ #
45
+ # [:key_provider]
46
+ # Key provider to use for the encryption operation. It will default to
47
+ # +ActiveRecord::Encryption.key_provider+ when not provided
48
+ #
49
+ # [:cipher_options]
50
+ # +Cipher+-specific options that will be passed to the Cipher configured in
51
+ # +ActiveRecord::Encryption.cipher+
52
+ def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
53
+ message = deserialize_message(encrypted_text)
54
+ keys = key_provider.decryption_keys(message)
55
+ raise Errors::Decryption unless keys.present?
56
+ uncompress_if_needed(cipher.decrypt(message, key: keys.collect(&:secret), **cipher_options), message.headers.compressed)
57
+ rescue *(ENCODING_ERRORS + DECRYPT_ERRORS)
58
+ raise Errors::Decryption
59
+ end
60
+
61
+ # Returns whether the text is encrypted or not
62
+ def encrypted?(text)
63
+ deserialize_message(text)
64
+ true
65
+ rescue Errors::Encoding, *DECRYPT_ERRORS
66
+ false
67
+ end
68
+
69
+ private
70
+ DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
71
+ ENCODING_ERRORS = [EncodingError, Errors::Encoding]
72
+ THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
73
+
74
+ def default_key_provider
75
+ ActiveRecord::Encryption.key_provider
76
+ end
77
+
78
+ def validate_payload_type(clear_text)
79
+ unless clear_text.is_a?(String)
80
+ raise ActiveRecord::Encryption::Errors::ForbiddenClass, "The encryptor can only encrypt string values (#{clear_text.class})"
81
+ end
82
+ end
83
+
84
+ def cipher
85
+ ActiveRecord::Encryption.cipher
86
+ end
87
+
88
+ def build_encrypted_message(clear_text, key_provider:, cipher_options:)
89
+ key = key_provider.encryption_key
90
+
91
+ clear_text, was_compressed = compress_if_worth_it(clear_text)
92
+ cipher.encrypt(clear_text, key: key.secret, **cipher_options).tap do |message|
93
+ message.headers.add(key.public_tags)
94
+ message.headers.compressed = true if was_compressed
95
+ end
96
+ end
97
+
98
+ def serialize_message(message)
99
+ serializer.dump(message)
100
+ end
101
+
102
+ def deserialize_message(message)
103
+ raise Errors::Encoding unless message.is_a?(String)
104
+ serializer.load message
105
+ rescue ArgumentError, TypeError, Errors::ForbiddenClass
106
+ raise Errors::Encoding
107
+ end
108
+
109
+ def serializer
110
+ ActiveRecord::Encryption.message_serializer
111
+ end
112
+
113
+ # Under certain threshold, ZIP compression is actually worse that not compressing
114
+ def compress_if_worth_it(string)
115
+ if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
116
+ [compress(string), true]
117
+ else
118
+ [string, false]
119
+ end
120
+ end
121
+
122
+ def compress(data)
123
+ Zlib::Deflate.deflate(data).tap do |compressed_data|
124
+ compressed_data.force_encoding(data.encoding)
125
+ end
126
+ end
127
+
128
+ def uncompress_if_needed(data, compressed)
129
+ if compressed
130
+ uncompress(data)
131
+ else
132
+ data
133
+ end
134
+ end
135
+
136
+ def uncompress(data)
137
+ Zlib::Inflate.inflate(data).tap do |uncompressed_data|
138
+ uncompressed_data.force_encoding(data.encoding)
139
+ end
140
+ end
141
+
142
+ def force_encoding_if_needed(value)
143
+ if forced_encoding_for_deterministic_encryption && value && value.encoding != forced_encoding_for_deterministic_encryption
144
+ value.encode(forced_encoding_for_deterministic_encryption, invalid: :replace, undef: :replace)
145
+ else
146
+ value
147
+ end
148
+ end
149
+
150
+ def forced_encoding_for_deterministic_encryption
151
+ ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Implements a simple envelope encryption approach where:
6
+ #
7
+ # * It generates a random data-encryption key for each encryption operation
8
+ # * It stores the generated key along with the encrypted payload. It encrypts this key
9
+ # with the master key provided in the credential +active_record.encryption.master key+
10
+ #
11
+ # This provider can work with multiple master keys. It will use the last one for encrypting.
12
+ #
13
+ # When `config.store_key_references` is true, it will also store a reference to
14
+ # the specific master key that was used to encrypt the data-encryption key. When not set,
15
+ # it will try all the configured master keys looking for the right one, in order to
16
+ # return the right decryption key.
17
+ class EnvelopeEncryptionKeyProvider
18
+ def encryption_key
19
+ random_secret = generate_random_secret
20
+ ActiveRecord::Encryption::Key.new(random_secret).tap do |key|
21
+ key.public_tags.encrypted_data_key = encrypt_data_key(random_secret)
22
+ key.public_tags.encrypted_data_key_id = active_primary_key.id if ActiveRecord::Encryption.config.store_key_references
23
+ end
24
+ end
25
+
26
+ def decryption_keys(encrypted_message)
27
+ secret = decrypt_data_key(encrypted_message)
28
+ secret ? [ActiveRecord::Encryption::Key.new(secret)] : []
29
+ end
30
+
31
+ def active_primary_key
32
+ @active_primary_key ||= primary_key_provider.encryption_key
33
+ end
34
+
35
+ private
36
+ def encrypt_data_key(random_secret)
37
+ ActiveRecord::Encryption.cipher.encrypt(random_secret, key: active_primary_key.secret)
38
+ end
39
+
40
+ def decrypt_data_key(encrypted_message)
41
+ encrypted_data_key = encrypted_message.headers.encrypted_data_key
42
+ key = primary_key_provider.decryption_keys(encrypted_message)&.collect(&:secret)
43
+ ActiveRecord::Encryption.cipher.decrypt encrypted_data_key, key: key if key
44
+ end
45
+
46
+ def primary_key_provider
47
+ @primary_key_provider ||= DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key)
48
+ end
49
+
50
+ def generate_random_secret
51
+ ActiveRecord::Encryption.key_generator.generate_random_key
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module Errors
6
+ class Base < StandardError; end
7
+ class Encoding < Base; end
8
+ class Decryption < Base; end
9
+ class Encryption < Base; end
10
+ class Configuration < Base; end
11
+ class ForbiddenClass < Base; end
12
+ class EncryptedContentIntegrity < Base; end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
4
+ #
5
+ # Active Record Encryption supports querying the db using deterministic attributes. For example:
6
+ #
7
+ # Contact.find_by(email_address: "jorge@hey.com")
8
+ #
9
+ # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
10
+ # a problem while the data is being encrypted. This won't work. During that time, you need these
11
+ # queries to be:
12
+ #
13
+ # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
14
+ #
15
+ # This patches ActiveRecord to support this automatically. It addresses both:
16
+ #
17
+ # * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
18
+ # * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
19
+ #
20
+ # +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
21
+ # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
22
+ # as it's invoked (so that the proper prepared statement is cached).
23
+ #
24
+ # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
25
+ # make sure performance overhead is acceptable.
26
+ #
27
+ # We will extend this to support previous "encryption context" versions in future iterations
28
+ #
29
+ # @TODO Experimental. Support for every kind of query is pending
30
+ # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
31
+ module ActiveRecord
32
+ module Encryption
33
+ module ExtendedDeterministicQueries
34
+ def self.install_support
35
+ ActiveRecord::Relation.prepend(RelationQueries)
36
+ ActiveRecord::Base.include(CoreQueries)
37
+ ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType)
38
+ Arel::Nodes::HomogeneousIn.prepend(InWithAdditionalValues)
39
+ end
40
+
41
+ module EncryptedQueryArgumentProcessor
42
+ extend ActiveSupport::Concern
43
+
44
+ private
45
+ def process_encrypted_query_arguments(args, check_for_additional_values)
46
+ if args.is_a?(Array) && (options = args.first).is_a?(Hash)
47
+ self.deterministic_encrypted_attributes&.each do |attribute_name|
48
+ type = type_for_attribute(attribute_name)
49
+ if !type.previous_types.empty? && value = options[attribute_name]
50
+ options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def process_encrypted_query_argument(value, check_for_additional_values, type)
57
+ return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
58
+
59
+ case value
60
+ when String, Array
61
+ list = Array(value)
62
+ list + list.flat_map do |each_value|
63
+ if check_for_additional_values && each_value.is_a?(AdditionalValue)
64
+ each_value
65
+ else
66
+ additional_values_for(each_value, type)
67
+ end
68
+ end
69
+ else
70
+ value
71
+ end
72
+ end
73
+
74
+ def additional_values_for(value, type)
75
+ type.previous_types.collect do |additional_type|
76
+ AdditionalValue.new(value, additional_type)
77
+ end
78
+ end
79
+ end
80
+
81
+ module RelationQueries
82
+ include EncryptedQueryArgumentProcessor
83
+
84
+ def where(*args)
85
+ process_encrypted_query_arguments_if_needed(args)
86
+ super
87
+ end
88
+
89
+ def exists?(*args)
90
+ process_encrypted_query_arguments_if_needed(args)
91
+ super
92
+ end
93
+
94
+ def find_or_create_by(attributes, &block)
95
+ find_by(attributes.dup) || create(attributes, &block)
96
+ end
97
+
98
+ def find_or_create_by!(attributes, &block)
99
+ find_by(attributes.dup) || create!(attributes, &block)
100
+ end
101
+
102
+ private
103
+ def process_encrypted_query_arguments_if_needed(args)
104
+ process_encrypted_query_arguments(args, true) unless self.deterministic_encrypted_attributes&.empty?
105
+ end
106
+ end
107
+
108
+ module CoreQueries
109
+ extend ActiveSupport::Concern
110
+
111
+ class_methods do
112
+ include EncryptedQueryArgumentProcessor
113
+
114
+ def find_by(*args)
115
+ process_encrypted_query_arguments(args, false) unless self.deterministic_encrypted_attributes&.empty?
116
+ super
117
+ end
118
+ end
119
+ end
120
+
121
+ class AdditionalValue
122
+ attr_reader :value, :type
123
+
124
+ def initialize(value, type)
125
+ @type = type
126
+ @value = process(value)
127
+ end
128
+
129
+ private
130
+ def process(value)
131
+ type.serialize(value)
132
+ end
133
+ end
134
+
135
+ module ExtendedEncryptableType
136
+ def serialize(data)
137
+ if data.is_a?(AdditionalValue)
138
+ data.value
139
+ else
140
+ super
141
+ end
142
+ end
143
+ end
144
+
145
+ module InWithAdditionalValues
146
+ def proc_for_binds
147
+ -> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, encryption_aware_type_caster) }
148
+ end
149
+
150
+ def encryption_aware_type_caster
151
+ if attribute.type_caster.is_a?(ActiveRecord::Encryption::EncryptedAttributeType)
152
+ attribute.type_caster.cast_type
153
+ else
154
+ attribute.type_caster
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module ExtendedDeterministicUniquenessValidator
6
+ def self.install_support
7
+ ActiveRecord::Validations::UniquenessValidator.prepend(EncryptedUniquenessValidator)
8
+ end
9
+
10
+ module EncryptedUniquenessValidator
11
+ def validate_each(record, attribute, value)
12
+ super(record, attribute, value)
13
+
14
+ klass = record.class
15
+ if klass.deterministic_encrypted_attributes&.each do |attribute_name|
16
+ encrypted_type = klass.type_for_attribute(attribute_name)
17
+ [ encrypted_type, *encrypted_type.previous_types ].each do |type|
18
+ encrypted_value = type.serialize(value)
19
+ ActiveRecord::Encryption.without_encryption do
20
+ super(record, attribute, encrypted_value)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end