activerecord 7.0.8.1 → 7.2.2.1
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 +642 -1925
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- 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 +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- 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 +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- 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 +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +29 -8
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- 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/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- 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 +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- 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 +106 -24
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type/time.rb +4 -0
- 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 +9 -3
- 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 +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- 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/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- 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
- metadata +59 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -28,8 +28,12 @@ module ActiveRecord
|
|
28
28
|
# initialization vector based on the encrypted content. This means that the same content will generate
|
29
29
|
# the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption
|
30
30
|
# will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
|
31
|
-
#
|
31
|
+
# <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
|
32
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.
|
33
37
|
# * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
|
34
38
|
# effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
|
35
39
|
# in preserving it.
|
@@ -42,13 +46,11 @@ module ActiveRecord
|
|
42
46
|
# * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
|
43
47
|
# the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
|
44
48
|
# 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)
|
49
|
+
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
46
50
|
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
51
|
|
50
52
|
names.each do |name|
|
51
|
-
encrypt_attribute 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
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
@@ -61,32 +63,35 @@ module ActiveRecord
|
|
61
63
|
|
62
64
|
# Given a attribute name, it returns the name of the source attribute when it's a preserved one.
|
63
65
|
def source_attribute_from_preserved_attribute(attribute_name)
|
64
|
-
attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if
|
66
|
+
attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if attribute_name.start_with?(ORIGINAL_ATTRIBUTE_PREFIX)
|
65
67
|
end
|
66
68
|
|
67
69
|
private
|
68
|
-
def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
|
70
|
+
def scheme_for(key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
69
71
|
ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
|
70
|
-
|
72
|
+
support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
|
71
73
|
scheme.previous_schemes = global_previous_schemes_for(scheme) +
|
72
|
-
|
74
|
+
Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
76
78
|
def global_previous_schemes_for(scheme)
|
77
|
-
ActiveRecord::Encryption.config.previous_schemes.
|
78
|
-
scheme.merge(previous_scheme)
|
79
|
+
ActiveRecord::Encryption.config.previous_schemes.filter_map do |previous_scheme|
|
80
|
+
scheme.merge(previous_scheme) if scheme.compatible_with?(previous_scheme)
|
79
81
|
end
|
80
82
|
end
|
81
83
|
|
82
|
-
def encrypt_attribute(name,
|
84
|
+
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
83
85
|
encrypted_attributes << name.to_sym
|
84
86
|
|
85
|
-
|
86
|
-
|
87
|
+
decorate_attributes([name]) do |name, 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)
|
87
92
|
end
|
88
93
|
|
89
|
-
preserve_original_encrypted(name) if
|
94
|
+
preserve_original_encrypted(name) if ignore_case
|
90
95
|
ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
|
91
96
|
end
|
92
97
|
|
@@ -118,7 +123,7 @@ module ActiveRecord
|
|
118
123
|
end)
|
119
124
|
end
|
120
125
|
|
121
|
-
def load_schema!
|
126
|
+
def load_schema! # :nodoc:
|
122
127
|
super
|
123
128
|
|
124
129
|
add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
|
@@ -139,12 +144,22 @@ module ActiveRecord
|
|
139
144
|
|
140
145
|
# Returns whether a given attribute is encrypted or not.
|
141
146
|
def encrypted_attribute?(attribute_name)
|
142
|
-
|
147
|
+
name = attribute_name.to_s
|
148
|
+
name = self.class.attribute_aliases[name] || name
|
149
|
+
|
150
|
+
return false unless self.class.encrypted_attributes&.include? name.to_sym
|
151
|
+
|
152
|
+
type = type_for_attribute(name)
|
153
|
+
type.encrypted? read_attribute_before_type_cast(name)
|
143
154
|
end
|
144
155
|
|
145
156
|
# Returns the ciphertext for +attribute_name+.
|
146
157
|
def ciphertext_for(attribute_name)
|
147
|
-
|
158
|
+
if encrypted_attribute?(attribute_name)
|
159
|
+
read_attribute_before_type_cast(attribute_name)
|
160
|
+
else
|
161
|
+
read_attribute_for_database(attribute_name)
|
162
|
+
end
|
148
163
|
end
|
149
164
|
|
150
165
|
# Encrypts all the encryptable attributes and saves the changes.
|
@@ -160,6 +175,15 @@ module ActiveRecord
|
|
160
175
|
private
|
161
176
|
ORIGINAL_ATTRIBUTE_PREFIX = "original_"
|
162
177
|
|
178
|
+
def _create_record(attribute_names = self.attribute_names)
|
179
|
+
if has_encrypted_attributes?
|
180
|
+
# Always persist encrypted attributes, because an attribute might be
|
181
|
+
# encrypting a column default value.
|
182
|
+
attribute_names |= self.class.encrypted_attributes.map(&:to_s)
|
183
|
+
end
|
184
|
+
super
|
185
|
+
end
|
186
|
+
|
163
187
|
def encrypt_attributes
|
164
188
|
validate_encryption_allowed
|
165
189
|
|
@@ -188,16 +212,16 @@ module ActiveRecord
|
|
188
212
|
end
|
189
213
|
|
190
214
|
def build_decrypt_attribute_assignments
|
191
|
-
Array(self.class.encrypted_attributes).
|
215
|
+
Array(self.class.encrypted_attributes).to_h do |attribute_name|
|
192
216
|
type = type_for_attribute(attribute_name)
|
193
217
|
encrypted_value = ciphertext_for(attribute_name)
|
194
218
|
new_value = type.deserialize(encrypted_value)
|
195
219
|
[attribute_name, new_value]
|
196
|
-
end
|
220
|
+
end
|
197
221
|
end
|
198
222
|
|
199
223
|
def cant_modify_encrypted_attributes_when_frozen
|
200
|
-
self.class
|
224
|
+
self.class.encrypted_attributes.each do |attribute|
|
201
225
|
errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
|
202
226
|
end
|
203
227
|
end
|
@@ -7,24 +7,29 @@ module ActiveRecord
|
|
7
7
|
# This is the central piece that connects the encryption system with +encrypts+ declarations in the
|
8
8
|
# model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
|
9
9
|
# for that attribute.
|
10
|
-
class EncryptedAttributeType < ::
|
10
|
+
class EncryptedAttributeType < ::ActiveModel::Type::Value
|
11
11
|
include ActiveModel::Type::Helpers::Mutable
|
12
12
|
|
13
13
|
attr_reader :scheme, :cast_type
|
14
14
|
|
15
15
|
delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
|
16
|
-
delegate :accessor, to: :cast_type
|
16
|
+
delegate :accessor, :type, to: :cast_type
|
17
17
|
|
18
18
|
# === Options
|
19
19
|
#
|
20
20
|
# * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
|
21
21
|
# * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
|
22
22
|
# (after decrypting). ActiveModel::Type::String by default.
|
23
|
-
def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false)
|
23
|
+
def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil)
|
24
24
|
super()
|
25
25
|
@scheme = scheme
|
26
26
|
@cast_type = cast_type
|
27
27
|
@previous_type = previous_type
|
28
|
+
@default = default
|
29
|
+
end
|
30
|
+
|
31
|
+
def cast(value)
|
32
|
+
cast_type.cast(value)
|
28
33
|
end
|
29
34
|
|
30
35
|
def deserialize(value)
|
@@ -39,6 +44,10 @@ module ActiveRecord
|
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
47
|
+
def encrypted?(value)
|
48
|
+
with_context { encryptor.encrypted? value }
|
49
|
+
end
|
50
|
+
|
42
51
|
def changed_in_place?(raw_old_value, new_value)
|
43
52
|
old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
|
44
53
|
old_value != new_value
|
@@ -49,6 +58,10 @@ module ActiveRecord
|
|
49
58
|
@previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
|
50
59
|
end
|
51
60
|
|
61
|
+
def support_unencrypted_data?
|
62
|
+
ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
|
63
|
+
end
|
64
|
+
|
52
65
|
private
|
53
66
|
def previous_schemes_including_clean_text
|
54
67
|
previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
|
@@ -68,9 +81,15 @@ module ActiveRecord
|
|
68
81
|
@previous_type
|
69
82
|
end
|
70
83
|
|
71
|
-
def
|
84
|
+
def decrypt_as_text(value)
|
72
85
|
with_context do
|
73
|
-
|
86
|
+
unless value.nil?
|
87
|
+
if @default && @default == value
|
88
|
+
value
|
89
|
+
else
|
90
|
+
encryptor.decrypt(value, **decryption_options)
|
91
|
+
end
|
92
|
+
end
|
74
93
|
end
|
75
94
|
rescue ActiveRecord::Encryption::Errors::Base => error
|
76
95
|
if previous_types_without_clean_text.blank?
|
@@ -80,6 +99,10 @@ module ActiveRecord
|
|
80
99
|
end
|
81
100
|
end
|
82
101
|
|
102
|
+
def decrypt(value)
|
103
|
+
text_to_database_type decrypt_as_text(value)
|
104
|
+
end
|
105
|
+
|
83
106
|
def try_to_deserialize_with_previous_encrypted_types(value)
|
84
107
|
previous_types.each.with_index do |type, index|
|
85
108
|
break type.deserialize(value)
|
@@ -110,31 +133,43 @@ module ActiveRecord
|
|
110
133
|
encrypt(casted_value.to_s) unless casted_value.nil?
|
111
134
|
end
|
112
135
|
|
113
|
-
def
|
136
|
+
def encrypt_as_text(value)
|
114
137
|
with_context do
|
138
|
+
if encryptor.binary? && !cast_type.binary?
|
139
|
+
raise Errors::Encoding, "Binary encoded data can only be stored in binary columns"
|
140
|
+
end
|
141
|
+
|
115
142
|
encryptor.encrypt(value, **encryption_options)
|
116
143
|
end
|
117
144
|
end
|
118
145
|
|
119
|
-
def
|
120
|
-
|
146
|
+
def encrypt(value)
|
147
|
+
text_to_database_type encrypt_as_text(value)
|
121
148
|
end
|
122
149
|
|
123
|
-
def
|
124
|
-
ActiveRecord::Encryption.
|
150
|
+
def encryptor
|
151
|
+
ActiveRecord::Encryption.encryptor
|
125
152
|
end
|
126
153
|
|
127
154
|
def encryption_options
|
128
|
-
|
155
|
+
{ key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
|
129
156
|
end
|
130
157
|
|
131
158
|
def decryption_options
|
132
|
-
|
159
|
+
{ key_provider: key_provider }.compact
|
133
160
|
end
|
134
161
|
|
135
162
|
def clean_text_scheme
|
136
163
|
@clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
|
137
164
|
end
|
165
|
+
|
166
|
+
def text_to_database_type(value)
|
167
|
+
if value && cast_type.binary?
|
168
|
+
ActiveModel::Type::Binary::Data.new(value)
|
169
|
+
else
|
170
|
+
value
|
171
|
+
end
|
172
|
+
end
|
138
173
|
end
|
139
174
|
end
|
140
175
|
end
|
@@ -12,6 +12,14 @@ module ActiveRecord
|
|
12
12
|
# It interacts with a KeyProvider for getting the keys, and delegate to
|
13
13
|
# ActiveRecord::Encryption::Cipher the actual encryption algorithm.
|
14
14
|
class Encryptor
|
15
|
+
# === Options
|
16
|
+
#
|
17
|
+
# * <tt>:compress</tt> - Boolean indicating whether records should be compressed before encryption.
|
18
|
+
# Defaults to +true+.
|
19
|
+
def initialize(compress: true)
|
20
|
+
@compress = compress
|
21
|
+
end
|
22
|
+
|
15
23
|
# Encrypts +clean_text+ and returns the encrypted result
|
16
24
|
#
|
17
25
|
# Internally, it will:
|
@@ -38,7 +46,7 @@ module ActiveRecord
|
|
38
46
|
serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
|
39
47
|
end
|
40
48
|
|
41
|
-
# Decrypts
|
49
|
+
# Decrypts an +encrypted_text+ and returns the result as clean text
|
42
50
|
#
|
43
51
|
# === Options
|
44
52
|
#
|
@@ -66,6 +74,10 @@ module ActiveRecord
|
|
66
74
|
false
|
67
75
|
end
|
68
76
|
|
77
|
+
def binary?
|
78
|
+
serializer.binary?
|
79
|
+
end
|
80
|
+
|
69
81
|
private
|
70
82
|
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
71
83
|
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
@@ -100,7 +112,6 @@ module ActiveRecord
|
|
100
112
|
end
|
101
113
|
|
102
114
|
def deserialize_message(message)
|
103
|
-
raise Errors::Encoding unless message.is_a?(String)
|
104
115
|
serializer.load message
|
105
116
|
rescue ArgumentError, TypeError, Errors::ForbiddenClass
|
106
117
|
raise Errors::Encoding
|
@@ -112,13 +123,17 @@ module ActiveRecord
|
|
112
123
|
|
113
124
|
# Under certain threshold, ZIP compression is actually worse that not compressing
|
114
125
|
def compress_if_worth_it(string)
|
115
|
-
if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
126
|
+
if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
116
127
|
[compress(string), true]
|
117
128
|
else
|
118
129
|
[string, false]
|
119
130
|
end
|
120
131
|
end
|
121
132
|
|
133
|
+
def compress?
|
134
|
+
@compress
|
135
|
+
end
|
136
|
+
|
122
137
|
def compress(data)
|
123
138
|
Zlib::Deflate.deflate(data).tap do |compressed_data|
|
124
139
|
compressed_data.force_encoding(data.encoding)
|
@@ -19,101 +19,112 @@ module ActiveRecord
|
|
19
19
|
# * ActiveRecord::Base - Used in <tt>Contact.find_by_email_address(...)</tt>
|
20
20
|
# * ActiveRecord::Relation - Used in <tt>Contact.internal.find_by_email_address(...)</tt>
|
21
21
|
#
|
22
|
-
#
|
23
|
-
# some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
|
24
|
-
# as it's invoked (so that the proper prepared statement is cached).
|
25
|
-
#
|
26
|
-
# When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
|
27
|
-
# make sure performance overhead is acceptable.
|
28
|
-
#
|
29
|
-
# We will extend this to support previous "encryption context" versions in future iterations
|
30
|
-
#
|
31
|
-
# @TODO Experimental. Support for every kind of query is pending
|
32
|
-
# @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
|
22
|
+
# This module is included if `config.active_record.encryption.extend_queries` is `true`.
|
33
23
|
module ExtendedDeterministicQueries
|
34
24
|
def self.install_support
|
25
|
+
# ActiveRecord::Base relies on ActiveRecord::Relation (ActiveRecord::QueryMethods) but it does
|
26
|
+
# some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
|
27
|
+
# as it's invoked (so that the proper prepared statement is cached).
|
35
28
|
ActiveRecord::Relation.prepend(RelationQueries)
|
36
29
|
ActiveRecord::Base.include(CoreQueries)
|
37
30
|
ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType)
|
38
|
-
Arel::Nodes::HomogeneousIn.prepend(InWithAdditionalValues)
|
39
31
|
end
|
40
32
|
|
41
|
-
|
42
|
-
|
33
|
+
# When modifying this file run performance tests in
|
34
|
+
# +activerecord/test/cases/encryption/performance/extended_deterministic_queries_performance_test.rb+
|
35
|
+
# to make sure performance overhead is acceptable.
|
36
|
+
#
|
37
|
+
# @TODO We will extend this to support previous "encryption context" versions in future iterations
|
38
|
+
# @TODO Experimental. Support for every kind of query is pending
|
39
|
+
# @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
|
40
|
+
|
41
|
+
module EncryptedQuery # :nodoc:
|
42
|
+
class << self
|
43
|
+
def process_arguments(owner, args, check_for_additional_values)
|
44
|
+
return args if owner.deterministic_encrypted_attributes&.empty?
|
43
45
|
|
44
|
-
private
|
45
|
-
def process_encrypted_query_arguments(args, check_for_additional_values)
|
46
46
|
if args.is_a?(Array) && (options = args.first).is_a?(Hash)
|
47
|
-
|
48
|
-
|
47
|
+
options = options.transform_keys do |key|
|
48
|
+
if key.is_a?(Array)
|
49
|
+
key.map(&:to_s)
|
50
|
+
else
|
51
|
+
key.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
args[0] = options
|
55
|
+
|
56
|
+
owner.deterministic_encrypted_attributes&.each do |attribute_name|
|
57
|
+
attribute_name = attribute_name.to_s
|
58
|
+
type = owner.type_for_attribute(attribute_name)
|
49
59
|
if !type.previous_types.empty? && value = options[attribute_name]
|
50
60
|
options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
|
51
61
|
end
|
52
62
|
end
|
53
63
|
end
|
54
|
-
end
|
55
64
|
|
56
|
-
|
57
|
-
|
65
|
+
args
|
66
|
+
end
|
58
67
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
68
|
+
private
|
69
|
+
def process_encrypted_query_argument(value, check_for_additional_values, type)
|
70
|
+
return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
|
71
|
+
|
72
|
+
case value
|
73
|
+
when String, Array
|
74
|
+
list = Array(value)
|
75
|
+
list + list.flat_map do |each_value|
|
76
|
+
if check_for_additional_values && each_value.is_a?(AdditionalValue)
|
77
|
+
each_value
|
78
|
+
else
|
79
|
+
additional_values_for(each_value, type)
|
80
|
+
end
|
67
81
|
end
|
82
|
+
else
|
83
|
+
value
|
68
84
|
end
|
69
|
-
else
|
70
|
-
value
|
71
85
|
end
|
72
|
-
end
|
73
86
|
|
74
|
-
|
75
|
-
|
76
|
-
|
87
|
+
def additional_values_for(value, type)
|
88
|
+
type.previous_types.collect do |additional_type|
|
89
|
+
AdditionalValue.new(value, additional_type)
|
90
|
+
end
|
77
91
|
end
|
78
|
-
|
92
|
+
end
|
79
93
|
end
|
80
94
|
|
81
95
|
module RelationQueries
|
82
|
-
include EncryptedQueryArgumentProcessor
|
83
|
-
|
84
96
|
def where(*args)
|
85
|
-
|
86
|
-
super
|
97
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
87
98
|
end
|
88
99
|
|
89
100
|
def exists?(*args)
|
90
|
-
|
91
|
-
super
|
101
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
92
102
|
end
|
93
103
|
|
94
|
-
def
|
95
|
-
|
96
|
-
end
|
104
|
+
def scope_for_create
|
105
|
+
return super unless klass.deterministic_encrypted_attributes&.any?
|
97
106
|
|
98
|
-
|
99
|
-
|
100
|
-
end
|
107
|
+
scope_attributes = super
|
108
|
+
wheres = where_values_hash
|
101
109
|
|
102
|
-
|
103
|
-
|
104
|
-
|
110
|
+
klass.deterministic_encrypted_attributes.each do |attribute_name|
|
111
|
+
attribute_name = attribute_name.to_s
|
112
|
+
values = wheres[attribute_name]
|
113
|
+
if values.is_a?(Array) && values[1..].all?(AdditionalValue)
|
114
|
+
scope_attributes[attribute_name] = values.first
|
115
|
+
end
|
105
116
|
end
|
117
|
+
|
118
|
+
scope_attributes
|
119
|
+
end
|
106
120
|
end
|
107
121
|
|
108
122
|
module CoreQueries
|
109
123
|
extend ActiveSupport::Concern
|
110
124
|
|
111
125
|
class_methods do
|
112
|
-
include EncryptedQueryArgumentProcessor
|
113
|
-
|
114
126
|
def find_by(*args)
|
115
|
-
|
116
|
-
super
|
127
|
+
super(*EncryptedQuery.process_arguments(self, args, false))
|
117
128
|
end
|
118
129
|
end
|
119
130
|
end
|
@@ -141,20 +152,6 @@ module ActiveRecord
|
|
141
152
|
end
|
142
153
|
end
|
143
154
|
end
|
144
|
-
|
145
|
-
module InWithAdditionalValues
|
146
|
-
def proc_for_binds
|
147
|
-
-> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, encryption_aware_type_caster) }
|
148
|
-
end
|
149
|
-
|
150
|
-
def encryption_aware_type_caster
|
151
|
-
if attribute.type_caster.is_a?(ActiveRecord::Encryption::EncryptedAttributeType)
|
152
|
-
attribute.type_caster.cast_type
|
153
|
-
else
|
154
|
-
attribute.type_caster
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
155
|
end
|
159
156
|
end
|
160
157
|
end
|
@@ -12,9 +12,9 @@ module ActiveRecord
|
|
12
12
|
super(record, attribute, value)
|
13
13
|
|
14
14
|
klass = record.class
|
15
|
-
klass.deterministic_encrypted_attributes&.
|
16
|
-
encrypted_type = klass.type_for_attribute(
|
17
|
-
|
15
|
+
if klass.deterministic_encrypted_attributes&.include?(attribute)
|
16
|
+
encrypted_type = klass.type_for_attribute(attribute)
|
17
|
+
encrypted_type.previous_types.each do |type|
|
18
18
|
encrypted_value = type.serialize(value)
|
19
19
|
ActiveRecord::Encryption.without_encryption do
|
20
20
|
super(record, attribute, encrypted_value)
|
@@ -6,6 +6,12 @@ module ActiveRecord
|
|
6
6
|
module Encryption
|
7
7
|
# Utility for generating and deriving random keys.
|
8
8
|
class KeyGenerator
|
9
|
+
attr_reader :hash_digest_class
|
10
|
+
|
11
|
+
def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class)
|
12
|
+
@hash_digest_class = hash_digest_class
|
13
|
+
end
|
14
|
+
|
9
15
|
# Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default)
|
10
16
|
def generate_random_key(length: key_length)
|
11
17
|
SecureRandom.random_bytes(length)
|
@@ -30,10 +36,15 @@ module ActiveRecord
|
|
30
36
|
#
|
31
37
|
# The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+
|
32
38
|
def derive_key_from(password, length: key_length)
|
33
|
-
ActiveSupport::KeyGenerator.new(password
|
39
|
+
ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class)
|
40
|
+
.generate_key(key_derivation_salt, length)
|
34
41
|
end
|
35
42
|
|
36
43
|
private
|
44
|
+
def key_derivation_salt
|
45
|
+
@key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt
|
46
|
+
end
|
47
|
+
|
37
48
|
def key_length
|
38
49
|
@key_length ||= ActiveRecord::Encryption.cipher.key_length
|
39
50
|
end
|
@@ -12,7 +12,7 @@ module ActiveRecord
|
|
12
12
|
@keys = Array(keys)
|
13
13
|
end
|
14
14
|
|
15
|
-
# Returns the
|
15
|
+
# Returns the last key in the list as the active key to perform encryptions
|
16
16
|
#
|
17
17
|
# When +ActiveRecord::Encryption.config.store_key_references+ is true, the key will include
|
18
18
|
# a public tag referencing the key itself. That key will be stored in the public
|
@@ -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
|