activerecord 6.1.7 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1516 -1019
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +50 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +35 -31
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +423 -289
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +61 -14
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -46
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
  100. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  104. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  107. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  108. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  109. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  110. data/lib/active_record/connection_adapters.rb +9 -6
  111. data/lib/active_record/connection_handling.rb +107 -136
  112. data/lib/active_record/core.rb +194 -224
  113. data/lib/active_record/counter_cache.rb +46 -25
  114. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  115. data/lib/active_record/database_configurations/database_config.rb +21 -12
  116. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  117. data/lib/active_record/database_configurations/url_config.rb +18 -12
  118. data/lib/active_record/database_configurations.rb +95 -59
  119. data/lib/active_record/delegated_type.rb +61 -15
  120. data/lib/active_record/deprecator.rb +7 -0
  121. data/lib/active_record/destroy_association_async_job.rb +3 -1
  122. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  123. data/lib/active_record/dynamic_matchers.rb +1 -1
  124. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  125. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  126. data/lib/active_record/encryption/cipher.rb +53 -0
  127. data/lib/active_record/encryption/config.rb +68 -0
  128. data/lib/active_record/encryption/configurable.rb +60 -0
  129. data/lib/active_record/encryption/context.rb +42 -0
  130. data/lib/active_record/encryption/contexts.rb +76 -0
  131. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  132. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  133. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  134. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  135. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  136. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  137. data/lib/active_record/encryption/encryptor.rb +155 -0
  138. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  139. data/lib/active_record/encryption/errors.rb +15 -0
  140. data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
  141. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  142. data/lib/active_record/encryption/key.rb +28 -0
  143. data/lib/active_record/encryption/key_generator.rb +53 -0
  144. data/lib/active_record/encryption/key_provider.rb +46 -0
  145. data/lib/active_record/encryption/message.rb +33 -0
  146. data/lib/active_record/encryption/message_serializer.rb +92 -0
  147. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  148. data/lib/active_record/encryption/properties.rb +76 -0
  149. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  150. data/lib/active_record/encryption/scheme.rb +96 -0
  151. data/lib/active_record/encryption.rb +56 -0
  152. data/lib/active_record/enum.rb +156 -62
  153. data/lib/active_record/errors.rb +171 -15
  154. data/lib/active_record/explain.rb +23 -3
  155. data/lib/active_record/explain_registry.rb +11 -6
  156. data/lib/active_record/explain_subscriber.rb +1 -1
  157. data/lib/active_record/fixture_set/file.rb +15 -1
  158. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  159. data/lib/active_record/fixture_set/render_context.rb +2 -0
  160. data/lib/active_record/fixture_set/table_row.rb +70 -14
  161. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  162. data/lib/active_record/fixtures.rb +131 -86
  163. data/lib/active_record/future_result.rb +164 -0
  164. data/lib/active_record/gem_version.rb +3 -3
  165. data/lib/active_record/inheritance.rb +81 -29
  166. data/lib/active_record/insert_all.rb +133 -20
  167. data/lib/active_record/integration.rb +11 -10
  168. data/lib/active_record/internal_metadata.rb +117 -33
  169. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  170. data/lib/active_record/locking/optimistic.rb +36 -21
  171. data/lib/active_record/locking/pessimistic.rb +15 -6
  172. data/lib/active_record/log_subscriber.rb +52 -19
  173. data/lib/active_record/marshalling.rb +56 -0
  174. data/lib/active_record/message_pack.rb +124 -0
  175. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  176. data/lib/active_record/middleware/database_selector.rb +23 -13
  177. data/lib/active_record/middleware/shard_selector.rb +62 -0
  178. data/lib/active_record/migration/command_recorder.rb +108 -13
  179. data/lib/active_record/migration/compatibility.rb +221 -48
  180. data/lib/active_record/migration/default_strategy.rb +23 -0
  181. data/lib/active_record/migration/execution_strategy.rb +19 -0
  182. data/lib/active_record/migration/join_table.rb +1 -1
  183. data/lib/active_record/migration.rb +355 -171
  184. data/lib/active_record/model_schema.rb +116 -97
  185. data/lib/active_record/nested_attributes.rb +36 -15
  186. data/lib/active_record/no_touching.rb +3 -3
  187. data/lib/active_record/normalization.rb +159 -0
  188. data/lib/active_record/persistence.rb +405 -85
  189. data/lib/active_record/promise.rb +84 -0
  190. data/lib/active_record/query_cache.rb +3 -21
  191. data/lib/active_record/query_logs.rb +174 -0
  192. data/lib/active_record/query_logs_formatter.rb +41 -0
  193. data/lib/active_record/querying.rb +29 -6
  194. data/lib/active_record/railtie.rb +219 -43
  195. data/lib/active_record/railties/controller_runtime.rb +13 -9
  196. data/lib/active_record/railties/databases.rake +185 -249
  197. data/lib/active_record/railties/job_runtime.rb +23 -0
  198. data/lib/active_record/readonly_attributes.rb +41 -3
  199. data/lib/active_record/reflection.rb +229 -80
  200. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  201. data/lib/active_record/relation/batches.rb +192 -63
  202. data/lib/active_record/relation/calculations.rb +211 -90
  203. data/lib/active_record/relation/delegation.rb +27 -13
  204. data/lib/active_record/relation/finder_methods.rb +108 -51
  205. data/lib/active_record/relation/merger.rb +22 -13
  206. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  207. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  208. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  209. data/lib/active_record/relation/predicate_builder.rb +27 -20
  210. data/lib/active_record/relation/query_attribute.rb +30 -12
  211. data/lib/active_record/relation/query_methods.rb +654 -127
  212. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  213. data/lib/active_record/relation/spawn_methods.rb +20 -3
  214. data/lib/active_record/relation/where_clause.rb +10 -19
  215. data/lib/active_record/relation.rb +262 -120
  216. data/lib/active_record/result.rb +37 -11
  217. data/lib/active_record/runtime_registry.rb +18 -13
  218. data/lib/active_record/sanitization.rb +65 -20
  219. data/lib/active_record/schema.rb +36 -22
  220. data/lib/active_record/schema_dumper.rb +73 -24
  221. data/lib/active_record/schema_migration.rb +68 -33
  222. data/lib/active_record/scoping/default.rb +72 -15
  223. data/lib/active_record/scoping/named.rb +5 -13
  224. data/lib/active_record/scoping.rb +65 -34
  225. data/lib/active_record/secure_password.rb +60 -0
  226. data/lib/active_record/secure_token.rb +21 -3
  227. data/lib/active_record/serialization.rb +6 -1
  228. data/lib/active_record/signed_id.rb +10 -8
  229. data/lib/active_record/store.rb +10 -10
  230. data/lib/active_record/suppressor.rb +13 -15
  231. data/lib/active_record/table_metadata.rb +16 -3
  232. data/lib/active_record/tasks/database_tasks.rb +225 -136
  233. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  234. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  235. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  236. data/lib/active_record/test_databases.rb +1 -1
  237. data/lib/active_record/test_fixtures.rb +116 -96
  238. data/lib/active_record/timestamp.rb +28 -17
  239. data/lib/active_record/token_for.rb +113 -0
  240. data/lib/active_record/touch_later.rb +11 -6
  241. data/lib/active_record/transactions.rb +48 -27
  242. data/lib/active_record/translation.rb +3 -3
  243. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  244. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  245. data/lib/active_record/type/internal/timezone.rb +7 -2
  246. data/lib/active_record/type/serialized.rb +9 -5
  247. data/lib/active_record/type/time.rb +4 -0
  248. data/lib/active_record/type/type_map.rb +17 -20
  249. data/lib/active_record/type.rb +1 -2
  250. data/lib/active_record/validations/absence.rb +1 -1
  251. data/lib/active_record/validations/associated.rb +4 -4
  252. data/lib/active_record/validations/numericality.rb +5 -4
  253. data/lib/active_record/validations/presence.rb +5 -28
  254. data/lib/active_record/validations/uniqueness.rb +51 -6
  255. data/lib/active_record/validations.rb +8 -4
  256. data/lib/active_record/version.rb +1 -1
  257. data/lib/active_record.rb +335 -32
  258. data/lib/arel/attributes/attribute.rb +0 -8
  259. data/lib/arel/crud.rb +28 -22
  260. data/lib/arel/delete_manager.rb +18 -4
  261. data/lib/arel/errors.rb +10 -0
  262. data/lib/arel/factory_methods.rb +4 -0
  263. data/lib/arel/filter_predications.rb +9 -0
  264. data/lib/arel/insert_manager.rb +2 -3
  265. data/lib/arel/nodes/and.rb +4 -0
  266. data/lib/arel/nodes/binary.rb +6 -1
  267. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  268. data/lib/arel/nodes/casted.rb +1 -1
  269. data/lib/arel/nodes/cte.rb +36 -0
  270. data/lib/arel/nodes/delete_statement.rb +12 -13
  271. data/lib/arel/nodes/filter.rb +10 -0
  272. data/lib/arel/nodes/fragments.rb +35 -0
  273. data/lib/arel/nodes/function.rb +1 -0
  274. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  275. data/lib/arel/nodes/insert_statement.rb +2 -2
  276. data/lib/arel/nodes/leading_join.rb +8 -0
  277. data/lib/arel/nodes/node.rb +111 -2
  278. data/lib/arel/nodes/select_core.rb +2 -2
  279. data/lib/arel/nodes/select_statement.rb +2 -2
  280. data/lib/arel/nodes/sql_literal.rb +6 -0
  281. data/lib/arel/nodes/table_alias.rb +4 -0
  282. data/lib/arel/nodes/update_statement.rb +8 -3
  283. data/lib/arel/nodes.rb +5 -0
  284. data/lib/arel/predications.rb +13 -3
  285. data/lib/arel/select_manager.rb +10 -4
  286. data/lib/arel/table.rb +9 -6
  287. data/lib/arel/tree_manager.rb +0 -12
  288. data/lib/arel/update_manager.rb +18 -4
  289. data/lib/arel/visitors/dot.rb +80 -90
  290. data/lib/arel/visitors/mysql.rb +16 -3
  291. data/lib/arel/visitors/postgresql.rb +0 -10
  292. data/lib/arel/visitors/to_sql.rb +139 -19
  293. data/lib/arel/visitors/visitor.rb +2 -2
  294. data/lib/arel.rb +18 -3
  295. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  296. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  297. data/lib/rails/generators/active_record/migration.rb +3 -1
  298. data/lib/rails/generators/active_record/model/USAGE +113 -0
  299. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  300. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  302. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  303. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  304. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  305. metadata +92 -13
  306. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  307. data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,18 @@
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, key_generator: ActiveRecord::Encryption.key_generator)
8
+ super(Array(passwords).collect { |password| derive_key_from(password, using: key_generator) })
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
16
+ end
17
+ end
18
+ 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,224 @@
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
+ # <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
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.
37
+ # * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
38
+ # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
39
+ # in preserving it.
40
+ # * <tt>:ignore_case</tt> - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
41
+ # designated column +original_<name>+. When reading the encrypted content, the version with the original case is
42
+ # served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
43
+ # is true.
44
+ # * <tt>:context_properties</tt> - Additional properties that will override +Context+ settings when this attribute is
45
+ # encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
46
+ # * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
47
+ # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
48
+ # encryption is used, they will be used to generate additional ciphertexts to check in the queries.
49
+ def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
50
+ self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
51
+
52
+ names.each do |name|
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
+ end
55
+ end
56
+
57
+ # Returns the list of deterministic encryptable attributes in the model class.
58
+ def deterministic_encrypted_attributes
59
+ @deterministic_encrypted_attributes ||= encrypted_attributes&.find_all do |attribute_name|
60
+ type_for_attribute(attribute_name).deterministic?
61
+ end
62
+ end
63
+
64
+ # Given a attribute name, it returns the name of the source attribute when it's a preserved one.
65
+ def source_attribute_from_preserved_attribute(attribute_name)
66
+ attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if attribute_name.start_with?(ORIGINAL_ATTRIBUTE_PREFIX)
67
+ end
68
+
69
+ private
70
+ def scheme_for(key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
71
+ ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
72
+ support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
73
+ scheme.previous_schemes = global_previous_schemes_for(scheme) +
74
+ Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
75
+ end
76
+ end
77
+
78
+ def global_previous_schemes_for(scheme)
79
+ ActiveRecord::Encryption.config.previous_schemes.filter_map do |previous_scheme|
80
+ scheme.merge(previous_scheme) if scheme.compatible_with?(previous_scheme)
81
+ end
82
+ end
83
+
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
+ encrypted_attributes << name.to_sym
86
+
87
+ attribute name do |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)
92
+ end
93
+
94
+ preserve_original_encrypted(name) if ignore_case
95
+ ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
96
+ end
97
+
98
+ def preserve_original_encrypted(name)
99
+ original_attribute_name = "#{ORIGINAL_ATTRIBUTE_PREFIX}#{name}".to_sym
100
+
101
+ if !ActiveRecord::Encryption.config.support_unencrypted_data && !column_names.include?(original_attribute_name.to_s)
102
+ raise Errors::Configuration, "To use :ignore_case for '#{name}' you must create an additional column named '#{original_attribute_name}'"
103
+ end
104
+
105
+ encrypts original_attribute_name
106
+ override_accessors_to_preserve_original name, original_attribute_name
107
+ end
108
+
109
+ def override_accessors_to_preserve_original(name, original_attribute_name)
110
+ include(Module.new do
111
+ define_method name do
112
+ if ((value = super()) && encrypted_attribute?(name)) || !ActiveRecord::Encryption.config.support_unencrypted_data
113
+ send(original_attribute_name)
114
+ else
115
+ value
116
+ end
117
+ end
118
+
119
+ define_method "#{name}=" do |value|
120
+ self.send "#{original_attribute_name}=", value
121
+ super(value)
122
+ end
123
+ end)
124
+ end
125
+
126
+ def load_schema!
127
+ super
128
+
129
+ add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
130
+ end
131
+
132
+ def add_length_validation_for_encrypted_columns
133
+ encrypted_attributes&.each do |attribute_name|
134
+ validate_column_size attribute_name
135
+ end
136
+ end
137
+
138
+ def validate_column_size(attribute_name)
139
+ if limit = columns_hash[attribute_name.to_s]&.limit
140
+ validates_length_of attribute_name, maximum: limit
141
+ end
142
+ end
143
+ end
144
+
145
+ # Returns whether a given attribute is encrypted or not.
146
+ def encrypted_attribute?(attribute_name)
147
+ ActiveRecord::Encryption.encryptor.encrypted? read_attribute_before_type_cast(attribute_name)
148
+ end
149
+
150
+ # Returns the ciphertext for +attribute_name+.
151
+ def ciphertext_for(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
157
+ end
158
+
159
+ # Encrypts all the encryptable attributes and saves the changes.
160
+ def encrypt
161
+ encrypt_attributes if has_encrypted_attributes?
162
+ end
163
+
164
+ # Decrypts all the encryptable attributes and saves the changes.
165
+ def decrypt
166
+ decrypt_attributes if has_encrypted_attributes?
167
+ end
168
+
169
+ private
170
+ ORIGINAL_ATTRIBUTE_PREFIX = "original_"
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
+
181
+ def encrypt_attributes
182
+ validate_encryption_allowed
183
+
184
+ update_columns build_encrypt_attribute_assignments
185
+ end
186
+
187
+ def decrypt_attributes
188
+ validate_encryption_allowed
189
+
190
+ decrypt_attribute_assignments = build_decrypt_attribute_assignments
191
+ ActiveRecord::Encryption.without_encryption { update_columns decrypt_attribute_assignments }
192
+ end
193
+
194
+ def validate_encryption_allowed
195
+ raise ActiveRecord::Encryption::Errors::Configuration, "can't be modified because it is encrypted" if ActiveRecord::Encryption.context.frozen_encryption?
196
+ end
197
+
198
+ def has_encrypted_attributes?
199
+ self.class.encrypted_attributes.present?
200
+ end
201
+
202
+ def build_encrypt_attribute_assignments
203
+ Array(self.class.encrypted_attributes).index_with do |attribute_name|
204
+ self[attribute_name]
205
+ end
206
+ end
207
+
208
+ def build_decrypt_attribute_assignments
209
+ Array(self.class.encrypted_attributes).to_h do |attribute_name|
210
+ type = type_for_attribute(attribute_name)
211
+ encrypted_value = ciphertext_for(attribute_name)
212
+ new_value = type.deserialize(encrypted_value)
213
+ [attribute_name, new_value]
214
+ end
215
+ end
216
+
217
+ def cant_modify_encrypted_attributes_when_frozen
218
+ self.class&.encrypted_attributes.each do |attribute|
219
+ errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,151 @@
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, default: nil)
24
+ super()
25
+ @scheme = scheme
26
+ @cast_type = cast_type
27
+ @previous_type = previous_type
28
+ @default = default
29
+ end
30
+
31
+ def cast(value)
32
+ cast_type.cast(value)
33
+ end
34
+
35
+ def deserialize(value)
36
+ cast_type.deserialize decrypt(value)
37
+ end
38
+
39
+ def serialize(value)
40
+ if serialize_with_oldest?
41
+ serialize_with_oldest(value)
42
+ else
43
+ serialize_with_current(value)
44
+ end
45
+ end
46
+
47
+ def changed_in_place?(raw_old_value, new_value)
48
+ old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
49
+ old_value != new_value
50
+ end
51
+
52
+ def previous_types # :nodoc:
53
+ @previous_types ||= {} # Memoizing on support_unencrypted_data so that we can tweak it during tests
54
+ @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
55
+ end
56
+
57
+ def support_unencrypted_data?
58
+ ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
59
+ end
60
+
61
+ private
62
+ def previous_schemes_including_clean_text
63
+ previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
64
+ end
65
+
66
+ def previous_types_without_clean_text
67
+ @previous_types_without_clean_text ||= build_previous_types_for(previous_schemes)
68
+ end
69
+
70
+ def build_previous_types_for(schemes)
71
+ schemes.collect do |scheme|
72
+ EncryptedAttributeType.new(scheme: scheme, previous_type: true)
73
+ end
74
+ end
75
+
76
+ def previous_type?
77
+ @previous_type
78
+ end
79
+
80
+ def decrypt(value)
81
+ with_context do
82
+ unless value.nil?
83
+ if @default && @default == value
84
+ value
85
+ else
86
+ encryptor.decrypt(value, **decryption_options)
87
+ end
88
+ end
89
+ end
90
+ rescue ActiveRecord::Encryption::Errors::Base => error
91
+ if previous_types_without_clean_text.blank?
92
+ handle_deserialize_error(error, value)
93
+ else
94
+ try_to_deserialize_with_previous_encrypted_types(value)
95
+ end
96
+ end
97
+
98
+ def try_to_deserialize_with_previous_encrypted_types(value)
99
+ previous_types.each.with_index do |type, index|
100
+ break type.deserialize(value)
101
+ rescue ActiveRecord::Encryption::Errors::Base => error
102
+ handle_deserialize_error(error, value) if index == previous_types.length - 1
103
+ end
104
+ end
105
+
106
+ def handle_deserialize_error(error, value)
107
+ if error.is_a?(Errors::Decryption) && support_unencrypted_data?
108
+ value
109
+ else
110
+ raise error
111
+ end
112
+ end
113
+
114
+ def serialize_with_oldest?
115
+ @serialize_with_oldest ||= fixed? && previous_types_without_clean_text.present?
116
+ end
117
+
118
+ def serialize_with_oldest(value)
119
+ previous_types.first.serialize(value)
120
+ end
121
+
122
+ def serialize_with_current(value)
123
+ casted_value = cast_type.serialize(value)
124
+ casted_value = casted_value&.downcase if downcase?
125
+ encrypt(casted_value.to_s) unless casted_value.nil?
126
+ end
127
+
128
+ def encrypt(value)
129
+ with_context do
130
+ encryptor.encrypt(value, **encryption_options)
131
+ end
132
+ end
133
+
134
+ def encryptor
135
+ ActiveRecord::Encryption.encryptor
136
+ end
137
+
138
+ def encryption_options
139
+ @encryption_options ||= { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
140
+ end
141
+
142
+ def decryption_options
143
+ @decryption_options ||= { key_provider: key_provider }.compact
144
+ end
145
+
146
+ def clean_text_scheme
147
+ @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
148
+ end
149
+ end
150
+ end
151
+ 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
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "zlib"
5
+ require "active_support/core_ext/numeric"
6
+
7
+ module ActiveRecord
8
+ module Encryption
9
+ # An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType
10
+ # uses for encrypting and decrypting attribute values.
11
+ #
12
+ # It interacts with a KeyProvider for getting the keys, and delegate to
13
+ # ActiveRecord::Encryption::Cipher the actual encryption algorithm.
14
+ class Encryptor
15
+ # Encrypts +clean_text+ and returns the encrypted result
16
+ #
17
+ # Internally, it will:
18
+ #
19
+ # 1. Create a new ActiveRecord::Encryption::Message
20
+ # 2. Compress and encrypt +clean_text+ as the message payload
21
+ # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
22
+ # by default)
23
+ # 4. Encode the result with Base 64
24
+ #
25
+ # === Options
26
+ #
27
+ # [:key_provider]
28
+ # Key provider to use for the encryption operation. It will default to
29
+ # +ActiveRecord::Encryption.key_provider+ when not provided.
30
+ #
31
+ # [:cipher_options]
32
+ # Cipher-specific options that will be passed to the Cipher configured in
33
+ # +ActiveRecord::Encryption.cipher+
34
+ def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
35
+ clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
36
+
37
+ validate_payload_type(clear_text)
38
+ serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
39
+ end
40
+
41
+ # Decrypts a +clean_text+ and returns the result as clean text
42
+ #
43
+ # === Options
44
+ #
45
+ # [:key_provider]
46
+ # Key provider to use for the encryption operation. It will default to
47
+ # +ActiveRecord::Encryption.key_provider+ when not provided
48
+ #
49
+ # [:cipher_options]
50
+ # Cipher-specific options that will be passed to the Cipher configured in
51
+ # +ActiveRecord::Encryption.cipher+
52
+ def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
53
+ message = deserialize_message(encrypted_text)
54
+ keys = key_provider.decryption_keys(message)
55
+ raise Errors::Decryption unless keys.present?
56
+ uncompress_if_needed(cipher.decrypt(message, key: keys.collect(&:secret), **cipher_options), message.headers.compressed)
57
+ rescue *(ENCODING_ERRORS + DECRYPT_ERRORS)
58
+ raise Errors::Decryption
59
+ end
60
+
61
+ # Returns whether the text is encrypted or not
62
+ def encrypted?(text)
63
+ deserialize_message(text)
64
+ true
65
+ rescue Errors::Encoding, *DECRYPT_ERRORS
66
+ false
67
+ end
68
+
69
+ private
70
+ DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
71
+ ENCODING_ERRORS = [EncodingError, Errors::Encoding]
72
+ THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
73
+
74
+ def default_key_provider
75
+ ActiveRecord::Encryption.key_provider
76
+ end
77
+
78
+ def validate_payload_type(clear_text)
79
+ unless clear_text.is_a?(String)
80
+ raise ActiveRecord::Encryption::Errors::ForbiddenClass, "The encryptor can only encrypt string values (#{clear_text.class})"
81
+ end
82
+ end
83
+
84
+ def cipher
85
+ ActiveRecord::Encryption.cipher
86
+ end
87
+
88
+ def build_encrypted_message(clear_text, key_provider:, cipher_options:)
89
+ key = key_provider.encryption_key
90
+
91
+ clear_text, was_compressed = compress_if_worth_it(clear_text)
92
+ cipher.encrypt(clear_text, key: key.secret, **cipher_options).tap do |message|
93
+ message.headers.add(key.public_tags)
94
+ message.headers.compressed = true if was_compressed
95
+ end
96
+ end
97
+
98
+ def serialize_message(message)
99
+ serializer.dump(message)
100
+ end
101
+
102
+ def deserialize_message(message)
103
+ raise Errors::Encoding unless message.is_a?(String)
104
+ serializer.load message
105
+ rescue ArgumentError, TypeError, Errors::ForbiddenClass
106
+ raise Errors::Encoding
107
+ end
108
+
109
+ def serializer
110
+ ActiveRecord::Encryption.message_serializer
111
+ end
112
+
113
+ # Under certain threshold, ZIP compression is actually worse that not compressing
114
+ def compress_if_worth_it(string)
115
+ if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
116
+ [compress(string), true]
117
+ else
118
+ [string, false]
119
+ end
120
+ end
121
+
122
+ def compress(data)
123
+ Zlib::Deflate.deflate(data).tap do |compressed_data|
124
+ compressed_data.force_encoding(data.encoding)
125
+ end
126
+ end
127
+
128
+ def uncompress_if_needed(data, compressed)
129
+ if compressed
130
+ uncompress(data)
131
+ else
132
+ data
133
+ end
134
+ end
135
+
136
+ def uncompress(data)
137
+ Zlib::Inflate.inflate(data).tap do |uncompressed_data|
138
+ uncompressed_data.force_encoding(data.encoding)
139
+ end
140
+ end
141
+
142
+ def force_encoding_if_needed(value)
143
+ if forced_encoding_for_deterministic_encryption && value && value.encoding != forced_encoding_for_deterministic_encryption
144
+ value.encode(forced_encoding_for_deterministic_encryption, invalid: :replace, undef: :replace)
145
+ else
146
+ value
147
+ end
148
+ end
149
+
150
+ def forced_encoding_for_deterministic_encryption
151
+ ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Implements a simple envelope encryption approach where:
6
+ #
7
+ # * It generates a random data-encryption key for each encryption operation.
8
+ # * It stores the generated key along with the encrypted payload. It encrypts this key
9
+ # with the master key provided in the +active_record_encryption.primary_key+ credential.
10
+ #
11
+ # This provider can work with multiple master keys. It will use the last one for encrypting.
12
+ #
13
+ # When +config.active_record.encryption.store_key_references+ is true, it will also store a reference to
14
+ # the specific master key that was used to encrypt the data-encryption key. When not set,
15
+ # it will try all the configured master keys looking for the right one, in order to
16
+ # return the right decryption key.
17
+ class EnvelopeEncryptionKeyProvider
18
+ def encryption_key
19
+ random_secret = generate_random_secret
20
+ ActiveRecord::Encryption::Key.new(random_secret).tap do |key|
21
+ key.public_tags.encrypted_data_key = encrypt_data_key(random_secret)
22
+ key.public_tags.encrypted_data_key_id = active_primary_key.id if ActiveRecord::Encryption.config.store_key_references
23
+ end
24
+ end
25
+
26
+ def decryption_keys(encrypted_message)
27
+ secret = decrypt_data_key(encrypted_message)
28
+ secret ? [ActiveRecord::Encryption::Key.new(secret)] : []
29
+ end
30
+
31
+ def active_primary_key
32
+ @active_primary_key ||= primary_key_provider.encryption_key
33
+ end
34
+
35
+ private
36
+ def encrypt_data_key(random_secret)
37
+ ActiveRecord::Encryption.cipher.encrypt(random_secret, key: active_primary_key.secret)
38
+ end
39
+
40
+ def decrypt_data_key(encrypted_message)
41
+ encrypted_data_key = encrypted_message.headers.encrypted_data_key
42
+ key = primary_key_provider.decryption_keys(encrypted_message)&.collect(&:secret)
43
+ ActiveRecord::Encryption.cipher.decrypt encrypted_data_key, key: key if key
44
+ end
45
+
46
+ def primary_key_provider
47
+ @primary_key_provider ||= DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key)
48
+ end
49
+
50
+ def generate_random_secret
51
+ ActiveRecord::Encryption.key_generator.generate_random_key
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module Errors
6
+ class Base < StandardError; end
7
+ class Encoding < Base; end
8
+ class Decryption < Base; end
9
+ class Encryption < Base; end
10
+ class Configuration < Base; end
11
+ class ForbiddenClass < Base; end
12
+ class EncryptedContentIntegrity < Base; end
13
+ end
14
+ end
15
+ end