activerecord 6.1.7.4 → 7.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1449 -1014
  3. data/README.rdoc +3 -3
  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 +14 -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 +50 -14
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +15 -7
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +138 -100
  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 +8 -6
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +19 -22
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +17 -28
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +14 -16
  47. data/lib/active_record/coders/yaml_column.rb +4 -8
  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 +52 -23
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +82 -25
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +153 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -84
  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 +45 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +4 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  68. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  70. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +19 -1
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -17
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/quoting.rb +71 -71
  83. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  86. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +40 -21
  88. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  89. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -106
  90. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +97 -32
  95. data/lib/active_record/connection_adapters.rb +6 -5
  96. data/lib/active_record/connection_handling.rb +49 -55
  97. data/lib/active_record/core.rb +123 -148
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  99. data/lib/active_record/database_configurations/database_config.rb +12 -9
  100. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  101. data/lib/active_record/database_configurations/url_config.rb +2 -2
  102. data/lib/active_record/database_configurations.rb +15 -32
  103. data/lib/active_record/delegated_type.rb +53 -12
  104. data/lib/active_record/destroy_association_async_job.rb +1 -1
  105. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  106. data/lib/active_record/dynamic_matchers.rb +1 -1
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  108. data/lib/active_record/encryption/cipher.rb +53 -0
  109. data/lib/active_record/encryption/config.rb +44 -0
  110. data/lib/active_record/encryption/configurable.rb +67 -0
  111. data/lib/active_record/encryption/context.rb +35 -0
  112. data/lib/active_record/encryption/contexts.rb +72 -0
  113. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  114. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  115. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  116. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  117. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  118. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  119. data/lib/active_record/encryption/encryptor.rb +155 -0
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  121. data/lib/active_record/encryption/errors.rb +15 -0
  122. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  123. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  124. data/lib/active_record/encryption/key.rb +28 -0
  125. data/lib/active_record/encryption/key_generator.rb +42 -0
  126. data/lib/active_record/encryption/key_provider.rb +46 -0
  127. data/lib/active_record/encryption/message.rb +33 -0
  128. data/lib/active_record/encryption/message_serializer.rb +90 -0
  129. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  130. data/lib/active_record/encryption/properties.rb +76 -0
  131. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  132. data/lib/active_record/encryption/scheme.rb +99 -0
  133. data/lib/active_record/encryption.rb +55 -0
  134. data/lib/active_record/enum.rb +50 -43
  135. data/lib/active_record/errors.rb +67 -4
  136. data/lib/active_record/explain_registry.rb +11 -6
  137. data/lib/active_record/explain_subscriber.rb +1 -1
  138. data/lib/active_record/fixture_set/file.rb +15 -1
  139. data/lib/active_record/fixture_set/table_row.rb +41 -6
  140. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  141. data/lib/active_record/fixtures.rb +20 -23
  142. data/lib/active_record/future_result.rb +139 -0
  143. data/lib/active_record/gem_version.rb +5 -5
  144. data/lib/active_record/inheritance.rb +55 -17
  145. data/lib/active_record/insert_all.rb +80 -14
  146. data/lib/active_record/integration.rb +4 -3
  147. data/lib/active_record/internal_metadata.rb +1 -5
  148. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  149. data/lib/active_record/locking/optimistic.rb +36 -21
  150. data/lib/active_record/locking/pessimistic.rb +10 -4
  151. data/lib/active_record/log_subscriber.rb +23 -7
  152. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  153. data/lib/active_record/middleware/database_selector.rb +18 -6
  154. data/lib/active_record/middleware/shard_selector.rb +60 -0
  155. data/lib/active_record/migration/command_recorder.rb +8 -9
  156. data/lib/active_record/migration/compatibility.rb +91 -2
  157. data/lib/active_record/migration/join_table.rb +1 -1
  158. data/lib/active_record/migration.rb +115 -84
  159. data/lib/active_record/model_schema.rb +58 -59
  160. data/lib/active_record/nested_attributes.rb +13 -12
  161. data/lib/active_record/no_touching.rb +3 -3
  162. data/lib/active_record/null_relation.rb +2 -6
  163. data/lib/active_record/persistence.rb +228 -60
  164. data/lib/active_record/query_cache.rb +2 -2
  165. data/lib/active_record/query_logs.rb +149 -0
  166. data/lib/active_record/querying.rb +16 -6
  167. data/lib/active_record/railtie.rb +136 -22
  168. data/lib/active_record/railties/controller_runtime.rb +1 -1
  169. data/lib/active_record/railties/databases.rake +78 -136
  170. data/lib/active_record/readonly_attributes.rb +11 -0
  171. data/lib/active_record/reflection.rb +80 -49
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  173. data/lib/active_record/relation/batches.rb +6 -6
  174. data/lib/active_record/relation/calculations.rb +92 -60
  175. data/lib/active_record/relation/delegation.rb +7 -7
  176. data/lib/active_record/relation/finder_methods.rb +31 -35
  177. data/lib/active_record/relation/merger.rb +20 -13
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  179. data/lib/active_record/relation/predicate_builder.rb +2 -6
  180. data/lib/active_record/relation/query_attribute.rb +5 -11
  181. data/lib/active_record/relation/query_methods.rb +285 -68
  182. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  183. data/lib/active_record/relation/spawn_methods.rb +2 -2
  184. data/lib/active_record/relation/where_clause.rb +10 -19
  185. data/lib/active_record/relation.rb +189 -88
  186. data/lib/active_record/result.rb +23 -11
  187. data/lib/active_record/runtime_registry.rb +9 -13
  188. data/lib/active_record/sanitization.rb +17 -12
  189. data/lib/active_record/schema.rb +38 -23
  190. data/lib/active_record/schema_dumper.rb +29 -19
  191. data/lib/active_record/schema_migration.rb +4 -4
  192. data/lib/active_record/scoping/default.rb +60 -13
  193. data/lib/active_record/scoping/named.rb +3 -11
  194. data/lib/active_record/scoping.rb +64 -34
  195. data/lib/active_record/serialization.rb +6 -1
  196. data/lib/active_record/signed_id.rb +3 -3
  197. data/lib/active_record/store.rb +2 -2
  198. data/lib/active_record/suppressor.rb +11 -15
  199. data/lib/active_record/table_metadata.rb +5 -1
  200. data/lib/active_record/tasks/database_tasks.rb +127 -60
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  203. data/lib/active_record/test_databases.rb +1 -1
  204. data/lib/active_record/test_fixtures.rb +9 -6
  205. data/lib/active_record/timestamp.rb +3 -4
  206. data/lib/active_record/transactions.rb +9 -14
  207. data/lib/active_record/translation.rb +3 -3
  208. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  209. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  210. data/lib/active_record/type/internal/timezone.rb +2 -2
  211. data/lib/active_record/type/serialized.rb +5 -5
  212. data/lib/active_record/type/type_map.rb +17 -20
  213. data/lib/active_record/type.rb +1 -2
  214. data/lib/active_record/validations/associated.rb +4 -4
  215. data/lib/active_record/validations/presence.rb +2 -2
  216. data/lib/active_record/validations/uniqueness.rb +4 -4
  217. data/lib/active_record/version.rb +1 -1
  218. data/lib/active_record.rb +225 -27
  219. data/lib/arel/attributes/attribute.rb +0 -8
  220. data/lib/arel/crud.rb +28 -22
  221. data/lib/arel/delete_manager.rb +18 -4
  222. data/lib/arel/filter_predications.rb +9 -0
  223. data/lib/arel/insert_manager.rb +2 -3
  224. data/lib/arel/nodes/casted.rb +1 -1
  225. data/lib/arel/nodes/delete_statement.rb +12 -13
  226. data/lib/arel/nodes/filter.rb +10 -0
  227. data/lib/arel/nodes/function.rb +1 -0
  228. data/lib/arel/nodes/insert_statement.rb +2 -2
  229. data/lib/arel/nodes/select_core.rb +2 -2
  230. data/lib/arel/nodes/select_statement.rb +2 -2
  231. data/lib/arel/nodes/update_statement.rb +8 -3
  232. data/lib/arel/nodes.rb +1 -0
  233. data/lib/arel/predications.rb +11 -3
  234. data/lib/arel/select_manager.rb +10 -4
  235. data/lib/arel/table.rb +0 -1
  236. data/lib/arel/tree_manager.rb +0 -12
  237. data/lib/arel/update_manager.rb +18 -4
  238. data/lib/arel/visitors/dot.rb +80 -90
  239. data/lib/arel/visitors/mysql.rb +8 -2
  240. data/lib/arel/visitors/postgresql.rb +0 -10
  241. data/lib/arel/visitors/to_sql.rb +58 -2
  242. data/lib/arel.rb +2 -1
  243. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  244. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  245. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  246. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  247. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  248. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  249. metadata +58 -14
@@ -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