activerecord 7.0.5 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (234) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1624 -1338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  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 +16 -10
  15. data/lib/active_record/associations/collection_proxy.rb +20 -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 -7
  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 +6 -8
  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 +290 -125
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +505 -102
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +214 -113
  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 +23 -144
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
  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 +75 -41
  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 +2 -2
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +15 -8
  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 +9 -5
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -83
  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/optimistic.rb +32 -18
  132. data/lib/active_record/locking/pessimistic.rb +5 -2
  133. data/lib/active_record/log_subscriber.rb +29 -12
  134. data/lib/active_record/marshalling.rb +56 -0
  135. data/lib/active_record/message_pack.rb +124 -0
  136. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  137. data/lib/active_record/middleware/database_selector.rb +6 -8
  138. data/lib/active_record/middleware/shard_selector.rb +3 -1
  139. data/lib/active_record/migration/command_recorder.rb +104 -5
  140. data/lib/active_record/migration/compatibility.rb +150 -58
  141. data/lib/active_record/migration/default_strategy.rb +23 -0
  142. data/lib/active_record/migration/execution_strategy.rb +19 -0
  143. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  144. data/lib/active_record/migration.rb +271 -114
  145. data/lib/active_record/model_schema.rb +64 -44
  146. data/lib/active_record/nested_attributes.rb +24 -6
  147. data/lib/active_record/normalization.rb +167 -0
  148. data/lib/active_record/persistence.rb +195 -42
  149. data/lib/active_record/promise.rb +84 -0
  150. data/lib/active_record/query_cache.rb +3 -21
  151. data/lib/active_record/query_logs.rb +77 -52
  152. data/lib/active_record/query_logs_formatter.rb +41 -0
  153. data/lib/active_record/querying.rb +15 -2
  154. data/lib/active_record/railtie.rb +109 -47
  155. data/lib/active_record/railties/controller_runtime.rb +14 -9
  156. data/lib/active_record/railties/databases.rake +142 -148
  157. data/lib/active_record/railties/job_runtime.rb +23 -0
  158. data/lib/active_record/readonly_attributes.rb +32 -5
  159. data/lib/active_record/reflection.rb +182 -44
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  161. data/lib/active_record/relation/batches.rb +190 -61
  162. data/lib/active_record/relation/calculations.rb +187 -63
  163. data/lib/active_record/relation/delegation.rb +23 -9
  164. data/lib/active_record/relation/finder_methods.rb +77 -16
  165. data/lib/active_record/relation/merger.rb +2 -0
  166. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  167. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  168. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  169. data/lib/active_record/relation/predicate_builder.rb +27 -16
  170. data/lib/active_record/relation/query_attribute.rb +25 -1
  171. data/lib/active_record/relation/query_methods.rb +386 -70
  172. data/lib/active_record/relation/spawn_methods.rb +18 -1
  173. data/lib/active_record/relation.rb +91 -35
  174. data/lib/active_record/result.rb +25 -9
  175. data/lib/active_record/runtime_registry.rb +24 -1
  176. data/lib/active_record/sanitization.rb +51 -11
  177. data/lib/active_record/schema.rb +2 -3
  178. data/lib/active_record/schema_dumper.rb +46 -7
  179. data/lib/active_record/schema_migration.rb +68 -33
  180. data/lib/active_record/scoping/default.rb +15 -5
  181. data/lib/active_record/scoping/named.rb +2 -2
  182. data/lib/active_record/scoping.rb +2 -1
  183. data/lib/active_record/secure_password.rb +60 -0
  184. data/lib/active_record/secure_token.rb +21 -3
  185. data/lib/active_record/signed_id.rb +7 -5
  186. data/lib/active_record/store.rb +8 -8
  187. data/lib/active_record/suppressor.rb +3 -1
  188. data/lib/active_record/table_metadata.rb +16 -3
  189. data/lib/active_record/tasks/database_tasks.rb +127 -105
  190. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  191. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  192. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  193. data/lib/active_record/test_fixtures.rb +113 -96
  194. data/lib/active_record/timestamp.rb +27 -15
  195. data/lib/active_record/token_for.rb +113 -0
  196. data/lib/active_record/touch_later.rb +11 -6
  197. data/lib/active_record/transactions.rb +39 -13
  198. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  199. data/lib/active_record/type/internal/timezone.rb +7 -2
  200. data/lib/active_record/type/serialized.rb +4 -0
  201. data/lib/active_record/type/time.rb +4 -0
  202. data/lib/active_record/validations/absence.rb +1 -1
  203. data/lib/active_record/validations/numericality.rb +5 -4
  204. data/lib/active_record/validations/presence.rb +5 -28
  205. data/lib/active_record/validations/uniqueness.rb +47 -2
  206. data/lib/active_record/validations.rb +8 -4
  207. data/lib/active_record/version.rb +1 -1
  208. data/lib/active_record.rb +121 -16
  209. data/lib/arel/errors.rb +10 -0
  210. data/lib/arel/factory_methods.rb +4 -0
  211. data/lib/arel/nodes/and.rb +4 -0
  212. data/lib/arel/nodes/binary.rb +6 -1
  213. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  214. data/lib/arel/nodes/cte.rb +36 -0
  215. data/lib/arel/nodes/fragments.rb +35 -0
  216. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  217. data/lib/arel/nodes/leading_join.rb +8 -0
  218. data/lib/arel/nodes/node.rb +111 -2
  219. data/lib/arel/nodes/sql_literal.rb +6 -0
  220. data/lib/arel/nodes/table_alias.rb +4 -0
  221. data/lib/arel/nodes.rb +4 -0
  222. data/lib/arel/predications.rb +2 -0
  223. data/lib/arel/table.rb +9 -5
  224. data/lib/arel/visitors/mysql.rb +8 -1
  225. data/lib/arel/visitors/to_sql.rb +81 -17
  226. data/lib/arel/visitors/visitor.rb +2 -2
  227. data/lib/arel.rb +16 -2
  228. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  229. data/lib/rails/generators/active_record/migration.rb +3 -1
  230. data/lib/rails/generators/active_record/model/USAGE +113 -0
  231. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  232. metadata +51 -15
  233. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  234. 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