activerecord 7.0.8 → 7.1.3.4

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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1554 -1452
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -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 +20 -4
  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 +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  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 +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  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 +313 -217
  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 +52 -34
  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 +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  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 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -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 +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -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 +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +48 -12
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. 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,22 @@ 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
+ name = attribute_name.to_s
148
+ name = self.class.attribute_aliases[name] || name
149
+
150
+ return false unless self.class.encrypted_attributes&.include? name.to_sym
151
+
152
+ type = type_for_attribute(name)
153
+ type.encrypted? read_attribute_before_type_cast(name)
143
154
  end
144
155
 
145
156
  # Returns the ciphertext for +attribute_name+.
146
157
  def ciphertext_for(attribute_name)
147
- read_attribute_before_type_cast(attribute_name)
158
+ if encrypted_attribute?(attribute_name)
159
+ read_attribute_before_type_cast(attribute_name)
160
+ else
161
+ read_attribute_for_database(attribute_name)
162
+ end
148
163
  end
149
164
 
150
165
  # Encrypts all the encryptable attributes and saves the changes.
@@ -160,6 +175,15 @@ module ActiveRecord
160
175
  private
161
176
  ORIGINAL_ATTRIBUTE_PREFIX = "original_"
162
177
 
178
+ def _create_record(attribute_names = self.attribute_names)
179
+ if has_encrypted_attributes?
180
+ # Always persist encrypted attributes, because an attribute might be
181
+ # encrypting a column default value.
182
+ attribute_names |= self.class.encrypted_attributes.map(&:to_s)
183
+ end
184
+ super
185
+ end
186
+
163
187
  def encrypt_attributes
164
188
  validate_encryption_allowed
165
189
 
@@ -188,12 +212,12 @@ module ActiveRecord
188
212
  end
189
213
 
190
214
  def build_decrypt_attribute_assignments
191
- Array(self.class.encrypted_attributes).collect do |attribute_name|
215
+ Array(self.class.encrypted_attributes).to_h do |attribute_name|
192
216
  type = type_for_attribute(attribute_name)
193
217
  encrypted_value = ciphertext_for(attribute_name)
194
218
  new_value = type.deserialize(encrypted_value)
195
219
  [attribute_name, new_value]
196
- end.to_h
220
+ end
197
221
  end
198
222
 
199
223
  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)
@@ -39,6 +44,10 @@ module ActiveRecord
39
44
  end
40
45
  end
41
46
 
47
+ def encrypted?(value)
48
+ with_context { encryptor.encrypted? value }
49
+ end
50
+
42
51
  def changed_in_place?(raw_old_value, new_value)
43
52
  old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
44
53
  old_value != new_value
@@ -49,6 +58,10 @@ module ActiveRecord
49
58
  @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
50
59
  end
51
60
 
61
+ def support_unencrypted_data?
62
+ ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
63
+ end
64
+
52
65
  private
53
66
  def previous_schemes_including_clean_text
54
67
  previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
@@ -70,7 +83,13 @@ module ActiveRecord
70
83
 
71
84
  def decrypt(value)
72
85
  with_context do
73
- encryptor.decrypt(value, **decryption_options) unless value.nil?
86
+ unless value.nil?
87
+ if @default && @default == value
88
+ value
89
+ else
90
+ encryptor.decrypt(value, **decryption_options)
91
+ end
92
+ end
74
93
  end
75
94
  rescue ActiveRecord::Encryption::Errors::Base => error
76
95
  if previous_types_without_clean_text.blank?
@@ -120,10 +139,6 @@ module ActiveRecord
120
139
  ActiveRecord::Encryption.encryptor
121
140
  end
122
141
 
123
- def support_unencrypted_data?
124
- ActiveRecord::Encryption.config.support_unencrypted_data && !previous_type?
125
- end
126
-
127
142
  def encryption_options
128
143
  @encryption_options ||= { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
129
144
  end
@@ -19,101 +19,112 @@ 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
- Arel::Nodes::HomogeneousIn.prepend(InWithAdditionalValues)
39
31
  end
40
32
 
41
- module EncryptedQueryArgumentProcessor
42
- extend ActiveSupport::Concern
33
+ # When modifying this file run performance tests in
34
+ # +activerecord/test/cases/encryption/performance/extended_deterministic_queries_performance_test.rb+
35
+ # to make sure performance overhead is acceptable.
36
+ #
37
+ # @TODO We will extend this to support previous "encryption context" versions in future iterations
38
+ # @TODO Experimental. Support for every kind of query is pending
39
+ # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
40
+
41
+ module EncryptedQuery # :nodoc:
42
+ class << self
43
+ def process_arguments(owner, args, check_for_additional_values)
44
+ return args if owner.deterministic_encrypted_attributes&.empty?
43
45
 
44
- private
45
- def process_encrypted_query_arguments(args, check_for_additional_values)
46
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)
47
+ options = options.transform_keys do |key|
48
+ if key.is_a?(Array)
49
+ key.map(&:to_s)
50
+ else
51
+ key.to_s
52
+ end
53
+ end
54
+ args[0] = options
55
+
56
+ owner.deterministic_encrypted_attributes&.each do |attribute_name|
57
+ attribute_name = attribute_name.to_s
58
+ type = owner.type_for_attribute(attribute_name)
49
59
  if !type.previous_types.empty? && value = options[attribute_name]
50
60
  options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
51
61
  end
52
62
  end
53
63
  end
54
- end
55
64
 
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)
65
+ args
66
+ end
58
67
 
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)
68
+ private
69
+ def process_encrypted_query_argument(value, check_for_additional_values, type)
70
+ return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
71
+
72
+ case value
73
+ when String, Array
74
+ list = Array(value)
75
+ list + list.flat_map do |each_value|
76
+ if check_for_additional_values && each_value.is_a?(AdditionalValue)
77
+ each_value
78
+ else
79
+ additional_values_for(each_value, type)
80
+ end
67
81
  end
82
+ else
83
+ value
68
84
  end
69
- else
70
- value
71
85
  end
72
- end
73
86
 
74
- def additional_values_for(value, type)
75
- type.previous_types.collect do |additional_type|
76
- AdditionalValue.new(value, additional_type)
87
+ def additional_values_for(value, type)
88
+ type.previous_types.collect do |additional_type|
89
+ AdditionalValue.new(value, additional_type)
90
+ end
77
91
  end
78
- end
92
+ end
79
93
  end
80
94
 
81
95
  module RelationQueries
82
- include EncryptedQueryArgumentProcessor
83
-
84
96
  def where(*args)
85
- process_encrypted_query_arguments_if_needed(args)
86
- super
97
+ super(*EncryptedQuery.process_arguments(self, args, true))
87
98
  end
88
99
 
89
100
  def exists?(*args)
90
- process_encrypted_query_arguments_if_needed(args)
91
- super
101
+ super(*EncryptedQuery.process_arguments(self, args, true))
92
102
  end
93
103
 
94
- def find_or_create_by(attributes, &block)
95
- find_by(attributes.dup) || create(attributes, &block)
96
- end
104
+ def scope_for_create
105
+ return super unless klass.deterministic_encrypted_attributes&.any?
97
106
 
98
- def find_or_create_by!(attributes, &block)
99
- find_by(attributes.dup) || create!(attributes, &block)
100
- end
107
+ scope_attributes = super
108
+ wheres = where_values_hash
101
109
 
102
- private
103
- def process_encrypted_query_arguments_if_needed(args)
104
- process_encrypted_query_arguments(args, true) unless self.deterministic_encrypted_attributes&.empty?
110
+ klass.deterministic_encrypted_attributes.each do |attribute_name|
111
+ attribute_name = attribute_name.to_s
112
+ values = wheres[attribute_name]
113
+ if values.is_a?(Array) && values[1..].all?(AdditionalValue)
114
+ scope_attributes[attribute_name] = values.first
115
+ end
105
116
  end
117
+
118
+ scope_attributes
119
+ end
106
120
  end
107
121
 
108
122
  module CoreQueries
109
123
  extend ActiveSupport::Concern
110
124
 
111
125
  class_methods do
112
- include EncryptedQueryArgumentProcessor
113
-
114
126
  def find_by(*args)
115
- process_encrypted_query_arguments(args, false) unless self.deterministic_encrypted_attributes&.empty?
116
- super
127
+ super(*EncryptedQuery.process_arguments(self, args, false))
117
128
  end
118
129
  end
119
130
  end
@@ -141,20 +152,6 @@ module ActiveRecord
141
152
  end
142
153
  end
143
154
  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
155
  end
159
156
  end
160
157
  end
@@ -12,9 +12,9 @@ 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)
17
- [ encrypted_type, *encrypted_type.previous_types ].each do |type|
15
+ if klass.deterministic_encrypted_attributes&.include?(attribute)
16
+ encrypted_type = klass.type_for_attribute(attribute)
17
+ encrypted_type.previous_types.each do |type|
18
18
  encrypted_value = type.serialize(value)
19
19
  ActiveRecord::Encryption.without_encryption do
20
20
  super(record, attribute, encrypted_value)
@@ -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