omg-activerecord 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +355 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +219 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record/aggregations.rb +287 -0
- data/lib/active_record/association_relation.rb +50 -0
- data/lib/active_record/associations/alias_tracker.rb +90 -0
- data/lib/active_record/associations/association.rb +417 -0
- data/lib/active_record/associations/association_scope.rb +175 -0
- data/lib/active_record/associations/belongs_to_association.rb +163 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/builder/association.rb +170 -0
- data/lib/active_record/associations/builder/belongs_to.rb +160 -0
- data/lib/active_record/associations/builder/collection_association.rb +80 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
- data/lib/active_record/associations/builder/has_many.rb +23 -0
- data/lib/active_record/associations/builder/has_one.rb +61 -0
- data/lib/active_record/associations/builder/singular_association.rb +48 -0
- data/lib/active_record/associations/collection_association.rb +535 -0
- data/lib/active_record/associations/collection_proxy.rb +1163 -0
- 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 +40 -0
- data/lib/active_record/associations/has_many_association.rb +167 -0
- data/lib/active_record/associations/has_many_through_association.rb +232 -0
- data/lib/active_record/associations/has_one_association.rb +142 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/join_dependency.rb +301 -0
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +316 -0
- 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 +150 -0
- data/lib/active_record/associations/preloader.rb +135 -0
- data/lib/active_record/associations/singular_association.rb +76 -0
- data/lib/active_record/associations/through_association.rb +132 -0
- data/lib/active_record/associations.rb +1897 -0
- data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
- data/lib/active_record/attribute_assignment.rb +82 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +262 -0
- data/lib/active_record/attribute_methods/primary_key.rb +158 -0
- data/lib/active_record/attribute_methods/query.rb +50 -0
- data/lib/active_record/attribute_methods/read.rb +46 -0
- data/lib/active_record/attribute_methods/serialization.rb +232 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
- data/lib/active_record/attribute_methods/write.rb +49 -0
- data/lib/active_record/attribute_methods.rb +542 -0
- data/lib/active_record/attributes.rb +307 -0
- data/lib/active_record/autosave_association.rb +586 -0
- data/lib/active_record/base.rb +338 -0
- data/lib/active_record/callbacks.rb +452 -0
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
- data/lib/active_record/connection_adapters/column.rb +122 -0
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- 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 +125 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +413 -0
- data/lib/active_record/core.rb +836 -0
- data/lib/active_record/counter_cache.rb +230 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
- data/lib/active_record/database_configurations/database_config.rb +104 -0
- data/lib/active_record/database_configurations/hash_config.rb +172 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +309 -0
- data/lib/active_record/delegated_type.rb +289 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +121 -0
- 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 +70 -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 +184 -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 +177 -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 +159 -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 +107 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +424 -0
- data/lib/active_record/errors.rb +614 -0
- data/lib/active_record/explain.rb +63 -0
- data/lib/active_record/explain_registry.rb +37 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +89 -0
- data/lib/active_record/fixture_set/model_metadata.rb +42 -0
- data/lib/active_record/fixture_set/render_context.rb +19 -0
- data/lib/active_record/fixture_set/table_row.rb +208 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +850 -0
- data/lib/active_record/future_result.rb +182 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +366 -0
- data/lib/active_record/insert_all.rb +328 -0
- data/lib/active_record/integration.rb +209 -0
- data/lib/active_record/internal_metadata.rb +164 -0
- data/lib/active_record/legacy_yaml_adapter.rb +15 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +228 -0
- data/lib/active_record/locking/pessimistic.rb +102 -0
- data/lib/active_record/log_subscriber.rb +149 -0
- 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/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +87 -0
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +406 -0
- data/lib/active_record/migration/compatibility.rb +490 -0
- 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 +16 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +1626 -0
- data/lib/active_record/model_schema.rb +635 -0
- data/lib/active_record/nested_attributes.rb +633 -0
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +968 -0
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/query_logs.rb +247 -0
- data/lib/active_record/query_logs_formatter.rb +30 -0
- data/lib/active_record/querying.rb +122 -0
- data/lib/active_record/railtie.rb +440 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +65 -0
- data/lib/active_record/railties/databases.rake +641 -0
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +66 -0
- data/lib/active_record/reflection.rb +1287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
- data/lib/active_record/relation/batches.rb +491 -0
- data/lib/active_record/relation/calculations.rb +679 -0
- data/lib/active_record/relation/delegation.rb +154 -0
- data/lib/active_record/relation/finder_methods.rb +661 -0
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +192 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
- data/lib/active_record/relation/predicate_builder.rb +181 -0
- data/lib/active_record/relation/query_attribute.rb +68 -0
- data/lib/active_record/relation/query_methods.rb +2235 -0
- data/lib/active_record/relation/record_fetch_warning.rb +52 -0
- data/lib/active_record/relation/spawn_methods.rb +78 -0
- data/lib/active_record/relation/where_clause.rb +218 -0
- data/lib/active_record/relation.rb +1495 -0
- data/lib/active_record/result.rb +249 -0
- data/lib/active_record/runtime_registry.rb +82 -0
- data/lib/active_record/sanitization.rb +254 -0
- data/lib/active_record/schema.rb +77 -0
- data/lib/active_record/schema_dumper.rb +364 -0
- data/lib/active_record/schema_migration.rb +106 -0
- data/lib/active_record/scoping/default.rb +205 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +136 -0
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +66 -0
- data/lib/active_record/serialization.rb +29 -0
- data/lib/active_record/signed_id.rb +137 -0
- data/lib/active_record/statement_cache.rb +164 -0
- data/lib/active_record/store.rb +299 -0
- data/lib/active_record/suppressor.rb +59 -0
- data/lib/active_record/table_metadata.rb +85 -0
- data/lib/active_record/tasks/database_tasks.rb +681 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +321 -0
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +177 -0
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +70 -0
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +523 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/adapter_specific_registry.rb +144 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
- data/lib/active_record/type/internal/timezone.rb +22 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +76 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +35 -0
- data/lib/active_record/type/type_map.rb +58 -0
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type.rb +83 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +65 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +45 -0
- data/lib/active_record/validations/uniqueness.rb +295 -0
- data/lib/active_record/validations.rb +101 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/active_record.rb +616 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +33 -0
- data/lib/arel/collectors/bind.rb +31 -0
- data/lib/arel/collectors/composite.rb +46 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +48 -0
- data/lib/arel/delete_manager.rb +32 -0
- data/lib/arel/errors.rb +19 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +53 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +48 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +125 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +44 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +45 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/nary.rb +39 -0
- data/lib/arel/nodes/node.rb +161 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +32 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +35 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +46 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +75 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +260 -0
- data/lib/arel/select_manager.rb +276 -0
- data/lib/arel/table.rb +121 -0
- data/lib/arel/tree_manager.rb +65 -0
- data/lib/arel/update_manager.rb +49 -0
- data/lib/arel/visitors/dot.rb +299 -0
- data/lib/arel/visitors/mysql.rb +111 -0
- data/lib/arel/visitors/postgresql.rb +99 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +1033 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +73 -0
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +54 -0
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- 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
- data/lib/rails/generators/active_record.rb +19 -0
- metadata +505 -0
|
@@ -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,159 @@
|
|
|
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
|
+
owner = owner.model if owner.is_a?(Relation)
|
|
45
|
+
|
|
46
|
+
return args if owner.deterministic_encrypted_attributes&.empty?
|
|
47
|
+
|
|
48
|
+
if args.is_a?(Array) && (options = args.first).is_a?(Hash)
|
|
49
|
+
options = options.transform_keys do |key|
|
|
50
|
+
if key.is_a?(Array)
|
|
51
|
+
key.map(&:to_s)
|
|
52
|
+
else
|
|
53
|
+
key.to_s
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
args[0] = options
|
|
57
|
+
|
|
58
|
+
owner.deterministic_encrypted_attributes&.each do |attribute_name|
|
|
59
|
+
attribute_name = attribute_name.to_s
|
|
60
|
+
type = owner.type_for_attribute(attribute_name)
|
|
61
|
+
if !type.previous_types.empty? && value = options[attribute_name]
|
|
62
|
+
options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
args
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
def process_encrypted_query_argument(value, check_for_additional_values, type)
|
|
72
|
+
return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
|
|
73
|
+
|
|
74
|
+
case value
|
|
75
|
+
when String, Array
|
|
76
|
+
list = Array(value)
|
|
77
|
+
list + list.flat_map do |each_value|
|
|
78
|
+
if check_for_additional_values && each_value.is_a?(AdditionalValue)
|
|
79
|
+
each_value
|
|
80
|
+
else
|
|
81
|
+
additional_values_for(each_value, type)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
value
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def additional_values_for(value, type)
|
|
90
|
+
type.previous_types.collect do |additional_type|
|
|
91
|
+
AdditionalValue.new(value, additional_type)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
module RelationQueries
|
|
98
|
+
def where(*args)
|
|
99
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def exists?(*args)
|
|
103
|
+
super(*EncryptedQuery.process_arguments(self, args, true))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def scope_for_create
|
|
107
|
+
return super unless model.deterministic_encrypted_attributes&.any?
|
|
108
|
+
|
|
109
|
+
scope_attributes = super
|
|
110
|
+
wheres = where_values_hash
|
|
111
|
+
|
|
112
|
+
model.deterministic_encrypted_attributes.each do |attribute_name|
|
|
113
|
+
attribute_name = attribute_name.to_s
|
|
114
|
+
values = wheres[attribute_name]
|
|
115
|
+
if values.is_a?(Array) && values[1..].all?(AdditionalValue)
|
|
116
|
+
scope_attributes[attribute_name] = values.first
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
scope_attributes
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
module CoreQueries
|
|
125
|
+
extend ActiveSupport::Concern
|
|
126
|
+
|
|
127
|
+
class_methods do
|
|
128
|
+
def find_by(*args)
|
|
129
|
+
super(*EncryptedQuery.process_arguments(self, args, false))
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class AdditionalValue
|
|
135
|
+
attr_reader :value, :type
|
|
136
|
+
|
|
137
|
+
def initialize(value, type)
|
|
138
|
+
@type = type
|
|
139
|
+
@value = process(value)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
def process(value)
|
|
144
|
+
type.serialize(value)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
module ExtendedEncryptableType
|
|
149
|
+
def serialize(data)
|
|
150
|
+
if data.is_a?(AdditionalValue)
|
|
151
|
+
data.value
|
|
152
|
+
else
|
|
153
|
+
super
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
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
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module Encryption
|
|
7
|
+
# Utility for generating and deriving random keys.
|
|
8
|
+
class KeyGenerator
|
|
9
|
+
attr_reader :hash_digest_class
|
|
10
|
+
|
|
11
|
+
def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class)
|
|
12
|
+
@hash_digest_class = hash_digest_class
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default)
|
|
16
|
+
def generate_random_key(length: key_length)
|
|
17
|
+
SecureRandom.random_bytes(length)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns a random key in hexadecimal format. The key will have a size in bytes of +:length+ (configured +Cipher+'s
|
|
21
|
+
# length by default)
|
|
22
|
+
#
|
|
23
|
+
# Hexadecimal format is handy for representing keys as printable text. To maximize the space of characters used, it is
|
|
24
|
+
# good practice including not printable characters. Hexadecimal format ensures that generated keys are representable with
|
|
25
|
+
# plain text
|
|
26
|
+
#
|
|
27
|
+
# To convert back to the original string with the desired length:
|
|
28
|
+
#
|
|
29
|
+
# [ value ].pack("H*")
|
|
30
|
+
def generate_random_hex_key(length: key_length)
|
|
31
|
+
generate_random_key(length: length).unpack("H*")[0]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Derives a key from the given password. The key will have a size in bytes of +:length+ (configured +Cipher+'s length
|
|
35
|
+
# by default)
|
|
36
|
+
#
|
|
37
|
+
# The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+
|
|
38
|
+
def derive_key_from(password, length: key_length)
|
|
39
|
+
ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class)
|
|
40
|
+
.generate_key(key_derivation_salt, length)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def key_derivation_salt
|
|
45
|
+
@key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def key_length
|
|
49
|
+
@key_length ||= ActiveRecord::Encryption.cipher.key_length
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Encryption
|
|
5
|
+
# A +KeyProvider+ serves keys:
|
|
6
|
+
#
|
|
7
|
+
# * An encryption key
|
|
8
|
+
# * A list of potential decryption keys. Serving multiple decryption keys supports rotation-schemes
|
|
9
|
+
# where new keys are added but old keys need to continue working
|
|
10
|
+
class KeyProvider
|
|
11
|
+
def initialize(keys)
|
|
12
|
+
@keys = Array(keys)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns the last key in the list as the active key to perform encryptions
|
|
16
|
+
#
|
|
17
|
+
# When +ActiveRecord::Encryption.config.store_key_references+ is true, the key will include
|
|
18
|
+
# a public tag referencing the key itself. That key will be stored in the public
|
|
19
|
+
# headers of the encrypted message
|
|
20
|
+
def encryption_key
|
|
21
|
+
@encryption_key ||= @keys.last.tap do |key|
|
|
22
|
+
key.public_tags.encrypted_data_key_id = key.id if ActiveRecord::Encryption.config.store_key_references
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@encryption_key
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the list of decryption keys
|
|
29
|
+
#
|
|
30
|
+
# When the message holds a reference to its encryption key, it will return an array
|
|
31
|
+
# with that key. If not, it will return the list of keys.
|
|
32
|
+
def decryption_keys(encrypted_message)
|
|
33
|
+
if encrypted_message.headers.encrypted_data_key_id
|
|
34
|
+
keys_grouped_by_id[encrypted_message.headers.encrypted_data_key_id]
|
|
35
|
+
else
|
|
36
|
+
@keys
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
def keys_grouped_by_id
|
|
42
|
+
@keys_grouped_by_id ||= @keys.group_by(&:id)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Encryption
|
|
5
|
+
# A message defines the structure of the data we store in encrypted attributes. It contains:
|
|
6
|
+
#
|
|
7
|
+
# * An encrypted payload
|
|
8
|
+
# * A list of unencrypted headers
|
|
9
|
+
#
|
|
10
|
+
# See Encryptor#encrypt
|
|
11
|
+
class Message
|
|
12
|
+
attr_accessor :payload, :headers
|
|
13
|
+
|
|
14
|
+
def initialize(payload: nil, headers: {})
|
|
15
|
+
validate_payload_type(payload)
|
|
16
|
+
|
|
17
|
+
@payload = payload
|
|
18
|
+
@headers = Properties.new(headers)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other_message)
|
|
22
|
+
payload == other_message.payload && headers == other_message.headers
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def validate_payload_type(payload)
|
|
27
|
+
unless payload.is_a?(String) || payload.nil?
|
|
28
|
+
raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Only string payloads allowed"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/message_pack"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module Encryption
|
|
7
|
+
# A message serializer that serializes +Messages+ with MessagePack.
|
|
8
|
+
#
|
|
9
|
+
# The message is converted to a hash with this structure:
|
|
10
|
+
#
|
|
11
|
+
# {
|
|
12
|
+
# p: <payload>,
|
|
13
|
+
# h: {
|
|
14
|
+
# header1: value1,
|
|
15
|
+
# header2: value2,
|
|
16
|
+
# ...
|
|
17
|
+
# }
|
|
18
|
+
# }
|
|
19
|
+
#
|
|
20
|
+
# Then it is converted to the MessagePack format.
|
|
21
|
+
class MessagePackMessageSerializer
|
|
22
|
+
def dump(message)
|
|
23
|
+
raise Errors::ForbiddenClass unless message.is_a?(Message)
|
|
24
|
+
ActiveSupport::MessagePack.dump(message_to_hash(message))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load(serialized_content)
|
|
28
|
+
data = ActiveSupport::MessagePack.load(serialized_content)
|
|
29
|
+
hash_to_message(data, 1)
|
|
30
|
+
rescue RuntimeError
|
|
31
|
+
raise Errors::Decryption
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def binary?
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def message_to_hash(message)
|
|
40
|
+
{
|
|
41
|
+
"p" => message.payload,
|
|
42
|
+
"h" => headers_to_hash(message.headers)
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def headers_to_hash(headers)
|
|
47
|
+
headers.transform_values do |value|
|
|
48
|
+
value.is_a?(Message) ? message_to_hash(value) : value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hash_to_message(data, level)
|
|
53
|
+
validate_message_data_format(data, level)
|
|
54
|
+
Message.new(payload: data["p"], headers: parse_properties(data["h"], level))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def validate_message_data_format(data, level)
|
|
58
|
+
if level > 2
|
|
59
|
+
raise Errors::Decryption, "More than one level of hash nesting in headers is not supported"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
unless data.is_a?(Hash) && data.has_key?("p")
|
|
63
|
+
raise Errors::Decryption, "Invalid data format: hash without payload"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parse_properties(headers, level)
|
|
68
|
+
Properties.new.tap do |properties|
|
|
69
|
+
headers&.each do |key, value|
|
|
70
|
+
properties[key] = value.is_a?(Hash) ? hash_to_message(value, level + 1) : value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module Encryption
|
|
7
|
+
# A message serializer that serializes +Messages+ with JSON.
|
|
8
|
+
#
|
|
9
|
+
# The generated structure is pretty simple:
|
|
10
|
+
#
|
|
11
|
+
# {
|
|
12
|
+
# p: <payload>,
|
|
13
|
+
# h: {
|
|
14
|
+
# header1: value1,
|
|
15
|
+
# header2: value2,
|
|
16
|
+
# ...
|
|
17
|
+
# }
|
|
18
|
+
# }
|
|
19
|
+
#
|
|
20
|
+
# Both the payload and the header values are encoded with Base64
|
|
21
|
+
# to prevent JSON parsing errors and encoding issues when
|
|
22
|
+
# storing the resulting serialized data.
|
|
23
|
+
class MessageSerializer
|
|
24
|
+
def load(serialized_content)
|
|
25
|
+
data = JSON.parse(serialized_content)
|
|
26
|
+
parse_message(data, 1)
|
|
27
|
+
rescue JSON::ParserError
|
|
28
|
+
raise ActiveRecord::Encryption::Errors::Encoding
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def dump(message)
|
|
32
|
+
raise ActiveRecord::Encryption::Errors::ForbiddenClass unless message.is_a?(ActiveRecord::Encryption::Message)
|
|
33
|
+
JSON.dump message_to_json(message)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def binary?
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
def parse_message(data, level)
|
|
42
|
+
validate_message_data_format(data, level)
|
|
43
|
+
ActiveRecord::Encryption::Message.new(payload: decode_if_needed(data["p"]), headers: parse_properties(data["h"], level))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_message_data_format(data, level)
|
|
47
|
+
if level > 2
|
|
48
|
+
raise ActiveRecord::Encryption::Errors::Decryption, "More than one level of hash nesting in headers is not supported"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
unless data.is_a?(Hash) && data.has_key?("p")
|
|
52
|
+
raise ActiveRecord::Encryption::Errors::Decryption, "Invalid data format: hash without payload"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def parse_properties(headers, level)
|
|
57
|
+
ActiveRecord::Encryption::Properties.new.tap do |properties|
|
|
58
|
+
headers&.each do |key, value|
|
|
59
|
+
properties[key] = value.is_a?(Hash) ? parse_message(value, level + 1) : decode_if_needed(value)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def message_to_json(message)
|
|
65
|
+
{
|
|
66
|
+
p: encode_if_needed(message.payload),
|
|
67
|
+
h: headers_to_json(message.headers)
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def headers_to_json(headers)
|
|
72
|
+
headers.transform_values do |value|
|
|
73
|
+
value.is_a?(ActiveRecord::Encryption::Message) ? message_to_json(value) : encode_if_needed(value)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def encode_if_needed(value)
|
|
78
|
+
if value.is_a?(String)
|
|
79
|
+
::Base64.strict_encode64 value
|
|
80
|
+
else
|
|
81
|
+
value
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def decode_if_needed(value)
|
|
86
|
+
if value.is_a?(String)
|
|
87
|
+
::Base64.strict_decode64(value)
|
|
88
|
+
else
|
|
89
|
+
value
|
|
90
|
+
end
|
|
91
|
+
rescue ArgumentError, TypeError
|
|
92
|
+
raise Errors::Encoding
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Encryption
|
|
5
|
+
# An encryptor that won't decrypt or encrypt. It will just return the passed
|
|
6
|
+
# values
|
|
7
|
+
class NullEncryptor
|
|
8
|
+
def encrypt(clean_text, key_provider: nil, cipher_options: {})
|
|
9
|
+
clean_text
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
|
|
13
|
+
encrypted_text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def encrypted?(text)
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def binary?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Encryption
|
|
5
|
+
# This is a wrapper for a hash of encryption properties. It is used by
|
|
6
|
+
# +Key+ (public tags) and +Message+ (headers).
|
|
7
|
+
#
|
|
8
|
+
# Since properties are serialized in messages, it is important for storage
|
|
9
|
+
# efficiency to keep their keys as short as possible. It defines accessors
|
|
10
|
+
# for common properties that will keep these keys very short while exposing
|
|
11
|
+
# a readable name.
|
|
12
|
+
#
|
|
13
|
+
# message.headers.encrypted_data_key # instead of message.headers[:k]
|
|
14
|
+
#
|
|
15
|
+
# See +Properties::DEFAULT_PROPERTIES+, Key, Message
|
|
16
|
+
class Properties
|
|
17
|
+
ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
|
|
18
|
+
|
|
19
|
+
delegate_missing_to :data
|
|
20
|
+
delegate :==, :[], :each, :key?, to: :data
|
|
21
|
+
|
|
22
|
+
# For each entry it generates an accessor exposing the full name
|
|
23
|
+
DEFAULT_PROPERTIES = {
|
|
24
|
+
encrypted_data_key: "k",
|
|
25
|
+
encrypted_data_key_id: "i",
|
|
26
|
+
compressed: "c",
|
|
27
|
+
iv: "iv",
|
|
28
|
+
auth_tag: "at",
|
|
29
|
+
encoding: "e"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
DEFAULT_PROPERTIES.each do |name, key|
|
|
33
|
+
define_method name do
|
|
34
|
+
self[key.to_sym]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
define_method "#{name}=" do |value|
|
|
38
|
+
self[key.to_sym] = value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize(initial_properties = {})
|
|
43
|
+
@data = {}
|
|
44
|
+
add(initial_properties)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Set a value for a given key
|
|
48
|
+
#
|
|
49
|
+
# It will raise an +EncryptedContentIntegrity+ if the value exists
|
|
50
|
+
def []=(key, value)
|
|
51
|
+
raise Errors::EncryptedContentIntegrity, "Properties can't be overridden: #{key}" if key?(key)
|
|
52
|
+
validate_value_type(value)
|
|
53
|
+
data[key] = value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_value_type(value)
|
|
57
|
+
unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
|
|
58
|
+
raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def add(other_properties)
|
|
63
|
+
other_properties.each do |key, value|
|
|
64
|
+
self[key.to_sym] = value
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_h
|
|
69
|
+
data
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
attr_reader :data
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|