activerecord 7.0.8.7 → 7.1.5.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 +1795 -1424
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +19 -13
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +319 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +145 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- 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 +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- 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 +296 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
- 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 +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- 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 +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- 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 +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- 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/quoting.rb +10 -6
- 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 +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
- 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 +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +15 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- 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 +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- 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/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- 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 +135 -71
- data/lib/active_record/future_result.rb +40 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- 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 +104 -5
- data/lib/active_record/migration/compatibility.rb +145 -5
- data/lib/active_record/migration/default_strategy.rb +23 -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 +219 -111
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +144 -150
- 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 +181 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- 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 +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +371 -68
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -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 +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +152 -108
- 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 +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- 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/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- 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 +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +122 -17
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -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/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- 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 +46 -10
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
class AutoFilteredParameters
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
@attributes_by_class = Concurrent::Map.new
|
9
|
+
@collecting = true
|
10
|
+
|
11
|
+
install_collecting_hook
|
12
|
+
end
|
13
|
+
|
14
|
+
def enable
|
15
|
+
apply_collected_attributes
|
16
|
+
@collecting = false
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
attr_reader :app
|
21
|
+
|
22
|
+
def install_collecting_hook
|
23
|
+
ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute|
|
24
|
+
attribute_was_declared(klass, attribute)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def attribute_was_declared(klass, attribute)
|
29
|
+
if collecting?
|
30
|
+
collect_for_later(klass, attribute)
|
31
|
+
else
|
32
|
+
apply_filter(klass, attribute)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply_collected_attributes
|
37
|
+
@attributes_by_class.each do |klass, attributes|
|
38
|
+
attributes.each do |attribute|
|
39
|
+
apply_filter(klass, attribute)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def collecting?
|
45
|
+
@collecting
|
46
|
+
end
|
47
|
+
|
48
|
+
def collect_for_later(klass, attribute)
|
49
|
+
@attributes_by_class[klass] ||= Concurrent::Array.new
|
50
|
+
@attributes_by_class[klass] << attribute
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply_filter(klass, attribute)
|
54
|
+
filter = [("#{klass.model_name.element}" if klass.name), attribute.to_s].compact.join(".")
|
55
|
+
unless excluded_from_filter_parameters?(filter)
|
56
|
+
app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
|
57
|
+
klass.filter_attributes += [ attribute ]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def excluded_from_filter_parameters?(filter_parameter)
|
62
|
+
ActiveRecord::Encryption.config.excluded_from_filter_parameters.find { |excluded_filter| excluded_filter.to_s == filter_parameter }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "openssl"
|
4
|
-
require "base64"
|
5
4
|
|
6
5
|
module ActiveRecord
|
7
6
|
module Encryption
|
@@ -80,6 +79,10 @@ module ActiveRecord
|
|
80
79
|
raise ActiveRecord::Encryption::Errors::Decryption
|
81
80
|
end
|
82
81
|
|
82
|
+
def inspect # :nodoc:
|
83
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
84
|
+
end
|
85
|
+
|
83
86
|
private
|
84
87
|
def generate_iv(cipher, clear_text)
|
85
88
|
if @deterministic
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "openssl"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module Encryption
|
5
7
|
# Container of configuration options
|
6
8
|
class Config
|
7
|
-
attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt,
|
9
|
+
attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class,
|
8
10
|
:support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
|
9
11
|
:excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
|
10
12
|
|
@@ -21,6 +23,27 @@ module ActiveRecord
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
26
|
+
def support_sha1_for_non_deterministic_encryption=(value)
|
27
|
+
if value && has_primary_key?
|
28
|
+
sha1_key_generator = ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: OpenSSL::Digest::SHA1)
|
29
|
+
sha1_key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key, key_generator: sha1_key_generator)
|
30
|
+
add_previous_scheme key_provider: sha1_key_provider
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
%w(key_derivation_salt primary_key deterministic_key).each do |key|
|
35
|
+
silence_redefinition_of_method "has_#{key}?"
|
36
|
+
define_method("has_#{key}?") do
|
37
|
+
instance_variable_get(:"@#{key}").presence
|
38
|
+
end
|
39
|
+
|
40
|
+
silence_redefinition_of_method key
|
41
|
+
define_method(key) do
|
42
|
+
public_send("has_#{key}?") or
|
43
|
+
raise Errors::Configuration, "Missing Active Record encryption credential: active_record_encryption.#{key}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
24
47
|
private
|
25
48
|
def set_defaults
|
26
49
|
self.store_key_references = false
|
@@ -31,6 +54,7 @@ module ActiveRecord
|
|
31
54
|
self.excluded_from_filter_parameters = []
|
32
55
|
self.previous_schemes = []
|
33
56
|
self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
|
57
|
+
self.hash_digest_class = OpenSSL::Digest::SHA1
|
34
58
|
|
35
59
|
# TODO: Setting to false for now as the implementation is a bit experimental
|
36
60
|
self.extend_queries = false
|
@@ -17,24 +17,29 @@ module ActiveRecord
|
|
17
17
|
delegate name, to: :context
|
18
18
|
end
|
19
19
|
|
20
|
-
def configure(primary_key
|
20
|
+
def configure(primary_key: nil, deterministic_key: nil, key_derivation_salt: nil, **properties) # :nodoc:
|
21
21
|
config.primary_key = primary_key
|
22
22
|
config.deterministic_key = deterministic_key
|
23
23
|
config.key_derivation_salt = key_derivation_salt
|
24
24
|
|
25
|
-
|
25
|
+
# Set the default for this property here instead of in +Config#set_defaults+ as this needs
|
26
|
+
# to happen *after* the keys have been set.
|
27
|
+
properties[:support_sha1_for_non_deterministic_encryption] = true if properties[:support_sha1_for_non_deterministic_encryption].nil?
|
26
28
|
|
27
29
|
properties.each do |name, value|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
ActiveRecord::Encryption.config.send "#{name}=", value if ActiveRecord::Encryption.config.respond_to?("#{name}=")
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::Encryption.reset_default_context
|
34
|
+
|
35
|
+
properties.each do |name, value|
|
36
|
+
ActiveRecord::Encryption.context.send "#{name}=", value if ActiveRecord::Encryption.context.respond_to?("#{name}=")
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
35
40
|
# Register callback to be invoked when an encrypted attribute is declared.
|
36
41
|
#
|
37
|
-
# === Example
|
42
|
+
# === Example
|
38
43
|
#
|
39
44
|
# ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_name|
|
40
45
|
# ...
|
@@ -49,18 +54,6 @@ module ActiveRecord
|
|
49
54
|
block.call(klass, name)
|
50
55
|
end
|
51
56
|
end
|
52
|
-
|
53
|
-
def install_auto_filtered_parameters_hook(application) # :nodoc:
|
54
|
-
ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name|
|
55
|
-
filter_parameter = [("#{klass.model_name.element}" if klass.name), encrypted_attribute_name.to_s].compact.join(".")
|
56
|
-
application.config.filter_parameters << filter_parameter unless excluded_from_filter_parameters?(filter_parameter)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
def excluded_from_filter_parameters?(filter_parameter)
|
62
|
-
ActiveRecord::Encryption.config.excluded_from_filter_parameters.find { |excluded_filter| excluded_filter.to_s == filter_parameter }
|
63
|
-
end
|
64
57
|
end
|
65
58
|
end
|
66
59
|
end
|
@@ -12,9 +12,7 @@ module ActiveRecord
|
|
12
12
|
class Context
|
13
13
|
PROPERTIES = %i[ key_provider key_generator cipher message_serializer encryptor frozen_encryption ]
|
14
14
|
|
15
|
-
PROPERTIES
|
16
|
-
attr_accessor name
|
17
|
-
end
|
15
|
+
attr_accessor(*PROPERTIES)
|
18
16
|
|
19
17
|
def initialize
|
20
18
|
set_defaults
|
@@ -22,6 +20,11 @@ module ActiveRecord
|
|
22
20
|
|
23
21
|
alias frozen_encryption? frozen_encryption
|
24
22
|
|
23
|
+
silence_redefinition_of_method :key_provider
|
24
|
+
def key_provider
|
25
|
+
@key_provider ||= build_default_key_provider
|
26
|
+
end
|
27
|
+
|
25
28
|
private
|
26
29
|
def set_defaults
|
27
30
|
self.frozen_encryption = false
|
@@ -30,6 +33,10 @@ module ActiveRecord
|
|
30
33
|
self.encryptor = ActiveRecord::Encryption::Encryptor.new
|
31
34
|
self.message_serializer = ActiveRecord::Encryption::MessageSerializer.new
|
32
35
|
end
|
36
|
+
|
37
|
+
def build_default_key_provider
|
38
|
+
ActiveRecord::Encryption::DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key)
|
39
|
+
end
|
33
40
|
end
|
34
41
|
end
|
35
42
|
end
|
@@ -14,7 +14,7 @@ module ActiveRecord
|
|
14
14
|
extend ActiveSupport::Concern
|
15
15
|
|
16
16
|
included do
|
17
|
-
|
17
|
+
mattr_accessor :default_context, default: Context.new
|
18
18
|
thread_mattr_accessor :custom_contexts
|
19
19
|
end
|
20
20
|
|
@@ -66,6 +66,10 @@ module ActiveRecord
|
|
66
66
|
def current_custom_context
|
67
67
|
self.custom_contexts&.last
|
68
68
|
end
|
69
|
+
|
70
|
+
def reset_default_context
|
71
|
+
self.default_context = Context.new
|
72
|
+
end
|
69
73
|
end
|
70
74
|
end
|
71
75
|
end
|
@@ -4,9 +4,15 @@ module ActiveRecord
|
|
4
4
|
module Encryption
|
5
5
|
# A KeyProvider that derives keys from passwords.
|
6
6
|
class DerivedSecretKeyProvider < KeyProvider
|
7
|
-
def initialize(passwords)
|
8
|
-
super(Array(passwords).collect { |password|
|
7
|
+
def initialize(passwords, key_generator: ActiveRecord::Encryption.key_generator)
|
8
|
+
super(Array(passwords).collect { |password| derive_key_from(password, using: key_generator) })
|
9
9
|
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def derive_key_from(password, using: key_generator)
|
13
|
+
secret = using.derive_key_from(password)
|
14
|
+
ActiveRecord::Encryption::Key.new(secret)
|
15
|
+
end
|
10
16
|
end
|
11
17
|
end
|
12
18
|
end
|
@@ -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
87
|
attribute name do |cast_type|
|
86
|
-
|
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
|
|
@@ -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,12 +212,12 @@ 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
|
@@ -20,11 +20,16 @@ module ActiveRecord
|
|
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
|
@@ -70,7 +83,13 @@ module ActiveRecord
|
|
70
83
|
|
71
84
|
def decrypt(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?
|
@@ -120,16 +139,12 @@ module ActiveRecord
|
|
120
139
|
ActiveRecord::Encryption.encryptor
|
121
140
|
end
|
122
141
|
|
123
|
-
def support_unencrypted_data?
|
124
|
-
ActiveRecord::Encryption.config.support_unencrypted_data && !previous_type?
|
125
|
-
end
|
126
|
-
|
127
142
|
def encryption_options
|
128
|
-
|
143
|
+
{ key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
|
129
144
|
end
|
130
145
|
|
131
146
|
def decryption_options
|
132
|
-
|
147
|
+
{ key_provider: key_provider }.compact
|
133
148
|
end
|
134
149
|
|
135
150
|
def clean_text_scheme
|
@@ -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)
|