activerecord 6.1.6 → 7.0.4

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

Potentially problematic release.


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

Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1314 -975
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +19 -21
  17. data/lib/active_record/associations/collection_proxy.rb +10 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +124 -95
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +14 -15
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +10 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -24
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +105 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +51 -51
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +37 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +208 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +49 -55
  94. data/lib/active_record/core.rb +124 -134
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +15 -32
  100. data/lib/active_record/delegated_type.rb +53 -12
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +67 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +50 -43
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +20 -23
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +1 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +10 -4
  147. data/lib/active_record/log_subscriber.rb +23 -7
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +18 -6
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +7 -7
  152. data/lib/active_record/migration/compatibility.rb +84 -2
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +114 -83
  155. data/lib/active_record/model_schema.rb +58 -59
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +228 -60
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +16 -6
  163. data/lib/active_record/railtie.rb +136 -22
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +78 -136
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +73 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +6 -6
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +276 -67
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +189 -88
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +17 -12
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +60 -13
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +6 -1
  191. data/lib/active_record/signed_id.rb +3 -3
  192. data/lib/active_record/store.rb +7 -2
  193. data/lib/active_record/suppressor.rb +11 -15
  194. data/lib/active_record/tasks/database_tasks.rb +127 -60
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  197. data/lib/active_record/test_databases.rb +1 -1
  198. data/lib/active_record/test_fixtures.rb +16 -9
  199. data/lib/active_record/timestamp.rb +3 -4
  200. data/lib/active_record/transactions.rb +9 -14
  201. data/lib/active_record/translation.rb +3 -3
  202. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  203. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  204. data/lib/active_record/type/internal/timezone.rb +2 -2
  205. data/lib/active_record/type/serialized.rb +1 -1
  206. data/lib/active_record/type/type_map.rb +17 -20
  207. data/lib/active_record/type.rb +1 -2
  208. data/lib/active_record/validations/associated.rb +4 -4
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +4 -4
  211. data/lib/active_record/version.rb +1 -1
  212. data/lib/active_record.rb +217 -27
  213. data/lib/arel/attributes/attribute.rb +0 -8
  214. data/lib/arel/crud.rb +28 -22
  215. data/lib/arel/delete_manager.rb +18 -4
  216. data/lib/arel/filter_predications.rb +9 -0
  217. data/lib/arel/insert_manager.rb +2 -3
  218. data/lib/arel/nodes/casted.rb +1 -1
  219. data/lib/arel/nodes/delete_statement.rb +12 -13
  220. data/lib/arel/nodes/filter.rb +10 -0
  221. data/lib/arel/nodes/function.rb +1 -0
  222. data/lib/arel/nodes/insert_statement.rb +2 -2
  223. data/lib/arel/nodes/select_core.rb +2 -2
  224. data/lib/arel/nodes/select_statement.rb +2 -2
  225. data/lib/arel/nodes/update_statement.rb +8 -3
  226. data/lib/arel/nodes.rb +1 -0
  227. data/lib/arel/predications.rb +11 -3
  228. data/lib/arel/select_manager.rb +10 -4
  229. data/lib/arel/table.rb +0 -1
  230. data/lib/arel/tree_manager.rb +0 -12
  231. data/lib/arel/update_manager.rb +18 -4
  232. data/lib/arel/visitors/dot.rb +80 -90
  233. data/lib/arel/visitors/mysql.rb +8 -2
  234. data/lib/arel/visitors/postgresql.rb +0 -10
  235. data/lib/arel/visitors/to_sql.rb +58 -2
  236. data/lib/arel.rb +2 -1
  237. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  238. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  239. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  240. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  241. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  242. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  243. metadata +55 -11
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Configuration API for ActiveRecord::Encryption
6
+ module Configurable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ mattr_reader :config, default: Config.new
11
+ mattr_accessor :encrypted_attribute_declaration_listeners
12
+ end
13
+
14
+ class_methods do
15
+ # Expose getters for context properties
16
+ Context::PROPERTIES.each do |name|
17
+ delegate name, to: :context
18
+ end
19
+
20
+ def configure(primary_key:, deterministic_key:, key_derivation_salt:, **properties) # :nodoc:
21
+ config.primary_key = primary_key
22
+ config.deterministic_key = deterministic_key
23
+ config.key_derivation_salt = key_derivation_salt
24
+
25
+ context.key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key)
26
+
27
+ properties.each do |name, value|
28
+ [:context, :config].each do |configurable_object_name|
29
+ configurable_object = ActiveRecord::Encryption.send(configurable_object_name)
30
+ configurable_object.send "#{name}=", value if configurable_object.respond_to?("#{name}=")
31
+ end
32
+ end
33
+ end
34
+
35
+ # Register callback to be invoked when an encrypted attribute is declared.
36
+ #
37
+ # === Example:
38
+ #
39
+ # ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_name|
40
+ # ...
41
+ # end
42
+ def on_encrypted_attribute_declared(&block)
43
+ self.encrypted_attribute_declaration_listeners ||= Concurrent::Array.new
44
+ self.encrypted_attribute_declaration_listeners << block
45
+ end
46
+
47
+ def encrypted_attribute_was_declared(klass, name) # :nodoc:
48
+ self.encrypted_attribute_declaration_listeners&.each do |block|
49
+ block.call(klass, name)
50
+ end
51
+ end
52
+
53
+ def install_auto_filtered_parameters_hook(application) # :nodoc:
54
+ ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name|
55
+ filter_parameter = [("#{klass.model_name.element}" if klass.name), encrypted_attribute_name.to_s].compact.join(".")
56
+ application.config.filter_parameters << filter_parameter unless excluded_from_filter_parameters?(filter_parameter)
57
+ end
58
+ end
59
+
60
+ private
61
+ def excluded_from_filter_parameters?(filter_parameter)
62
+ ActiveRecord::Encryption.config.excluded_from_filter_parameters.find { |excluded_filter| excluded_filter.to_s == filter_parameter }
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An encryption context configures the different entities used to perform encryption:
6
+ #
7
+ # * A key provider
8
+ # * A key generator
9
+ # * An encryptor, the facade to encrypt data
10
+ # * A cipher, the encryption algorithm
11
+ # * A message serializer
12
+ class Context
13
+ PROPERTIES = %i[ key_provider key_generator cipher message_serializer encryptor frozen_encryption ]
14
+
15
+ PROPERTIES.each do |name|
16
+ attr_accessor name
17
+ end
18
+
19
+ def initialize
20
+ set_defaults
21
+ end
22
+
23
+ alias frozen_encryption? frozen_encryption
24
+
25
+ private
26
+ def set_defaults
27
+ self.frozen_encryption = false
28
+ self.key_generator = ActiveRecord::Encryption::KeyGenerator.new
29
+ self.cipher = ActiveRecord::Encryption::Cipher.new
30
+ self.encryptor = ActiveRecord::Encryption::Encryptor.new
31
+ self.message_serializer = ActiveRecord::Encryption::MessageSerializer.new
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # ActiveRecord::Encryption uses encryption contexts to configure the different entities used to
6
+ # encrypt/decrypt at a given moment in time.
7
+ #
8
+ # By default, the library uses a default encryption context. This is the Context that gets configured
9
+ # initially via +config.active_record.encryption+ options. Library users can define nested encryption contexts
10
+ # when running blocks of code.
11
+ #
12
+ # See Context.
13
+ module Contexts
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ mattr_reader :default_context, default: Context.new
18
+ thread_mattr_accessor :custom_contexts
19
+ end
20
+
21
+ class_methods do
22
+ # Configures a custom encryption context to use when running the provided block of code.
23
+ #
24
+ # It supports overriding all the properties defined in +Context+.
25
+ #
26
+ # Example:
27
+ #
28
+ # ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do
29
+ # ...
30
+ # end
31
+ #
32
+ # Encryption contexts can be nested.
33
+ def with_encryption_context(properties)
34
+ self.custom_contexts ||= []
35
+ self.custom_contexts << default_context.dup
36
+ properties.each do |key, value|
37
+ self.current_custom_context.send("#{key}=", value)
38
+ end
39
+
40
+ yield
41
+ ensure
42
+ self.custom_contexts.pop
43
+ end
44
+
45
+ # Runs the provided block in an encryption context where encryption is disabled:
46
+ #
47
+ # * Reading encrypted content will return its ciphertexts.
48
+ # * Writing encrypted content will write its clear text.
49
+ def without_encryption(&block)
50
+ with_encryption_context encryptor: ActiveRecord::Encryption::NullEncryptor.new, &block
51
+ end
52
+
53
+ # Runs the provided block in an encryption context where:
54
+ #
55
+ # * Reading encrypted content will return its ciphertext.
56
+ # * Writing encrypted content will fail.
57
+ def protecting_encrypted_data(&block)
58
+ with_encryption_context encryptor: ActiveRecord::Encryption::EncryptingOnlyEncryptor.new, frozen_encryption: true, &block
59
+ end
60
+
61
+ # Returns the current context. By default it will return the current context.
62
+ def context
63
+ self.current_custom_context || self.default_context
64
+ end
65
+
66
+ def current_custom_context
67
+ self.custom_contexts&.last
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A KeyProvider that derives keys from passwords.
6
+ class DerivedSecretKeyProvider < KeyProvider
7
+ def initialize(passwords)
8
+ super(Array(passwords).collect { |password| Key.derive_from(password) })
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A KeyProvider that derives keys from passwords.
6
+ class DeterministicKeyProvider < DerivedSecretKeyProvider
7
+ def initialize(password)
8
+ passwords = Array(password)
9
+ raise ActiveRecord::Encryption::Errors::Configuration, "Deterministic encryption keys can't be rotated" if passwords.length > 1
10
+ super(passwords)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # This is the concern mixed in Active Record models to make them encryptable. It adds the +encrypts+
6
+ # attribute declaration, as well as the API to encrypt and decrypt records.
7
+ module EncryptableRecord
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class_attribute :encrypted_attributes
12
+
13
+ validate :cant_modify_encrypted_attributes_when_frozen, if: -> { has_encrypted_attributes? && ActiveRecord::Encryption.context.frozen_encryption? }
14
+ end
15
+
16
+ class_methods do
17
+ # Encrypts the +name+ attribute.
18
+ #
19
+ # === Options
20
+ #
21
+ # * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
22
+ # +ActiveRecord::Encryption.key_provider+.
23
+ # * <tt>:key</tt> - A password to derive the key from. It's a shorthand for a +:key_provider+ that
24
+ # serves derivated keys. Both options can't be used at the same time.
25
+ # * <tt>:deterministic</tt> - By default, encryption is not deterministic. It will use a random
26
+ # initialization vector for each encryption operation. This means that encrypting the same content
27
+ # with the same key twice will generate different ciphertexts. When set to +true+, it will generate the
28
+ # initialization vector based on the encrypted content. This means that the same content will generate
29
+ # the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption
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
32
+ # data.
33
+ # * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
34
+ # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
35
+ # in preserving it.
36
+ # * <tt>:ignore_case</tt> - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
37
+ # designated column +original_<name>+. When reading the encrypted content, the version with the original case is
38
+ # served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
39
+ # is true.
40
+ # * <tt>:context_properties</tt> - Additional properties that will override +Context+ settings when this attribute is
41
+ # encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
42
+ # * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
43
+ # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
44
+ # 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)
46
+ 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
+
50
+ names.each do |name|
51
+ encrypt_attribute name, scheme
52
+ end
53
+ end
54
+
55
+ # Returns the list of deterministic encryptable attributes in the model class.
56
+ def deterministic_encrypted_attributes
57
+ @deterministic_encrypted_attributes ||= encrypted_attributes&.find_all do |attribute_name|
58
+ type_for_attribute(attribute_name).deterministic?
59
+ end
60
+ end
61
+
62
+ # Given a attribute name, it returns the name of the source attribute when it's a preserved one.
63
+ def source_attribute_from_preserved_attribute(attribute_name)
64
+ attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if /^#{ORIGINAL_ATTRIBUTE_PREFIX}/.match?(attribute_name)
65
+ end
66
+
67
+ private
68
+ def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
69
+ ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
70
+ downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
71
+ scheme.previous_schemes = global_previous_schemes_for(scheme) +
72
+ Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
73
+ end
74
+ end
75
+
76
+ def global_previous_schemes_for(scheme)
77
+ ActiveRecord::Encryption.config.previous_schemes.collect do |previous_scheme|
78
+ scheme.merge(previous_scheme)
79
+ end
80
+ end
81
+
82
+ def encrypt_attribute(name, attribute_scheme)
83
+ encrypted_attributes << name.to_sym
84
+
85
+ attribute name do |cast_type|
86
+ ActiveRecord::Encryption::EncryptedAttributeType.new scheme: attribute_scheme, cast_type: cast_type
87
+ end
88
+
89
+ preserve_original_encrypted(name) if attribute_scheme.ignore_case?
90
+ ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
91
+ end
92
+
93
+ def preserve_original_encrypted(name)
94
+ original_attribute_name = "#{ORIGINAL_ATTRIBUTE_PREFIX}#{name}".to_sym
95
+
96
+ if !ActiveRecord::Encryption.config.support_unencrypted_data && !column_names.include?(original_attribute_name.to_s)
97
+ raise Errors::Configuration, "To use :ignore_case for '#{name}' you must create an additional column named '#{original_attribute_name}'"
98
+ end
99
+
100
+ encrypts original_attribute_name
101
+ override_accessors_to_preserve_original name, original_attribute_name
102
+ end
103
+
104
+ def override_accessors_to_preserve_original(name, original_attribute_name)
105
+ include(Module.new do
106
+ define_method name do
107
+ if ((value = super()) && encrypted_attribute?(name)) || !ActiveRecord::Encryption.config.support_unencrypted_data
108
+ send(original_attribute_name)
109
+ else
110
+ value
111
+ end
112
+ end
113
+
114
+ define_method "#{name}=" do |value|
115
+ self.send "#{original_attribute_name}=", value
116
+ super(value)
117
+ end
118
+ end)
119
+ end
120
+
121
+ def load_schema!
122
+ super
123
+
124
+ add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
125
+ end
126
+
127
+ def add_length_validation_for_encrypted_columns
128
+ encrypted_attributes&.each do |attribute_name|
129
+ validate_column_size attribute_name
130
+ end
131
+ end
132
+
133
+ def validate_column_size(attribute_name)
134
+ if limit = columns_hash[attribute_name.to_s]&.limit
135
+ validates_length_of attribute_name, maximum: limit
136
+ end
137
+ end
138
+ end
139
+
140
+ # Returns whether a given attribute is encrypted or not.
141
+ def encrypted_attribute?(attribute_name)
142
+ ActiveRecord::Encryption.encryptor.encrypted? ciphertext_for(attribute_name)
143
+ end
144
+
145
+ # Returns the ciphertext for +attribute_name+.
146
+ def ciphertext_for(attribute_name)
147
+ read_attribute_before_type_cast(attribute_name)
148
+ end
149
+
150
+ # Encrypts all the encryptable attributes and saves the changes.
151
+ def encrypt
152
+ encrypt_attributes if has_encrypted_attributes?
153
+ end
154
+
155
+ # Decrypts all the encryptable attributes and saves the changes.
156
+ def decrypt
157
+ decrypt_attributes if has_encrypted_attributes?
158
+ end
159
+
160
+ private
161
+ ORIGINAL_ATTRIBUTE_PREFIX = "original_"
162
+
163
+ def encrypt_attributes
164
+ validate_encryption_allowed
165
+
166
+ update_columns build_encrypt_attribute_assignments
167
+ end
168
+
169
+ def decrypt_attributes
170
+ validate_encryption_allowed
171
+
172
+ decrypt_attribute_assignments = build_decrypt_attribute_assignments
173
+ ActiveRecord::Encryption.without_encryption { update_columns decrypt_attribute_assignments }
174
+ end
175
+
176
+ def validate_encryption_allowed
177
+ raise ActiveRecord::Encryption::Errors::Configuration, "can't be modified because it is encrypted" if ActiveRecord::Encryption.context.frozen_encryption?
178
+ end
179
+
180
+ def has_encrypted_attributes?
181
+ self.class.encrypted_attributes.present?
182
+ end
183
+
184
+ def build_encrypt_attribute_assignments
185
+ Array(self.class.encrypted_attributes).index_with do |attribute_name|
186
+ self[attribute_name]
187
+ end
188
+ end
189
+
190
+ def build_decrypt_attribute_assignments
191
+ Array(self.class.encrypted_attributes).collect do |attribute_name|
192
+ type = type_for_attribute(attribute_name)
193
+ encrypted_value = ciphertext_for(attribute_name)
194
+ new_value = type.deserialize(encrypted_value)
195
+ [attribute_name, new_value]
196
+ end.to_h
197
+ end
198
+
199
+ def cant_modify_encrypted_attributes_when_frozen
200
+ self.class&.encrypted_attributes.each do |attribute|
201
+ errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An ActiveModel::Type::Value that encrypts/decrypts strings of text.
6
+ #
7
+ # This is the central piece that connects the encryption system with +encrypts+ declarations in the
8
+ # model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
9
+ # for that attribute.
10
+ class EncryptedAttributeType < ::ActiveRecord::Type::Text
11
+ include ActiveModel::Type::Helpers::Mutable
12
+
13
+ attr_reader :scheme, :cast_type
14
+
15
+ delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
16
+ delegate :accessor, to: :cast_type
17
+
18
+ # === Options
19
+ #
20
+ # * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
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)
24
+ super()
25
+ @scheme = scheme
26
+ @cast_type = cast_type
27
+ @previous_type = previous_type
28
+ end
29
+
30
+ def deserialize(value)
31
+ cast_type.deserialize decrypt(value)
32
+ end
33
+
34
+ def serialize(value)
35
+ if serialize_with_oldest?
36
+ serialize_with_oldest(value)
37
+ else
38
+ serialize_with_current(value)
39
+ end
40
+ end
41
+
42
+ def changed_in_place?(raw_old_value, new_value)
43
+ old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
44
+ old_value != new_value
45
+ end
46
+
47
+ def previous_types # :nodoc:
48
+ @previous_types ||= {} # Memoizing on support_unencrypted_data so that we can tweak it during tests
49
+ @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
50
+ end
51
+
52
+ private
53
+ def previous_schemes_including_clean_text
54
+ previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
55
+ end
56
+
57
+ def previous_types_without_clean_text
58
+ @previous_types_without_clean_text ||= build_previous_types_for(previous_schemes)
59
+ end
60
+
61
+ def build_previous_types_for(schemes)
62
+ schemes.collect do |scheme|
63
+ EncryptedAttributeType.new(scheme: scheme, previous_type: true)
64
+ end
65
+ end
66
+
67
+ def previous_type?
68
+ @previous_type
69
+ end
70
+
71
+ def decrypt(value)
72
+ with_context do
73
+ encryptor.decrypt(value, **decryption_options) unless value.nil?
74
+ end
75
+ rescue ActiveRecord::Encryption::Errors::Base => error
76
+ if previous_types_without_clean_text.blank?
77
+ handle_deserialize_error(error, value)
78
+ else
79
+ try_to_deserialize_with_previous_encrypted_types(value)
80
+ end
81
+ end
82
+
83
+ def try_to_deserialize_with_previous_encrypted_types(value)
84
+ previous_types.each.with_index do |type, index|
85
+ break type.deserialize(value)
86
+ rescue ActiveRecord::Encryption::Errors::Base => error
87
+ handle_deserialize_error(error, value) if index == previous_types.length - 1
88
+ end
89
+ end
90
+
91
+ def handle_deserialize_error(error, value)
92
+ if error.is_a?(Errors::Decryption) && support_unencrypted_data?
93
+ value
94
+ else
95
+ raise error
96
+ end
97
+ end
98
+
99
+ def serialize_with_oldest?
100
+ @serialize_with_oldest ||= fixed? && previous_types_without_clean_text.present?
101
+ end
102
+
103
+ def serialize_with_oldest(value)
104
+ previous_types.first.serialize(value)
105
+ end
106
+
107
+ def serialize_with_current(value)
108
+ casted_value = cast_type.serialize(value)
109
+ casted_value = casted_value&.downcase if downcase?
110
+ encrypt(casted_value.to_s) unless casted_value.nil?
111
+ end
112
+
113
+ def encrypt(value)
114
+ with_context do
115
+ encryptor.encrypt(value, **encryption_options)
116
+ end
117
+ end
118
+
119
+ def encryptor
120
+ ActiveRecord::Encryption.encryptor
121
+ end
122
+
123
+ def support_unencrypted_data?
124
+ ActiveRecord::Encryption.config.support_unencrypted_data && !previous_type?
125
+ end
126
+
127
+ def encryption_options
128
+ @encryption_options ||= { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
129
+ end
130
+
131
+ def decryption_options
132
+ @decryption_options ||= { key_provider: key_provider }.compact
133
+ end
134
+
135
+ def clean_text_scheme
136
+ @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module EncryptedFixtures
6
+ def initialize(fixture, model_class)
7
+ @clean_values = {}
8
+ encrypt_fixture_data(fixture, model_class)
9
+ process_preserved_original_columns(fixture, model_class)
10
+ super
11
+ end
12
+
13
+ private
14
+ def encrypt_fixture_data(fixture, model_class)
15
+ model_class&.encrypted_attributes&.each do |attribute_name|
16
+ if clean_value = fixture[attribute_name.to_s]
17
+ @clean_values[attribute_name.to_s] = clean_value
18
+
19
+ type = model_class.type_for_attribute(attribute_name)
20
+ encrypted_value = type.serialize(clean_value)
21
+ fixture[attribute_name.to_s] = encrypted_value
22
+ end
23
+ end
24
+ end
25
+
26
+ def process_preserved_original_columns(fixture, model_class)
27
+ model_class&.encrypted_attributes&.each do |attribute_name|
28
+ if source_attribute_name = model_class.source_attribute_from_preserved_attribute(attribute_name)
29
+ clean_value = @clean_values[source_attribute_name.to_s]
30
+ type = model_class.type_for_attribute(attribute_name)
31
+ encrypted_value = type.serialize(clean_value)
32
+ fixture[attribute_name.to_s] = encrypted_value
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An encryptor that can encrypt data but can't decrypt it.
6
+ class EncryptingOnlyEncryptor < Encryptor
7
+ def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
8
+ encrypted_text
9
+ end
10
+ end
11
+ end
12
+ end