activerecord 6.1.7 → 7.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +520 -1385
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +2 -12
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +60 -21
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +37 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +41 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +4 -4
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +46 -36
- data/lib/active_record/associations/collection_proxy.rb +44 -16
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +29 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -7
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +23 -15
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +212 -53
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -16
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +15 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +404 -509
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +2 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +47 -27
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +14 -11
- data/lib/active_record/attribute_methods/serialization.rb +174 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
- data/lib/active_record/attribute_methods/write.rb +12 -15
- data/lib/active_record/attribute_methods.rb +164 -52
- data/lib/active_record/attributes.rb +51 -49
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +18 -34
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -46
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
- data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
- data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
- data/lib/active_record/connection_adapters/pool_config.rb +26 -16
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
- data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +130 -6
- data/lib/active_record/connection_handling.rb +132 -146
- data/lib/active_record/core.rb +276 -251
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
- data/lib/active_record/database_configurations/database_config.rb +34 -10
- data/lib/active_record/database_configurations/hash_config.rb +107 -31
- data/lib/active_record/database_configurations/url_config.rb +38 -13
- data/lib/active_record/database_configurations.rb +96 -60
- data/lib/active_record/delegated_type.rb +90 -20
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +4 -2
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +3 -3
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +170 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +56 -0
- data/lib/active_record/enum.rb +163 -63
- data/lib/active_record/errors.rb +210 -27
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +179 -112
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +85 -31
- data/lib/active_record/insert_all.rb +148 -32
- data/lib/active_record/integration.rb +14 -10
- data/lib/active_record/internal_metadata.rb +123 -23
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +43 -27
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +41 -29
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +113 -16
- data/lib/active_record/migration/compatibility.rb +235 -46
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +374 -177
- data/lib/active_record/model_schema.rb +143 -159
- data/lib/active_record/nested_attributes.rb +48 -21
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +282 -283
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +189 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +44 -9
- data/lib/active_record/railtie.rb +234 -71
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +189 -256
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +325 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +300 -111
- data/lib/active_record/relation/delegation.rb +33 -22
- data/lib/active_record/relation/finder_methods.rb +123 -52
- data/lib/active_record/relation/merger.rb +26 -19
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +29 -22
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +842 -150
- data/lib/active_record/relation/record_fetch_warning.rb +10 -9
- data/lib/active_record/relation/spawn_methods.rb +7 -6
- data/lib/active_record/relation/where_clause.rb +15 -36
- data/lib/active_record/relation.rb +736 -145
- data/lib/active_record/result.rb +67 -54
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +84 -34
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +90 -31
- data/lib/active_record/schema_migration.rb +74 -23
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +5 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +30 -9
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +277 -149
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +173 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +32 -19
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +118 -41
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -7
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +64 -15
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +444 -32
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +7 -2
- data/lib/arel/predications.rb +14 -4
- data/lib/arel/select_manager.rb +11 -5
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +20 -5
- data/lib/arel/visitors/dot.rb +81 -90
- data/lib/arel/visitors/mysql.rb +23 -5
- data/lib/arel/visitors/postgresql.rb +1 -22
- data/lib/arel/visitors/to_sql.rb +170 -36
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +23 -4
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +100 -14
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Encryption
|
7
|
+
# Utility for generating and deriving random keys.
|
8
|
+
class KeyGenerator
|
9
|
+
attr_reader :hash_digest_class
|
10
|
+
|
11
|
+
def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class)
|
12
|
+
@hash_digest_class = hash_digest_class
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default)
|
16
|
+
def generate_random_key(length: key_length)
|
17
|
+
SecureRandom.random_bytes(length)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a random key in hexadecimal format. The key will have a size in bytes of +:length+ (configured +Cipher+'s
|
21
|
+
# length by default)
|
22
|
+
#
|
23
|
+
# Hexadecimal format is handy for representing keys as printable text. To maximize the space of characters used, it is
|
24
|
+
# good practice including not printable characters. Hexadecimal format ensures that generated keys are representable with
|
25
|
+
# plain text
|
26
|
+
#
|
27
|
+
# To convert back to the original string with the desired length:
|
28
|
+
#
|
29
|
+
# [ value ].pack("H*")
|
30
|
+
def generate_random_hex_key(length: key_length)
|
31
|
+
generate_random_key(length: length).unpack("H*")[0]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Derives a key from the given password. The key will have a size in bytes of +:length+ (configured +Cipher+'s length
|
35
|
+
# by default)
|
36
|
+
#
|
37
|
+
# The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+
|
38
|
+
def derive_key_from(password, length: key_length)
|
39
|
+
ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class)
|
40
|
+
.generate_key(key_derivation_salt, length)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def key_derivation_salt
|
45
|
+
@key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt
|
46
|
+
end
|
47
|
+
|
48
|
+
def key_length
|
49
|
+
@key_length ||= ActiveRecord::Encryption.cipher.key_length
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# A +KeyProvider+ serves keys:
|
6
|
+
#
|
7
|
+
# * An encryption key
|
8
|
+
# * A list of potential decryption keys. Serving multiple decryption keys supports rotation-schemes
|
9
|
+
# where new keys are added but old keys need to continue working
|
10
|
+
class KeyProvider
|
11
|
+
def initialize(keys)
|
12
|
+
@keys = Array(keys)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the first key in the list as the active key to perform encryptions
|
16
|
+
#
|
17
|
+
# When +ActiveRecord::Encryption.config.store_key_references+ is true, the key will include
|
18
|
+
# a public tag referencing the key itself. That key will be stored in the public
|
19
|
+
# headers of the encrypted message
|
20
|
+
def encryption_key
|
21
|
+
@encryption_key ||= @keys.last.tap do |key|
|
22
|
+
key.public_tags.encrypted_data_key_id = key.id if ActiveRecord::Encryption.config.store_key_references
|
23
|
+
end
|
24
|
+
|
25
|
+
@encryption_key
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the list of decryption keys
|
29
|
+
#
|
30
|
+
# When the message holds a reference to its encryption key, it will return an array
|
31
|
+
# with that key. If not, it will return the list of keys.
|
32
|
+
def decryption_keys(encrypted_message)
|
33
|
+
if encrypted_message.headers.encrypted_data_key_id
|
34
|
+
keys_grouped_by_id[encrypted_message.headers.encrypted_data_key_id]
|
35
|
+
else
|
36
|
+
@keys
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def keys_grouped_by_id
|
42
|
+
@keys_grouped_by_id ||= @keys.group_by(&:id)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# A message defines the structure of the data we store in encrypted attributes. It contains:
|
6
|
+
#
|
7
|
+
# * An encrypted payload
|
8
|
+
# * A list of unencrypted headers
|
9
|
+
#
|
10
|
+
# See Encryptor#encrypt
|
11
|
+
class Message
|
12
|
+
attr_accessor :payload, :headers
|
13
|
+
|
14
|
+
def initialize(payload: nil, headers: {})
|
15
|
+
validate_payload_type(payload)
|
16
|
+
|
17
|
+
@payload = payload
|
18
|
+
@headers = Properties.new(headers)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other_message)
|
22
|
+
payload == other_message.payload && headers == other_message.headers
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def validate_payload_type(payload)
|
27
|
+
unless payload.is_a?(String) || payload.nil?
|
28
|
+
raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Only string payloads allowed"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/message_pack"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Encryption
|
7
|
+
# A message serializer that serializes +Messages+ with MessagePack.
|
8
|
+
#
|
9
|
+
# The message is converted to a hash with this structure:
|
10
|
+
#
|
11
|
+
# {
|
12
|
+
# p: <payload>,
|
13
|
+
# h: {
|
14
|
+
# header1: value1,
|
15
|
+
# header2: value2,
|
16
|
+
# ...
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# Then it is converted to the MessagePack format.
|
21
|
+
class MessagePackMessageSerializer
|
22
|
+
def dump(message)
|
23
|
+
raise Errors::ForbiddenClass unless message.is_a?(Message)
|
24
|
+
ActiveSupport::MessagePack.dump(message_to_hash(message))
|
25
|
+
end
|
26
|
+
|
27
|
+
def load(serialized_content)
|
28
|
+
data = ActiveSupport::MessagePack.load(serialized_content)
|
29
|
+
hash_to_message(data, 1)
|
30
|
+
rescue RuntimeError
|
31
|
+
raise Errors::Decryption
|
32
|
+
end
|
33
|
+
|
34
|
+
def binary?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def message_to_hash(message)
|
40
|
+
{
|
41
|
+
"p" => message.payload,
|
42
|
+
"h" => headers_to_hash(message.headers)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def headers_to_hash(headers)
|
47
|
+
headers.transform_values do |value|
|
48
|
+
value.is_a?(Message) ? message_to_hash(value) : value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def hash_to_message(data, level)
|
53
|
+
validate_message_data_format(data, level)
|
54
|
+
Message.new(payload: data["p"], headers: parse_properties(data["h"], level))
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_message_data_format(data, level)
|
58
|
+
if level > 2
|
59
|
+
raise Errors::Decryption, "More than one level of hash nesting in headers is not supported"
|
60
|
+
end
|
61
|
+
|
62
|
+
unless data.is_a?(Hash) && data.has_key?("p")
|
63
|
+
raise Errors::Decryption, "Invalid data format: hash without payload"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_properties(headers, level)
|
68
|
+
Properties.new.tap do |properties|
|
69
|
+
headers&.each do |key, value|
|
70
|
+
properties[key] = value.is_a?(Hash) ? hash_to_message(value, level + 1) : value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Encryption
|
7
|
+
# A message serializer that serializes +Messages+ with JSON.
|
8
|
+
#
|
9
|
+
# The generated structure is pretty simple:
|
10
|
+
#
|
11
|
+
# {
|
12
|
+
# p: <payload>,
|
13
|
+
# h: {
|
14
|
+
# header1: value1,
|
15
|
+
# header2: value2,
|
16
|
+
# ...
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# Both the payload and the header values are encoded with Base64
|
21
|
+
# to prevent JSON parsing errors and encoding issues when
|
22
|
+
# storing the resulting serialized data.
|
23
|
+
class MessageSerializer
|
24
|
+
def load(serialized_content)
|
25
|
+
data = JSON.parse(serialized_content)
|
26
|
+
parse_message(data, 1)
|
27
|
+
rescue JSON::ParserError
|
28
|
+
raise ActiveRecord::Encryption::Errors::Encoding
|
29
|
+
end
|
30
|
+
|
31
|
+
def dump(message)
|
32
|
+
raise ActiveRecord::Encryption::Errors::ForbiddenClass unless message.is_a?(ActiveRecord::Encryption::Message)
|
33
|
+
JSON.dump message_to_json(message)
|
34
|
+
end
|
35
|
+
|
36
|
+
def binary?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def parse_message(data, level)
|
42
|
+
validate_message_data_format(data, level)
|
43
|
+
ActiveRecord::Encryption::Message.new(payload: decode_if_needed(data["p"]), headers: parse_properties(data["h"], level))
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_message_data_format(data, level)
|
47
|
+
if level > 2
|
48
|
+
raise ActiveRecord::Encryption::Errors::Decryption, "More than one level of hash nesting in headers is not supported"
|
49
|
+
end
|
50
|
+
|
51
|
+
unless data.is_a?(Hash) && data.has_key?("p")
|
52
|
+
raise ActiveRecord::Encryption::Errors::Decryption, "Invalid data format: hash without payload"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_properties(headers, level)
|
57
|
+
ActiveRecord::Encryption::Properties.new.tap do |properties|
|
58
|
+
headers&.each do |key, value|
|
59
|
+
properties[key] = value.is_a?(Hash) ? parse_message(value, level + 1) : decode_if_needed(value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def message_to_json(message)
|
65
|
+
{
|
66
|
+
p: encode_if_needed(message.payload),
|
67
|
+
h: headers_to_json(message.headers)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def headers_to_json(headers)
|
72
|
+
headers.transform_values do |value|
|
73
|
+
value.is_a?(ActiveRecord::Encryption::Message) ? message_to_json(value) : encode_if_needed(value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def encode_if_needed(value)
|
78
|
+
if value.is_a?(String)
|
79
|
+
::Base64.strict_encode64 value
|
80
|
+
else
|
81
|
+
value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def decode_if_needed(value)
|
86
|
+
if value.is_a?(String)
|
87
|
+
::Base64.strict_decode64(value)
|
88
|
+
else
|
89
|
+
value
|
90
|
+
end
|
91
|
+
rescue ArgumentError, TypeError
|
92
|
+
raise Errors::Encoding
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# An encryptor that won't decrypt or encrypt. It will just return the passed
|
6
|
+
# values
|
7
|
+
class NullEncryptor
|
8
|
+
def encrypt(clean_text, key_provider: nil, cipher_options: {})
|
9
|
+
clean_text
|
10
|
+
end
|
11
|
+
|
12
|
+
def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
|
13
|
+
encrypted_text
|
14
|
+
end
|
15
|
+
|
16
|
+
def encrypted?(text)
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def binary?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# This is a wrapper for a hash of encryption properties. It is used by
|
6
|
+
# +Key+ (public tags) and +Message+ (headers).
|
7
|
+
#
|
8
|
+
# Since properties are serialized in messages, it is important for storage
|
9
|
+
# efficiency to keep their keys as short as possible. It defines accessors
|
10
|
+
# for common properties that will keep these keys very short while exposing
|
11
|
+
# a readable name.
|
12
|
+
#
|
13
|
+
# message.headers.encrypted_data_key # instead of message.headers[:k]
|
14
|
+
#
|
15
|
+
# See +Properties::DEFAULT_PROPERTIES+, Key, Message
|
16
|
+
class Properties
|
17
|
+
ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
|
18
|
+
|
19
|
+
delegate_missing_to :data
|
20
|
+
delegate :==, :[], :each, :key?, to: :data
|
21
|
+
|
22
|
+
# For each entry it generates an accessor exposing the full name
|
23
|
+
DEFAULT_PROPERTIES = {
|
24
|
+
encrypted_data_key: "k",
|
25
|
+
encrypted_data_key_id: "i",
|
26
|
+
compressed: "c",
|
27
|
+
iv: "iv",
|
28
|
+
auth_tag: "at",
|
29
|
+
encoding: "e"
|
30
|
+
}
|
31
|
+
|
32
|
+
DEFAULT_PROPERTIES.each do |name, key|
|
33
|
+
define_method name do
|
34
|
+
self[key.to_sym]
|
35
|
+
end
|
36
|
+
|
37
|
+
define_method "#{name}=" do |value|
|
38
|
+
self[key.to_sym] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(initial_properties = {})
|
43
|
+
@data = {}
|
44
|
+
add(initial_properties)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set a value for a given key
|
48
|
+
#
|
49
|
+
# It will raise an +EncryptedContentIntegrity+ if the value exists
|
50
|
+
def []=(key, value)
|
51
|
+
raise Errors::EncryptedContentIntegrity, "Properties can't be overridden: #{key}" if key?(key)
|
52
|
+
validate_value_type(value)
|
53
|
+
data[key] = value
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_value_type(value)
|
57
|
+
unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
|
58
|
+
raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def add(other_properties)
|
63
|
+
other_properties.each do |key, value|
|
64
|
+
self[key.to_sym] = value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_h
|
69
|
+
data
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
attr_reader :data
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# A +NullEncryptor+ that will raise an error when trying to encrypt data
|
6
|
+
#
|
7
|
+
# This is useful when you want to reveal ciphertexts for debugging purposes
|
8
|
+
# and you want to make sure you won't overwrite any encryptable attribute with
|
9
|
+
# the wrong content.
|
10
|
+
class ReadOnlyNullEncryptor
|
11
|
+
def encrypt(clean_text, key_provider: nil, cipher_options: {})
|
12
|
+
raise Errors::Encryption, "This encryptor is read-only"
|
13
|
+
end
|
14
|
+
|
15
|
+
def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
|
16
|
+
encrypted_text
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypted?(text)
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def binary?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# A container of attribute encryption options.
|
6
|
+
#
|
7
|
+
# It validates and serves attribute encryption options.
|
8
|
+
#
|
9
|
+
# See EncryptedAttributeType, Context
|
10
|
+
class Scheme
|
11
|
+
attr_accessor :previous_schemes
|
12
|
+
|
13
|
+
def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
|
14
|
+
previous_schemes: nil, **context_properties)
|
15
|
+
# Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
|
16
|
+
# can merge schemes without overriding values with defaults. See +#merge+
|
17
|
+
|
18
|
+
@key_provider_param = key_provider
|
19
|
+
@key = key
|
20
|
+
@deterministic = deterministic
|
21
|
+
@support_unencrypted_data = support_unencrypted_data
|
22
|
+
@downcase = downcase || ignore_case
|
23
|
+
@ignore_case = ignore_case
|
24
|
+
@previous_schemes_param = previous_schemes
|
25
|
+
@previous_schemes = Array.wrap(previous_schemes)
|
26
|
+
@context_properties = context_properties
|
27
|
+
|
28
|
+
validate_config!
|
29
|
+
end
|
30
|
+
|
31
|
+
def ignore_case?
|
32
|
+
@ignore_case
|
33
|
+
end
|
34
|
+
|
35
|
+
def downcase?
|
36
|
+
@downcase
|
37
|
+
end
|
38
|
+
|
39
|
+
def deterministic?
|
40
|
+
!!@deterministic
|
41
|
+
end
|
42
|
+
|
43
|
+
def support_unencrypted_data?
|
44
|
+
@support_unencrypted_data.nil? ? ActiveRecord::Encryption.config.support_unencrypted_data : @support_unencrypted_data
|
45
|
+
end
|
46
|
+
|
47
|
+
def fixed?
|
48
|
+
# by default deterministic encryption is fixed
|
49
|
+
@fixed ||= @deterministic && (!@deterministic.is_a?(Hash) || @deterministic[:fixed])
|
50
|
+
end
|
51
|
+
|
52
|
+
def key_provider
|
53
|
+
@key_provider_param || key_provider_from_key || deterministic_key_provider || default_key_provider
|
54
|
+
end
|
55
|
+
|
56
|
+
def merge(other_scheme)
|
57
|
+
self.class.new(**to_h.merge(other_scheme.to_h))
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_h
|
61
|
+
{ key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
|
62
|
+
previous_schemes: @previous_schemes_param, **@context_properties }.compact
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_context(&block)
|
66
|
+
if @context_properties.present?
|
67
|
+
ActiveRecord::Encryption.with_encryption_context(**@context_properties, &block)
|
68
|
+
else
|
69
|
+
block.call
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def compatible_with?(other_scheme)
|
74
|
+
deterministic? == other_scheme.deterministic?
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def validate_config!
|
79
|
+
raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
|
80
|
+
raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
|
81
|
+
end
|
82
|
+
|
83
|
+
def key_provider_from_key
|
84
|
+
@key_provider_from_key ||= if @key.present?
|
85
|
+
DerivedSecretKeyProvider.new(@key)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def deterministic_key_provider
|
90
|
+
@deterministic_key_provider ||= if @deterministic
|
91
|
+
DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def default_key_provider
|
96
|
+
ActiveRecord::Encryption.key_provider
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module"
|
4
|
+
require "active_support/core_ext/array"
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module Encryption
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
eager_autoload do
|
11
|
+
autoload :AutoFilteredParameters
|
12
|
+
autoload :Cipher
|
13
|
+
autoload :Config
|
14
|
+
autoload :Configurable
|
15
|
+
autoload :Context
|
16
|
+
autoload :Contexts
|
17
|
+
autoload :DerivedSecretKeyProvider
|
18
|
+
autoload :EncryptableRecord
|
19
|
+
autoload :EncryptedAttributeType
|
20
|
+
autoload :EncryptedFixtures
|
21
|
+
autoload :EncryptingOnlyEncryptor
|
22
|
+
autoload :DeterministicKeyProvider
|
23
|
+
autoload :Encryptor
|
24
|
+
autoload :EnvelopeEncryptionKeyProvider
|
25
|
+
autoload :Errors
|
26
|
+
autoload :ExtendedDeterministicQueries
|
27
|
+
autoload :ExtendedDeterministicUniquenessValidator
|
28
|
+
autoload :Key
|
29
|
+
autoload :KeyGenerator
|
30
|
+
autoload :KeyProvider
|
31
|
+
autoload :Message
|
32
|
+
autoload :MessageSerializer
|
33
|
+
autoload :NullEncryptor
|
34
|
+
autoload :Properties
|
35
|
+
autoload :ReadOnlyNullEncryptor
|
36
|
+
autoload :Scheme
|
37
|
+
end
|
38
|
+
|
39
|
+
class Cipher
|
40
|
+
extend ActiveSupport::Autoload
|
41
|
+
|
42
|
+
eager_autoload do
|
43
|
+
autoload :Aes256Gcm
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
include Configurable
|
48
|
+
include Contexts
|
49
|
+
|
50
|
+
def self.eager_load!
|
51
|
+
super
|
52
|
+
|
53
|
+
Cipher.eager_load!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|