From 698d957fac6ee2f5a6861e09ebdd808e5f0b20fb Mon Sep 17 00:00:00 2001 From: Zeke Gabrielse Date: Mon, 11 Nov 2024 16:55:09 -0600 Subject: [PATCH 1/4] upgrade rails to 7.2 --- Gemfile | 8 +- Gemfile.lock | 172 +++++++++--------- .../relationships/licenses_controller.rb | 2 +- .../relationships/machines_controller.rb | 2 +- .../relationships/policies_controller.rb | 2 +- .../release_arches_controller.rb | 2 +- .../release_artifacts_controller.rb | 2 +- .../release_channels_controller.rb | 2 +- .../release_engines_controller.rb | 2 +- .../release_packages_controller.rb | 2 +- .../release_platforms_controller.rb | 2 +- .../relationships/releases_controller.rb | 2 +- .../relationships/tokens_controller.rb | 2 +- .../relationships/users_controller.rb | 2 +- app/controllers/api/v1/products_controller.rb | 2 +- .../npm/package_metadata_controller.rb | 2 +- .../release_engines/pypi/simple_controller.rb | 2 +- .../raw/release_artifacts_controller.rb | 2 +- .../rubygems/compact_index_controller.rb | 2 +- .../rubygems/gems_controller.rb | 2 +- .../rubygems/specs_controller.rb | 2 +- .../tauri/upgrades_controller.rb | 2 +- .../api/v1/release_engines_controller.rb | 2 +- .../api/v1/release_packages_controller.rb | 2 +- app/models/concerns/accountable.rb | 4 + app/models/concerns/environmental.rb | 9 +- app/models/concerns/roleable.rb | 3 + app/models/current.rb | 6 + app/models/environment.rb | 2 +- config/application.rb | 12 +- .../to_time_preserves_timezone.rb | 12 -- db/seeds/development.rb | 2 +- .../api/v1/engines/tauri/v1/upgrade.feature | 36 ++-- .../step_definitions/before_after_steps.rb | 2 +- spec/factories/entitlement.rb | 2 +- spec/factories/environment.rb | 2 +- spec/factories/license.rb | 4 +- spec/factories/release_arch.rb | 2 +- spec/factories/release_channel.rb | 2 +- spec/factories/release_download_link.rb | 4 +- spec/factories/release_engine.rb | 2 +- spec/factories/release_filetype.rb | 2 +- spec/factories/release_platform.rb | 2 +- spec/factories/release_upgrade_link.rb | 4 +- spec/factories/release_upload_link.rb | 4 +- spec/factories/role.rb | 4 + spec/lib/keygen/exporter_spec.rb | 3 + spec/lib/keygen/importer_spec.rb | 3 + spec/models/policy_spec.rb | 3 + spec/models/user_spec.rb | 3 + spec/services/broadcast_event_service_spec.rb | 9 +- 51 files changed, 191 insertions(+), 176 deletions(-) delete mode 100644 config/initializers/to_time_preserves_timezone.rb diff --git a/Gemfile b/Gemfile index 349ac37dd9..caea774fc8 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' ruby '3.3.6' -gem 'rails', '~> 7.1.4.1' +gem 'rails', '~> 7.2.2' gem 'pg', '~> 1.3.4' gem 'puma', '~> 6.4.3' gem 'bcrypt', '~> 3.1.7' @@ -51,8 +51,8 @@ gem 'kaminari', '~> 1.2.0' # Postgres/DB extensions gem 'active_record_union' -gem 'active_record_distinct_on', '~> 1.6' -gem 'activerecord_where_assoc', '~> 1.1.4' +gem 'active_record_distinct_on', '~> 1.7' +gem 'activerecord_where_assoc', '~> 1.2' gem 'ar_lazy_preload', '~> 2.0' gem 'strong_migrations' gem 'verbose_migrations' @@ -120,7 +120,7 @@ group :development, :test do gem 'byebug', platform: :mri gem 'dotenv-rails' gem 'timecop', '~> 0.9.5' - gem 'bullet', '~> 7.1.6' + gem 'bullet', '~> 7.2' gem 'parallel_tests', '~> 4.2.1' gem 'cuke_modeler', '~> 3.19' # for running `parallel_test --group-by scenarios` gem 'faker', '~> 2.20.0' diff --git a/Gemfile.lock b/Gemfile.lock index 9c46a38877..e421d2ee1f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,86 +15,83 @@ GEM concurrent-ruby (~> 1.0) action_policy (0.6.3) ruby-next-core (>= 0.14.0) - actioncable (7.1.4.1) - actionpack (= 7.1.4.1) - activesupport (= 7.1.4.1) + actioncable (7.2.2) + actionpack (= 7.2.2) + activesupport (= 7.2.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.4.1) - actionpack (= 7.1.4.1) - activejob (= 7.1.4.1) - activerecord (= 7.1.4.1) - activestorage (= 7.1.4.1) - activesupport (= 7.1.4.1) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp - actionmailer (7.1.4.1) - actionpack (= 7.1.4.1) - actionview (= 7.1.4.1) - activejob (= 7.1.4.1) - activesupport (= 7.1.4.1) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp + actionmailbox (7.2.2) + actionpack (= 7.2.2) + activejob (= 7.2.2) + activerecord (= 7.2.2) + activestorage (= 7.2.2) + activesupport (= 7.2.2) + mail (>= 2.8.0) + actionmailer (7.2.2) + actionpack (= 7.2.2) + actionview (= 7.2.2) + activejob (= 7.2.2) + activesupport (= 7.2.2) + mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.1.4.1) - actionview (= 7.1.4.1) - activesupport (= 7.1.4.1) + actionpack (7.2.2) + actionview (= 7.2.2) + activesupport (= 7.2.2) nokogiri (>= 1.8.5) racc - rack (>= 2.2.4) + rack (>= 2.2.4, < 3.2) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.4.1) - actionpack (= 7.1.4.1) - activerecord (= 7.1.4.1) - activestorage (= 7.1.4.1) - activesupport (= 7.1.4.1) + useragent (~> 0.16) + actiontext (7.2.2) + actionpack (= 7.2.2) + activerecord (= 7.2.2) + activestorage (= 7.2.2) + activesupport (= 7.2.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.4.1) - activesupport (= 7.1.4.1) + actionview (7.2.2) + activesupport (= 7.2.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - active_record_distinct_on (1.6.0) - activerecord (>= 6.1, < 7.2) + active_record_distinct_on (1.7.0) + activerecord (>= 6.1, < 7.3) active_record_union (1.3.0) activerecord (>= 4.0) - activejob (7.1.4.1) - activesupport (= 7.1.4.1) + activejob (7.2.2) + activesupport (= 7.2.2) globalid (>= 0.3.6) - activemodel (7.1.4.1) - activesupport (= 7.1.4.1) - activerecord (7.1.4.1) - activemodel (= 7.1.4.1) - activesupport (= 7.1.4.1) + activemodel (7.2.2) + activesupport (= 7.2.2) + activerecord (7.2.2) + activemodel (= 7.2.2) + activesupport (= 7.2.2) timeout (>= 0.4.0) - activerecord_where_assoc (1.1.4) + activerecord_where_assoc (1.2.0) activerecord (>= 4.1.0) - activestorage (7.1.4.1) - actionpack (= 7.1.4.1) - activejob (= 7.1.4.1) - activerecord (= 7.1.4.1) - activesupport (= 7.1.4.1) + activestorage (7.2.2) + actionpack (= 7.2.2) + activejob (= 7.2.2) + activerecord (= 7.2.2) + activesupport (= 7.2.2) marcel (~> 1.0) - activesupport (7.1.4.1) + activesupport (7.2.2) base64 + benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) anbt-sql-formatter (0.1.0) @@ -120,9 +117,10 @@ GEM statsd-ruby (~> 1.1) base64 (0.2.0) bcrypt (3.1.17) + benchmark (0.4.0) bigdecimal (3.1.8) builder (3.3.0) - bullet (7.1.6) + bullet (7.2.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) byebug (11.1.3) @@ -193,7 +191,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.4) + date (3.4.0) diff-lcs (1.5.0) dotenv (2.7.6) dotenv-rails (2.7.6) @@ -284,12 +282,13 @@ GEM listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.1) lograge (0.12.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.22.0) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -312,8 +311,7 @@ GEM multi_test (0.1.2) multi_xml (0.7.1) bigdecimal (~> 3.1) - mutex_m (0.2.0) - net-imap (0.5.0) + net-imap (0.5.1) date net-protocol net-pop (0.1.2) @@ -322,7 +320,7 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) + nio4r (2.7.4) nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) @@ -341,7 +339,7 @@ GEM premailer-rails (1.11.1) actionmailer (>= 3) premailer (~> 1.7, >= 1.7.9) - psych (5.1.2) + psych (5.2.0) stringio public_suffix (4.0.6) puma (6.4.3) @@ -358,23 +356,23 @@ GEM rack-test (2.1.0) rack (>= 1.3) rack-timeout (0.6.0) - rackup (1.0.0) + rackup (1.0.1) rack (< 3) webrick - rails (7.1.4.1) - actioncable (= 7.1.4.1) - actionmailbox (= 7.1.4.1) - actionmailer (= 7.1.4.1) - actionpack (= 7.1.4.1) - actiontext (= 7.1.4.1) - actionview (= 7.1.4.1) - activejob (= 7.1.4.1) - activemodel (= 7.1.4.1) - activerecord (= 7.1.4.1) - activestorage (= 7.1.4.1) - activesupport (= 7.1.4.1) + rails (7.2.2) + actioncable (= 7.2.2) + actionmailbox (= 7.2.2) + actionmailer (= 7.2.2) + actionpack (= 7.2.2) + actiontext (= 7.2.2) + actionview (= 7.2.2) + activejob (= 7.2.2) + activemodel (= 7.2.2) + activerecord (= 7.2.2) + activestorage (= 7.2.2) + activesupport (= 7.2.2) bundler (>= 1.15.0) - railties (= 7.1.4.1) + railties (= 7.2.2) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -385,10 +383,10 @@ GEM rails-pattern_matching (0.2.0) activemodel activerecord - railties (7.1.4.1) - actionpack (= 7.1.4.1) - activesupport (= 7.1.4.1) - irb + railties (7.2.2) + actionpack (= 7.2.2) + activesupport (= 7.2.2) + irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) @@ -403,7 +401,7 @@ GEM redis-client (0.22.1) connection_pool regexp_parser (2.2.1) - reline (0.5.10) + reline (0.5.11) io-console (~> 0.5) request_migrations (1.1.1) rails (>= 6.0) @@ -443,6 +441,7 @@ GEM sprockets (> 3.0) sprockets-rails tilt + securerandom (0.3.2) semverse (3.0.2) sendgrid-actionmailer (3.2.0) mail (~> 2.7) @@ -481,7 +480,7 @@ GEM statement_timeout (1.0.0) rails (>= 6.0) statsd-ruby (1.5.0) - stringio (3.1.1) + stringio (3.1.2) stripe (5.48.0) strong_migrations (1.8.0) activerecord (>= 5.2) @@ -493,7 +492,7 @@ GEM thor (1.3.2) tilt (2.0.10) timecop (0.9.5) - timeout (0.4.1) + timeout (0.4.2) tracer (0.1.1) typed_params (1.2.5) rails (>= 6.0) @@ -503,19 +502,20 @@ GEM union_of (1.0.0) rails (>= 7.0) uri (0.12.2) + useragent (0.16.10) verbose_migrations (1.0.1) rails (>= 6.0) webmock (3.14.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.2) + webrick (1.9.0) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.7.0) + zeitwerk (2.7.1) PLATFORMS ruby @@ -523,15 +523,15 @@ PLATFORMS DEPENDENCIES aasm (~> 5.0.3) action_policy (~> 0.6.3) - active_record_distinct_on (~> 1.6) + active_record_distinct_on (~> 1.7) active_record_union - activerecord_where_assoc (~> 1.1.4) + activerecord_where_assoc (~> 1.2) anbt-sql-formatter ar_lazy_preload (~> 2.0) aws-sdk-s3 (~> 1) barnes bcrypt (~> 3.1.7) - bullet (~> 7.1.6) + bullet (~> 7.2) byebug compact_index cucumber-rails (~> 2.5) @@ -569,7 +569,7 @@ DEPENDENCIES rack-attack (~> 6.6) rack-cors rack-timeout - rails (~> 7.1.4.1) + rails (~> 7.2.2) rails-pattern_matching redis (~> 4.7.1) request_migrations (~> 1.1) diff --git a/app/controllers/api/v1/products/relationships/licenses_controller.rb b/app/controllers/api/v1/products/relationships/licenses_controller.rb index 405d9dde11..baac636e81 100644 --- a/app/controllers/api/v1/products/relationships/licenses_controller.rb +++ b/app/controllers/api/v1/products/relationships/licenses_controller.rb @@ -36,7 +36,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/machines_controller.rb b/app/controllers/api/v1/products/relationships/machines_controller.rb index 32c36ecd94..b9b9e4792e 100644 --- a/app/controllers/api/v1/products/relationships/machines_controller.rb +++ b/app/controllers/api/v1/products/relationships/machines_controller.rb @@ -36,7 +36,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/policies_controller.rb b/app/controllers/api/v1/products/relationships/policies_controller.rb index ed02fd89b7..89d22a88e4 100644 --- a/app/controllers/api/v1/products/relationships/policies_controller.rb +++ b/app/controllers/api/v1/products/relationships/policies_controller.rb @@ -32,7 +32,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/release_arches_controller.rb b/app/controllers/api/v1/products/relationships/release_arches_controller.rb index 7b79741483..bb62d1e8dd 100644 --- a/app/controllers/api/v1/products/relationships/release_arches_controller.rb +++ b/app/controllers/api/v1/products/relationships/release_arches_controller.rb @@ -32,7 +32,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/release_artifacts_controller.rb b/app/controllers/api/v1/products/relationships/release_artifacts_controller.rb index a57efb1080..0f6cad27ec 100644 --- a/app/controllers/api/v1/products/relationships/release_artifacts_controller.rb +++ b/app/controllers/api/v1/products/relationships/release_artifacts_controller.rb @@ -61,7 +61,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/release_channels_controller.rb b/app/controllers/api/v1/products/relationships/release_channels_controller.rb index 25d4928adf..3fb75c3da5 100644 --- a/app/controllers/api/v1/products/relationships/release_channels_controller.rb +++ b/app/controllers/api/v1/products/relationships/release_channels_controller.rb @@ -32,7 +32,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/release_engines_controller.rb b/app/controllers/api/v1/products/relationships/release_engines_controller.rb index 46d7ef2cee..49c6ff02ec 100644 --- a/app/controllers/api/v1/products/relationships/release_engines_controller.rb +++ b/app/controllers/api/v1/products/relationships/release_engines_controller.rb @@ -32,7 +32,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/release_packages_controller.rb b/app/controllers/api/v1/products/relationships/release_packages_controller.rb index c0c7dbc3f1..a38d5bb490 100644 --- a/app/controllers/api/v1/products/relationships/release_packages_controller.rb +++ b/app/controllers/api/v1/products/relationships/release_packages_controller.rb @@ -34,7 +34,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/release_platforms_controller.rb b/app/controllers/api/v1/products/relationships/release_platforms_controller.rb index b3060d4866..fdc81307d3 100644 --- a/app/controllers/api/v1/products/relationships/release_platforms_controller.rb +++ b/app/controllers/api/v1/products/relationships/release_platforms_controller.rb @@ -32,7 +32,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/releases_controller.rb b/app/controllers/api/v1/products/relationships/releases_controller.rb index ed45fd7bed..943de73c49 100644 --- a/app/controllers/api/v1/products/relationships/releases_controller.rb +++ b/app/controllers/api/v1/products/relationships/releases_controller.rb @@ -44,7 +44,7 @@ def show def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/tokens_controller.rb b/app/controllers/api/v1/products/relationships/tokens_controller.rb index fb138a5b0b..09b8e56bd6 100644 --- a/app/controllers/api/v1/products/relationships/tokens_controller.rb +++ b/app/controllers/api/v1/products/relationships/tokens_controller.rb @@ -90,7 +90,7 @@ def create def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products/relationships/users_controller.rb b/app/controllers/api/v1/products/relationships/users_controller.rb index ee72cbe347..1c97bbc5bc 100644 --- a/app/controllers/api/v1/products/relationships/users_controller.rb +++ b/app/controllers/api/v1/products/relationships/users_controller.rb @@ -38,7 +38,7 @@ def product_users = current_account.users.for_product(product) def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:product_id], aliases: :code, diff --git a/app/controllers/api/v1/products_controller.rb b/app/controllers/api/v1/products_controller.rb index d132115017..5588587d37 100644 --- a/app/controllers/api/v1/products_controller.rb +++ b/app/controllers/api/v1/products_controller.rb @@ -138,7 +138,7 @@ def destroy def set_product scoped_products = authorized_scope(current_account.products) - Current.resource = @product = FindByAliasService.call( + @product = Current.resource = FindByAliasService.call( scoped_products, id: params[:id], aliases: :code, diff --git a/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb b/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb index a3078b350b..6e939c180a 100644 --- a/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb +++ b/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb @@ -70,7 +70,7 @@ def set_package %i[releases artifacts manifest], # must exist ) - Current.resource = @package = FindByAliasService.call( + @package = Current.resource = FindByAliasService.call( scoped_packages, id: params[:package], aliases: :key, diff --git a/app/controllers/api/v1/release_engines/pypi/simple_controller.rb b/app/controllers/api/v1/release_engines/pypi/simple_controller.rb index d7644a3a83..a701b57200 100644 --- a/app/controllers/api/v1/release_engines/pypi/simple_controller.rb +++ b/app/controllers/api/v1/release_engines/pypi/simple_controller.rb @@ -51,7 +51,7 @@ def show attr_reader :package def set_package - Current.resource = @package = FindByAliasService.call( + @package = Current.resource = FindByAliasService.call( authorized_scope(current_account.release_packages.pypi), id: params[:package], aliases: :key, diff --git a/app/controllers/api/v1/release_engines/raw/release_artifacts_controller.rb b/app/controllers/api/v1/release_engines/raw/release_artifacts_controller.rb index 39349bfc9c..80c90f72c2 100644 --- a/app/controllers/api/v1/release_engines/raw/release_artifacts_controller.rb +++ b/app/controllers/api/v1/release_engines/raw/release_artifacts_controller.rb @@ -24,7 +24,7 @@ def set_artifact .for_package(params[:package_id]) .for_release(params[:release_id]) - Current.resource = @artifact = FindByAliasService.call( + @artifact = Current.resource = FindByAliasService.call( scoped_artifacts.order_by_version, id: params[:id], aliases: :filename, diff --git a/app/controllers/api/v1/release_engines/rubygems/compact_index_controller.rb b/app/controllers/api/v1/release_engines/rubygems/compact_index_controller.rb index a383fc5755..b1f6a4cc43 100644 --- a/app/controllers/api/v1/release_engines/rubygems/compact_index_controller.rb +++ b/app/controllers/api/v1/release_engines/rubygems/compact_index_controller.rb @@ -134,7 +134,7 @@ def set_package %i[releases artifacts manifest], # must exist ) - Current.resource = @package = FindByAliasService.call( + @package = Current.resource = FindByAliasService.call( scoped_packages, id: params[:gem], aliases: :key, diff --git a/app/controllers/api/v1/release_engines/rubygems/gems_controller.rb b/app/controllers/api/v1/release_engines/rubygems/gems_controller.rb index c31ea0acc3..41fa7b1208 100644 --- a/app/controllers/api/v1/release_engines/rubygems/gems_controller.rb +++ b/app/controllers/api/v1/release_engines/rubygems/gems_controller.rb @@ -21,7 +21,7 @@ def show def set_artifact scoped_artifacts = authorized_scope(current_account.release_artifacts.gems) - Current.resource = @artifact = FindByAliasService.call( + @artifact = Current.resource = FindByAliasService.call( scoped_artifacts, id: "#{params[:gem]}.gem", aliases: :filename, diff --git a/app/controllers/api/v1/release_engines/rubygems/specs_controller.rb b/app/controllers/api/v1/release_engines/rubygems/specs_controller.rb index 48d41d3d65..d2ade06e41 100644 --- a/app/controllers/api/v1/release_engines/rubygems/specs_controller.rb +++ b/app/controllers/api/v1/release_engines/rubygems/specs_controller.rb @@ -136,7 +136,7 @@ def set_artifact :manifest, ) - Current.resource = @artifact = FindByAliasService.call( + @artifact = Current.resource = FindByAliasService.call( scoped_artifacts, id: "#{params[:gem]}.gem", aliases: :filename, diff --git a/app/controllers/api/v1/release_engines/tauri/upgrades_controller.rb b/app/controllers/api/v1/release_engines/tauri/upgrades_controller.rb index 8f85b11f79..52b5eb9586 100644 --- a/app/controllers/api/v1/release_engines/tauri/upgrades_controller.rb +++ b/app/controllers/api/v1/release_engines/tauri/upgrades_controller.rb @@ -81,7 +81,7 @@ def show attr_reader :package def set_package - Current.resource = @package = FindByAliasService.call( + @package = Current.resource = FindByAliasService.call( authorized_scope(current_account.release_packages.tauri), id: params[:package], aliases: :key, diff --git a/app/controllers/api/v1/release_engines_controller.rb b/app/controllers/api/v1/release_engines_controller.rb index 3f4960aaf2..b80f0c1bae 100644 --- a/app/controllers/api/v1/release_engines_controller.rb +++ b/app/controllers/api/v1/release_engines_controller.rb @@ -27,7 +27,7 @@ def show def set_engine scoped_engines = authorized_scope(current_account.release_engines) - Current.resource = @engine = FindByAliasService.call( + @engine = Current.resource = FindByAliasService.call( scoped_engines, id: params[:id], aliases: :key, diff --git a/app/controllers/api/v1/release_packages_controller.rb b/app/controllers/api/v1/release_packages_controller.rb index 1a1dee26d0..780e6af471 100644 --- a/app/controllers/api/v1/release_packages_controller.rb +++ b/app/controllers/api/v1/release_packages_controller.rb @@ -129,7 +129,7 @@ def destroy def set_package scoped_packages = authorized_scope(current_account.release_packages) - Current.resource = @package = FindByAliasService.call( + @package = Current.resource = FindByAliasService.call( scoped_packages, id: params[:id], aliases: :key, diff --git a/app/models/concerns/accountable.rb b/app/models/concerns/accountable.rb index eb98698209..92ecad9819 100644 --- a/app/models/concerns/accountable.rb +++ b/app/models/concerns/accountable.rb @@ -29,6 +29,10 @@ def has_account(default: nil, **kwargs) # Hook into both initialization and validation to ensure the current account # is applied to new records (given no :account was provided). + # + # We're not using belongs_to(default:) because it only adds a before_validation + # callback, but we want to also do it after_initialize because new children + # may rely on the account being set on their parent. after_initialize -> { self.account_id ||= Current.account&.id }, unless: -> { account_id_attribute_assigned? || account_attribute_assigned? }, if: -> { new_record? && account_id.nil? } diff --git a/app/models/concerns/environmental.rb b/app/models/concerns/environmental.rb index 2c872a9ade..5d9388384b 100644 --- a/app/models/concerns/environmental.rb +++ b/app/models/concerns/environmental.rb @@ -61,15 +61,18 @@ module Environmental # # Use :default to automatically configure a default environment for the model. # Accepts a proc that resolves into an Environment or environment ID. - def has_environment(default: nil) - belongs_to :environment, - optional: true + def has_environment(default: nil, **kwargs) + belongs_to :environment, optional: true, **kwargs tracks_attributes :environment_id, :environment # Hook into both initialization and validation to ensure the current environment # is applied to new records (given no :environment was provided). + # + # We're not using belongs_to(default:) because it only adds a before_validation + # callback, but we want to also do it after_initialize because new children + # may rely on the environment being set on their parent. after_initialize -> { self.environment_id ||= Current.environment&.id }, unless: -> { environment_id_attribute_assigned? || environment_attribute_assigned? }, if: -> { new_record? && environment.nil? } diff --git a/app/models/concerns/roleable.rb b/app/models/concerns/roleable.rb index a0300424b6..9fee24e904 100644 --- a/app/models/concerns/roleable.rb +++ b/app/models/concerns/roleable.rb @@ -97,6 +97,9 @@ def define_roleable_association_and_delgate accepts_nested_attributes_for :role, update_only: true tracks_nested_attributes_for :role + validates :role, + presence: { message: 'must exist' } + delegate :permissions, :permission_ids, :role_permissions, :role_permissions_attributes_assigned?, :role_permissions_attributes, allow_nil: true, diff --git a/app/models/current.rb b/app/models/current.rb index 1d67b7d5c8..3a8ccb9381 100644 --- a/app/models/current.rb +++ b/app/models/current.rb @@ -19,4 +19,10 @@ def account=(account) self.token = nil self.resource = nil end + + def account_id = account&.id + def environment_id = environment&.id + def bearer_id = bearer&.id + def token_id = token&.id + def resource_id = resource&.id end diff --git a/app/models/environment.rb b/app/models/environment.rb index f431ca0730..40ba2a16f7 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -20,7 +20,7 @@ def owned = where(bearer: proxy_association.owner) # TODO(ezekg) Should deleting queue up a cancelable background job? has_many :webhook_endpoints, dependent: :destroy_async - has_many :webhook_event, dependent: :destroy_async + has_many :webhook_events, dependent: :destroy_async has_many :entitlements, dependent: :destroy_async has_many :groups, dependent: :destroy_async has_many :products, dependent: :destroy_async diff --git a/config/application.rb b/config/application.rb index fb4ccc351c..f96a75c54b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -20,7 +20,7 @@ module Keygen class Application < Rails::Application - config.load_defaults 7.1 + config.load_defaults 7.2 config.generators do |generator| # Use UUIDs for table primary keys @@ -40,6 +40,9 @@ class Application < Rails::Application config.middleware.delete Rack::Sendfile config.middleware.delete Rack::Runtime + # remove unneeded Rails middlware + config.middleware.delete ActionDispatch::ShowExceptions + # Ignore X-Forwarded-For header config.middleware.insert_before 0, Keygen::Middleware::IgnoreForwardedHost @@ -87,6 +90,9 @@ class Application < Rails::Application **config.active_record.encryption, ) + # Show all attributes in Rails console + config.active_record.attributes_for_inspect = :all + # Update async destroy batch size config.active_record.destroy_association_async_batch_size = 1_000 @@ -96,6 +102,10 @@ class Application < Rails::Application # We don't need this: https://guides.rubyonrails.org/security.html#unsafe-query-generation config.action_dispatch.perform_deep_munge = false + # we don't want to ever show exceptions + config.action_dispatch.exceptions_app = self.routes + config.action_dispatch.show_exceptions = :none + # Add support for trusted proxies config.action_dispatch.trusted_proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES + ENV.fetch('TRUSTED_PROXIES') { '' } diff --git a/config/initializers/to_time_preserves_timezone.rb b/config/initializers/to_time_preserves_timezone.rb deleted file mode 100644 index e1e6c75f5c..0000000000 --- a/config/initializers/to_time_preserves_timezone.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -# Be sure to restart your server when you modify this file. - -# Preserve the timezone of the receiver when calling to `to_time`. -# Ruby 2.4 will change the behavior of `to_time` to preserve the timezone -# when converting to an instance of `Time` instead of the previous behavior -# of converting to the local system timezone. -# -# Rails 5.0 introduced this config option so that apps made with earlier -# versions of Rails are not affected when upgrading. -ActiveSupport.to_time_preserves_timezone = true diff --git a/db/seeds/development.rb b/db/seeds/development.rb index 6fb5dfe8f0..79b5d05a5d 100644 --- a/db/seeds/development.rb +++ b/db/seeds/development.rb @@ -317,7 +317,7 @@ def srand(range) = rand < 0.9 ? brand(range.begin..(range.begin + range.end * 0. request_body: method.in?(%w[POST PUT PATCH]) ? '{"data":null,"meta":{"sample":true}}' : nil, response_signature: SecureRandom.base64, response_body: '{"data":null,"errors":[],"meta":{"sample":true}}', - status: %w[200 201 204 303 302 307 400 401 403 404 422], + status: %w[200 201 204 303 302 307 400 401 403 404 422].sample, method:, url:, environment:, diff --git a/features/api/v1/engines/tauri/v1/upgrade.feature b/features/api/v1/engines/tauri/v1/upgrade.feature index e4a445e661..15de8eca70 100644 --- a/features/api/v1/engines/tauri/v1/upgrade.feature +++ b/features/api/v1/engines/tauri/v1/upgrade.feature @@ -335,18 +335,15 @@ Feature: Tauri v1 upgrade application Scenario: License retrieves an upgrade for a release that has entitlement constraints (no entitlements) Given the current account has 3 "entitlements" - And the current account has 1 "release-entitlement-constraint" with the following: + And the current account has 1 "release-entitlement-constraint" for the first "release" with the following: """ - { - "entitlementId": "$entitlements[0]", - "releaseId": "$releases[0]" - } + { "entitlementId": "$entitlements[0]" } """ - And the current account has 1 "policy" for the last "product" with the following: + And the current account has 1 "policy" for the first "product" with the following: """ { "authenticationStrategy": "LICENSE" } """ - And the current account has 1 "license" for the last "policy" + And the current account has 1 "license" for the first "policy" And I am a license of account "test1" And I authenticate with my key When I send a GET request to "/accounts/test1/engines/tauri/app1?platform=darwin&arch=x86_64&version=1.0.0" @@ -354,18 +351,15 @@ Feature: Tauri v1 upgrade application Scenario: License retrieves an upgrade that has entitlement constraints (no entitlements) Given the current account has 3 "entitlements" - And the current account has 1 "release-entitlement-constraint" with the following: + And the current account has 1 "release-entitlement-constraint" for the second "release" with the following: """ - { - "entitlementId": "$entitlements[0]", - "releaseId": "$releases[1]" - } + { "entitlementId": "$entitlements[0]" } """ - And the current account has 1 "policy" for the last "product" with the following: + And the current account has 1 "policy" for the first "product" with the following: """ { "authenticationStrategy": "LICENSE" } """ - And the current account has 1 "license" for the last "policy" + And the current account has 1 "license" for the first "policy" And I am a license of account "test1" And I authenticate with my key When I send a GET request to "/accounts/test1/engines/tauri/app1?platform=darwin&arch=x86_64&version=1.0.0" @@ -373,24 +367,18 @@ Feature: Tauri v1 upgrade application Scenario: License retrieves an upgrade that has entitlement constraints (has entitlements) Given the current account has 3 "entitlements" - And the current account has 1 "release-entitlement-constraint" with the following: + And the current account has 1 "release-entitlement-constraint" for the second "release" with the following: """ - { - "entitlementId": "$entitlements[0]", - "releaseId": "$releases[1]" - } + { "entitlementId": "$entitlements[0]" } """ And the current account has 1 "policy" for the last "product" with the following: """ { "authenticationStrategy": "LICENSE" } """ And the current account has 1 "license" for the last "policy" - And the current account has 1 "license-entitlement" with the following: + And the current account has 1 "license-entitlement" for the first "license" with the following: """ - { - "entitlementId": "$entitlements[0]", - "licenseId": "$licenses[0]" - } + { "entitlementId": "$entitlements[0]" } """ And I am a license of account "test1" And I authenticate with my key diff --git a/features/step_definitions/before_after_steps.rb b/features/step_definitions/before_after_steps.rb index 1394239bbe..b0eaeecb3a 100644 --- a/features/step_definitions/before_after_steps.rb +++ b/features/step_definitions/before_after_steps.rb @@ -17,7 +17,7 @@ # FIXME(ezekg) This is super hacky but there's no easy way to disable # bullet outside of adding controller filters Before("@skip/bullet") { Bullet.instance_variable_set :@enable, false } -After("@skip/bullet") { Bullet.instance_variable_set :@enable, true } +After("@skip/bullet") { Bullet.instance_variable_set :@enable, true } Before do |scenario| # Skip CE tests if we're running in an EE env, and vice-versa diff --git a/spec/factories/entitlement.rb b/spec/factories/entitlement.rb index 7769291656..9ff3013113 100644 --- a/spec/factories/entitlement.rb +++ b/spec/factories/entitlement.rb @@ -4,7 +4,7 @@ factory :entitlement do # Prevent duplicates due to cyclic entitlement codes below. Attempting # to insert duplicate codes would fail, and this prevents that. - initialize_with { Entitlement.find_by(code:) || new(**attributes.reject { _2 in NIL_ACCOUNT | NIL_ENVIRONMENT }) } + initialize_with { Entitlement.find_by(account:, code:) || new(**attributes.reject { _2 in NIL_ACCOUNT | NIL_ENVIRONMENT }) } account { NIL_ACCOUNT } environment { NIL_ENVIRONMENT } diff --git a/spec/factories/environment.rb b/spec/factories/environment.rb index f07c7f26f7..ce818f893a 100644 --- a/spec/factories/environment.rb +++ b/spec/factories/environment.rb @@ -4,7 +4,7 @@ factory :environment do # Prevent duplicates due to recurring environment codes. Attempting # to insert duplicate codes would fail, and this prevents that. - initialize_with { Environment.find_by(code:) || new(**attributes.reject { NIL_ACCOUNT == _2 }) } + initialize_with { Environment.find_by(account:, code:) || new(**attributes.reject { NIL_ACCOUNT == _2 }) } account { NIL_ACCOUNT } diff --git a/spec/factories/license.rb b/spec/factories/license.rb index 387d462d78..07831665fd 100644 --- a/spec/factories/license.rb +++ b/spec/factories/license.rb @@ -148,7 +148,7 @@ end trait :in_isolated_environment do - environment { create(:environment, :isolated, account:) } + environment { build(:environment, :isolated, account:) } end trait :isolated do @@ -156,7 +156,7 @@ end trait :in_shared_environment do - environment { create(:environment, :shared, account:) } + environment { build(:environment, :shared, account:) } end trait :shared do diff --git a/spec/factories/release_arch.rb b/spec/factories/release_arch.rb index 5293163659..b3897f5f12 100644 --- a/spec/factories/release_arch.rb +++ b/spec/factories/release_arch.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :release_arch, aliases: %i[arch] do - initialize_with { new(**attributes) } + initialize_with { new(**attributes.reject { NIL_ACCOUNT == _2 }) } sequence :key, %w[386 amd64 arm arm64 mips mips64 mips64le mipsle ppc64 ppc64le s390x].cycle diff --git a/spec/factories/release_channel.rb b/spec/factories/release_channel.rb index dd1334dee4..83f8c1f931 100644 --- a/spec/factories/release_channel.rb +++ b/spec/factories/release_channel.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :release_channel, aliases: %i[channel] do - initialize_with { ReleaseChannel.find_by(key:) || new(**attributes) } + initialize_with { new(**attributes.reject { NIL_ACCOUNT == _2 }) } sequence :key, %w[stable rc beta alpha dev].cycle diff --git a/spec/factories/release_download_link.rb b/spec/factories/release_download_link.rb index c73e30ea51..3968820286 100644 --- a/spec/factories/release_download_link.rb +++ b/spec/factories/release_download_link.rb @@ -2,9 +2,9 @@ FactoryBot.define do factory :release_download_link do - initialize_with { new(**attributes.reject { NIL_ENVIRONMENT == _2 }) } + initialize_with { new(**attributes.reject { _2 in NIL_ACCOUNT | NIL_ENVIRONMENT }) } - account { nil } + account { NIL_ACCOUNT } environment { NIL_ENVIRONMENT } release { build(:release, account:, environment:) } diff --git a/spec/factories/release_engine.rb b/spec/factories/release_engine.rb index 92c4118dae..d2204c9578 100644 --- a/spec/factories/release_engine.rb +++ b/spec/factories/release_engine.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :release_engine, aliases: %i[engine] do - initialize_with { new(**attributes) } + initialize_with { new(**attributes.reject { NIL_ACCOUNT == _2 }) } sequence :key, %w[pypi tauri raw].cycle diff --git a/spec/factories/release_filetype.rb b/spec/factories/release_filetype.rb index 29479a3a67..2fc897d3f8 100644 --- a/spec/factories/release_filetype.rb +++ b/spec/factories/release_filetype.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :release_filetype, aliases: %i[filetype] do - initialize_with { new(**attributes) } + initialize_with { new(**attributes.reject { NIL_ACCOUNT == _2 }) } sequence :key, %w[dmg exe zip tar.gz appimage].cycle diff --git a/spec/factories/release_platform.rb b/spec/factories/release_platform.rb index 6f14ce43bf..58d04f7584 100644 --- a/spec/factories/release_platform.rb +++ b/spec/factories/release_platform.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :release_platform, aliases: %i[platform] do - initialize_with { new(**attributes) } + initialize_with { new(**attributes.reject { _2 in NIL_ACCOUNT }) } sequence :key, %w[darwin linux windows].cycle diff --git a/spec/factories/release_upgrade_link.rb b/spec/factories/release_upgrade_link.rb index 8fc337efbb..eb67f61e8e 100644 --- a/spec/factories/release_upgrade_link.rb +++ b/spec/factories/release_upgrade_link.rb @@ -2,9 +2,9 @@ FactoryBot.define do factory :release_upgrade_link do - initialize_with { new(**attributes.reject { NIL_ENVIRONMENT == _2 }) } + initialize_with { new(**attributes.reject { _2 in NIL_ACCOUNT | NIL_ENVIRONMENT }) } - account { nil } + account { NIL_ACCOUNT } environment { NIL_ENVIRONMENT } release { build(:release, account:, environment:) } diff --git a/spec/factories/release_upload_link.rb b/spec/factories/release_upload_link.rb index 6681f53074..bee875ff22 100644 --- a/spec/factories/release_upload_link.rb +++ b/spec/factories/release_upload_link.rb @@ -2,9 +2,9 @@ FactoryBot.define do factory :release_upload_link do - initialize_with { new(**attributes.reject { NIL_ENVIRONMENT == _2 }) } + initialize_with { new(**attributes.reject { _2 in NIL_ACCOUNT | NIL_ENVIRONMENT }) } - account { nil } + account { NIL_ACCOUNT } environment { NIL_ENVIRONMENT } release { build(:release, account:, environment:) } diff --git a/spec/factories/role.rb b/spec/factories/role.rb index db7e86fba3..34ff88ea4a 100644 --- a/spec/factories/role.rb +++ b/spec/factories/role.rb @@ -30,6 +30,10 @@ name { :read_only } end + trait :environment do + name { :environment } + end + trait :product do name { :product } end diff --git a/spec/lib/keygen/exporter_spec.rb b/spec/lib/keygen/exporter_spec.rb index 901823261c..a2f95ea9b8 100644 --- a/spec/lib/keygen/exporter_spec.rb +++ b/spec/lib/keygen/exporter_spec.rb @@ -10,6 +10,9 @@ let(:account_id) { account.id } # run association async destroys inline + before { Sidekiq::Testing.inline! } + after { Sidekiq::Testing.fake! } + around do |example| perform_enqueued_jobs { example.run } end diff --git a/spec/lib/keygen/importer_spec.rb b/spec/lib/keygen/importer_spec.rb index 1549b51488..3ee241b921 100644 --- a/spec/lib/keygen/importer_spec.rb +++ b/spec/lib/keygen/importer_spec.rb @@ -10,6 +10,9 @@ let(:account_id) { account.id } # run association async destroys inline + before { Sidekiq::Testing.inline! } + after { Sidekiq::Testing.fake! } + around do |example| perform_enqueued_jobs { example.run } end diff --git a/spec/models/policy_spec.rb b/spec/models/policy_spec.rb index 573f803451..5cfe7acf00 100644 --- a/spec/models/policy_spec.rb +++ b/spec/models/policy_spec.rb @@ -75,6 +75,9 @@ end context 'on update' do + before { Sidekiq::Testing.inline! } + after { Sidekiq::Testing.fake! } + it 'should denormalize product to licenses' do product = create(:product, account:) policy = create(:policy, account:, licenses: build_list(:license, 10, account:)) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8ec71fdba9..3a3f4a4637 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -310,6 +310,9 @@ end describe '#destroy' do + before { Sidekiq::Testing.inline! } + after { Sidekiq::Testing.fake! } + it 'should destroy owned machines' do user = create(:user, account:) license = create(:license, account:) diff --git a/spec/services/broadcast_event_service_spec.rb b/spec/services/broadcast_event_service_spec.rb index d78bbe439a..42f42ba9b8 100644 --- a/spec/services/broadcast_event_service_spec.rb +++ b/spec/services/broadcast_event_service_spec.rb @@ -43,9 +43,10 @@ def jsonapi_render(resource, account:, meta: nil, **options) .to_json end - before do - Sidekiq::Testing.inline! + before { Sidekiq::Testing.inline! } + after { Sidekiq::Testing.fake! } + before do # FIXME(ezekg) Instantiate models so Active Record lazy loads the model's # attributes otherwise the mocks below will fail WebhookEvent.new @@ -63,10 +64,6 @@ def jsonapi_render(resource, account:, meta: nil, **options) } end - after do - Sidekiq::Worker.clear_all - end - it 'should create a new webhook event' do event = create_webhook_event!(account, resource) From 09ab2b4c43c20ec5bbd58f01342251a51835ef7b Mon Sep 17 00:00:00 2001 From: Zeke Gabrielse Date: Tue, 12 Nov 2024 09:58:55 -0600 Subject: [PATCH 2/4] hack: fix modifying unmodifiable/loaded relation --- .../api/v1/release_engines/npm/package_metadata_controller.rb | 2 +- .../api/v1/release_engines/pypi/simple_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb b/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb index 6e939c180a..537ffd80be 100644 --- a/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb +++ b/app/controllers/api/v1/release_engines/npm/package_metadata_controller.rb @@ -20,7 +20,7 @@ def show to: :index? # FIXME(ezekg) https://github.com/brianhempel/active_record_union/issues/35 - last_modified = artifacts.maximum(:"#{artifacts.table_name}.updated_at") + last_modified = artifacts.collect(&:updated_at).max latest = artifacts.first metadata = artifacts.reduce( name: package.key, diff --git a/app/controllers/api/v1/release_engines/pypi/simple_controller.rb b/app/controllers/api/v1/release_engines/pypi/simple_controller.rb index a701b57200..6fd462f8d3 100644 --- a/app/controllers/api/v1/release_engines/pypi/simple_controller.rb +++ b/app/controllers/api/v1/release_engines/pypi/simple_controller.rb @@ -33,7 +33,7 @@ def show to: :index? # FIXME(ezekg) https://github.com/brianhempel/active_record_union/issues/35 - last_modified = artifacts.maximum(:"#{artifacts.table_name}.updated_at") + last_modified = artifacts.collect(&:updated_at).max return unless stale?(artifacts, last_modified:, cache_control: { max_age: 1.day, private: true }) From 58e60b01e4bc4b4e48e066df63c96937e28f0ef2 Mon Sep 17 00:00:00 2001 From: Zeke Gabrielse Date: Tue, 12 Nov 2024 10:11:07 -0600 Subject: [PATCH 3/4] add deprecation for v1 tokens --- Gemfile | 2 +- Gemfile.lock | 2 +- app/models/concerns/tokenable.rb | 8 +++++++- app/services/license_key_lookup_service.rb | 2 ++ app/services/token_lookup_service.rb | 2 ++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index caea774fc8..b92973f5ed 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ ruby '3.3.6' gem 'rails', '~> 7.2.2' gem 'pg', '~> 1.3.4' gem 'puma', '~> 6.4.3' -gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '3.1.17' gem 'rack', '~> 2.2.8.1' gem 'rack-timeout', require: 'rack/timeout/base' unless ENV.key?('NO_RACK_ATTACK') diff --git a/Gemfile.lock b/Gemfile.lock index e421d2ee1f..b424765897 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -530,7 +530,7 @@ DEPENDENCIES ar_lazy_preload (~> 2.0) aws-sdk-s3 (~> 1) barnes - bcrypt (~> 3.1.7) + bcrypt (= 3.1.17) bullet (~> 7.2) byebug compact_index diff --git a/app/models/concerns/tokenable.rb b/app/models/concerns/tokenable.rb index 16bf71317f..ee795186ab 100644 --- a/app/models/concerns/tokenable.rb +++ b/app/models/concerns/tokenable.rb @@ -57,7 +57,13 @@ def compare_hashed_token(attribute, token, version: ALGO_VERSION) case version when "v1" bcrypt = BCrypt::Password.new a - b = BCrypt::Engine.hash_secret Digest::SHA256.digest(token), bcrypt.salt + digest = Digest::SHA256.digest(token) + + if digest.include?("\x00") # null byte + Keygen.logger.warn { "[tokenable] v1 token must be regenerated: tokenable_type=#{self.class.name.inspect} tokenable_id=#{id.inspect} tokenable_attr=#{attribute.inspect}" } + end + + b = BCrypt::Engine.hash_secret digest, bcrypt.salt when "v2" b = OpenSSL::HMAC.hexdigest "SHA512", account.private_key, token when "v3" diff --git a/app/services/license_key_lookup_service.rb b/app/services/license_key_lookup_service.rb index 46fc0f0e54..f4e05a53be 100644 --- a/app/services/license_key_lookup_service.rb +++ b/app/services/license_key_lookup_service.rb @@ -20,6 +20,8 @@ def call license = licenses.find_by(id: matches[:license_id]) if license&.compare_hashed_token(:key, key, version: 'v1') + Keygen.logger.warn { "[license-key-lookup-service] v1 keys are deprecated and must be regenerated: license_id=#{license.id.inspect}" } + license else nil diff --git a/app/services/token_lookup_service.rb b/app/services/token_lookup_service.rb index af611b1094..0874b71a42 100644 --- a/app/services/token_lookup_service.rb +++ b/app/services/token_lookup_service.rb @@ -32,6 +32,8 @@ def call instance = tokens.find_by(id: m[:token_id]) if instance&.compare_hashed_token(:digest, token, version: 'v1') + Keygen.logger.warn { "[token-lookup-service] v1 tokens are deprecated and must be regenerated: bearer_type=#{instance.bearer.class.name.inspect} bearer_id=#{instance.bearer.id.inspect} token_id=#{instance.id.inspect}" } + instance else nil From 86bd9d6694b726fbbe5e84bad2054a5565421372 Mon Sep 17 00:00:00 2001 From: Zeke Gabrielse Date: Tue, 12 Nov 2024 10:55:57 -0600 Subject: [PATCH 4/4] add jit v1 token rehashing --- app/models/concerns/tokenable.rb | 12 +- app/services/license_key_lookup_service.rb | 11 ++ app/services/token_lookup_service.rb | 11 ++ .../license_key_lookup_service_spec.rb | 111 +++++++++++++++ spec/services/token_lookup_service_spec.rb | 128 ++++++++++++++++++ 5 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 spec/services/license_key_lookup_service_spec.rb create mode 100644 spec/services/token_lookup_service_spec.rb diff --git a/app/models/concerns/tokenable.rb b/app/models/concerns/tokenable.rb index ee795186ab..eb5b26d215 100644 --- a/app/models/concerns/tokenable.rb +++ b/app/models/concerns/tokenable.rb @@ -28,7 +28,7 @@ def generate_hashed_token(attribute, length: 64, version: ALGO_VERSION) # length, since the first 66 chars of our string consist of the account # and the bearer's UUID. This lets us use larger tokens (as seen here) # and avoid the nasty truncation. - res = BCrypt::Password.create Digest::SHA256.digest(raw) + res = BCrypt::Password.create Digest::SHA256.hexdigest(raw) when "v2" raw = SecureRandom.hex(length).gsub /.{#{version.length}}\z/, version raw = yield raw if block_given? @@ -53,6 +53,7 @@ def compare_hashed_token(attribute, token, version: ALGO_VERSION) a = self.send attribute b = nil + c = nil case version when "v1" @@ -64,6 +65,9 @@ def compare_hashed_token(attribute, token, version: ALGO_VERSION) end b = BCrypt::Engine.hash_secret digest, bcrypt.salt + + # FIXME(ezekg) support rehashing: https://github.com/bcrypt-ruby/bcrypt-ruby/pull/168 + c = BCrypt::Engine.hash_secret Digest::SHA256.hexdigest(token), bcrypt.salt when "v2" b = OpenSSL::HMAC.hexdigest "SHA512", account.private_key, token when "v3" @@ -72,7 +76,11 @@ def compare_hashed_token(attribute, token, version: ALGO_VERSION) raise NotImplementedError.new "token #{version} not implemented" end - secure_compare a, b + unless c.nil? + secure_compare(a, b) || secure_compare(a, c) + else + secure_compare(a, b) + end rescue false end diff --git a/app/services/license_key_lookup_service.rb b/app/services/license_key_lookup_service.rb index f4e05a53be..c7fffd0791 100644 --- a/app/services/license_key_lookup_service.rb +++ b/app/services/license_key_lookup_service.rb @@ -22,6 +22,17 @@ def call if license&.compare_hashed_token(:key, key, version: 'v1') Keygen.logger.warn { "[license-key-lookup-service] v1 keys are deprecated and must be regenerated: license_id=#{license.id.inspect}" } + # FIXME(ezekg) jit rehash key: https://github.com/bcrypt-ruby/bcrypt-ruby/pull/168 + digest = BCrypt::Engine.hash_secret( + Digest::SHA256.hexdigest(key), + BCrypt::Password.new(license.key).salt, # reuse salt + ) + unless license.send(:secure_compare, digest, license.key) + Keygen.logger.warn { "[license-key-lookup-service] rehashing key: license_id=#{license.id.inspect}" } + + license.update!(key: digest) + end + license else nil diff --git a/app/services/token_lookup_service.rb b/app/services/token_lookup_service.rb index 0874b71a42..8b60ae91b4 100644 --- a/app/services/token_lookup_service.rb +++ b/app/services/token_lookup_service.rb @@ -34,6 +34,17 @@ def call if instance&.compare_hashed_token(:digest, token, version: 'v1') Keygen.logger.warn { "[token-lookup-service] v1 tokens are deprecated and must be regenerated: bearer_type=#{instance.bearer.class.name.inspect} bearer_id=#{instance.bearer.id.inspect} token_id=#{instance.id.inspect}" } + # FIXME(ezekg) jit rehash token: https://github.com/bcrypt-ruby/bcrypt-ruby/pull/168 + digest = BCrypt::Engine.hash_secret( + Digest::SHA256.hexdigest(token), + BCrypt::Password.new(instance.digest).salt, # reuse salt + ) + unless instance.send(:secure_compare, digest, instance.digest) + Keygen.logger.warn { "[license-key-lookup-service] rehashing token: token_id=#{instance.id.inspect}" } + + instance.update!(digest:) + end + instance else nil diff --git a/spec/services/license_key_lookup_service_spec.rb b/spec/services/license_key_lookup_service_spec.rb new file mode 100644 index 0000000000..254b3c4995 --- /dev/null +++ b/spec/services/license_key_lookup_service_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'spec_helper' + +describe LicenseKeyLookupService do + let(:account) { create(:account) } + + context 'when key is a legacy encrypted key' do + let(:key) { 'bdcecdc23c0f48229f77357151d1e67f-8e1ae3236636bfe57893b835871553eb-524ffec53ad05c14bbf3339eec741300-295512a0539cd0863abe42ddc29cf9v1' } + let(:license_id) { key.split('-').first } + + context 'when key is a good key' do + let(:digest) { '$2a$12$kh33FHu.TWYMZlWldcOMKet4YiVNqMHq8/A343/GTfPz.NRCPq2Q.' } + + subject! { + create(:license, id: license_id, key: digest, account:) + } + + it 'should fail unencrypted lookup' do + record = described_class.call(key:, account:) + + expect(record).to be nil + end + + it 'should pass encrypted lookup' do + record = described_class.call(key:, account:, legacy_encrypted: true) + + expect(record).to eq subject + end + + it 'should not rehash' do + expect { described_class.call(key:, account:, legacy_encrypted: true) }.to_not( + change { subject.reload.key }, + ) + end + end + + context 'when key is a bad key' do + let(:digest) { '$2a$12$kh33FHu.TWYMZlWldcOMKevM0Jj4VrN/8.EbfAj4cBEcS51A7hYUK' } + + subject!{ + create(:license, id: license_id, key: digest, account:) + } + + it 'should fail unencrypted lookup' do + record = described_class.call(key:, account:) + + expect(record).to be nil + end + + it 'should pass encrypted lookup' do + record = described_class.call(key:, account:, legacy_encrypted: true) + + expect(record).to eq subject + end + + it 'should rehash' do + expect { described_class.call(key:, account:, legacy_encrypted: true) }.to( + change { subject.reload.key }, + ) + + # sanity check + ok = subject.compare_hashed_token(:key, key, version: 'v1') + expect(ok).to be true + end + end + end + + context 'when key is a normal key' do + let(:key) { 'FC1ECF-659627-58D58E-42130E-ADD88F-V3' } + + subject! { + create(:license, key:, account:) + } + + it 'should pass unencrypted lookup' do + record = described_class.call(key:, account:) + + expect(record).to eq subject + end + + it 'should fail encrypted lookup' do + record = described_class.call(key:, account:, legacy_encrypted: true) + + expect(record).to be nil + end + + it 'should not rehash' do + expect { described_class.call(key:, account:) }.to_not( + change { subject.reload.key }, + ) + end + end + + context 'when key is invalid' do + let(:key) { '6798CE-A9478B-42027F-4046E8-3FFD66-V3' } + + it 'should fail unencrypted lookup' do + record = described_class.call(key:, account:) + + expect(record).to be nil + end + + it 'should fail encrypted lookup' do + record = described_class.call(key:, account:, legacy_encrypted: true) + + expect(record).to be nil + end + end +end diff --git a/spec/services/token_lookup_service_spec.rb b/spec/services/token_lookup_service_spec.rb new file mode 100644 index 0000000000..3e3ecd637d --- /dev/null +++ b/spec/services/token_lookup_service_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'spec_helper' + +describe TokenLookupService do + context 'when token is a v1 token' do + let(:token) { '1780aeccbbb14b4dad0cf8b8165faba2.52f61713fd0c49a2a6537e3142d043cb.3e2373c60b7df8514ff763dad707faad44956c2421fdb2588fff2a73099b07v1' } + let(:account_id) { token.split('.').first } + let(:token_id) { token.split('.').second } + let(:account) { create(:account, id: account_id) } + let(:product) { create(:product, account:) } + + context 'when token is a good token' do + let(:digest) { '$2a$12$7sgr9E9ZURyXzttcuIl/muYRww.wHxNgCpgUHsKODV2ZXpPWiqLkq' } + + subject! { + record = create(:token, id: token_id, bearer: product, account:) + record.update!(digest:) + record + } + + it 'should pass lookup' do + record = described_class.call(token:, account:) + + expect(record).to eq subject + end + + it 'should not rehash' do + expect { described_class.call(token:, account:) }.to_not( + change { subject.reload.digest }, + ) + end + end + + context 'when token is a bad token' do + let(:digest) { '$2a$12$7sgr9E9ZURyXzttcuIl/muSl7JViEcBcytnbouQooX22PP43AzZ4C' } + + subject! { + record = create(:token, id: token_id, bearer: product, account:) + record.update!(digest:) + record + } + + it 'should pass lookup' do + record = described_class.call(token:, account:) + + expect(record).to eq subject + end + + it 'should rehash' do + expect { described_class.call(token:, account:) }.to( + change { subject.reload.digest }, + ) + + # sanity check + ok = subject.compare_hashed_token(:digest, token, version: 'v1') + expect(ok).to be true + end + end + end + + context 'when token is a v2 token' do + let(:token) { 'prod-c5cf2bc0986bb90cae46dade120172c1451abfc3429a4f9057a9786738d192v2' } + let(:digest) { '0c8f765a79a45992c1031f8cc69b858a960257ee16bfd67e26487913565e04337c25897b62011d45b75a7bc20a7e16057fef2573d32f91c1f264f595cfdd2a04' } + let(:account) { create(:account) } + let(:product) { create(:product, account:) } + + subject! { + record = create(:token, bearer: product, account:) + record.update!(digest:) + record + } + + it 'should pass lookup' do + record = described_class.call(token:, account:) + + expect(record).to eq subject + end + + it 'should not rehash' do + expect { described_class.call(token:, account:) }.to_not( + change { subject.reload.digest }, + ) + end + end + + context 'when token is a v3 token' do + let(:secret_key) { '9ef57edb7f2a91bed90805744cdbf4ece13905ad2670bc1f54212074043cede710ca6a60c95114cf16fbbef7d696b0c61649250a9baf14aab878e5f85f769836' } + let(:token) { 'prod-580839d4388a61216398c83f2c987a80dee472f556db7bc9aa66775574de54f4v3' } + let(:digest) { '0e555b39a256a319674356f834d39de70c06b0d0ef94f4b8f9c7d34bdc287f93' } + let(:product) { create(:product, account:) } + let(:account) { + record = create(:account) + record.update(secret_key:) + record + } + + subject! { + record = create(:token, bearer: product, account:) + record.update!(digest:) + record + } + + it 'should pass lookup' do + record = described_class.call(token:, account:) + + expect(record).to eq subject + end + + it 'should not rehash' do + expect { described_class.call(token:, account:) }.to_not( + change { subject.reload.digest }, + ) + end + end + + context 'when token is invalid' do + let(:token) { 'user-f25149752c704e0c8281150c6751ba77052111ad14214d5adacd0a80d45cc957v3' } + let(:account) { create(:account) } + + it 'should fail lookup' do + record = described_class.call(token:, account:) + + expect(record).to be nil + end + end +end