activerecord 6.1.7 → 7.2.2
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 +616 -1290
- 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 +19 -8
- 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 +30 -27
- data/lib/active_record/associations/join_dependency.rb +28 -20
- 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 +429 -522
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -5
- 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 +15 -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 +57 -54
- data/lib/active_record/autosave_association.rb +74 -57
- data/lib/active_record/base.rb +27 -5
- data/lib/active_record/callbacks.rb +19 -35
- 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 +325 -604
- 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 +230 -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 +378 -143
- 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 +348 -165
- 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 +403 -77
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
- 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 +310 -253
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
- 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 +58 -0
- data/lib/active_record/enum.rb +170 -62
- 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 +59 -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 +145 -158
- data/lib/active_record/nested_attributes.rb +61 -23
- 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 +18 -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 +229 -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 +332 -103
- data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
- data/lib/active_record/relation/batches.rb +200 -65
- data/lib/active_record/relation/calculations.rb +301 -112
- 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 +870 -163
- 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 +6 -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 +288 -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 +65 -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/sqlite.rb +25 -0
- 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 +103 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,175 @@
|
|
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 < ::ActiveModel::Type::Value
|
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, :type, 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 encrypted?(value)
|
48
|
+
with_context { encryptor.encrypted? value }
|
49
|
+
end
|
50
|
+
|
51
|
+
def changed_in_place?(raw_old_value, new_value)
|
52
|
+
old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
|
53
|
+
old_value != new_value
|
54
|
+
end
|
55
|
+
|
56
|
+
def previous_types # :nodoc:
|
57
|
+
@previous_types ||= {} # Memoizing on support_unencrypted_data so that we can tweak it during tests
|
58
|
+
@previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
|
59
|
+
end
|
60
|
+
|
61
|
+
def support_unencrypted_data?
|
62
|
+
ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def previous_schemes_including_clean_text
|
67
|
+
previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
|
68
|
+
end
|
69
|
+
|
70
|
+
def previous_types_without_clean_text
|
71
|
+
@previous_types_without_clean_text ||= build_previous_types_for(previous_schemes)
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_previous_types_for(schemes)
|
75
|
+
schemes.collect do |scheme|
|
76
|
+
EncryptedAttributeType.new(scheme: scheme, previous_type: true)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def previous_type?
|
81
|
+
@previous_type
|
82
|
+
end
|
83
|
+
|
84
|
+
def decrypt_as_text(value)
|
85
|
+
with_context do
|
86
|
+
unless value.nil?
|
87
|
+
if @default && @default == value
|
88
|
+
value
|
89
|
+
else
|
90
|
+
encryptor.decrypt(value, **decryption_options)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
rescue ActiveRecord::Encryption::Errors::Base => error
|
95
|
+
if previous_types_without_clean_text.blank?
|
96
|
+
handle_deserialize_error(error, value)
|
97
|
+
else
|
98
|
+
try_to_deserialize_with_previous_encrypted_types(value)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def decrypt(value)
|
103
|
+
text_to_database_type decrypt_as_text(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
def try_to_deserialize_with_previous_encrypted_types(value)
|
107
|
+
previous_types.each.with_index do |type, index|
|
108
|
+
break type.deserialize(value)
|
109
|
+
rescue ActiveRecord::Encryption::Errors::Base => error
|
110
|
+
handle_deserialize_error(error, value) if index == previous_types.length - 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def handle_deserialize_error(error, value)
|
115
|
+
if error.is_a?(Errors::Decryption) && support_unencrypted_data?
|
116
|
+
value
|
117
|
+
else
|
118
|
+
raise error
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def serialize_with_oldest?
|
123
|
+
@serialize_with_oldest ||= fixed? && previous_types_without_clean_text.present?
|
124
|
+
end
|
125
|
+
|
126
|
+
def serialize_with_oldest(value)
|
127
|
+
previous_types.first.serialize(value)
|
128
|
+
end
|
129
|
+
|
130
|
+
def serialize_with_current(value)
|
131
|
+
casted_value = cast_type.serialize(value)
|
132
|
+
casted_value = casted_value&.downcase if downcase?
|
133
|
+
encrypt(casted_value.to_s) unless casted_value.nil?
|
134
|
+
end
|
135
|
+
|
136
|
+
def encrypt_as_text(value)
|
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
|
+
|
142
|
+
encryptor.encrypt(value, **encryption_options)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def encrypt(value)
|
147
|
+
text_to_database_type encrypt_as_text(value)
|
148
|
+
end
|
149
|
+
|
150
|
+
def encryptor
|
151
|
+
ActiveRecord::Encryption.encryptor
|
152
|
+
end
|
153
|
+
|
154
|
+
def encryption_options
|
155
|
+
{ key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
|
156
|
+
end
|
157
|
+
|
158
|
+
def decryption_options
|
159
|
+
{ key_provider: key_provider }.compact
|
160
|
+
end
|
161
|
+
|
162
|
+
def clean_text_scheme
|
163
|
+
@clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
|
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
|
173
|
+
end
|
174
|
+
end
|
175
|
+
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,170 @@
|
|
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
|
+
# === 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
|
+
|
23
|
+
# Encrypts +clean_text+ and returns the encrypted result
|
24
|
+
#
|
25
|
+
# Internally, it will:
|
26
|
+
#
|
27
|
+
# 1. Create a new ActiveRecord::Encryption::Message
|
28
|
+
# 2. Compress and encrypt +clean_text+ as the message payload
|
29
|
+
# 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
|
30
|
+
# by default)
|
31
|
+
# 4. Encode the result with Base 64
|
32
|
+
#
|
33
|
+
# === Options
|
34
|
+
#
|
35
|
+
# [:key_provider]
|
36
|
+
# Key provider to use for the encryption operation. It will default to
|
37
|
+
# +ActiveRecord::Encryption.key_provider+ when not provided.
|
38
|
+
#
|
39
|
+
# [:cipher_options]
|
40
|
+
# Cipher-specific options that will be passed to the Cipher configured in
|
41
|
+
# +ActiveRecord::Encryption.cipher+
|
42
|
+
def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
|
43
|
+
clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
|
44
|
+
|
45
|
+
validate_payload_type(clear_text)
|
46
|
+
serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Decrypts an +encrypted_text+ and returns the result as clean text
|
50
|
+
#
|
51
|
+
# === Options
|
52
|
+
#
|
53
|
+
# [:key_provider]
|
54
|
+
# Key provider to use for the encryption operation. It will default to
|
55
|
+
# +ActiveRecord::Encryption.key_provider+ when not provided
|
56
|
+
#
|
57
|
+
# [:cipher_options]
|
58
|
+
# Cipher-specific options that will be passed to the Cipher configured in
|
59
|
+
# +ActiveRecord::Encryption.cipher+
|
60
|
+
def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
|
61
|
+
message = deserialize_message(encrypted_text)
|
62
|
+
keys = key_provider.decryption_keys(message)
|
63
|
+
raise Errors::Decryption unless keys.present?
|
64
|
+
uncompress_if_needed(cipher.decrypt(message, key: keys.collect(&:secret), **cipher_options), message.headers.compressed)
|
65
|
+
rescue *(ENCODING_ERRORS + DECRYPT_ERRORS)
|
66
|
+
raise Errors::Decryption
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns whether the text is encrypted or not
|
70
|
+
def encrypted?(text)
|
71
|
+
deserialize_message(text)
|
72
|
+
true
|
73
|
+
rescue Errors::Encoding, *DECRYPT_ERRORS
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
def binary?
|
78
|
+
serializer.binary?
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
83
|
+
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
84
|
+
THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
|
85
|
+
|
86
|
+
def default_key_provider
|
87
|
+
ActiveRecord::Encryption.key_provider
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_payload_type(clear_text)
|
91
|
+
unless clear_text.is_a?(String)
|
92
|
+
raise ActiveRecord::Encryption::Errors::ForbiddenClass, "The encryptor can only encrypt string values (#{clear_text.class})"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def cipher
|
97
|
+
ActiveRecord::Encryption.cipher
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_encrypted_message(clear_text, key_provider:, cipher_options:)
|
101
|
+
key = key_provider.encryption_key
|
102
|
+
|
103
|
+
clear_text, was_compressed = compress_if_worth_it(clear_text)
|
104
|
+
cipher.encrypt(clear_text, key: key.secret, **cipher_options).tap do |message|
|
105
|
+
message.headers.add(key.public_tags)
|
106
|
+
message.headers.compressed = true if was_compressed
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def serialize_message(message)
|
111
|
+
serializer.dump(message)
|
112
|
+
end
|
113
|
+
|
114
|
+
def deserialize_message(message)
|
115
|
+
serializer.load message
|
116
|
+
rescue ArgumentError, TypeError, Errors::ForbiddenClass
|
117
|
+
raise Errors::Encoding
|
118
|
+
end
|
119
|
+
|
120
|
+
def serializer
|
121
|
+
ActiveRecord::Encryption.message_serializer
|
122
|
+
end
|
123
|
+
|
124
|
+
# Under certain threshold, ZIP compression is actually worse that not compressing
|
125
|
+
def compress_if_worth_it(string)
|
126
|
+
if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
127
|
+
[compress(string), true]
|
128
|
+
else
|
129
|
+
[string, false]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def compress?
|
134
|
+
@compress
|
135
|
+
end
|
136
|
+
|
137
|
+
def compress(data)
|
138
|
+
Zlib::Deflate.deflate(data).tap do |compressed_data|
|
139
|
+
compressed_data.force_encoding(data.encoding)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def uncompress_if_needed(data, compressed)
|
144
|
+
if compressed
|
145
|
+
uncompress(data)
|
146
|
+
else
|
147
|
+
data
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def uncompress(data)
|
152
|
+
Zlib::Inflate.inflate(data).tap do |uncompressed_data|
|
153
|
+
uncompressed_data.force_encoding(data.encoding)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def force_encoding_if_needed(value)
|
158
|
+
if forced_encoding_for_deterministic_encryption && value && value.encoding != forced_encoding_for_deterministic_encryption
|
159
|
+
value.encode(forced_encoding_for_deterministic_encryption, invalid: :replace, undef: :replace)
|
160
|
+
else
|
161
|
+
value
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def forced_encoding_for_deterministic_encryption
|
166
|
+
ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
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
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
|
6
|
+
#
|
7
|
+
# Active Record \Encryption supports querying the db using deterministic attributes. For example:
|
8
|
+
#
|
9
|
+
# Contact.find_by(email_address: "jorge@hey.com")
|
10
|
+
#
|
11
|
+
# The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
|
12
|
+
# a problem while the data is being encrypted. This won't work. During that time, you need these
|
13
|
+
# queries to be:
|
14
|
+
#
|
15
|
+
# Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
|
16
|
+
#
|
17
|
+
# This patches ActiveRecord to support this automatically. It addresses both:
|
18
|
+
#
|
19
|
+
# * ActiveRecord::Base - Used in <tt>Contact.find_by_email_address(...)</tt>
|
20
|
+
# * ActiveRecord::Relation - Used in <tt>Contact.internal.find_by_email_address(...)</tt>
|
21
|
+
#
|
22
|
+
# This module is included if `config.active_record.encryption.extend_queries` is `true`.
|
23
|
+
module ExtendedDeterministicQueries
|
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).
|
28
|
+
ActiveRecord::Relation.prepend(RelationQueries)
|
29
|
+
ActiveRecord::Base.include(CoreQueries)
|
30
|
+
ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType)
|
31
|
+
end
|
32
|
+
|
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?
|
45
|
+
|
46
|
+
if args.is_a?(Array) && (options = args.first).is_a?(Hash)
|
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)
|
59
|
+
if !type.previous_types.empty? && value = options[attribute_name]
|
60
|
+
options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
args
|
66
|
+
end
|
67
|
+
|
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
|
81
|
+
end
|
82
|
+
else
|
83
|
+
value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def additional_values_for(value, type)
|
88
|
+
type.previous_types.collect do |additional_type|
|
89
|
+
AdditionalValue.new(value, additional_type)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module RelationQueries
|
96
|
+
def where(*args)
|
97
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
98
|
+
end
|
99
|
+
|
100
|
+
def exists?(*args)
|
101
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
102
|
+
end
|
103
|
+
|
104
|
+
def scope_for_create
|
105
|
+
return super unless klass.deterministic_encrypted_attributes&.any?
|
106
|
+
|
107
|
+
scope_attributes = super
|
108
|
+
wheres = where_values_hash
|
109
|
+
|
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
|
116
|
+
end
|
117
|
+
|
118
|
+
scope_attributes
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
module CoreQueries
|
123
|
+
extend ActiveSupport::Concern
|
124
|
+
|
125
|
+
class_methods do
|
126
|
+
def find_by(*args)
|
127
|
+
super(*EncryptedQuery.process_arguments(self, args, false))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class AdditionalValue
|
133
|
+
attr_reader :value, :type
|
134
|
+
|
135
|
+
def initialize(value, type)
|
136
|
+
@type = type
|
137
|
+
@value = process(value)
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
def process(value)
|
142
|
+
type.serialize(value)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
module ExtendedEncryptableType
|
147
|
+
def serialize(data)
|
148
|
+
if data.is_a?(AdditionalValue)
|
149
|
+
data.value
|
150
|
+
else
|
151
|
+
super
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
module ExtendedDeterministicUniquenessValidator
|
6
|
+
def self.install_support
|
7
|
+
ActiveRecord::Validations::UniquenessValidator.prepend(EncryptedUniquenessValidator)
|
8
|
+
end
|
9
|
+
|
10
|
+
module EncryptedUniquenessValidator
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
super(record, attribute, value)
|
13
|
+
|
14
|
+
klass = record.class
|
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
|
+
encrypted_value = type.serialize(value)
|
19
|
+
ActiveRecord::Encryption.without_encryption do
|
20
|
+
super(record, attribute, encrypted_value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# A key is a container for a given +secret+
|
6
|
+
#
|
7
|
+
# Optionally, it can include +public_tags+. These tags are meant to be stored
|
8
|
+
# in clean (public) and can be used, for example, to include information that
|
9
|
+
# references the key for a future retrieval operation.
|
10
|
+
class Key
|
11
|
+
attr_reader :secret, :public_tags
|
12
|
+
|
13
|
+
def initialize(secret)
|
14
|
+
@secret = secret
|
15
|
+
@public_tags = Properties.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.derive_from(password)
|
19
|
+
secret = ActiveRecord::Encryption.key_generator.derive_key_from(password)
|
20
|
+
ActiveRecord::Encryption::Key.new(secret)
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
Digest::SHA1.hexdigest(secret).first(4)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|