activerecord 7.0.0 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  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 +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 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  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 +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -2,11 +2,17 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Encryption
5
- # A +KeyProvider+ that derives keys from passwords.
5
+ # A KeyProvider that derives keys from passwords.
6
6
  class DerivedSecretKeyProvider < KeyProvider
7
- def initialize(passwords)
8
- super(Array(passwords).collect { |password| Key.derive_from(password) })
7
+ def initialize(passwords, key_generator: ActiveRecord::Encryption.key_generator)
8
+ super(Array(passwords).collect { |password| derive_key_from(password, using: key_generator) })
9
9
  end
10
+
11
+ private
12
+ def derive_key_from(password, using: key_generator)
13
+ secret = using.derive_key_from(password)
14
+ ActiveRecord::Encryption::Key.new(secret)
15
+ end
10
16
  end
11
17
  end
12
18
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Encryption
5
- # A +KeyProvider+ that derives keys from passwords.
5
+ # A KeyProvider that derives keys from passwords.
6
6
  class DeterministicKeyProvider < DerivedSecretKeyProvider
7
7
  def initialize(password)
8
8
  passwords = Array(password)
@@ -18,20 +18,22 @@ module ActiveRecord
18
18
  #
19
19
  # === Options
20
20
  #
21
- # * <tt>:key_provider</tt> - Configure a +KeyProvider+ for serving the keys to encrypt and
22
- # decrypt this attribute. If not provided, it will default to +ActiveRecord::Encryption.key_provider+.
21
+ # * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
22
+ # +ActiveRecord::Encryption.key_provider+.
23
23
  # * <tt>:key</tt> - A password to derive the key from. It's a shorthand for a +:key_provider+ that
24
24
  # serves derivated keys. Both options can't be used at the same time.
25
- # * <tt>:key_provider</tt> - Set a +:key_provider+ to provide encryption and decryption keys. If not
26
- # provided, it will default to the key provider set with `config.key_provider`.
27
25
  # * <tt>:deterministic</tt> - By default, encryption is not deterministic. It will use a random
28
26
  # initialization vector for each encryption operation. This means that encrypting the same content
29
27
  # with the same key twice will generate different ciphertexts. When set to +true+, it will generate the
30
28
  # initialization vector based on the encrypted content. This means that the same content will generate
31
29
  # the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption
32
30
  # will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
33
- # +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
34
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.
35
37
  # * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
36
38
  # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
37
39
  # in preserving it.
@@ -44,13 +46,11 @@ module ActiveRecord
44
46
  # * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
45
47
  # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
46
48
  # encryption is used, they will be used to generate additional ciphertexts to check in the queries.
47
- 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)
48
50
  self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
49
- scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, \
50
- ignore_case: ignore_case, previous: previous, **context_properties
51
51
 
52
52
  names.each do |name|
53
- 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
54
54
  end
55
55
  end
56
56
 
@@ -63,32 +63,35 @@ module ActiveRecord
63
63
 
64
64
  # Given a attribute name, it returns the name of the source attribute when it's a preserved one.
65
65
  def source_attribute_from_preserved_attribute(attribute_name)
66
- 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)
67
67
  end
68
68
 
69
69
  private
70
- 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)
71
71
  ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
72
- 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|
73
73
  scheme.previous_schemes = global_previous_schemes_for(scheme) +
74
- 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) }
75
75
  end
76
76
  end
77
77
 
78
78
  def global_previous_schemes_for(scheme)
79
- ActiveRecord::Encryption.config.previous_schemes.collect do |previous_scheme|
80
- 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)
81
81
  end
82
82
  end
83
83
 
84
- 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)
85
85
  encrypted_attributes << name.to_sym
86
86
 
87
87
  attribute name do |cast_type|
88
- 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)
89
92
  end
90
93
 
91
- preserve_original_encrypted(name) if attribute_scheme.ignore_case?
94
+ preserve_original_encrypted(name) if ignore_case
92
95
  ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
93
96
  end
94
97
 
@@ -141,12 +144,16 @@ module ActiveRecord
141
144
 
142
145
  # Returns whether a given attribute is encrypted or not.
143
146
  def encrypted_attribute?(attribute_name)
144
- ActiveRecord::Encryption.encryptor.encrypted? ciphertext_for(attribute_name)
147
+ ActiveRecord::Encryption.encryptor.encrypted? read_attribute_before_type_cast(attribute_name)
145
148
  end
146
149
 
147
150
  # Returns the ciphertext for +attribute_name+.
148
151
  def ciphertext_for(attribute_name)
149
- 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
150
157
  end
151
158
 
152
159
  # Encrypts all the encryptable attributes and saves the changes.
@@ -162,6 +169,15 @@ module ActiveRecord
162
169
  private
163
170
  ORIGINAL_ATTRIBUTE_PREFIX = "original_"
164
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
+
165
181
  def encrypt_attributes
166
182
  validate_encryption_allowed
167
183
 
@@ -190,12 +206,12 @@ module ActiveRecord
190
206
  end
191
207
 
192
208
  def build_decrypt_attribute_assignments
193
- Array(self.class.encrypted_attributes).collect do |attribute_name|
209
+ Array(self.class.encrypted_attributes).to_h do |attribute_name|
194
210
  type = type_for_attribute(attribute_name)
195
211
  encrypted_value = ciphertext_for(attribute_name)
196
212
  new_value = type.deserialize(encrypted_value)
197
213
  [attribute_name, new_value]
198
- end.to_h
214
+ end
199
215
  end
200
216
 
201
217
  def cant_modify_encrypted_attributes_when_frozen
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Encryption
5
- # An +ActiveModel::Type+ that encrypts/decrypts strings of text.
5
+ # An ActiveModel::Type::Value that encrypts/decrypts strings of text.
6
6
  #
7
7
  # This is the central piece that connects the encryption system with +encrypts+ declarations in the
8
8
  # model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
@@ -19,12 +19,17 @@ module ActiveRecord
19
19
  #
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
- # (after decrypting). +ActiveModel::Type::String+ by default.
23
- def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false)
22
+ # (after decrypting). ActiveModel::Type::String by default.
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
@@ -6,17 +6,17 @@ require "active_support/core_ext/numeric"
6
6
 
7
7
  module ActiveRecord
8
8
  module Encryption
9
- # An encryptor exposes the encryption API that +ActiveRecord::Encryption::EncryptedAttributeType+
9
+ # An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType
10
10
  # uses for encrypting and decrypting attribute values.
11
11
  #
12
- # It interacts with a +KeyProvider+ for getting the keys, and delegate to
13
- # +ActiveRecord::Encryption::Cipher+ the actual encryption algorithm.
12
+ # It interacts with a KeyProvider for getting the keys, and delegate to
13
+ # ActiveRecord::Encryption::Cipher the actual encryption algorithm.
14
14
  class Encryptor
15
15
  # Encrypts +clean_text+ and returns the encrypted result
16
16
  #
17
17
  # Internally, it will:
18
18
  #
19
- # 1. Create a new +ActiveRecord::Encryption::Message+
19
+ # 1. Create a new ActiveRecord::Encryption::Message
20
20
  # 2. Compress and encrypt +clean_text+ as the message payload
21
21
  # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
22
22
  # by default)
@@ -26,10 +26,10 @@ module ActiveRecord
26
26
  #
27
27
  # [:key_provider]
28
28
  # Key provider to use for the encryption operation. It will default to
29
- # +ActiveRecord::Encryption.key_provider+ when not provided
29
+ # +ActiveRecord::Encryption.key_provider+ when not provided.
30
30
  #
31
31
  # [:cipher_options]
32
- # +Cipher+-specific options that will be passed to the Cipher configured in
32
+ # Cipher-specific options that will be passed to the Cipher configured in
33
33
  # +ActiveRecord::Encryption.cipher+
34
34
  def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
35
35
  clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
@@ -47,7 +47,7 @@ module ActiveRecord
47
47
  # +ActiveRecord::Encryption.key_provider+ when not provided
48
48
  #
49
49
  # [:cipher_options]
50
- # +Cipher+-specific options that will be passed to the Cipher configured in
50
+ # Cipher-specific options that will be passed to the Cipher configured in
51
51
  # +ActiveRecord::Encryption.cipher+
52
52
  def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
53
53
  message = deserialize_message(encrypted_text)
@@ -4,13 +4,13 @@ module ActiveRecord
4
4
  module Encryption
5
5
  # Implements a simple envelope encryption approach where:
6
6
  #
7
- # * It generates a random data-encryption key for each encryption operation
7
+ # * It generates a random data-encryption key for each encryption operation.
8
8
  # * It stores the generated key along with the encrypted payload. It encrypts this key
9
- # with the master key provided in the credential +active_record.encryption.master key+
9
+ # with the master key provided in the +active_record_encryption.primary_key+ credential.
10
10
  #
11
11
  # This provider can work with multiple master keys. It will use the last one for encrypting.
12
12
  #
13
- # When `config.store_key_references` is true, it will also store a reference to
13
+ # When +config.active_record.encryption.store_key_references+ is true, it will also store a reference to
14
14
  # the specific master key that was used to encrypt the data-encryption key. When not set,
15
15
  # it will try all the configured master keys looking for the right one, in order to
16
16
  # return the right decryption key.
@@ -1,119 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
4
- #
5
- # Active Record Encryption supports querying the db using deterministic attributes. For example:
6
- #
7
- # Contact.find_by(email_address: "jorge@hey.com")
8
- #
9
- # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
10
- # a problem while the data is being encrypted. This won't work. During that time, you need these
11
- # queries to be:
12
- #
13
- # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
14
- #
15
- # This patches ActiveRecord to support this automatically. It addresses both:
16
- #
17
- # * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
18
- # * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
19
- #
20
- # +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
21
- # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
22
- # as it's invoked (so that the proper prepared statement is cached).
23
- #
24
- # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
25
- # make sure performance overhead is acceptable.
26
- #
27
- # We will extend this to support previous "encryption context" versions in future iterations
28
- #
29
- # @TODO Experimental. Support for every kind of query is pending
30
- # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
31
3
  module ActiveRecord
32
4
  module Encryption
5
+ # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
6
+ #
7
+ # Active Record \Encryption supports querying the db using deterministic attributes. For example:
8
+ #
9
+ # Contact.find_by(email_address: "jorge@hey.com")
10
+ #
11
+ # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
12
+ # a problem while the data is being encrypted. This won't work. During that time, you need these
13
+ # queries to be:
14
+ #
15
+ # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
16
+ #
17
+ # This patches ActiveRecord to support this automatically. It addresses both:
18
+ #
19
+ # * ActiveRecord::Base - Used in <tt>Contact.find_by_email_address(...)</tt>
20
+ # * ActiveRecord::Relation - Used in <tt>Contact.internal.find_by_email_address(...)</tt>
21
+ #
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
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  # * An encrypted payload
8
8
  # * A list of unencrypted headers
9
9
  #
10
- # See +Encryptor#encrypt+
10
+ # See Encryptor#encrypt
11
11
  class Message
12
12
  attr_accessor :payload, :headers
13
13
 
@@ -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.
@@ -12,12 +12,12 @@ module ActiveRecord
12
12
  #
13
13
  # message.headers.encrypted_data_key # instead of message.headers[:k]
14
14
  #
15
- # See +Properties#DEFAULT_PROPERTIES+, +Key+, +Message+
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