activerecord 7.0.8.7 → 7.1.0.beta1
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 +1339 -1572
- data/MIT-LICENSE +1 -1
- data/README.rdoc +15 -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 +18 -3
- 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 +17 -9
- data/lib/active_record/associations/collection_proxy.rb +16 -11
- 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.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader.rb +12 -9
- 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 +193 -97
- 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 +40 -26
- 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 +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- 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 +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- 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 +289 -122
- data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
- 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 +17 -12
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- 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 +1 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -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 +42 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
- 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 +42 -36
- 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 +162 -77
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +71 -94
- data/lib/active_record/core.rb +128 -138
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- 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 +8 -3
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- 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 +36 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
- 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 +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -26
- data/lib/active_record/errors.rb +89 -15
- 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 +119 -71
- data/lib/active_record/future_result.rb +30 -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 +55 -8
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +5 -7
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +100 -4
- data/lib/active_record/migration/compatibility.rb +131 -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.rb +213 -109
- data/lib/active_record/model_schema.rb +47 -27
- data/lib/active_record/nested_attributes.rb +28 -3
- data/lib/active_record/normalization.rb +158 -0
- data/lib/active_record/persistence.rb +183 -33
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- 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 +10 -5
- data/lib/active_record/railties/databases.rake +139 -145
- 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 +169 -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 +152 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- data/lib/active_record/relation/finder_methods.rb +85 -15
- 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/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 +351 -62
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +76 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +41 -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 +127 -105
- 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 +14 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +26 -14
- 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 +121 -16
- 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 +0 -8
- 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/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- 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 +52 -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
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,16 @@ module ActiveRecord
|
|
139
144
|
|
140
145
|
# Returns whether a given attribute is encrypted or not.
|
141
146
|
def encrypted_attribute?(attribute_name)
|
142
|
-
ActiveRecord::Encryption.encryptor.encrypted?
|
147
|
+
ActiveRecord::Encryption.encryptor.encrypted? read_attribute_before_type_cast(attribute_name)
|
143
148
|
end
|
144
149
|
|
145
150
|
# Returns the ciphertext for +attribute_name+.
|
146
151
|
def ciphertext_for(attribute_name)
|
147
|
-
|
152
|
+
if encrypted_attribute?(attribute_name)
|
153
|
+
read_attribute_before_type_cast(attribute_name)
|
154
|
+
else
|
155
|
+
read_attribute_for_database(attribute_name)
|
156
|
+
end
|
148
157
|
end
|
149
158
|
|
150
159
|
# Encrypts all the encryptable attributes and saves the changes.
|
@@ -160,6 +169,15 @@ module ActiveRecord
|
|
160
169
|
private
|
161
170
|
ORIGINAL_ATTRIBUTE_PREFIX = "original_"
|
162
171
|
|
172
|
+
def _create_record(attribute_names = self.attribute_names)
|
173
|
+
if has_encrypted_attributes?
|
174
|
+
# Always persist encrypted attributes, because an attribute might be
|
175
|
+
# encrypting a column default value.
|
176
|
+
attribute_names |= self.class.encrypted_attributes.map(&:to_s)
|
177
|
+
end
|
178
|
+
super
|
179
|
+
end
|
180
|
+
|
163
181
|
def encrypt_attributes
|
164
182
|
validate_encryption_allowed
|
165
183
|
|
@@ -188,12 +206,12 @@ module ActiveRecord
|
|
188
206
|
end
|
189
207
|
|
190
208
|
def build_decrypt_attribute_assignments
|
191
|
-
Array(self.class.encrypted_attributes).
|
209
|
+
Array(self.class.encrypted_attributes).to_h do |attribute_name|
|
192
210
|
type = type_for_attribute(attribute_name)
|
193
211
|
encrypted_value = ciphertext_for(attribute_name)
|
194
212
|
new_value = type.deserialize(encrypted_value)
|
195
213
|
[attribute_name, new_value]
|
196
|
-
end
|
214
|
+
end
|
197
215
|
end
|
198
216
|
|
199
217
|
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)
|
@@ -49,6 +54,10 @@ module ActiveRecord
|
|
49
54
|
@previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
|
50
55
|
end
|
51
56
|
|
57
|
+
def support_unencrypted_data?
|
58
|
+
ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
|
59
|
+
end
|
60
|
+
|
52
61
|
private
|
53
62
|
def previous_schemes_including_clean_text
|
54
63
|
previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
|
@@ -70,7 +79,13 @@ module ActiveRecord
|
|
70
79
|
|
71
80
|
def decrypt(value)
|
72
81
|
with_context do
|
73
|
-
|
82
|
+
unless value.nil?
|
83
|
+
if @default && @default == value
|
84
|
+
value
|
85
|
+
else
|
86
|
+
encryptor.decrypt(value, **decryption_options)
|
87
|
+
end
|
88
|
+
end
|
74
89
|
end
|
75
90
|
rescue ActiveRecord::Encryption::Errors::Base => error
|
76
91
|
if previous_types_without_clean_text.blank?
|
@@ -120,10 +135,6 @@ module ActiveRecord
|
|
120
135
|
ActiveRecord::Encryption.encryptor
|
121
136
|
end
|
122
137
|
|
123
|
-
def support_unencrypted_data?
|
124
|
-
ActiveRecord::Encryption.config.support_unencrypted_data && !previous_type?
|
125
|
-
end
|
126
|
-
|
127
138
|
def encryption_options
|
128
139
|
@encryption_options ||= { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
|
129
140
|
end
|
@@ -19,101 +19,113 @@ 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
31
|
Arel::Nodes::HomogeneousIn.prepend(InWithAdditionalValues)
|
39
32
|
end
|
40
33
|
|
41
|
-
|
42
|
-
|
34
|
+
# When modifying this file run performance tests in
|
35
|
+
# +activerecord/test/cases/encryption/performance/extended_deterministic_queries_performance_test.rb+
|
36
|
+
# to make sure performance overhead is acceptable.
|
37
|
+
#
|
38
|
+
# @TODO We will extend this to support previous "encryption context" versions in future iterations
|
39
|
+
# @TODO Experimental. Support for every kind of query is pending
|
40
|
+
# @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
|
41
|
+
|
42
|
+
module EncryptedQuery # :nodoc:
|
43
|
+
class << self
|
44
|
+
def process_arguments(owner, args, check_for_additional_values)
|
45
|
+
return args if owner.deterministic_encrypted_attributes&.empty?
|
43
46
|
|
44
|
-
private
|
45
|
-
def process_encrypted_query_arguments(args, check_for_additional_values)
|
46
47
|
if args.is_a?(Array) && (options = args.first).is_a?(Hash)
|
47
|
-
|
48
|
-
|
48
|
+
options = options.transform_keys do |key|
|
49
|
+
if key.is_a?(Array)
|
50
|
+
key.map(&:to_s)
|
51
|
+
else
|
52
|
+
key.to_s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
args[0] = options
|
56
|
+
|
57
|
+
owner.deterministic_encrypted_attributes&.each do |attribute_name|
|
58
|
+
attribute_name = attribute_name.to_s
|
59
|
+
type = owner.type_for_attribute(attribute_name)
|
49
60
|
if !type.previous_types.empty? && value = options[attribute_name]
|
50
61
|
options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
|
51
62
|
end
|
52
63
|
end
|
53
64
|
end
|
54
|
-
end
|
55
65
|
|
56
|
-
|
57
|
-
|
66
|
+
args
|
67
|
+
end
|
58
68
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
private
|
70
|
+
def process_encrypted_query_argument(value, check_for_additional_values, type)
|
71
|
+
return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
|
72
|
+
|
73
|
+
case value
|
74
|
+
when String, Array
|
75
|
+
list = Array(value)
|
76
|
+
list + list.flat_map do |each_value|
|
77
|
+
if check_for_additional_values && each_value.is_a?(AdditionalValue)
|
78
|
+
each_value
|
79
|
+
else
|
80
|
+
additional_values_for(each_value, type)
|
81
|
+
end
|
67
82
|
end
|
83
|
+
else
|
84
|
+
value
|
68
85
|
end
|
69
|
-
else
|
70
|
-
value
|
71
86
|
end
|
72
|
-
end
|
73
87
|
|
74
|
-
|
75
|
-
|
76
|
-
|
88
|
+
def additional_values_for(value, type)
|
89
|
+
type.previous_types.collect do |additional_type|
|
90
|
+
AdditionalValue.new(value, additional_type)
|
91
|
+
end
|
77
92
|
end
|
78
|
-
|
93
|
+
end
|
79
94
|
end
|
80
95
|
|
81
96
|
module RelationQueries
|
82
|
-
include EncryptedQueryArgumentProcessor
|
83
|
-
|
84
97
|
def where(*args)
|
85
|
-
|
86
|
-
super
|
98
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
87
99
|
end
|
88
100
|
|
89
101
|
def exists?(*args)
|
90
|
-
|
91
|
-
super
|
102
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
92
103
|
end
|
93
104
|
|
94
|
-
def
|
95
|
-
|
96
|
-
end
|
105
|
+
def scope_for_create
|
106
|
+
return super unless klass.deterministic_encrypted_attributes&.any?
|
97
107
|
|
98
|
-
|
99
|
-
|
100
|
-
end
|
108
|
+
scope_attributes = super
|
109
|
+
wheres = where_values_hash
|
101
110
|
|
102
|
-
|
103
|
-
|
104
|
-
|
111
|
+
klass.deterministic_encrypted_attributes.each do |attribute_name|
|
112
|
+
attribute_name = attribute_name.to_s
|
113
|
+
values = wheres[attribute_name]
|
114
|
+
if values.is_a?(Array) && values[1..].all?(AdditionalValue)
|
115
|
+
scope_attributes[attribute_name] = values.first
|
116
|
+
end
|
105
117
|
end
|
118
|
+
|
119
|
+
scope_attributes
|
120
|
+
end
|
106
121
|
end
|
107
122
|
|
108
123
|
module CoreQueries
|
109
124
|
extend ActiveSupport::Concern
|
110
125
|
|
111
126
|
class_methods do
|
112
|
-
include EncryptedQueryArgumentProcessor
|
113
|
-
|
114
127
|
def find_by(*args)
|
115
|
-
|
116
|
-
super
|
128
|
+
super(*EncryptedQuery.process_arguments(self, args, false))
|
117
129
|
end
|
118
130
|
end
|
119
131
|
end
|
@@ -12,8 +12,8 @@ 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(
|
15
|
+
if klass.deterministic_encrypted_attributes&.include?(attribute)
|
16
|
+
encrypted_type = klass.type_for_attribute(attribute)
|
17
17
|
[ encrypted_type, *encrypted_type.previous_types ].each do |type|
|
18
18
|
encrypted_value = type.serialize(value)
|
19
19
|
ActiveRecord::Encryption.without_encryption do
|
@@ -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
|
@@ -14,10 +14,10 @@ module ActiveRecord
|
|
14
14
|
#
|
15
15
|
# See +Properties::DEFAULT_PROPERTIES+, Key, Message
|
16
16
|
class Properties
|
17
|
-
ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
|
17
|
+
ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
|
18
18
|
|
19
19
|
delegate_missing_to :data
|
20
|
-
delegate :==, to: :data
|
20
|
+
delegate :==, :[], :each, :key?, to: :data
|
21
21
|
|
22
22
|
# For each entry it generates an accessor exposing the full name
|
23
23
|
DEFAULT_PROPERTIES = {
|
@@ -54,7 +54,7 @@ module ActiveRecord
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def validate_value_type(value)
|
57
|
-
unless ALLOWED_VALUE_CLASSES.
|
57
|
+
unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
|
58
58
|
raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
|
59
59
|
end
|
60
60
|
end
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
class Scheme
|
11
11
|
attr_accessor :previous_schemes
|
12
12
|
|
13
|
-
def initialize(key_provider: nil, key: nil, deterministic: nil, downcase: nil, ignore_case: nil,
|
13
|
+
def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
|
14
14
|
previous_schemes: nil, **context_properties)
|
15
15
|
# Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
|
16
16
|
# can merge schemes without overriding values with defaults. See +#merge+
|
@@ -18,6 +18,7 @@ module ActiveRecord
|
|
18
18
|
@key_provider_param = key_provider
|
19
19
|
@key = key
|
20
20
|
@deterministic = deterministic
|
21
|
+
@support_unencrypted_data = support_unencrypted_data
|
21
22
|
@downcase = downcase || ignore_case
|
22
23
|
@ignore_case = ignore_case
|
23
24
|
@previous_schemes_param = previous_schemes
|
@@ -36,7 +37,11 @@ module ActiveRecord
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def deterministic?
|
39
|
-
|
40
|
+
!!@deterministic
|
41
|
+
end
|
42
|
+
|
43
|
+
def support_unencrypted_data?
|
44
|
+
@support_unencrypted_data.nil? ? ActiveRecord::Encryption.config.support_unencrypted_data : @support_unencrypted_data
|
40
45
|
end
|
41
46
|
|
42
47
|
def fixed?
|
@@ -45,10 +50,7 @@ module ActiveRecord
|
|
45
50
|
end
|
46
51
|
|
47
52
|
def key_provider
|
48
|
-
@key_provider ||=
|
49
|
-
validate_keys!
|
50
|
-
@key_provider_param || build_key_provider
|
51
|
-
end
|
53
|
+
@key_provider ||= @key_provider_param || build_key_provider || default_key_provider
|
52
54
|
end
|
53
55
|
|
54
56
|
def merge(other_scheme)
|
@@ -56,7 +58,7 @@ module ActiveRecord
|
|
56
58
|
end
|
57
59
|
|
58
60
|
def to_h
|
59
|
-
{ key_provider: @key_provider_param,
|
61
|
+
{ key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
|
60
62
|
previous_schemes: @previous_schemes_param, **@context_properties }.compact
|
61
63
|
end
|
62
64
|
|
@@ -68,32 +70,27 @@ module ActiveRecord
|
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
73
|
+
def compatible_with?(other_scheme)
|
74
|
+
deterministic? == other_scheme.deterministic?
|
75
|
+
end
|
76
|
+
|
71
77
|
private
|
72
78
|
def validate_config!
|
73
79
|
raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
|
74
80
|
raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
|
75
81
|
end
|
76
82
|
|
77
|
-
def validate_keys!
|
78
|
-
validate_credential :key_derivation_salt
|
79
|
-
validate_credential :primary_key, "needs to be configured to use non-deterministic encryption" unless @deterministic
|
80
|
-
validate_credential :deterministic_key, "needs to be configured to use deterministic encryption" if @deterministic
|
81
|
-
end
|
82
|
-
|
83
|
-
def validate_credential(key, error_message = "is not configured")
|
84
|
-
unless ActiveRecord::Encryption.config.public_send(key).present?
|
85
|
-
raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential "\
|
86
|
-
"active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
83
|
def build_key_provider
|
91
84
|
return DerivedSecretKeyProvider.new(@key) if @key.present?
|
92
85
|
|
93
|
-
if @deterministic
|
94
|
-
DeterministicKeyProvider.new(deterministic_key)
|
86
|
+
if @deterministic
|
87
|
+
DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
|
95
88
|
end
|
96
89
|
end
|
90
|
+
|
91
|
+
def default_key_provider
|
92
|
+
ActiveRecord::Encryption.key_provider
|
93
|
+
end
|
97
94
|
end
|
98
95
|
end
|
99
96
|
end
|