activerecord 7.0.8.7 → 7.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. data/lib/active_record/null_relation.rb +0 -63
@@ -28,8 +28,12 @@ module ActiveRecord
28
28
  # initialization vector based on the encrypted content. This means that the same content will generate
29
29
  # the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption
30
30
  # will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
31
- # +deterministic: { fixed: false }+. That will make it use the newest encryption scheme for encrypting new
31
+ # <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
32
32
  # data.
33
+ # * <tt>:support_unencrypted_data</tt> - If `config.active_record.encryption.support_unencrypted_data` is +true+,
34
+ # you can set this to +false+ to opt out of unencrypted data support for this attribute. This is useful for
35
+ # scenarios where you encrypt one column, and want to disable support for unencrypted data without having to tweak
36
+ # the global setting.
33
37
  # * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
34
38
  # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
35
39
  # in preserving it.
@@ -42,13 +46,11 @@ module ActiveRecord
42
46
  # * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
43
47
  # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
44
48
  # encryption is used, they will be used to generate additional ciphertexts to check in the queries.
45
- def encrypts(*names, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
49
+ def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
46
50
  self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
47
- scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, \
48
- ignore_case: ignore_case, previous: previous, **context_properties
49
51
 
50
52
  names.each do |name|
51
- encrypt_attribute name, scheme
53
+ encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
52
54
  end
53
55
  end
54
56
 
@@ -61,32 +63,35 @@ module ActiveRecord
61
63
 
62
64
  # Given a attribute name, it returns the name of the source attribute when it's a preserved one.
63
65
  def source_attribute_from_preserved_attribute(attribute_name)
64
- attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if /^#{ORIGINAL_ATTRIBUTE_PREFIX}/.match?(attribute_name)
66
+ attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if attribute_name.start_with?(ORIGINAL_ATTRIBUTE_PREFIX)
65
67
  end
66
68
 
67
69
  private
68
- def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
70
+ def scheme_for(key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
69
71
  ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
70
- downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
72
+ support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
71
73
  scheme.previous_schemes = global_previous_schemes_for(scheme) +
72
- Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
74
+ Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
73
75
  end
74
76
  end
75
77
 
76
78
  def global_previous_schemes_for(scheme)
77
- ActiveRecord::Encryption.config.previous_schemes.collect do |previous_scheme|
78
- scheme.merge(previous_scheme)
79
+ ActiveRecord::Encryption.config.previous_schemes.filter_map do |previous_scheme|
80
+ scheme.merge(previous_scheme) if scheme.compatible_with?(previous_scheme)
79
81
  end
80
82
  end
81
83
 
82
- def encrypt_attribute(name, attribute_scheme)
84
+ def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
83
85
  encrypted_attributes << name.to_sym
84
86
 
85
87
  attribute name do |cast_type|
86
- ActiveRecord::Encryption::EncryptedAttributeType.new scheme: attribute_scheme, cast_type: cast_type
88
+ scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
89
+ downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
90
+
91
+ ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme, cast_type: cast_type, default: columns_hash[name.to_s]&.default)
87
92
  end
88
93
 
89
- preserve_original_encrypted(name) if attribute_scheme.ignore_case?
94
+ preserve_original_encrypted(name) if ignore_case
90
95
  ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
91
96
  end
92
97
 
@@ -139,12 +144,16 @@ module ActiveRecord
139
144
 
140
145
  # Returns whether a given attribute is encrypted or not.
141
146
  def encrypted_attribute?(attribute_name)
142
- ActiveRecord::Encryption.encryptor.encrypted? ciphertext_for(attribute_name)
147
+ ActiveRecord::Encryption.encryptor.encrypted? read_attribute_before_type_cast(attribute_name)
143
148
  end
144
149
 
145
150
  # Returns the ciphertext for +attribute_name+.
146
151
  def ciphertext_for(attribute_name)
147
- read_attribute_before_type_cast(attribute_name)
152
+ if encrypted_attribute?(attribute_name)
153
+ read_attribute_before_type_cast(attribute_name)
154
+ else
155
+ read_attribute_for_database(attribute_name)
156
+ end
148
157
  end
149
158
 
150
159
  # Encrypts all the encryptable attributes and saves the changes.
@@ -160,6 +169,15 @@ module ActiveRecord
160
169
  private
161
170
  ORIGINAL_ATTRIBUTE_PREFIX = "original_"
162
171
 
172
+ def _create_record(attribute_names = self.attribute_names)
173
+ if has_encrypted_attributes?
174
+ # Always persist encrypted attributes, because an attribute might be
175
+ # encrypting a column default value.
176
+ attribute_names |= self.class.encrypted_attributes.map(&:to_s)
177
+ end
178
+ super
179
+ end
180
+
163
181
  def encrypt_attributes
164
182
  validate_encryption_allowed
165
183
 
@@ -188,12 +206,12 @@ module ActiveRecord
188
206
  end
189
207
 
190
208
  def build_decrypt_attribute_assignments
191
- Array(self.class.encrypted_attributes).collect do |attribute_name|
209
+ Array(self.class.encrypted_attributes).to_h do |attribute_name|
192
210
  type = type_for_attribute(attribute_name)
193
211
  encrypted_value = ciphertext_for(attribute_name)
194
212
  new_value = type.deserialize(encrypted_value)
195
213
  [attribute_name, new_value]
196
- end.to_h
214
+ end
197
215
  end
198
216
 
199
217
  def cant_modify_encrypted_attributes_when_frozen
@@ -20,11 +20,16 @@ module ActiveRecord
20
20
  # * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
21
21
  # * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
22
22
  # (after decrypting). ActiveModel::Type::String by default.
23
- def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false)
23
+ def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil)
24
24
  super()
25
25
  @scheme = scheme
26
26
  @cast_type = cast_type
27
27
  @previous_type = previous_type
28
+ @default = default
29
+ end
30
+
31
+ def cast(value)
32
+ cast_type.cast(value)
28
33
  end
29
34
 
30
35
  def deserialize(value)
@@ -49,6 +54,10 @@ module ActiveRecord
49
54
  @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
50
55
  end
51
56
 
57
+ def support_unencrypted_data?
58
+ ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
59
+ end
60
+
52
61
  private
53
62
  def previous_schemes_including_clean_text
54
63
  previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
@@ -70,7 +79,13 @@ module ActiveRecord
70
79
 
71
80
  def decrypt(value)
72
81
  with_context do
73
- encryptor.decrypt(value, **decryption_options) unless value.nil?
82
+ unless value.nil?
83
+ if @default && @default == value
84
+ value
85
+ else
86
+ encryptor.decrypt(value, **decryption_options)
87
+ end
88
+ end
74
89
  end
75
90
  rescue ActiveRecord::Encryption::Errors::Base => error
76
91
  if previous_types_without_clean_text.blank?
@@ -120,10 +135,6 @@ module ActiveRecord
120
135
  ActiveRecord::Encryption.encryptor
121
136
  end
122
137
 
123
- def support_unencrypted_data?
124
- ActiveRecord::Encryption.config.support_unencrypted_data && !previous_type?
125
- end
126
-
127
138
  def encryption_options
128
139
  @encryption_options ||= { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
129
140
  end
@@ -19,101 +19,113 @@ module ActiveRecord
19
19
  # * ActiveRecord::Base - Used in <tt>Contact.find_by_email_address(...)</tt>
20
20
  # * ActiveRecord::Relation - Used in <tt>Contact.internal.find_by_email_address(...)</tt>
21
21
  #
22
- # ActiveRecord::Base relies on ActiveRecord::Relation (ActiveRecord::QueryMethods) but it does
23
- # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
24
- # as it's invoked (so that the proper prepared statement is cached).
25
- #
26
- # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
27
- # make sure performance overhead is acceptable.
28
- #
29
- # We will extend this to support previous "encryption context" versions in future iterations
30
- #
31
- # @TODO Experimental. Support for every kind of query is pending
32
- # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
22
+ # This module is included if `config.active_record.encryption.extend_queries` is `true`.
33
23
  module ExtendedDeterministicQueries
34
24
  def self.install_support
25
+ # ActiveRecord::Base relies on ActiveRecord::Relation (ActiveRecord::QueryMethods) but it does
26
+ # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
27
+ # as it's invoked (so that the proper prepared statement is cached).
35
28
  ActiveRecord::Relation.prepend(RelationQueries)
36
29
  ActiveRecord::Base.include(CoreQueries)
37
30
  ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType)
38
31
  Arel::Nodes::HomogeneousIn.prepend(InWithAdditionalValues)
39
32
  end
40
33
 
41
- module EncryptedQueryArgumentProcessor
42
- extend ActiveSupport::Concern
34
+ # When modifying this file run performance tests in
35
+ # +activerecord/test/cases/encryption/performance/extended_deterministic_queries_performance_test.rb+
36
+ # to make sure performance overhead is acceptable.
37
+ #
38
+ # @TODO We will extend this to support previous "encryption context" versions in future iterations
39
+ # @TODO Experimental. Support for every kind of query is pending
40
+ # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
41
+
42
+ module EncryptedQuery # :nodoc:
43
+ class << self
44
+ def process_arguments(owner, args, check_for_additional_values)
45
+ return args if owner.deterministic_encrypted_attributes&.empty?
43
46
 
44
- private
45
- def process_encrypted_query_arguments(args, check_for_additional_values)
46
47
  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)
48
+ options = options.transform_keys do |key|
49
+ if key.is_a?(Array)
50
+ key.map(&:to_s)
51
+ else
52
+ key.to_s
53
+ end
54
+ end
55
+ args[0] = options
56
+
57
+ owner.deterministic_encrypted_attributes&.each do |attribute_name|
58
+ attribute_name = attribute_name.to_s
59
+ type = owner.type_for_attribute(attribute_name)
49
60
  if !type.previous_types.empty? && value = options[attribute_name]
50
61
  options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
51
62
  end
52
63
  end
53
64
  end
54
- end
55
65
 
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)
66
+ args
67
+ end
58
68
 
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)
69
+ private
70
+ def process_encrypted_query_argument(value, check_for_additional_values, type)
71
+ return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
72
+
73
+ case value
74
+ when String, Array
75
+ list = Array(value)
76
+ list + list.flat_map do |each_value|
77
+ if check_for_additional_values && each_value.is_a?(AdditionalValue)
78
+ each_value
79
+ else
80
+ additional_values_for(each_value, type)
81
+ end
67
82
  end
83
+ else
84
+ value
68
85
  end
69
- else
70
- value
71
86
  end
72
- end
73
87
 
74
- def additional_values_for(value, type)
75
- type.previous_types.collect do |additional_type|
76
- AdditionalValue.new(value, additional_type)
88
+ def additional_values_for(value, type)
89
+ type.previous_types.collect do |additional_type|
90
+ AdditionalValue.new(value, additional_type)
91
+ end
77
92
  end
78
- end
93
+ end
79
94
  end
80
95
 
81
96
  module RelationQueries
82
- include EncryptedQueryArgumentProcessor
83
-
84
97
  def where(*args)
85
- process_encrypted_query_arguments_if_needed(args)
86
- super
98
+ super(*EncryptedQuery.process_arguments(self, args, true))
87
99
  end
88
100
 
89
101
  def exists?(*args)
90
- process_encrypted_query_arguments_if_needed(args)
91
- super
102
+ super(*EncryptedQuery.process_arguments(self, args, true))
92
103
  end
93
104
 
94
- def find_or_create_by(attributes, &block)
95
- find_by(attributes.dup) || create(attributes, &block)
96
- end
105
+ def scope_for_create
106
+ return super unless klass.deterministic_encrypted_attributes&.any?
97
107
 
98
- def find_or_create_by!(attributes, &block)
99
- find_by(attributes.dup) || create!(attributes, &block)
100
- end
108
+ scope_attributes = super
109
+ wheres = where_values_hash
101
110
 
102
- private
103
- def process_encrypted_query_arguments_if_needed(args)
104
- process_encrypted_query_arguments(args, true) unless self.deterministic_encrypted_attributes&.empty?
111
+ klass.deterministic_encrypted_attributes.each do |attribute_name|
112
+ attribute_name = attribute_name.to_s
113
+ values = wheres[attribute_name]
114
+ if values.is_a?(Array) && values[1..].all?(AdditionalValue)
115
+ scope_attributes[attribute_name] = values.first
116
+ end
105
117
  end
118
+
119
+ scope_attributes
120
+ end
106
121
  end
107
122
 
108
123
  module CoreQueries
109
124
  extend ActiveSupport::Concern
110
125
 
111
126
  class_methods do
112
- include EncryptedQueryArgumentProcessor
113
-
114
127
  def find_by(*args)
115
- process_encrypted_query_arguments(args, false) unless self.deterministic_encrypted_attributes&.empty?
116
- super
128
+ super(*EncryptedQuery.process_arguments(self, args, false))
117
129
  end
118
130
  end
119
131
  end
@@ -12,8 +12,8 @@ module ActiveRecord
12
12
  super(record, attribute, value)
13
13
 
14
14
  klass = record.class
15
- klass.deterministic_encrypted_attributes&.each do |attribute_name|
16
- encrypted_type = klass.type_for_attribute(attribute_name)
15
+ if klass.deterministic_encrypted_attributes&.include?(attribute)
16
+ encrypted_type = klass.type_for_attribute(attribute)
17
17
  [ encrypted_type, *encrypted_type.previous_types ].each do |type|
18
18
  encrypted_value = type.serialize(value)
19
19
  ActiveRecord::Encryption.without_encryption do
@@ -6,6 +6,12 @@ module ActiveRecord
6
6
  module Encryption
7
7
  # Utility for generating and deriving random keys.
8
8
  class KeyGenerator
9
+ attr_reader :hash_digest_class
10
+
11
+ def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class)
12
+ @hash_digest_class = hash_digest_class
13
+ end
14
+
9
15
  # Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default)
10
16
  def generate_random_key(length: key_length)
11
17
  SecureRandom.random_bytes(length)
@@ -30,10 +36,15 @@ module ActiveRecord
30
36
  #
31
37
  # The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+
32
38
  def derive_key_from(password, length: key_length)
33
- ActiveSupport::KeyGenerator.new(password).generate_key(ActiveRecord::Encryption.config.key_derivation_salt, length)
39
+ ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class)
40
+ .generate_key(key_derivation_salt, length)
34
41
  end
35
42
 
36
43
  private
44
+ def key_derivation_salt
45
+ @key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt
46
+ end
47
+
37
48
  def key_length
38
49
  @key_length ||= ActiveRecord::Encryption.cipher.key_length
39
50
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
4
+
3
5
  module ActiveRecord
4
6
  module Encryption
5
7
  # A message serializer that serializes +Messages+ with JSON.
@@ -14,10 +14,10 @@ module ActiveRecord
14
14
  #
15
15
  # See +Properties::DEFAULT_PROPERTIES+, Key, Message
16
16
  class Properties
17
- ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
17
+ ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
18
18
 
19
19
  delegate_missing_to :data
20
- delegate :==, to: :data
20
+ delegate :==, :[], :each, :key?, to: :data
21
21
 
22
22
  # For each entry it generates an accessor exposing the full name
23
23
  DEFAULT_PROPERTIES = {
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  end
55
55
 
56
56
  def validate_value_type(value)
57
- unless ALLOWED_VALUE_CLASSES.find { |klass| value.is_a?(klass) }
57
+ unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
58
58
  raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
59
59
  end
60
60
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  class Scheme
11
11
  attr_accessor :previous_schemes
12
12
 
13
- def initialize(key_provider: nil, key: nil, deterministic: nil, downcase: nil, ignore_case: nil,
13
+ def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
14
14
  previous_schemes: nil, **context_properties)
15
15
  # Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
16
16
  # can merge schemes without overriding values with defaults. See +#merge+
@@ -18,6 +18,7 @@ module ActiveRecord
18
18
  @key_provider_param = key_provider
19
19
  @key = key
20
20
  @deterministic = deterministic
21
+ @support_unencrypted_data = support_unencrypted_data
21
22
  @downcase = downcase || ignore_case
22
23
  @ignore_case = ignore_case
23
24
  @previous_schemes_param = previous_schemes
@@ -36,7 +37,11 @@ module ActiveRecord
36
37
  end
37
38
 
38
39
  def deterministic?
39
- @deterministic
40
+ !!@deterministic
41
+ end
42
+
43
+ def support_unencrypted_data?
44
+ @support_unencrypted_data.nil? ? ActiveRecord::Encryption.config.support_unencrypted_data : @support_unencrypted_data
40
45
  end
41
46
 
42
47
  def fixed?
@@ -45,10 +50,7 @@ module ActiveRecord
45
50
  end
46
51
 
47
52
  def key_provider
48
- @key_provider ||= begin
49
- validate_keys!
50
- @key_provider_param || build_key_provider
51
- end
53
+ @key_provider ||= @key_provider_param || build_key_provider || default_key_provider
52
54
  end
53
55
 
54
56
  def merge(other_scheme)
@@ -56,7 +58,7 @@ module ActiveRecord
56
58
  end
57
59
 
58
60
  def to_h
59
- { key_provider: @key_provider_param, key: @key, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
61
+ { key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
60
62
  previous_schemes: @previous_schemes_param, **@context_properties }.compact
61
63
  end
62
64
 
@@ -68,32 +70,27 @@ module ActiveRecord
68
70
  end
69
71
  end
70
72
 
73
+ def compatible_with?(other_scheme)
74
+ deterministic? == other_scheme.deterministic?
75
+ end
76
+
71
77
  private
72
78
  def validate_config!
73
79
  raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
74
80
  raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
75
81
  end
76
82
 
77
- def validate_keys!
78
- validate_credential :key_derivation_salt
79
- validate_credential :primary_key, "needs to be configured to use non-deterministic encryption" unless @deterministic
80
- validate_credential :deterministic_key, "needs to be configured to use deterministic encryption" if @deterministic
81
- end
82
-
83
- def validate_credential(key, error_message = "is not configured")
84
- unless ActiveRecord::Encryption.config.public_send(key).present?
85
- raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential "\
86
- "active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
87
- end
88
- end
89
-
90
83
  def build_key_provider
91
84
  return DerivedSecretKeyProvider.new(@key) if @key.present?
92
85
 
93
- if @deterministic && (deterministic_key = ActiveRecord::Encryption.config.deterministic_key)
94
- DeterministicKeyProvider.new(deterministic_key)
86
+ if @deterministic
87
+ DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
95
88
  end
96
89
  end
90
+
91
+ def default_key_provider
92
+ ActiveRecord::Encryption.key_provider
93
+ end
97
94
  end
98
95
  end
99
96
  end
@@ -8,6 +8,7 @@ module ActiveRecord
8
8
  extend ActiveSupport::Autoload
9
9
 
10
10
  eager_autoload do
11
+ autoload :AutoFilteredParameters
11
12
  autoload :Cipher
12
13
  autoload :Config
13
14
  autoload :Configurable