From 6ee3a7cd9ba7c50c5f3dacbbcaf722de865eda8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:29:22 +0000 Subject: [PATCH 001/112] Bump shoryuken from 5.3.2 to 6.1.0 Bumps [shoryuken](https://github.com/phstc/shoryuken) from 5.3.2 to 6.1.0. - [Changelog](https://github.com/ruby-shoryuken/shoryuken/blob/main/CHANGELOG.md) - [Commits](https://github.com/phstc/shoryuken/commits) --- updated-dependencies: - dependency-name: shoryuken dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 04ece0f3847..e81a36f01f7 100644 --- a/Gemfile +++ b/Gemfile @@ -34,7 +34,7 @@ gem "rbtrace", "~> 0.4.8" gem "rdoc", "~> 6.5" gem "roadie-rails", "~> 3.0" gem "ruby-magic", "~> 0.6" -gem "shoryuken", "~> 5.0", require: false +gem "shoryuken", "~> 6.1", require: false gem "statsd-instrument", "~> 3.5" gem "validates_formatting_of", "~> 0.9" gem "opensearch-dsl", "~> 0.2.0" diff --git a/Gemfile.lock b/Gemfile.lock index 189aacbe924..1d7eabe2d01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -603,7 +603,7 @@ GEM semantic (1.6.1) semantic_logger (4.15.0) concurrent-ruby (~> 1.0) - shoryuken (5.3.2) + shoryuken (6.1.0) aws-sdk-core (>= 2) concurrent-ruby thor @@ -789,7 +789,7 @@ DEPENDENCIES ruby-magic (~> 0.6) searchkick (~> 5.2) selenium-webdriver (~> 4.8) - shoryuken (~> 5.0) + shoryuken (~> 6.1) shoulda (~> 4.0) simplecov (~> 0.22) simplecov-cobertura (~> 2.1) From 1c64ae5362daa1babb6d02bfbe63c344f67c8218 Mon Sep 17 00:00:00 2001 From: JP <85654561+jp524@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:36:44 -0800 Subject: [PATCH 002/112] Update installation instructions OS X --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a6f2fe06e7..3d399e72976 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,7 +94,7 @@ Follow the instructions below on how to install Bundler and setup the database. * Setup information: `brew info postgresql` * Install memcached: `brew install memcached` * Show all memcached options: `memcached -h` -* Install Google-Chrome: `brew cask install google-chrome` +* Install Google-Chrome: `brew install google-chrome --cask` #### Environment (Linux - Debian/Ubuntu) From 00962118577f934c85ba32044a292b61f3b307cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:28:08 -0800 Subject: [PATCH 003/112] Bump ddtrace from 1.16.1 to 1.16.2 (#4202) Bumps [ddtrace](https://github.com/DataDog/dd-trace-rb) from 1.16.1 to 1.16.2. - [Release notes](https://github.com/DataDog/dd-trace-rb/releases) - [Changelog](https://github.com/DataDog/dd-trace-rb/blob/master/CHANGELOG.md) - [Commits](https://github.com/DataDog/dd-trace-rb/compare/v1.16.1...v1.16.2) --- updated-dependencies: - dependency-name: ddtrace dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e81a36f01f7..17d1acb9179 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem "aws-sdk-sqs", "~> 1.67" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" gem "dalli", "~> 3.2" -gem "ddtrace", "~> 1.10", require: "ddtrace/auto_instrument" +gem "ddtrace", "~> 1.16", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" gem "google-protobuf", "~> 3.22" gem "faraday", "~> 1.10" diff --git a/Gemfile.lock b/Gemfile.lock index 1d7eabe2d01..925cc45a10a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -177,7 +177,7 @@ GEM datadog-ci (0.3.0) msgpack date (3.3.3) - ddtrace (1.16.1) + ddtrace (1.16.2) datadog-ci (~> 0.3.0) debase-ruby_core_source (= 3.2.2) libdatadog (~> 5.0.0.1.0) @@ -726,7 +726,7 @@ DEPENDENCIES compact_index (~> 0.14.0) dalli (~> 3.2) dartsass-sprockets (~> 3.0) - ddtrace (~> 1.10) + ddtrace (~> 1.16) derailed_benchmarks (~> 2.1) dogstatsd-ruby (~> 5.5) dotenv-rails (~> 2.8) From dbae043ec991813ff51e48002669d375da582e01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:37:49 +0100 Subject: [PATCH 004/112] Bump openid_connect, faraday and faraday_middleware-aws-sigv4 (#4195) * Bump openid_connect, faraday and faraday_middleware-aws-sigv4 Bumps [openid_connect](https://github.com/nov/openid_connect), [faraday](https://github.com/lostisland/faraday) and [faraday_middleware-aws-sigv4](https://github.com/winebarrel/faraday_middleware-aws-sigv4). These dependencies needed to be updated together. Updates `openid_connect` from 1.4.2 to 2.2.0 - [Release notes](https://github.com/nov/openid_connect/releases) - [Changelog](https://github.com/nov/openid_connect/blob/master/CHANGELOG.md) - [Commits](https://github.com/nov/openid_connect/compare/v1.4.2...v2.2.0) Updates `faraday` from 1.10.3 to 2.7.11 - [Release notes](https://github.com/lostisland/faraday/releases) - [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md) - [Commits](https://github.com/lostisland/faraday/compare/v1.10.3...v2.7.11) Updates `faraday_middleware-aws-sigv4` from 0.6.1 to 1.0.1 - [Commits](https://github.com/winebarrel/faraday_middleware-aws-sigv4/compare/v0.6.1...v1.0.1) --- updated-dependencies: - dependency-name: openid_connect dependency-type: direct:production update-type: version-update:semver-major - dependency-name: faraday dependency-type: direct:production update-type: version-update:semver-major - dependency-name: faraday_middleware-aws-sigv4 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Remove reference to remove exception class --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Samuel Giddins --- Gemfile | 6 +-- Gemfile.lock | 83 ++++++++++++++++++----------------------- lib/elastic_searcher.rb | 3 +- 3 files changed, 40 insertions(+), 52 deletions(-) diff --git a/Gemfile b/Gemfile index 17d1acb9179..4a66d4129f0 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ gem "dalli", "~> 3.2" gem "ddtrace", "~> 1.16", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" gem "google-protobuf", "~> 3.22" -gem "faraday", "~> 1.10" +gem "faraday", "~> 2.7" gem "good_job", "~> 3.17" gem "gravtastic", "~> 3.2" gem "high_voltage", "~> 3.1" @@ -25,7 +25,7 @@ gem "octokit", "~> 8.0" gem "omniauth-github", "~> 2.0" gem "omniauth", "~> 2.1" gem "omniauth-rails_csrf_protection", "~> 1.0" -gem "openid_connect", "~> 1.4" +gem "openid_connect", "~> 2.2" gem "pg", "~> 1.4" gem "puma", "~> 6.1" gem "rack", "~> 2.2" @@ -40,7 +40,7 @@ gem "validates_formatting_of", "~> 0.9" gem "opensearch-dsl", "~> 0.2.0" gem "opensearch-ruby", "~> 1.0" gem "searchkick", "~> 5.2" -gem "faraday_middleware-aws-sigv4", "~> 0.6" +gem "faraday_middleware-aws-sigv4", "~> 1.0" gem "xml-simple", "~> 1.1" gem "compact_index", "~> 0.14.0" gem "sprockets-rails", "~> 3.4" diff --git a/Gemfile.lock b/Gemfile.lock index 925cc45a10a..2f8419bdf30 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -119,6 +119,7 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.6.1) aws-eventstream (~> 1, >= 1.0.2) + base64 (0.2.0) bcrypt (3.1.19) benchmark-ips (2.12.0) bindata (2.4.15) @@ -176,7 +177,7 @@ GEM tilt datadog-ci (0.3.0) msgpack - date (3.3.3) + date (3.3.4) ddtrace (1.16.2) datadog-ci (~> 0.3.0) debase-ruby_core_source (= 3.2.2) @@ -219,32 +220,16 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) + faraday (2.7.11) + base64 + faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware-aws-sigv4 (0.6.1) + faraday-follow_redirects (0.3.0) + faraday (>= 1, < 3) + faraday-net_http (3.0.2) + faraday_middleware-aws-sigv4 (1.0.1) aws-sigv4 (~> 1.0) - faraday (>= 1.8, < 2) + faraday (>= 2.0, < 3) ffi (1.16.3) ffi-compiler (1.0.1) ffi (>= 1.0.0) @@ -285,7 +270,6 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httpclient (2.8.3) i18n (1.14.1) concurrent-ruby (~> 1.0) inline_svg (1.9.0) @@ -299,11 +283,12 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.6.3) - json-jwt (1.15.3) + json-jwt (1.16.3) activesupport (>= 4.2) aes_key_wrap bindata - httpclient + faraday (~> 2.0) + faraday-follow_redirects jwt (2.7.0) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -381,15 +366,14 @@ GEM msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.3.0) - net-imap (0.3.7) + net-imap (0.4.5) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol nio4r (2.5.9) nokogiri (1.15.4) @@ -418,17 +402,19 @@ GEM omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - openid_connect (1.4.2) + openid_connect (2.2.0) activemodel attr_required (>= 1.0.0) - json-jwt (>= 1.15.0) + faraday (~> 2.0) + faraday-follow_redirects + json-jwt (>= 1.16) net-smtp - rack-oauth2 (~> 1.21) - swd (~> 1.3) + rack-oauth2 (~> 2.2) + swd (~> 2.0) tzinfo validate_email validate_url - webfinger (~> 1.2) + webfinger (~> 2.0) opensearch-api (1.0.0) multi_json opensearch-dsl (0.2.1) @@ -474,10 +460,11 @@ GEM rack (2.2.8) rack-attack (6.7.0) rack (>= 1.0, < 4) - rack-oauth2 (1.21.3) + rack-oauth2 (2.2.0) activesupport attr_required - httpclient + faraday (~> 2.0) + faraday-follow_redirects json-jwt (>= 1.11.0) rack (>= 2.1.0) rack-protection (3.0.5) @@ -636,17 +623,18 @@ GEM stringio (3.0.8) strong_migrations (1.6.4) activerecord (>= 5.2) - swd (1.3.0) + swd (2.0.2) activesupport (>= 3) attr_required (>= 0.0.5) - httpclient (>= 2.4) + faraday (~> 2.0) + faraday-follow_redirects tailwindcss-rails (2.0.32) railties (>= 6.0.0) terser (1.1.19) execjs (>= 0.3.0, < 3) thor (1.3.0) tilt (2.2.0) - timeout (0.4.0) + timeout (0.4.1) toxiproxy (2.0.2) tpm-key_attestation (0.12.0) bindata (~> 2.4) @@ -689,9 +677,10 @@ GEM openssl (>= 2.2) safety_net_attestation (~> 0.4.0) tpm-key_attestation (~> 0.12.0) - webfinger (1.2.0) + webfinger (2.1.2) activesupport - httpclient (>= 2.4) + faraday (~> 2.0) + faraday-follow_redirects webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -731,8 +720,8 @@ DEPENDENCIES dogstatsd-ruby (~> 5.5) dotenv-rails (~> 2.8) factory_bot_rails (~> 6.2) - faraday (~> 1.10) - faraday_middleware-aws-sigv4 (~> 0.6) + faraday (~> 2.7) + faraday_middleware-aws-sigv4 (~> 1.0) good_job (~> 3.17) google-protobuf (~> 3.22) gravtastic (~> 3.2) @@ -759,7 +748,7 @@ DEPENDENCIES omniauth (~> 2.1) omniauth-github (~> 2.0) omniauth-rails_csrf_protection (~> 1.0) - openid_connect (~> 1.4) + openid_connect (~> 2.2) opensearch-dsl (~> 0.2.0) opensearch-ruby (~> 1.0) pg (~> 1.4) diff --git a/lib/elastic_searcher.rb b/lib/elastic_searcher.rb index ac59b178ac4..8436e740ccb 100644 --- a/lib/elastic_searcher.rb +++ b/lib/elastic_searcher.rb @@ -4,8 +4,7 @@ class ElasticSearcher Faraday::TimeoutError, Searchkick::Error, OpenSearch::Transport::Transport::Error, - Errno::ECONNRESET, - HTTPClient::KeepAliveDisconnected + Errno::ECONNRESET ].freeze SearchNotAvailableError = Class.new(StandardError) From 3820e93146490e0d5058affa6ea1eb170507d5c6 Mon Sep 17 00:00:00 2001 From: Martin Emde Date: Tue, 14 Nov 2023 09:59:44 -0800 Subject: [PATCH 005/112] Return Repr-Digest from compact index, in addition to Digest (#4174) Repr-Digest is the digest of the entire representation, the full file. Continue to support Digest for the time being, even though there are no official or known clients that use it. --- .../api/compact_index_controller.rb | 6 ++- test/integration/api/compact_index_test.rb | 37 ++++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/compact_index_controller.rb b/app/controllers/api/compact_index_controller.rb index 92d153a9a75..386e3cad3c1 100644 --- a/app/controllers/api/compact_index_controller.rb +++ b/app/controllers/api/compact_index_controller.rb @@ -28,8 +28,10 @@ def info private def render_range(response_body) - headers["ETag"] = '"' << Digest::MD5.hexdigest(response_body) << '"' - headers["Digest"] = "sha-256=#{Digest::SHA256.base64digest(response_body)}" + headers["ETag"] = %("#{Digest::MD5.hexdigest(response_body)}") + digest = Digest::SHA256.base64digest(response_body) + headers["Digest"] = "sha-256=#{digest}" + headers["Repr-Digest"] = "sha-256=:#{digest}:" headers["Accept-Ranges"] = "bytes" ranges = Rack::Utils.byte_ranges(request.env, response_body.bytesize) diff --git a/test/integration/api/compact_index_test.rb b/test/integration/api/compact_index_test.rb index 16f16fda6f5..cc86fe99874 100644 --- a/test/integration/api/compact_index_test.rb +++ b/test/integration/api/compact_index_test.rb @@ -3,11 +3,11 @@ class CompactIndexTest < ActionDispatch::IntegrationTest def etag(body) - '"' << Digest::MD5.hexdigest(body) << '"' + %("#{Digest::MD5.hexdigest(body)}") end def digest(body) - "sha-256=#{Base64.encode64(Digest::SHA256.digest(body)).strip}" + Digest::SHA256.base64digest(body) end setup do @@ -62,10 +62,12 @@ def digest(body) assert_response :success expected_body = "---\ngemA\ngemA1\ngemA2\ngemB\n" + expected_digest = digest(expected_body) assert_equal expected_body, @response.body assert_equal etag(expected_body), @response.headers["ETag"] - assert_equal digest(expected_body), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] assert_equal %w[gemA gemA1 gemA2 gemB], Rails.cache.read("names") end @@ -74,9 +76,11 @@ def digest(body) assert_response 206 full_body = "---\ngemA\ngemA1\ngemA2\ngemB\n" + expected_digest = digest(full_body) assert_equal etag(full_body), @response.headers["ETag"] - assert_equal digest(full_body), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] assert_equal "gemA2\ngemB\n", @response.body end @@ -87,25 +91,29 @@ def digest(body) gem_b_match = "gemB 1.0.0 qw2dwe\n" get versions_path + expected_digest = digest(@response.body) assert_response :success assert_match file_contents, @response.body assert_match(/#{gem_b_match}#{gem_a_match}/, @response.body) assert_equal etag(@response.body), @response.headers["ETag"] - assert_equal digest(@response.body), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] end test "/versions partial response" do get versions_path full_response_body = @response.body partial_body = "1.0.0 013we2\ngemA 2.0.0 1cf94r\ngemA 1.2.0 13q4es\ngemA 2.1.0 e217fz\n" + expected_digest = digest(full_response_body) get versions_path, env: { range: "bytes=229-" } assert_response 206 assert_equal partial_body, @response.body assert_equal etag(full_response_body), @response.headers["ETag"] - assert_equal digest(full_response_body), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] end test "/versions updates on gem yank" do @@ -121,10 +129,13 @@ def digest(body) get versions_path full_response_body = @response.body + expected_digest = digest(full_response_body) + get versions_path, env: { range: "bytes=206-" } assert_equal etag(full_response_body), @response.headers["ETag"] - assert_equal digest(full_response_body), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] assert_equal expected, @response.body end @@ -143,13 +154,15 @@ def digest(body) 1.2.0 |checksum:b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78,ruby:>= 2.0.0,rubygems:>1.9 2.1.0 gemA1:= 1.0.0,gemA2:= 1.0.0|checksum:b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78,ruby:>= 2.0.0,rubygems:>=2.0 VERSIONS_FILE + expected_digest = digest(expected) get info_path(gem_name: "gemA") assert_response :success assert_equal expected, @response.body assert_equal etag(expected), @response.headers["ETag"] - assert_equal digest(expected), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] assert_equal expected, CompactIndex.info(Rails.cache.read("info/gemA")) end @@ -182,13 +195,15 @@ def digest(body) --- 1.0.0 |checksum:b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78,ruby:>= 2.0.0,rubygems:>= 2.6.3 VERSIONS_FILE + expected_digest = digest(expected) get info_path(gem_name: "gemC") assert_response :success assert_equal(expected, @response.body) assert_equal etag(expected), @response.headers["ETag"] - assert_equal digest(expected), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] end test "/info with nonexistent gem" do @@ -217,12 +232,14 @@ def digest(body) --- 1.0.0 aaab:>= 0,aaab:~> 0.2,bbcc:= 1.0.0|checksum:b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78,ruby:>= 2.0.0,rubygems:>= 2.6.3 VERSIONS_FILE + expected_digest = digest(expected) get info_path(gem_name: "gemB") assert_response :success assert_equal(expected, @response.body) assert_equal etag(expected), @response.headers["ETag"] - assert_equal digest(expected), @response.headers["Digest"] + assert_equal "sha-256=#{expected_digest}", @response.headers["Digest"] + assert_equal "sha-256=:#{expected_digest}:", @response.headers["Repr-Digest"] end end From 6eb44c41cd8e1c639b6aaf07c0b14fcd78e0674c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:20:00 -0800 Subject: [PATCH 006/112] Bump good_job from 3.21.0 to 3.21.1 (#4206) Bumps [good_job](https://github.com/bensheldon/good_job) from 3.21.0 to 3.21.1. - [Release notes](https://github.com/bensheldon/good_job/releases) - [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md) - [Commits](https://github.com/bensheldon/good_job/compare/v3.21.0...v3.21.1) --- updated-dependencies: - dependency-name: good_job dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 4a66d4129f0..2f09ebb0210 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem "ddtrace", "~> 1.16", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" gem "google-protobuf", "~> 3.22" gem "faraday", "~> 2.7" -gem "good_job", "~> 3.17" +gem "good_job", "~> 3.21" gem "gravtastic", "~> 3.2" gem "high_voltage", "~> 3.1" gem "honeybadger", "~> 5.2" diff --git a/Gemfile.lock b/Gemfile.lock index 2f8419bdf30..82a63dc574e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,7 +241,7 @@ GEM ffi (~> 1.0) globalid (1.2.1) activesupport (>= 6.1) - good_job (3.21.0) + good_job (3.21.1) activejob (>= 6.0.0) activerecord (>= 6.0.0) concurrent-ruby (>= 1.0.2) @@ -329,7 +329,7 @@ GEM llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -722,7 +722,7 @@ DEPENDENCIES factory_bot_rails (~> 6.2) faraday (~> 2.7) faraday_middleware-aws-sigv4 (~> 1.0) - good_job (~> 3.17) + good_job (~> 3.21) google-protobuf (~> 3.22) gravtastic (~> 3.2) groupdate (~> 6.2) From 67ad67a07cd3051e0557e6bf8e31175fb9a4b83c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:25:31 -0800 Subject: [PATCH 007/112] Bump ruby/setup-ruby from 1.160.0 to 1.161.0 (#4205) Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.160.0 to 1.161.0. - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Commits](https://github.com/ruby/setup-ruby/compare/036ef458ddccddb148a2b9fb67e95a22fdbf728b...8575951200e472d5f2d95c625da0c7bec8217c42) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 026d6fbc7c5..e1f94598ee4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@036ef458ddccddb148a2b9fb67e95a22fdbf728b # v1.160.0 + - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 with: bundler-cache: true - name: Rubocop @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@036ef458ddccddb148a2b9fb67e95a22fdbf728b # v1.160.0 + - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 with: bundler-cache: true - name: Brakeman @@ -41,7 +41,7 @@ jobs: - name: login to Github Packages run: echo "${{ github.token }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@036ef458ddccddb148a2b9fb67e95a22fdbf728b # v1.160.0 + - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 with: bundler-cache: true - name: krane render From b5a321160afc32ac2fcf6fe011382d38018d8a40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:44:21 -0800 Subject: [PATCH 008/112] Bump google-protobuf from 3.25.0 to 3.25.1 (#4207) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 2f09ebb0210..a6c17300557 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ gem "clearance", "~> 2.6" gem "dalli", "~> 3.2" gem "ddtrace", "~> 1.16", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" -gem "google-protobuf", "~> 3.22" +gem "google-protobuf", "~> 3.25" gem "faraday", "~> 2.7" gem "good_job", "~> 3.21" gem "gravtastic", "~> 3.2" diff --git a/Gemfile.lock b/Gemfile.lock index 82a63dc574e..2ab86ed7176 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -248,7 +248,7 @@ GEM fugit (>= 1.1) railties (>= 6.0.0) thor (>= 0.14.1) - google-protobuf (3.25.0) + google-protobuf (3.25.1) gravtastic (3.2.6) groupdate (6.4.0) activesupport (>= 6.1) @@ -723,7 +723,7 @@ DEPENDENCIES faraday (~> 2.7) faraday_middleware-aws-sigv4 (~> 1.0) good_job (~> 3.21) - google-protobuf (~> 3.22) + google-protobuf (~> 3.25) gravtastic (~> 3.2) groupdate (~> 6.2) high_voltage (~> 3.1) From d630c5ed5cd3d0988f0b3c24971a41b22bba074f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:17:12 +0000 Subject: [PATCH 009/112] Bump rails_semantic_logger from 4.13.0 to 4.14.0 (#4208) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index a6c17300557..901296a7450 100644 --- a/Gemfile +++ b/Gemfile @@ -64,7 +64,7 @@ gem "groupdate", "~> 6.2" # Logging gem "amazing_print", "~> 1.4" -gem "rails_semantic_logger", "~> 4.13" +gem "rails_semantic_logger", "~> 4.14" group :assets, :development do gem "tailwindcss-rails", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index 2ab86ed7176..44a9c63aa9f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -506,7 +506,7 @@ GEM rails-i18n (7.0.8) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - rails_semantic_logger (4.13.0) + rails_semantic_logger (4.14.0) rack railties (>= 5.1) semantic_logger (~> 4.13) @@ -764,7 +764,7 @@ DEPENDENCIES rails-controller-testing (~> 1.0) rails-erd (~> 1.7) rails-i18n (~> 7.0) - rails_semantic_logger (~> 4.13) + rails_semantic_logger (~> 4.14) rbtrace (~> 0.4.8) rdoc (~> 6.5) roadie-rails (~> 3.0) From 159632f0894093591141dda7bfdf8bf8239d1bd0 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Mon, 20 Nov 2023 00:30:58 -0500 Subject: [PATCH 010/112] Add job to refresh all OIDC provider configs every 30m (#4211) So they do not need to be manually refreshed --- app/jobs/refresh_oidc_providers_job.rb | 9 +++++++++ config/initializers/good_job.rb | 6 ++++++ test/jobs/refresh_oidc_providers_job_test.rb | 16 ++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 app/jobs/refresh_oidc_providers_job.rb create mode 100644 test/jobs/refresh_oidc_providers_job_test.rb diff --git a/app/jobs/refresh_oidc_providers_job.rb b/app/jobs/refresh_oidc_providers_job.rb new file mode 100644 index 00000000000..c91b336e74e --- /dev/null +++ b/app/jobs/refresh_oidc_providers_job.rb @@ -0,0 +1,9 @@ +class RefreshOIDCProvidersJob < ApplicationJob + queue_as :default + + def perform(*_args) + OIDC::Provider.find_each do |provider| + RefreshOIDCProviderJob.perform_later(provider:) + end + end +end diff --git a/config/initializers/good_job.rb b/config/initializers/good_job.rb index 0e52a1ef4b4..4c3af798491 100644 --- a/config/initializers/good_job.rb +++ b/config/initializers/good_job.rb @@ -19,6 +19,12 @@ class: "MfaUsageStatsJob", set: { priority: 10 }, description: "Sending MFA usage metrics to statsd every hour" + }, + refresh_oidc_providers: { + cron: "every 30m", + class: "RefreshOIDCProvidersJob", + set: { priority: 10 }, + description: "Refreshing all OIDC provider configurations every 30m" } } diff --git a/test/jobs/refresh_oidc_providers_job_test.rb b/test/jobs/refresh_oidc_providers_job_test.rb new file mode 100644 index 00000000000..9ad6f795703 --- /dev/null +++ b/test/jobs/refresh_oidc_providers_job_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class RefreshOIDCProvidersJobTest < ActiveJob::TestCase + test "enqueues refresh jobs" do + provider1 = create(:oidc_provider) + provider2 = create(:oidc_provider) + + assert_enqueued_jobs 2, only: RefreshOIDCProviderJob do + assert_enqueued_with(job: RefreshOIDCProviderJob, args: [{ provider: provider1 }]) do + assert_enqueued_with(job: RefreshOIDCProviderJob, args: [{ provider: provider2 }]) do + RefreshOIDCProvidersJob.perform_now + end + end + end + end +end From ff77086914577dbdb241877727b9b5e0fb63e6f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:33:18 -0600 Subject: [PATCH 011/112] Bump faraday from 2.7.11 to 2.7.12 (#4218) Bumps [faraday](https://github.com/lostisland/faraday) from 2.7.11 to 2.7.12. - [Release notes](https://github.com/lostisland/faraday/releases) - [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md) - [Commits](https://github.com/lostisland/faraday/compare/v2.7.11...v2.7.12) --- updated-dependencies: - dependency-name: faraday dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 44a9c63aa9f..f5c25064cff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -220,7 +220,7 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faraday (2.7.11) + faraday (2.7.12) base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) From f3e359375d0c5969997f5f95ca875e587568c018 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:36:45 +0000 Subject: [PATCH 012/112] Bump aws-sdk-s3 from 1.136.0 to 1.138.0 (#4223) Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.136.0 to 1.138.0. - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-s3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 901296a7450..70f0a0be8c7 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" -gem "aws-sdk-s3", "~> 1.119" +gem "aws-sdk-s3", "~> 1.138" gem "aws-sdk-sqs", "~> 1.67" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" diff --git a/Gemfile.lock b/Gemfile.lock index f5c25064cff..b4323107de6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,16 +101,16 @@ GEM zeitwerk (>= 2.6.2) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.848.0) - aws-sdk-core (3.186.0) + aws-partitions (1.855.0) + aws-sdk-core (3.187.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.71.0) - aws-sdk-core (~> 3, >= 3.177.0) + aws-sdk-kms (1.72.0) + aws-sdk-core (~> 3, >= 3.184.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.136.0) + aws-sdk-s3 (1.138.0) aws-sdk-core (~> 3, >= 3.181.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) @@ -703,7 +703,7 @@ DEPENDENCIES amazing_print (~> 1.4) autoprefixer-rails (~> 10.4) avo (~> 2.42) - aws-sdk-s3 (~> 1.119) + aws-sdk-s3 (~> 1.138) aws-sdk-sqs (~> 1.67) bcrypt (~> 3.1, >= 3.1.18) bootsnap (~> 1.16) From e9311703ce78a066c58b3de2157e41f6c7473dc1 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 21 Nov 2023 23:43:27 -0800 Subject: [PATCH 013/112] Upload compact index /names to S3 For consistency with other compact index endpoints --- app/avo/actions/upload_names_file.rb | 18 ++++++++ app/avo/resources/rubygem_resource.rb | 1 + app/jobs/upload_names_file_job.rb | 42 +++++++++++++++++ app/models/deletion.rb | 1 + app/models/pusher.rb | 1 + .../api/v1/deletions_controller_test.rb | 1 + test/jobs/upload_names_file_job_test.rb | 45 +++++++++++++++++++ test/system/avo/rubygems_test.rb | 21 +++++++++ 8 files changed, 130 insertions(+) create mode 100644 app/avo/actions/upload_names_file.rb create mode 100644 app/jobs/upload_names_file_job.rb create mode 100644 test/jobs/upload_names_file_job_test.rb diff --git a/app/avo/actions/upload_names_file.rb b/app/avo/actions/upload_names_file.rb new file mode 100644 index 00000000000..1ab53b4ce95 --- /dev/null +++ b/app/avo/actions/upload_names_file.rb @@ -0,0 +1,18 @@ +class UploadNamesFile < BaseAction + self.name = "Upload Names File" + self.visible = lambda { + current_user.team_member?("rubygems-org") && view == :index + } + self.standalone = true + self.confirm_button_label = "Upload" + + class ActionHandler < ActionHandler + def handle_standalone + UploadNamesFileJob.perform_later + + succeed("Upload job scheduled") + + Version.last + end + end +end diff --git a/app/avo/resources/rubygem_resource.rb b/app/avo/resources/rubygem_resource.rb index f11e4b65e64..8de4d3be225 100644 --- a/app/avo/resources/rubygem_resource.rb +++ b/app/avo/resources/rubygem_resource.rb @@ -9,6 +9,7 @@ class RubygemResource < Avo::BaseResource action AddOwner action YankRubygem action UploadInfoFile + action UploadNamesFile action UploadVersionsFile class IndexedFilter < ScopeBooleanFilter; end diff --git a/app/jobs/upload_names_file_job.rb b/app/jobs/upload_names_file_job.rb new file mode 100644 index 00000000000..167791b4110 --- /dev/null +++ b/app/jobs/upload_names_file_job.rb @@ -0,0 +1,42 @@ +class UploadNamesFileJob < ApplicationJob + queue_with_priority PRIORITIES.fetch(:push) + + include GoodJob::ActiveJobExtensions::Concurrency + good_job_control_concurrency_with( + # Maximum number of jobs with the concurrency key to be + # concurrently enqueued (excludes performing jobs) + # + # Because the job only uses current state at time of perform, + # it makes no sense to enqueue more than one at a time + enqueue_limit: good_job_concurrency_enqueue_limit(default: 1), + perform_limit: good_job_concurrency_perform_limit(default: 1), + key: name + ) + + def perform + names = GemInfo.ordered_names + response_body = CompactIndex.names(names) + + content_md5 = Digest::MD5.base64digest(response_body) + checksum_sha256 = Digest::SHA256.base64digest(response_body) + + response = RubygemFs.compact_index.store( + "names", response_body, + public_acl: false, # the compact-index bucket does not have ACLs enabled + metadata: { + "surrogate-control" => "max-age=3600, stale-while-revalidate=1800", + "surrogate-key" => "names s3-compact-index s3-names", + "sha256" => checksum_sha256, + "md5" => content_md5 + }, + cache_control: "max-age=60, public", + content_type: "text/plain; charset=utf-8", + checksum_sha256:, + content_md5: + ) + + logger.info(message: "Uploading names file succeeded", response:) + + FastlyPurgeJob.perform_later(key: "s3-names", soft: true) + end +end diff --git a/app/models/deletion.rb b/app/models/deletion.rb index 073576573da..0bc3d28a4d4 100644 --- a/app/models/deletion.rb +++ b/app/models/deletion.rb @@ -68,6 +68,7 @@ def reindex Indexer.perform_later UploadInfoFileJob.perform_later(rubygem_name: rubygem_name) UploadVersionsFileJob.perform_later + UploadNamesFileJob.perform_later end def remove_from_storage diff --git a/app/models/pusher.rb b/app/models/pusher.rb index d6278ee73e1..50599c58fcc 100644 --- a/app/models/pusher.rb +++ b/app/models/pusher.rb @@ -152,6 +152,7 @@ def after_write Indexer.perform_later UploadVersionsFileJob.perform_later UploadInfoFileJob.perform_later(rubygem_name: rubygem.name) + UploadNamesFileJob.perform_later ReindexRubygemJob.perform_later(rubygem:) GemCachePurger.call(rubygem.name) StoreVersionContentsJob.perform_later(version:) if ld_variation(key: "gemcutter.pusher.store_version_contents", default: false) diff --git a/test/functional/api/v1/deletions_controller_test.rb b/test/functional/api/v1/deletions_controller_test.rb index 75a8d7908f0..f7f2fd90e75 100644 --- a/test/functional/api/v1/deletions_controller_test.rb +++ b/test/functional/api/v1/deletions_controller_test.rb @@ -397,6 +397,7 @@ class Api::V1::DeletionsControllerTest < ActionController::TestCase should "have enqueued reindexing job" do assert_enqueued_jobs 1, only: Indexer assert_enqueued_jobs 1, only: UploadVersionsFileJob + assert_enqueued_jobs 1, only: UploadNamesFileJob assert_enqueued_with job: UploadInfoFileJob, args: [{ rubygem_name: @rubygem.name }] end end diff --git a/test/jobs/upload_names_file_job_test.rb b/test/jobs/upload_names_file_job_test.rb new file mode 100644 index 00000000000..09437026bcb --- /dev/null +++ b/test/jobs/upload_names_file_job_test.rb @@ -0,0 +1,45 @@ +require "test_helper" + +class UploadNamesFileJobTest < ActiveJob::TestCase + make_my_diffs_pretty! + + test "uploads the names file" do + version = create(:version, number: "0.0.1", required_ruby_version: ">= 2.0.0", required_rubygems_version: ">= 2.6.3") + + perform_enqueued_jobs only: [UploadNamesFileJob] do + UploadNamesFileJob.perform_now + end + + content = <<~INFO + --- + #{version.rubygem.name} + INFO + + assert_equal content, RubygemFs.compact_index.get("names") + + assert_equal( + { + metadata: { + "surrogate-control" => "max-age=3600, stale-while-revalidate=1800", + "surrogate-key" => + "names s3-compact-index s3-names", + "sha256" => Digest::SHA256.base64digest(content), + "md5" => Digest::MD5.base64digest(content) + }, + cache_control: "max-age=60, public", + content_type: "text/plain; charset=utf-8", + checksum_sha256: Digest::SHA256.base64digest(content), + content_md5: Digest::MD5.base64digest(content), + key: "names" + }, RubygemFs.compact_index.head("names") + ) + + assert_enqueued_with(job: FastlyPurgeJob, args: [{ key: "s3-names", soft: true }]) + end + + test "#good_job_concurrency_key" do + job = UploadNamesFileJob.new + + assert_equal "UploadNamesFileJob", job.good_job_concurrency_key + end +end diff --git a/test/system/avo/rubygems_test.rb b/test/system/avo/rubygems_test.rb index 98351428ab7..49a9cacf319 100644 --- a/test/system/avo/rubygems_test.rb +++ b/test/system/avo/rubygems_test.rb @@ -326,6 +326,27 @@ def sign_in_as(user) assert_not_nil Audit.last end + test "upload names file" do + admin_user = create(:admin_github_user, :is_admin) + sign_in_as admin_user + + visit avo.resources_rubygems_path + + _ = create(:version) + + click_button "Actions" + click_on "Upload Names File" + fill_in "Comment", with: "A nice long comment" + + assert_enqueued_jobs 1, only: UploadNamesFileJob do + click_button "Upload" + + page.assert_text "Upload job scheduled" + end + + assert_not_nil Audit.last + end + test "upload info file" do admin_user = create(:admin_github_user, :is_admin) sign_in_as admin_user From 523f47c7f1d3580099a6911a67018d7e2e734a06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:29:40 -0600 Subject: [PATCH 014/112] Bump bcrypt from 3.1.19 to 3.1.20 (#4214) Bumps [bcrypt](https://github.com/codahale/bcrypt-ruby) from 3.1.19 to 3.1.20. - [Release notes](https://github.com/codahale/bcrypt-ruby/releases) - [Changelog](https://github.com/bcrypt-ruby/bcrypt-ruby/blob/master/CHANGELOG) - [Commits](https://github.com/codahale/bcrypt-ruby/compare/v3.1.19...v3.1.20) --- updated-dependencies: - dependency-name: bcrypt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 70f0a0be8c7..5b1b77c5cb5 100644 --- a/Gemfile +++ b/Gemfile @@ -50,7 +50,7 @@ gem "rotp", "~> 6.2" gem "unpwn", "~> 1.0" gem "webauthn", "~> 3.0" gem "browser", "~> 5.3", ">= 5.3.1" -gem "bcrypt", "~> 3.1", ">= 3.1.18" +gem "bcrypt", "~> 3.1" gem "maintenance_tasks", "~> 2.1" gem "strong_migrations", "~> 1.6" gem "phlex-rails", "~> 1.0" diff --git a/Gemfile.lock b/Gemfile.lock index b4323107de6..d2582a1973e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -120,7 +120,7 @@ GEM aws-sigv4 (1.6.1) aws-eventstream (~> 1, >= 1.0.2) base64 (0.2.0) - bcrypt (3.1.19) + bcrypt (3.1.20) benchmark-ips (2.12.0) bindata (2.4.15) bitarray (1.2.0) @@ -705,7 +705,7 @@ DEPENDENCIES avo (~> 2.42) aws-sdk-s3 (~> 1.138) aws-sdk-sqs (~> 1.67) - bcrypt (~> 3.1, >= 3.1.18) + bcrypt (~> 3.1) bootsnap (~> 1.16) brakeman (~> 6.0) browser (~> 5.3, >= 5.3.1) From 18df5ecabbfc839e23c159195da08bddabc7d3f6 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 22 Nov 2023 10:33:45 -0800 Subject: [PATCH 015/112] Skip committing the session for API endpoints (#4222) This will help avoid setting the Set-Cookie header, which makes responses non-cacheable, and opens us up to someone causing a thundering herd by visiting an API endpoint in the browser and causing fastly to cache a hit-for-pass object, see https://developer.fastly.com/learning/concepts/edge-state/cache/request-collapsing/#hit-for-pass --- app/controllers/api/base_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 69013864306..ced1af0b435 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -1,5 +1,6 @@ class Api::BaseController < ApplicationController skip_before_action :verify_authenticity_token + after_action :skip_session private @@ -117,4 +118,8 @@ def render_api_key_forbidden def render_soft_deleted_api_key render plain: "An invalid API key cannot be used. Please delete it and create a new one.", status: :forbidden end + + def skip_session + request.session_options[:skip] = true + end end From 53dd7b0bf8f53d0ce1d04fd1467b61831c4dc29e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 18:39:43 +0000 Subject: [PATCH 016/112] Bump factory_bot_rails from 6.2.0 to 6.4.0 (#4216) Bumps [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails) from 6.2.0 to 6.4.0. - [Release notes](https://github.com/thoughtbot/factory_bot_rails/releases) - [Changelog](https://github.com/thoughtbot/factory_bot_rails/blob/main/NEWS.md) - [Commits](https://github.com/thoughtbot/factory_bot_rails/compare/v6.2.0...v6.4.0) --- updated-dependencies: - dependency-name: factory_bot_rails dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 5b1b77c5cb5..56f7808ecde 100644 --- a/Gemfile +++ b/Gemfile @@ -79,7 +79,7 @@ end group :development, :test do gem "pry-byebug", "~> 3.10" gem "toxiproxy", "~> 2.0" - gem "factory_bot_rails", "~> 6.2" + gem "factory_bot_rails", "~> 6.4" gem "dotenv-rails", "~> 2.8" gem "brakeman", "~> 6.0", require: false diff --git a/Gemfile.lock b/Gemfile.lock index d2582a1973e..d26362f8280 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -215,10 +215,10 @@ GEM et-orbi (1.2.7) tzinfo execjs (2.9.1) - factory_bot (6.2.0) + factory_bot (6.4.2) activesupport (>= 5.0.0) - factory_bot_rails (6.2.0) - factory_bot (~> 6.2.0) + factory_bot_rails (6.4.0) + factory_bot (~> 6.4) railties (>= 5.0.0) faraday (2.7.12) base64 @@ -376,7 +376,7 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.5.9) - nokogiri (1.15.4) + nokogiri (1.15.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) oauth2 (2.0.9) @@ -719,7 +719,7 @@ DEPENDENCIES derailed_benchmarks (~> 2.1) dogstatsd-ruby (~> 5.5) dotenv-rails (~> 2.8) - factory_bot_rails (~> 6.2) + factory_bot_rails (~> 6.4) faraday (~> 2.7) faraday_middleware-aws-sigv4 (~> 1.0) good_job (~> 3.21) From 71abc0fafec2ac557e67a8f8789b7b4c2221c096 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 22 Nov 2023 11:46:13 -0800 Subject: [PATCH 017/112] Add polymorphic owner to ApiKey (#4212) In preparation for ApiKeys belonging to trusted publishers in addition to users Additionally, add a backfill so all ApiKeys will have an owner, and we can move to only read from that column, eventually deleting the user_id column --- Gemfile | 3 ++- Gemfile.lock | 10 +++----- app/models/api_key.rb | 11 ++++++-- .../backfill_api_key_owners_task.rb | 12 +++++++++ .../20231102190427_add_owner_to_api_keys.rb | 7 ++++++ db/schema.rb | 5 +++- test/models/api_key_test.rb | 8 +++++- .../backfill_api_key_owners_task_test.rb | 25 +++++++++++++++++++ test/test_helper.rb | 3 ++- 9 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 app/tasks/maintenance/backfill_api_key_owners_task.rb create mode 100644 db/migrate/20231102190427_add_owner_to_api_keys.rb create mode 100644 test/tasks/maintenance/backfill_api_key_owners_task_test.rb diff --git a/Gemfile b/Gemfile index 56f7808ecde..a9ae2e76e9c 100644 --- a/Gemfile +++ b/Gemfile @@ -106,7 +106,8 @@ group :test do gem "rack-test", "~> 2.1", require: "rack/test" gem "rails-controller-testing", "~> 1.0" gem "mocha", "~> 2.0", require: false - gem "shoulda", "~> 4.0" + gem "shoulda-context", "~> 2.0" + gem "shoulda-matchers", "~> 5.3" gem "selenium-webdriver", "~> 4.8" gem "webmock", "~> 3.18" gem "simplecov", "~> 0.22", require: false diff --git a/Gemfile.lock b/Gemfile.lock index d26362f8280..d704846987c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -594,12 +594,9 @@ GEM aws-sdk-core (>= 2) concurrent-ruby thor - shoulda (4.0.0) - shoulda-context (~> 2.0) - shoulda-matchers (~> 4.0) shoulda-context (2.0.0) - shoulda-matchers (4.4.1) - activesupport (>= 4.2.0) + shoulda-matchers (5.3.0) + activesupport (>= 5.2.0) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) @@ -779,7 +776,8 @@ DEPENDENCIES searchkick (~> 5.2) selenium-webdriver (~> 4.8) shoryuken (~> 6.1) - shoulda (~> 4.0) + shoulda-context (~> 2.0) + shoulda-matchers (~> 5.3) simplecov (~> 0.22) simplecov-cobertura (~> 2.1) sprockets-rails (~> 3.4) diff --git a/app/models/api_key.rb b/app/models/api_key.rb index e34d9357ec9..064d37b2d10 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -2,7 +2,8 @@ class ApiKey < ApplicationRecord API_SCOPES = %i[index_rubygems push_rubygem yank_rubygem add_owner remove_owner access_webhooks show_dashboard].freeze APPLICABLE_GEM_API_SCOPES = %i[push_rubygem yank_rubygem add_owner remove_owner].freeze - belongs_to :user + belongs_to :user, inverse_of: :api_keys + belongs_to :owner, polymorphic: true has_one :api_key_rubygem_scope, dependent: :destroy has_one :ownership, through: :api_key_rubygem_scope @@ -10,7 +11,9 @@ class ApiKey < ApplicationRecord has_one :oidc_api_key_role, through: :oidc_id_token, inverse_of: :api_key has_many :pushed_versions, class_name: "Version", inverse_of: :pusher_api_key, foreign_key: :pusher_api_key_id, dependent: :nullify - validates :user, :name, :hashed_key, presence: true + before_validation :set_owner_from_user + + validates :name, :hashed_key, presence: true validate :exclusive_show_dashboard_scope, if: :can_show_dashboard? validate :scope_presence validates :name, length: { maximum: Gemcutter::MAX_FIELD_LENGTH } @@ -115,4 +118,8 @@ def not_expired? return if changed == %w[expires_at] errors.add :base, "An expired API key cannot be used. Please create a new one." if expired? end + + def set_owner_from_user + self.owner ||= user + end end diff --git a/app/tasks/maintenance/backfill_api_key_owners_task.rb b/app/tasks/maintenance/backfill_api_key_owners_task.rb new file mode 100644 index 00000000000..ae6d13aee61 --- /dev/null +++ b/app/tasks/maintenance/backfill_api_key_owners_task.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Maintenance::BackfillApiKeyOwnersTask < MaintenanceTasks::Task + def collection + ApiKey.where(owner_id: nil).or(ApiKey.where(owner_type: nil)) + end + + def process(api_key) + api_key.owner ||= api_key.user + api_key.save!(validate: false) + end +end diff --git a/db/migrate/20231102190427_add_owner_to_api_keys.rb b/db/migrate/20231102190427_add_owner_to_api_keys.rb new file mode 100644 index 00000000000..3a47d2e7567 --- /dev/null +++ b/db/migrate/20231102190427_add_owner_to_api_keys.rb @@ -0,0 +1,7 @@ +class AddOwnerToApiKeys < ActiveRecord::Migration[7.0] + disable_ddl_transaction! + + def change + add_reference :api_keys, :owner, polymorphic: true, null: true, index: {algorithm: :concurrently} + end +end diff --git a/db/schema.rb b/db/schema.rb index 7f0cd97abff..aa18aa8c2e8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_10_18_235829) do +ActiveRecord::Schema[7.0].define(version: 2023_11_02_190427) do # These are extensions that must be enabled in order to support this database enable_extension "hstore" enable_extension "pgcrypto" @@ -54,7 +54,10 @@ t.datetime "soft_deleted_at" t.string "soft_deleted_rubygem_name" t.datetime "expires_at", precision: nil + t.string "owner_type" + t.bigint "owner_id" t.index ["hashed_key"], name: "index_api_keys_on_hashed_key", unique: true + t.index ["owner_type", "owner_id"], name: "index_api_keys_on_owner" t.index ["user_id"], name: "index_api_keys_on_user_id" end diff --git a/test/models/api_key_test.rb b/test/models/api_key_test.rb index 1d7c0740f99..7f02a0f86cf 100644 --- a/test/models/api_key_test.rb +++ b/test/models/api_key_test.rb @@ -2,8 +2,8 @@ class ApiKeyTest < ActiveSupport::TestCase should belong_to :user + should belong_to :owner should validate_presence_of(:name) - should validate_presence_of(:user) should validate_presence_of(:hashed_key) should have_one(:api_key_rubygem_scope).dependent(:destroy) @@ -11,6 +11,12 @@ class ApiKeyTest < ActiveSupport::TestCase assert_predicate build(:api_key), :valid? end + should "set owner to user by default" do + api_key = create(:api_key) + + assert_equal api_key.user, api_key.owner + end + should "be invalid when name is empty string" do api_key = build(:api_key, name: "") diff --git a/test/tasks/maintenance/backfill_api_key_owners_task_test.rb b/test/tasks/maintenance/backfill_api_key_owners_task_test.rb new file mode 100644 index 00000000000..1f833cf11b6 --- /dev/null +++ b/test/tasks/maintenance/backfill_api_key_owners_task_test.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "test_helper" + +class Maintenance::BackfillApiKeyOwnersTaskTest < ActiveSupport::TestCase + test "#process performs a task iteration" do + element = create(:api_key) + element.update_columns(owner_id: nil, owner_type: nil) + + assert_nil element.reload.owner + + Maintenance::BackfillApiKeyOwnersTask.process(element) + + assert_equal element.reload.user, element.owner + end + + test "#collection returns the elements to process" do + create(:api_key) + nil_owner = create(:api_key, key: "other") + nil_owner.update_columns(owner_id: nil, owner_type: nil) + + assert_nil nil_owner.reload.owner + assert_same_elements [nil_owner], Maintenance::BackfillApiKeyOwnersTask.collection + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 7ebe992745a..6198b1c8083 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -28,7 +28,8 @@ require "capybara/minitest" require "clearance/test_unit" require "webauthn/fake_client" -require "shoulda" +require "shoulda/context" +require "shoulda/matchers" require "helpers/admin_helpers" require "helpers/gem_helpers" require "helpers/email_helpers" From f865ea14c3932f63520499a64d929133fbf1dd49 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 23 Nov 2023 14:08:20 -0600 Subject: [PATCH 018/112] Use an uncached query to compute compact index info in jobs (#4231) This way, there is no race with purging the cache & computing the new files --- app/jobs/upload_info_file_job.rb | 2 +- app/jobs/upload_names_file_job.rb | 2 +- app/models/gem_info.rb | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/jobs/upload_info_file_job.rb b/app/jobs/upload_info_file_job.rb index c47704fac5b..61d33e4652a 100644 --- a/app/jobs/upload_info_file_job.rb +++ b/app/jobs/upload_info_file_job.rb @@ -14,7 +14,7 @@ class UploadInfoFileJob < ApplicationJob ) def perform(rubygem_name:) - compact_index_info = GemInfo.new(rubygem_name).compact_index_info + compact_index_info = GemInfo.new(rubygem_name, cached: false).compact_index_info response_body = CompactIndex.info(compact_index_info) content_md5 = Digest::MD5.base64digest(response_body) diff --git a/app/jobs/upload_names_file_job.rb b/app/jobs/upload_names_file_job.rb index 167791b4110..3e9cc1cd130 100644 --- a/app/jobs/upload_names_file_job.rb +++ b/app/jobs/upload_names_file_job.rb @@ -14,7 +14,7 @@ class UploadNamesFileJob < ApplicationJob ) def perform - names = GemInfo.ordered_names + names = GemInfo.ordered_names(cached: false) response_body = CompactIndex.names(names) content_md5 = Digest::MD5.base64digest(response_body) diff --git a/app/models/gem_info.rb b/app/models/gem_info.rb index 3b12936252a..5dc5cdb6350 100644 --- a/app/models/gem_info.rb +++ b/app/models/gem_info.rb @@ -1,11 +1,11 @@ class GemInfo - def initialize(rubygem_name) + def initialize(rubygem_name, cached: true) @rubygem_name = rubygem_name + @cached = cached end def compact_index_info - info = Rails.cache.read("info/#{@rubygem_name}") - if info + if @cached && (info = Rails.cache.read("info/#{@rubygem_name}")) StatsD.increment "compact_index.memcached.info.hit" info else @@ -21,9 +21,8 @@ def info_checksum Digest::MD5.hexdigest(compact_index_info) end - def self.ordered_names - names = Rails.cache.read("names") - if names + def self.ordered_names(cached: true) + if cached && (names = Rails.cache.read("names")) StatsD.increment "compact_index.memcached.names.hit" else StatsD.increment "compact_index.memcached.names.miss" From ea145d9e88ffa4d71b6379950abd13d197c3b68d Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 23 Nov 2023 14:09:39 -0600 Subject: [PATCH 019/112] Extract verified session logic into a concern (#4210) It was being duplicated a lot --- app/controllers/adoptions_controller.rb | 10 ++----- app/controllers/api_keys_controller.rb | 25 ++++++++++------ .../concerns/session_verifiable.rb | 29 +++++++++++++++++++ .../oidc/api_key_roles_controller.rb | 23 +++++++++------ app/controllers/oidc/id_tokens_controller.rb | 12 ++------ app/controllers/oidc/providers_controller.rb | 12 ++------ app/controllers/owners_controller.rb | 9 +++--- 7 files changed, 73 insertions(+), 47 deletions(-) create mode 100644 app/controllers/concerns/session_verifiable.rb diff --git a/app/controllers/adoptions_controller.rb b/app/controllers/adoptions_controller.rb index f1dce7e204d..1cc0b4858b6 100644 --- a/app/controllers/adoptions_controller.rb +++ b/app/controllers/adoptions_controller.rb @@ -1,7 +1,9 @@ class AdoptionsController < ApplicationController + include SessionVerifiable + before_action :find_rubygem before_action :verify_ownership_requestable - before_action :redirect_to_verify, if: :current_user_is_owner? + before_action :redirect_to_verify, if: -> { current_user_is_owner? && !password_session_active? } def index @ownership_call = @rubygem.ownership_call @@ -18,10 +20,4 @@ def verify_ownership_requestable def current_user_is_owner? @rubygem.owned_by?(current_user) end - - def redirect_to_verify - return if password_session_active? - session[:redirect_uri] = rubygem_adoptions_path(@rubygem.slug) - redirect_to verify_session_path - end end diff --git a/app/controllers/api_keys_controller.rb b/app/controllers/api_keys_controller.rb index 286b0b10afb..d76752dbd3c 100644 --- a/app/controllers/api_keys_controller.rb +++ b/app/controllers/api_keys_controller.rb @@ -1,9 +1,8 @@ class ApiKeysController < ApplicationController include ApiKeyable - before_action :redirect_to_signin, unless: :signed_in? - before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled? - before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled? - before_action :redirect_to_verify, unless: :password_session_active? + + include SessionVerifiable + verify_session_before def index @api_key = session.delete(:api_key) @@ -84,12 +83,20 @@ def reset private - def api_key_params - params.require(:api_key).permit(:name, *ApiKey::API_SCOPES, :mfa, :rubygem_id) + def verify_session_redirect_path + case action_name + when "reset", "destroy" + profile_api_keys_path + when "create" + new_profile_api_key_path + when "update" + edit_profile_api_key_path(params.require(:id)) + else + super + end end - def redirect_to_verify - session[:redirect_uri] = profile_api_keys_path - redirect_to verify_session_path + def api_key_params + params.require(:api_key).permit(:name, *ApiKey::API_SCOPES, :mfa, :rubygem_id) end end diff --git a/app/controllers/concerns/session_verifiable.rb b/app/controllers/concerns/session_verifiable.rb new file mode 100644 index 00000000000..2f7a6d68b8d --- /dev/null +++ b/app/controllers/concerns/session_verifiable.rb @@ -0,0 +1,29 @@ +module SessionVerifiable + extend ActiveSupport::Concern + + class_methods do + def verify_session_before(**opts) + before_action :redirect_to_signin, **opts, unless: :signed_in? + before_action :redirect_to_new_mfa, **opts, if: :mfa_required_not_yet_enabled? + before_action :redirect_to_settings_strong_mfa_required, **opts, if: :mfa_required_weak_level_enabled? + before_action :redirect_to_verify, **opts, unless: :password_session_active? + end + end + + private + + def verify_session_redirect_path + redirect_uri = request.path_info + redirect_uri += "?#{request.query_string}" if request.query_string.present? + redirect_uri + end + + included do + private + + def redirect_to_verify + session[:redirect_uri] = verify_session_redirect_path + redirect_to verify_session_path + end + end +end diff --git a/app/controllers/oidc/api_key_roles_controller.rb b/app/controllers/oidc/api_key_roles_controller.rb index aa7e4b985a3..5e0c34ce610 100644 --- a/app/controllers/oidc/api_key_roles_controller.rb +++ b/app/controllers/oidc/api_key_roles_controller.rb @@ -1,12 +1,11 @@ class OIDC::ApiKeyRolesController < ApplicationController include ApiKeyable + include SessionVerifiable + verify_session_before + helper RubygemsHelper - before_action :redirect_to_signin, unless: :signed_in? - before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled? - before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled? - before_action :redirect_to_verify, unless: :password_session_active? before_action :find_api_key_role, except: %i[index new create] before_action :redirect_for_deleted, only: %i[edit update destroy] before_action :set_page, only: :index @@ -90,17 +89,23 @@ def destroy private + def verify_session_redirect_path + case action_name + when "create" + new_profile_api_key_path + when "update" + edit_profile_api_key_path + else + super + end + end + def find_api_key_role @api_key_role = current_user.oidc_api_key_roles .includes(:provider) .find_by!(token: params.require(:token)) end - def redirect_to_verify - session[:redirect_uri] = request.path_info + (request.query_string.present? ? "?#{request.query_string}" : "") - redirect_to verify_session_path - end - def redirect_for_deleted redirect_to profile_oidc_api_key_roles_path, flash: { error: t(".deleted") } if @api_key_role.deleted_at? end diff --git a/app/controllers/oidc/id_tokens_controller.rb b/app/controllers/oidc/id_tokens_controller.rb index c5e98a8a372..1447457a4de 100644 --- a/app/controllers/oidc/id_tokens_controller.rb +++ b/app/controllers/oidc/id_tokens_controller.rb @@ -3,10 +3,9 @@ class OIDC::IdTokensController < ApplicationController include ApiKeyable - before_action :redirect_to_signin, unless: :signed_in? - before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled? - before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled? - before_action :redirect_to_verify, unless: :password_session_active? + include SessionVerifiable + verify_session_before + before_action :find_id_token, except: %i[index] before_action :set_page, only: :index @@ -26,9 +25,4 @@ def show def find_id_token @id_token = current_user.oidc_id_tokens.find(params.require(:id)) end - - def redirect_to_verify - session[:redirect_uri] = request.path_info - redirect_to verify_session_path - end end diff --git a/app/controllers/oidc/providers_controller.rb b/app/controllers/oidc/providers_controller.rb index d8eef9deb80..1e7d6b39899 100644 --- a/app/controllers/oidc/providers_controller.rb +++ b/app/controllers/oidc/providers_controller.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true class OIDC::ProvidersController < ApplicationController - before_action :redirect_to_signin, unless: :signed_in? - before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled? - before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled? - before_action :redirect_to_verify, unless: :password_session_active? + include SessionVerifiable + verify_session_before + before_action :find_provider, except: %i[index] before_action :set_page, only: :index @@ -22,9 +21,4 @@ def show def find_provider @provider = OIDC::Provider.find(params.require(:id)) end - - def redirect_to_verify - session[:redirect_uri] = request.path_info - redirect_to verify_session_path - end end diff --git a/app/controllers/owners_controller.rb b/app/controllers/owners_controller.rb index bec7639f0e7..fe530b1d09e 100644 --- a/app/controllers/owners_controller.rb +++ b/app/controllers/owners_controller.rb @@ -1,7 +1,9 @@ class OwnersController < ApplicationController + include SessionVerifiable + before_action :find_rubygem, except: :confirm before_action :render_forbidden, unless: :owner?, except: %i[confirm resend_confirmation] - before_action :redirect_to_verify, unless: :password_session_active?, only: %i[index create destroy] + verify_session_before only: %i[index create destroy] before_action :verify_mfa_requirement, only: %i[create destroy] def confirm @@ -53,9 +55,8 @@ def destroy private - def redirect_to_verify - session[:redirect_uri] = rubygem_owners_url(@rubygem.slug) - redirect_to verify_session_path + def verify_session_redirect_path + rubygem_owners_url(params[:rubygem_id]) end def token_params From 4ada8cb66e2b0f5ab0aa7650d459f21b562e226d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:26:39 +0000 Subject: [PATCH 020/112] Bump aws-sdk-s3 from 1.138.0 to 1.139.0 (#4226) Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.138.0 to 1.139.0. - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-s3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index a9ae2e76e9c..4a72862ac68 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" -gem "aws-sdk-s3", "~> 1.138" +gem "aws-sdk-s3", "~> 1.139" gem "aws-sdk-sqs", "~> 1.67" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" diff --git a/Gemfile.lock b/Gemfile.lock index d704846987c..1c22f9b14d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,24 +100,24 @@ GEM view_component (>= 2.54.0) zeitwerk (>= 2.6.2) awrence (1.2.1) - aws-eventstream (1.2.0) - aws-partitions (1.855.0) - aws-sdk-core (3.187.1) + aws-eventstream (1.3.0) + aws-partitions (1.856.0) + aws-sdk-core (3.188.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.72.0) - aws-sdk-core (~> 3, >= 3.184.0) + aws-sdk-kms (1.73.0) + aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.138.0) - aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-s3 (1.139.0) + aws-sdk-core (~> 3, >= 3.188.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) aws-sdk-sqs (1.67.0) aws-sdk-core (~> 3, >= 3.184.0) aws-sigv4 (~> 1.1) - aws-sigv4 (1.6.1) + aws-sigv4 (1.7.0) aws-eventstream (~> 1, >= 1.0.2) base64 (0.2.0) bcrypt (3.1.20) @@ -700,7 +700,7 @@ DEPENDENCIES amazing_print (~> 1.4) autoprefixer-rails (~> 10.4) avo (~> 2.42) - aws-sdk-s3 (~> 1.138) + aws-sdk-s3 (~> 1.139) aws-sdk-sqs (~> 1.67) bcrypt (~> 3.1) bootsnap (~> 1.16) From cc0924a855e55c724e1ec3e56e528a540a995274 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:27:12 -0600 Subject: [PATCH 021/112] Bump autoprefixer-rails from 10.4.15.0 to 10.4.16.0 (#4228) Bumps [autoprefixer-rails](https://github.com/ai/autoprefixer-rails) from 10.4.15.0 to 10.4.16.0. - [Changelog](https://github.com/ai/autoprefixer-rails/blob/master/CHANGELOG.md) - [Commits](https://github.com/ai/autoprefixer-rails/compare/10.4.15.0...10.4.16.0) --- updated-dependencies: - dependency-name: autoprefixer-rails dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1c22f9b14d2..0aa3fe071a3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,7 +82,7 @@ GEM ffi-compiler (~> 1.0) ast (2.4.2) attr_required (1.0.1) - autoprefixer-rails (10.4.15.0) + autoprefixer-rails (10.4.16.0) execjs (~> 2) avo (2.44.0) actionview (>= 6.0) From 7bc92577ce8e0c3e323c5eff49b57e42d650620a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:29:21 +0000 Subject: [PATCH 022/112] Bump ddtrace from 1.16.2 to 1.17.0 (#4225) Bumps [ddtrace](https://github.com/DataDog/dd-trace-rb) from 1.16.2 to 1.17.0. - [Release notes](https://github.com/DataDog/dd-trace-rb/releases) - [Changelog](https://github.com/DataDog/dd-trace-rb/blob/master/CHANGELOG.md) - [Commits](https://github.com/DataDog/dd-trace-rb/compare/v1.16.2...v1.17.0) --- updated-dependencies: - dependency-name: ddtrace dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 4a72862ac68..a802c05a88b 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem "aws-sdk-sqs", "~> 1.67" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" gem "dalli", "~> 3.2" -gem "ddtrace", "~> 1.16", require: "ddtrace/auto_instrument" +gem "ddtrace", "~> 1.17", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" gem "google-protobuf", "~> 3.25" gem "faraday", "~> 2.7" diff --git a/Gemfile.lock b/Gemfile.lock index 0aa3fe071a3..ef54f2c1e94 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,11 +175,11 @@ GEM sprockets (> 3.0) sprockets-rails tilt - datadog-ci (0.3.0) + datadog-ci (0.4.1) msgpack date (3.3.4) - ddtrace (1.16.2) - datadog-ci (~> 0.3.0) + ddtrace (1.17.0) + datadog-ci (~> 0.4.0) debase-ruby_core_source (= 3.2.2) libdatadog (~> 5.0.0.1.0) libddwaf (~> 1.14.0.0.0) @@ -712,7 +712,7 @@ DEPENDENCIES compact_index (~> 0.14.0) dalli (~> 3.2) dartsass-sprockets (~> 3.0) - ddtrace (~> 1.16) + ddtrace (~> 1.17) derailed_benchmarks (~> 2.1) dogstatsd-ruby (~> 5.5) dotenv-rails (~> 2.8) From b20f34ee836c734f87ea729e4c80dd896d3387ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:38:29 +0000 Subject: [PATCH 023/112] Bump aws-sdk-sqs from 1.67.0 to 1.68.0 (#4227) Bumps [aws-sdk-sqs](https://github.com/aws/aws-sdk-ruby) from 1.67.0 to 1.68.0. - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-sqs/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-sqs dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index a802c05a88b..5dacd3f3668 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" gem "aws-sdk-s3", "~> 1.139" -gem "aws-sdk-sqs", "~> 1.67" +gem "aws-sdk-sqs", "~> 1.68" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" gem "dalli", "~> 3.2" diff --git a/Gemfile.lock b/Gemfile.lock index ef54f2c1e94..de5d77925a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -114,8 +114,8 @@ GEM aws-sdk-core (~> 3, >= 3.188.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) - aws-sdk-sqs (1.67.0) - aws-sdk-core (~> 3, >= 3.184.0) + aws-sdk-sqs (1.68.0) + aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) aws-sigv4 (1.7.0) aws-eventstream (~> 1, >= 1.0.2) @@ -701,7 +701,7 @@ DEPENDENCIES autoprefixer-rails (~> 10.4) avo (~> 2.42) aws-sdk-s3 (~> 1.139) - aws-sdk-sqs (~> 1.67) + aws-sdk-sqs (~> 1.68) bcrypt (~> 3.1) bootsnap (~> 1.16) brakeman (~> 6.0) From e40a0cfc652c56d29853f7351c56951a2c545f40 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 23 Nov 2023 14:40:09 -0600 Subject: [PATCH 024/112] Add maintenance_task to backfill info files into S3 (#4232) --- .../upload_info_files_to_s3_task.rb | 11 +++++++++ .../upload_info_files_to_s3_task_test.rb | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 app/tasks/maintenance/upload_info_files_to_s3_task.rb create mode 100644 test/tasks/maintenance/upload_info_files_to_s3_task_test.rb diff --git a/app/tasks/maintenance/upload_info_files_to_s3_task.rb b/app/tasks/maintenance/upload_info_files_to_s3_task.rb new file mode 100644 index 00000000000..8810166ecc3 --- /dev/null +++ b/app/tasks/maintenance/upload_info_files_to_s3_task.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Maintenance::UploadInfoFilesToS3Task < MaintenanceTasks::Task + def collection + Rubygem.with_versions + end + + def process(rubygem) + UploadInfoFileJob.perform_later(rubygem_name: rubygem.name) + end +end diff --git a/test/tasks/maintenance/upload_info_files_to_s3_task_test.rb b/test/tasks/maintenance/upload_info_files_to_s3_task_test.rb new file mode 100644 index 00000000000..dbdc1559b8e --- /dev/null +++ b/test/tasks/maintenance/upload_info_files_to_s3_task_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "test_helper" + +class Maintenance::UploadInfoFilesToS3TaskTest < ActiveSupport::TestCase + include ActiveJob::TestHelper + + test "#process performs a task iteration" do + rubygem = create(:rubygem) + assert_enqueued_jobs 1, only: UploadInfoFileJob do + assert_enqueued_with(job: UploadInfoFileJob, args: [{ rubygem_name: rubygem.name }]) do + Maintenance::UploadInfoFilesToS3Task.process(rubygem) + end + end + end + + test "#collection returns the elements to process" do + create(:rubygem) + rubygem = create(:rubygem) + create(:version, rubygem: rubygem) + + assert_same_elements [rubygem], Maintenance::UploadInfoFilesToS3Task.collection + end +end From bfde3fd19190c88b95133184e97cf39de32317a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:52:51 +0000 Subject: [PATCH 025/112] Bump opensearch-ruby from 1.0.1 to 3.0.1 - remove opensearch-dsl gem (it was merged into opensearch gem) Bumps [opensearch-ruby](https://github.com/opensearch-project/opensearch-ruby) from 1.0.1 to 3.0.1. - [Release notes](https://github.com/opensearch-project/opensearch-ruby/releases) - [Changelog](https://github.com/opensearch-project/opensearch-ruby/blob/main/CHANGELOG.md) - [Commits](https://github.com/opensearch-project/opensearch-ruby/compare/v1.0.1...3.0.1) --- updated-dependencies: - dependency-name: opensearch-ruby dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Gemfile | 3 +-- Gemfile.lock | 13 +++---------- config/initializers/elasticsearch.rb | 1 + 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 5dacd3f3668..6735865461b 100644 --- a/Gemfile +++ b/Gemfile @@ -37,8 +37,7 @@ gem "ruby-magic", "~> 0.6" gem "shoryuken", "~> 6.1", require: false gem "statsd-instrument", "~> 3.5" gem "validates_formatting_of", "~> 0.9" -gem "opensearch-dsl", "~> 0.2.0" -gem "opensearch-ruby", "~> 1.0" +gem "opensearch-ruby", "~> 3.0" gem "searchkick", "~> 5.2" gem "faraday_middleware-aws-sigv4", "~> 1.0" gem "xml-simple", "~> 1.1" diff --git a/Gemfile.lock b/Gemfile.lock index de5d77925a5..3bf825043b3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -415,15 +415,9 @@ GEM validate_email validate_url webfinger (~> 2.0) - opensearch-api (1.0.0) - multi_json - opensearch-dsl (0.2.1) - opensearch-ruby (1.0.1) - opensearch-api (= 1.0.0) - opensearch-transport (~> 1.0.0) - opensearch-transport (1.0.1) + opensearch-ruby (3.0.1) faraday (>= 1.0, < 3) - multi_json + multi_json (>= 1.0) openssl (3.1.0) openssl-signature_algorithm (1.3.0) openssl (> 2.0) @@ -746,8 +740,7 @@ DEPENDENCIES omniauth-github (~> 2.0) omniauth-rails_csrf_protection (~> 1.0) openid_connect (~> 2.2) - opensearch-dsl (~> 0.2.0) - opensearch-ruby (~> 1.0) + opensearch-ruby (~> 3.0) pg (~> 1.4) phlex-rails (~> 1.0) pry-byebug (~> 3.10) diff --git a/config/initializers/elasticsearch.rb b/config/initializers/elasticsearch.rb index 08285a6b5df..b95dbfc428a 100644 --- a/config/initializers/elasticsearch.rb +++ b/config/initializers/elasticsearch.rb @@ -1,4 +1,5 @@ require 'faraday_middleware/aws_sigv4' +require 'opensearch-dsl' port = 9200 if (Rails.env.test? || Rails.env.development?) && Toxiproxy.running? From c816328b451a5bfb196934d71b0aaa5131d4a086 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 23 Nov 2023 19:21:15 -0600 Subject: [PATCH 026/112] Fix api_key_created email when api key belongs to an oidc id token (#4233) Fixing ApiKey association & use url helper instead of path in mailer view Fixes https://app.datadoghq.com/logs?query=%40traceid%3A511637059039611422&cols=host%2Cservice&index=%2A&messageDisplay=inline&refresh_mode=sliding&stream_sort=desc&view=spans&viz=stream&from_ts=1700692471686&to_ts=1700778871686&live=true --- app/models/api_key.rb | 2 +- app/models/oidc/id_token.rb | 2 +- app/views/mailer/api_key_created.html.erb | 2 +- test/mailers/previews/mailer_preview.rb | 5 +++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 064d37b2d10..fce947e74e3 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -8,7 +8,7 @@ class ApiKey < ApplicationRecord has_one :api_key_rubygem_scope, dependent: :destroy has_one :ownership, through: :api_key_rubygem_scope has_one :oidc_id_token, class_name: "OIDC::IdToken", dependent: :restrict_with_error - has_one :oidc_api_key_role, through: :oidc_id_token, inverse_of: :api_key + has_one :oidc_api_key_role, through: :oidc_id_token, source: :api_key_role, inverse_of: :api_keys has_many :pushed_versions, class_name: "Version", inverse_of: :pusher_api_key, foreign_key: :pusher_api_key_id, dependent: :nullify before_validation :set_owner_from_user diff --git a/app/models/oidc/id_token.rb b/app/models/oidc/id_token.rb index 2e03f5d45ec..e9e3f9f23cd 100644 --- a/app/models/oidc/id_token.rb +++ b/app/models/oidc/id_token.rb @@ -1,5 +1,5 @@ class OIDC::IdToken < ApplicationRecord - belongs_to :api_key_role, class_name: "OIDC::ApiKeyRole", foreign_key: "oidc_api_key_role_id", inverse_of: :id_tokens + belongs_to :api_key_role, class_name: "OIDC::ApiKeyRole", foreign_key: :oidc_api_key_role_id, inverse_of: :id_tokens belongs_to :api_key, inverse_of: :oidc_id_token has_one :provider, through: :api_key_role, inverse_of: :id_tokens has_one :user, through: :api_key_role, inverse_of: :oidc_id_tokens diff --git a/app/views/mailer/api_key_created.html.erb b/app/views/mailer/api_key_created.html.erb index 6e711be7434..d12c13778df 100644 --- a/app/views/mailer/api_key_created.html.erb +++ b/app/views/mailer/api_key_created.html.erb @@ -20,7 +20,7 @@ Created at: <%= @api_key.created_at.to_formatted_s(:rfc822) %> <% if @api_key.oidc_id_token.present? %>
- <%= ApiKey.human_attribute_name(:oidc_api_key_role) %>: <%= link_to(@api_key.oidc_api_key_role.name, profile_oidc_api_key_role_path(@api_key.oidc_api_key_role.token), target: :_blank) %> + <%= ApiKey.human_attribute_name(:oidc_api_key_role) %>: <%= link_to(@api_key.oidc_api_key_role.name, profile_oidc_api_key_role_url(@api_key.oidc_api_key_role.token), target: :_blank) %> <% end %>

diff --git a/test/mailers/previews/mailer_preview.rb b/test/mailers/previews/mailer_preview.rb index 2f5a833fa6f..7ccdb7ba1a7 100644 --- a/test/mailers/previews/mailer_preview.rb +++ b/test/mailers/previews/mailer_preview.rb @@ -88,6 +88,11 @@ def api_key_created Mailer.api_key_created(api_key.id) end + def api_key_created_oidc_api_key_role + api_key = OIDC::IdToken.last.api_key + Mailer.api_key_created(api_key.id) + end + def api_key_revoked api_key = ApiKey.last Mailer.api_key_revoked(api_key.user.id, api_key.name, api_key.enabled_scopes.join(", "), "https://example.com") From c55aaaf491bbed4cea14fac638deceaa6ed820dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 08:34:10 -0600 Subject: [PATCH 027/112] Bump phlex-rails from 1.0.0 to 1.1.0 (#4237) --- Gemfile | 2 +- Gemfile.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 6735865461b..da3589f5449 100644 --- a/Gemfile +++ b/Gemfile @@ -52,7 +52,7 @@ gem "browser", "~> 5.3", ">= 5.3.1" gem "bcrypt", "~> 3.1" gem "maintenance_tasks", "~> 2.1" gem "strong_migrations", "~> 1.6" -gem "phlex-rails", "~> 1.0" +gem "phlex-rails", "~> 1.1" # Admin dashboard gem "avo", "~> 2.42" diff --git a/Gemfile.lock b/Gemfile.lock index 3bf825043b3..71dedba9dcf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -143,7 +143,7 @@ GEM regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) cbor (0.5.9.6) - cgi (0.3.6) + cgi (0.4.0) chartkick (5.0.4) choice (0.2.0) chunky_png (1.4.0) @@ -366,7 +366,7 @@ GEM msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) - net-imap (0.4.5) + net-imap (0.4.6) date net-protocol net-pop (0.1.2) @@ -375,7 +375,7 @@ GEM timeout net-smtp (0.4.0) net-protocol - nio4r (2.5.9) + nio4r (2.6.1) nokogiri (1.15.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) @@ -427,13 +427,13 @@ GEM parser (3.2.1.1) ast (~> 2.4.1) pg (1.5.4) - phlex (1.8.1) + phlex (1.9.0) concurrent-ruby (~> 1.2) erb (>= 4) zeitwerk (~> 2.6) - phlex-rails (1.0.0) - phlex (~> 1.7) - rails (>= 6.1, < 8) + phlex-rails (1.1.0) + phlex (~> 1.9) + railties (>= 6.1, < 8) zeitwerk (~> 2.6) pry (0.14.1) coderay (~> 1.1) @@ -742,7 +742,7 @@ DEPENDENCIES openid_connect (~> 2.2) opensearch-ruby (~> 3.0) pg (~> 1.4) - phlex-rails (~> 1.0) + phlex-rails (~> 1.1) pry-byebug (~> 3.10) puma (~> 6.1) pundit (~> 2.3) From 2321d32c4314e2132b6217298faf7001164360f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:47:58 -0600 Subject: [PATCH 028/112] Bump avo from 2.44.0 to 2.45.0 (#4238) Bumps [avo](https://github.com/avo-hq/avo) from 2.44.0 to 2.45.0. - [Release notes](https://github.com/avo-hq/avo/releases) - [Changelog](https://github.com/avo-hq/avo/blob/main/RELEASE.MD) - [Commits](https://github.com/avo-hq/avo/compare/v2.44.0...v2.45.0) --- updated-dependencies: - dependency-name: avo dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index da3589f5449..c58a448bd65 100644 --- a/Gemfile +++ b/Gemfile @@ -55,7 +55,7 @@ gem "strong_migrations", "~> 1.6" gem "phlex-rails", "~> 1.1" # Admin dashboard -gem "avo", "~> 2.42" +gem "avo", "~> 2.45" gem "view_component", "~> 3.6" gem "pundit", "~> 2.3" gem "chartkick", "~> 5.0" diff --git a/Gemfile.lock b/Gemfile.lock index 71dedba9dcf..b89dabe1f64 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,7 +84,7 @@ GEM attr_required (1.0.1) autoprefixer-rails (10.4.16.0) execjs (~> 2) - avo (2.44.0) + avo (2.45.0) actionview (>= 6.0) active_link_to activerecord (>= 6.0) @@ -422,7 +422,7 @@ GEM openssl-signature_algorithm (1.3.0) openssl (> 2.0) optimist (3.0.1) - pagy (6.1.0) + pagy (6.2.0) parallel (1.22.1) parser (3.2.1.1) ast (~> 2.4.1) @@ -443,7 +443,7 @@ GEM pry (>= 0.13, < 0.15) psych (5.1.1.1) stringio - public_suffix (5.0.3) + public_suffix (5.0.4) puma (6.4.0) nio4r (~> 2.0) pundit (2.3.1) @@ -693,7 +693,7 @@ DEPENDENCIES aggregate_assertions (~> 0.2.0) amazing_print (~> 1.4) autoprefixer-rails (~> 10.4) - avo (~> 2.42) + avo (~> 2.45) aws-sdk-s3 (~> 1.139) aws-sdk-sqs (~> 1.68) bcrypt (~> 3.1) From 0f125655a6d63ee8100f0e746d8ea9383eed034d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 21:48:58 -0600 Subject: [PATCH 029/112] Bump factory_bot_rails from 6.4.0 to 6.4.2 (#4236) --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index b89dabe1f64..e33e4458d53 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -217,7 +217,7 @@ GEM execjs (2.9.1) factory_bot (6.4.2) activesupport (>= 5.0.0) - factory_bot_rails (6.4.0) + factory_bot_rails (6.4.2) factory_bot (~> 6.4) railties (>= 5.0.0) faraday (2.7.12) From 1a297f8a083a20e4aed6c6996870e71f023ce37d Mon Sep 17 00:00:00 2001 From: Martin Emde Date: Fri, 24 Nov 2023 20:34:52 -0800 Subject: [PATCH 030/112] Constrain Puma to non-vuln version (already locked) (#4240) --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index c58a448bd65..c0a3cb1f781 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,7 @@ gem "omniauth", "~> 2.1" gem "omniauth-rails_csrf_protection", "~> 1.0" gem "openid_connect", "~> 2.2" gem "pg", "~> 1.4" -gem "puma", "~> 6.1" +gem "puma", "~> 6.4" gem "rack", "~> 2.2" gem "rack-utf8_sanitizer", "~> 1.8" gem "rbtrace", "~> 0.4.8" diff --git a/Gemfile.lock b/Gemfile.lock index e33e4458d53..1ea9f621b7f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -744,7 +744,7 @@ DEPENDENCIES pg (~> 1.4) phlex-rails (~> 1.1) pry-byebug (~> 3.10) - puma (~> 6.1) + puma (~> 6.4) pundit (~> 2.3) rack (~> 2.2) rack-attack (~> 6.6) From a481cb6752d876109fb4266215fcb860bf9c026e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:00:25 -0600 Subject: [PATCH 031/112] Bump aws-sdk-s3 from 1.139.0 to 1.140.0 (#4242) Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.139.0 to 1.140.0. - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-s3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index c0a3cb1f781..ec864c28089 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" -gem "aws-sdk-s3", "~> 1.139" +gem "aws-sdk-s3", "~> 1.140" gem "aws-sdk-sqs", "~> 1.68" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" diff --git a/Gemfile.lock b/Gemfile.lock index 1ea9f621b7f..afc643d0039 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,7 +101,7 @@ GEM zeitwerk (>= 2.6.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.856.0) + aws-partitions (1.857.0) aws-sdk-core (3.188.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) @@ -110,7 +110,7 @@ GEM aws-sdk-kms (1.73.0) aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.139.0) + aws-sdk-s3 (1.140.0) aws-sdk-core (~> 3, >= 3.188.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) @@ -694,7 +694,7 @@ DEPENDENCIES amazing_print (~> 1.4) autoprefixer-rails (~> 10.4) avo (~> 2.45) - aws-sdk-s3 (~> 1.139) + aws-sdk-s3 (~> 1.140) aws-sdk-sqs (~> 1.68) bcrypt (~> 3.1) bootsnap (~> 1.16) From a8e759ff231509e235d4c82843d2c53fee6dd91a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:01:08 -0600 Subject: [PATCH 032/112] Bump phlex-rails from 1.1.0 to 1.1.1 (#4244) Bumps [phlex-rails](https://github.com/phlex-ruby/phlex-rails) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/phlex-ruby/phlex-rails/releases) - [Changelog](https://github.com/phlex-ruby/phlex-rails/blob/main/CHANGELOG.md) - [Commits](https://github.com/phlex-ruby/phlex-rails/compare/1.1.0...1.1.1) --- updated-dependencies: - dependency-name: phlex-rails dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index afc643d0039..4e918373a53 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -431,7 +431,7 @@ GEM concurrent-ruby (~> 1.2) erb (>= 4) zeitwerk (~> 2.6) - phlex-rails (1.1.0) + phlex-rails (1.1.1) phlex (~> 1.9) railties (>= 6.1, < 8) zeitwerk (~> 2.6) From c0a25b73dcd08b7f80eba1b2e468fad3c1ab0816 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:05:00 +0000 Subject: [PATCH 033/112] Bump good_job from 3.21.1 to 3.21.2 (#4243) Bumps [good_job](https://github.com/bensheldon/good_job) from 3.21.1 to 3.21.2. - [Release notes](https://github.com/bensheldon/good_job/releases) - [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md) - [Commits](https://github.com/bensheldon/good_job/compare/v3.21.1...v3.21.2) --- updated-dependencies: - dependency-name: good_job dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4e918373a53..da6544916e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,7 +241,7 @@ GEM ffi (~> 1.0) globalid (1.2.1) activesupport (>= 6.1) - good_job (3.21.1) + good_job (3.21.2) activejob (>= 6.0.0) activerecord (>= 6.0.0) concurrent-ruby (>= 1.0.2) From 6ceaba7df81ecaf04e71919571e4049b86386cec Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Mon, 27 Nov 2023 11:42:59 -0600 Subject: [PATCH 034/112] Use ApiKey#owner instead of user (#4213) * Change api_keys null constraints * Use ApiKey#owner instead of user To be merged once backfill has run Part of the work to support trusted publishers --- app/avo/resources/api_key_resource.rb | 5 +- app/controllers/api/base_controller.rb | 14 ++-- app/controllers/api/v1/api_keys_controller.rb | 2 +- .../api/v1/deletions_controller.rb | 1 + .../v1/github_secret_scanning_controller.rb | 5 +- .../api/v1/oidc/api_key_roles_controller.rb | 1 + .../api/v1/oidc/id_tokens_controller.rb | 1 + .../api/v1/oidc/providers_controller.rb | 1 + app/controllers/api/v1/owners_controller.rb | 1 + app/controllers/api/v1/rubygems_controller.rb | 3 +- .../api/v1/web_hooks_controller.rb | 1 + .../v1/webauthn_verifications_controller.rb | 4 +- app/controllers/api_keys_controller.rb | 2 +- app/controllers/dashboards_controller.rb | 2 +- app/mailers/mailer.rb | 4 +- app/models/api_key.rb | 16 +++- app/models/pusher.rb | 58 ++++++------- app/models/user.rb | 6 +- app/policies/api_key_policy.rb | 2 +- .../backfill_api_key_owners_task.rb | 12 --- config/initializers/rack_attack.rb | 12 ++- ...20032536_change_api_key_user_id_to_null.rb | 5 ++ ...033231_change_api_key_owner_to_not_null.rb | 6 ++ ...lidate_change_api_key_owner_to_not_null.rb | 6 ++ db/schema.rb | 6 +- db/seeds.rb | 1 - test/factories/api_key.rb | 2 +- .../api/v1/api_keys_controller_test.rb | 4 +- .../api/v1/deletions_controller_test.rb | 2 +- .../api/v1/owners_controller_test.rb | 4 +- .../api/v1/rubygems_controller_test.rb | 6 +- .../webauthn_verifications_controller_test.rb | 2 +- test/functional/api_keys_controller_test.rb | 20 ++--- test/functional/passwords_controller_test.rb | 2 +- test/helpers/rate_limit_helpers.rb | 9 +- .../api/v1/github_secret_scanning_test.rb | 2 +- .../api/v1/oidc/api_key_roles_test.rb | 6 +- .../integration/api/v1/oidc/id_tokens_test.rb | 2 +- .../integration/api/v1/oidc/providers_test.rb | 2 +- test/integration/api/v1/rubygems_test.rb | 4 +- test/integration/dashboard_test.rb | 2 +- test/integration/push_test.rb | 22 ++++- test/integration/rack_attack_test.rb | 22 ++--- test/integration/yank_test.rb | 2 +- test/jobs/delete_user_job_test.rb | 4 +- test/jobs/store_version_contents_job_test.rb | 2 +- test/jobs/yank_version_contents_job_test.rb | 3 +- test/mailers/previews/mailer_preview.rb | 6 +- test/models/api_key_test.rb | 21 +++-- test/models/deletion_test.rb | 3 +- test/models/oidc/api_key_role_test.rb | 8 +- test/models/parallel_pusher_test.rb | 5 +- test/models/pusher_test.rb | 82 +++++++++++++++---- test/models/user_test.rb | 2 +- test/models/web_hook_test.rb | 2 +- test/system/api_keys_test.rb | 22 ++--- test/system/multifactor_auths_test.rb | 4 +- test/system/webauthn_verification_test.rb | 2 +- .../backfill_api_key_owners_task_test.rb | 25 ------ test/unit/helpers/api_keys_helper_test.rb | 4 +- 60 files changed, 286 insertions(+), 201 deletions(-) delete mode 100644 app/tasks/maintenance/backfill_api_key_owners_task.rb create mode 100644 db/migrate/20231120032536_change_api_key_user_id_to_null.rb create mode 100644 db/migrate/20231120033231_change_api_key_owner_to_not_null.rb create mode 100644 db/migrate/20231120033411_validate_change_api_key_owner_to_not_null.rb delete mode 100644 test/tasks/maintenance/backfill_api_key_owners_task_test.rb diff --git a/app/avo/resources/api_key_resource.rb b/app/avo/resources/api_key_resource.rb index d54462c3413..4843f45c154 100644 --- a/app/avo/resources/api_key_resource.rb +++ b/app/avo/resources/api_key_resource.rb @@ -9,7 +9,10 @@ class ExpiredFilter < ScopeBooleanFilter; end field :name, as: :text, link_to_resource: true field :hashed_key, as: :text, visible: ->(_) { false } - field :user, as: :belongs_to + field :user, as: :belongs_to, visible: ->(_) { false } + field :owner, as: :belongs_to, + polymorphic_as: :owner, + types: [::User] field :last_accessed_at, as: :date_time field :soft_deleted_at, as: :date_time field :soft_deleted_rubygem_name, as: :text diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index ced1af0b435..c385e5de60d 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -45,23 +45,23 @@ def verify_with_otp def verify_mfa_requirement if @rubygem && !@rubygem.mfa_requirement_satisfied_for?(@api_key.user) render plain: "Gem requires MFA enabled; You do not have MFA enabled yet.", status: :forbidden - elsif @api_key.user.mfa_required_not_yet_enabled? + elsif @api_key.mfa_required_not_yet_enabled? render_mfa_setup_required_error - elsif @api_key.user.mfa_required_weak_level_enabled? + elsif @api_key.mfa_required_weak_level_enabled? render_mfa_strong_level_required_error end end def response_with_mfa_warning(response) message = response - if @api_key.user.mfa_recommended_not_yet_enabled? + if @api_key.mfa_recommended_not_yet_enabled? message += <<~WARN.chomp [WARNING] For protection of your account and gems, we encourage you to set up multi-factor authentication \ at https://rubygems.org/multifactor_auth/new. Your account will be required to have MFA enabled in the future. WARN - elsif @api_key.user.mfa_recommended_weak_level_enabled? + elsif @api_key.mfa_recommended_weak_level_enabled? message += <<~WARN.chomp @@ -99,10 +99,14 @@ def authenticate_with_api_key hashed_key = Digest::SHA256.hexdigest(params_key) @api_key = ApiKey.unexpired.find_by_hashed_key(hashed_key) return render_unauthorized unless @api_key - set_tags "gemcutter.user.id" => @api_key.user_id, "gemcutter.user.api_key_id" => @api_key.id + set_tags "gemcutter.api_key.owner" => @api_key.owner.to_gid, "gemcutter.user.api_key_id" => @api_key.id render_soft_deleted_api_key if @api_key.soft_deleted? end + def verify_user_api_key + render_api_key_forbidden if @api_key.user.blank? + end + def render_unauthorized render plain: t(:please_sign_up), status: :unauthorized end diff --git a/app/controllers/api/v1/api_keys_controller.rb b/app/controllers/api/v1/api_keys_controller.rb index 71e66afa45e..bf026ec097d 100644 --- a/app/controllers/api/v1/api_keys_controller.rb +++ b/app/controllers/api/v1/api_keys_controller.rb @@ -20,7 +20,7 @@ def create check_mfa(user) do key = generate_unique_rubygems_key - build_params = { user: user, hashed_key: hashed_key(key), **api_key_create_params } + build_params = { owner: user, hashed_key: hashed_key(key), **api_key_create_params } api_key = ApiKey.new(build_params) save_and_respond(api_key, key) diff --git a/app/controllers/api/v1/deletions_controller.rb b/app/controllers/api/v1/deletions_controller.rb index 68b048ff659..ecdef8322cc 100644 --- a/app/controllers/api/v1/deletions_controller.rb +++ b/app/controllers/api/v1/deletions_controller.rb @@ -1,5 +1,6 @@ class Api::V1::DeletionsController < Api::BaseController before_action :authenticate_with_api_key + before_action :verify_user_api_key before_action :find_rubygem_by_name before_action :verify_api_key_gem_scope before_action :validate_gem_and_version diff --git a/app/controllers/api/v1/github_secret_scanning_controller.rb b/app/controllers/api/v1/github_secret_scanning_controller.rb index a74fab71d6e..c49e6676973 100644 --- a/app/controllers/api/v1/github_secret_scanning_controller.rb +++ b/app/controllers/api/v1/github_secret_scanning_controller.rb @@ -28,7 +28,7 @@ def revoke resp = [] tokens.each do |t| api_key = ApiKey.find_by(hashed_key: hashed_key(t[:token])) - label = if api_key&.destroy + label = if api_key&.expire! schedule_revoke_email(api_key, t[:url]) "true_positive" else @@ -50,7 +50,8 @@ def revoke private def schedule_revoke_email(api_key, url) - Mailer.api_key_revoked(api_key.user_id, api_key.name, api_key.enabled_scopes.join(", "), url).deliver_later + return unless api_key.user? + Mailer.api_key_revoked(api_key.owner_id, api_key.name, api_key.enabled_scopes.join(", "), url).deliver_later end def secret_scanning_key(key_id) diff --git a/app/controllers/api/v1/oidc/api_key_roles_controller.rb b/app/controllers/api/v1/oidc/api_key_roles_controller.rb index 77784d0e07a..fde11eb2c3a 100644 --- a/app/controllers/api/v1/oidc/api_key_roles_controller.rb +++ b/app/controllers/api/v1/oidc/api_key_roles_controller.rb @@ -2,6 +2,7 @@ class Api::V1::OIDC::ApiKeyRolesController < Api::BaseController include ApiKeyable before_action :authenticate_with_api_key, except: :assume_role + before_action :verify_user_api_key, except: :assume_role with_options only: :assume_role do before_action :set_api_key_role diff --git a/app/controllers/api/v1/oidc/id_tokens_controller.rb b/app/controllers/api/v1/oidc/id_tokens_controller.rb index 70e6aa6be9b..36b3f5ef11c 100644 --- a/app/controllers/api/v1/oidc/id_tokens_controller.rb +++ b/app/controllers/api/v1/oidc/id_tokens_controller.rb @@ -1,5 +1,6 @@ class Api::V1::OIDC::IdTokensController < Api::BaseController before_action :authenticate_with_api_key + before_action :verify_user_api_key def index render json: @api_key.user.oidc_id_tokens diff --git a/app/controllers/api/v1/oidc/providers_controller.rb b/app/controllers/api/v1/oidc/providers_controller.rb index bb285adfd02..dd848058526 100644 --- a/app/controllers/api/v1/oidc/providers_controller.rb +++ b/app/controllers/api/v1/oidc/providers_controller.rb @@ -1,5 +1,6 @@ class Api::V1::OIDC::ProvidersController < Api::BaseController before_action :authenticate_with_api_key + before_action :verify_user_api_key def index render json: OIDC::Provider.all diff --git a/app/controllers/api/v1/owners_controller.rb b/app/controllers/api/v1/owners_controller.rb index 96ba0b81e08..6c71e10de93 100644 --- a/app/controllers/api/v1/owners_controller.rb +++ b/app/controllers/api/v1/owners_controller.rb @@ -1,5 +1,6 @@ class Api::V1::OwnersController < Api::BaseController before_action :authenticate_with_api_key, except: %i[show gems] + before_action :verify_user_api_key, except: %i[show gems] before_action :find_rubygem, except: :gems before_action :verify_api_key_gem_scope, except: %i[show gems] before_action :verify_gem_ownership, except: %i[show gems] diff --git a/app/controllers/api/v1/rubygems_controller.rb b/app/controllers/api/v1/rubygems_controller.rb index 42f85e7319c..f0400afd0a0 100644 --- a/app/controllers/api/v1/rubygems_controller.rb +++ b/app/controllers/api/v1/rubygems_controller.rb @@ -1,6 +1,7 @@ class Api::V1::RubygemsController < Api::BaseController before_action :authenticate_with_api_key, except: %i[show reverse_dependencies] - before_action :find_rubygem, only: %i[show reverse_dependencies] + before_action :verify_user_api_key, except: %i[show reverse_dependencies create] + before_action :find_rubygem, only: %i[show reverse_dependencies] before_action :cors_preflight_check, only: :show before_action :verify_with_otp, only: %i[create] before_action :verify_mfa_requirement, only: %i[create] diff --git a/app/controllers/api/v1/web_hooks_controller.rb b/app/controllers/api/v1/web_hooks_controller.rb index 783630646c3..3127ac4411f 100644 --- a/app/controllers/api/v1/web_hooks_controller.rb +++ b/app/controllers/api/v1/web_hooks_controller.rb @@ -1,5 +1,6 @@ class Api::V1::WebHooksController < Api::BaseController before_action :authenticate_with_api_key + before_action :verify_user_api_key before_action :render_api_key_forbidden, if: :api_key_unauthorized? before_action :find_rubygem_by_name, :set_url, except: :index diff --git a/app/controllers/api/v1/webauthn_verifications_controller.rb b/app/controllers/api/v1/webauthn_verifications_controller.rb index 6fbdd2663dc..d2ed0e48bb5 100644 --- a/app/controllers/api/v1/webauthn_verifications_controller.rb +++ b/app/controllers/api/v1/webauthn_verifications_controller.rb @@ -35,13 +35,13 @@ def status def authenticate_with_credentials params_key = request.headers["Authorization"] || "" hashed_key = Digest::SHA256.hexdigest(params_key) - api_key = ApiKey.find_by_hashed_key(hashed_key) + api_key = ApiKey.unexpired.find_by_hashed_key(hashed_key) @user = authenticated_user(api_key) end def authenticated_user(api_key) - return api_key.user if api_key + return api_key.user if api_key&.user? authenticate_or_request_with_http_basic do |username, password| User.authenticate(username.strip, password) end diff --git a/app/controllers/api_keys_controller.rb b/app/controllers/api_keys_controller.rb index d76752dbd3c..ec7ce441ca6 100644 --- a/app/controllers/api_keys_controller.rb +++ b/app/controllers/api_keys_controller.rb @@ -24,7 +24,7 @@ def edit def create key = generate_unique_rubygems_key - build_params = { user: current_user, hashed_key: hashed_key(key), **api_key_params } + build_params = { owner: current_user, hashed_key: hashed_key(key), **api_key_params } @api_key = ApiKey.new(build_params) if @api_key.errors.present? diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index c2671b13bf7..6e23b8c6df8 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -25,7 +25,7 @@ def authenticate_with_api_key hashed_key = Digest::SHA256.hexdigest(params_key) return unless (@api_key = ApiKey.unexpired.find_by_hashed_key(hashed_key)) - set_tags "gemcutter.user.id" => @api_key.user_id, "gemcutter.user.api_key_id" => @api_key.id + set_tags "gemcutter.api_key.owner" => @api_key.owner.to_gid, "gemcutter.api_key" => @api_key.to_gid render plain: "An invalid API key cannot be used. Please delete it and create a new one.", status: :forbidden if @api_key.soft_deleted? end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index c5f9361c62b..c9b46dd0db1 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -57,10 +57,10 @@ def notifiers_changed(user_id) default: "You changed your RubyGems.org email notification settings") end - def gem_pushed(pushed_by_user_id, version_id, notified_user_id) + def gem_pushed(pushed_by, version_id, notified_user_id) @version = Version.find(version_id) notified_user = User.find(notified_user_id) - @pushed_by_user = User.find(pushed_by_user_id) + @pushed_by_user = pushed_by mail to: notified_user.email, subject: I18n.t("mailer.gem_pushed.subject", gem: @version.to_title, host: Gemcutter::HOST_DISPLAY, diff --git a/app/models/api_key.rb b/app/models/api_key.rb index fce947e74e3..72aec9c6472 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -2,7 +2,8 @@ class ApiKey < ApplicationRecord API_SCOPES = %i[index_rubygems push_rubygem yank_rubygem add_owner remove_owner access_webhooks show_dashboard].freeze APPLICABLE_GEM_API_SCOPES = %i[push_rubygem yank_rubygem add_owner remove_owner].freeze - belongs_to :user, inverse_of: :api_keys + self.ignored_columns += %w[user_id] + belongs_to :owner, polymorphic: true has_one :api_key_rubygem_scope, dependent: :destroy @@ -47,6 +48,18 @@ def enabled_scopes end end + def user + owner if user? + end + + def user? + owner_type == "User" + end + + delegate :mfa_required_not_yet_enabled?, :mfa_required_weak_level_enabled?, + :mfa_recommended_not_yet_enabled?, :mfa_recommended_weak_level_enabled?, + to: :user, allow_nil: true + def mfa_authorized?(otp) return true unless mfa_enabled? return true if oidc_id_token.present? @@ -54,6 +67,7 @@ def mfa_authorized?(otp) end def mfa_enabled? + return false unless user? return false unless user.mfa_enabled? user.mfa_ui_and_api? || mfa end diff --git a/app/models/pusher.rb b/app/models/pusher.rb index 50599c58fcc..d1800271043 100644 --- a/app/models/pusher.rb +++ b/app/models/pusher.rb @@ -4,34 +4,26 @@ class Pusher include TraceTagger include SemanticLogger::Loggable - attr_reader :api_key, :user, :spec, :spec_contents, :message, :code, :rubygem, :body, :version, :version_id, :size - - def initialize(api_key, body, remote_ip = "", scoped_rubygem = nil) - # this is ugly, but easier than updating all the unit tests, for now - case api_key - when ApiKey - @api_key = api_key - @user = api_key.user - raise ArgumentError if scoped_rubygem - scoped_rubygem = api_key.rubygem - else - @user = api_key - end + attr_reader :api_key, :owner, :spec, :spec_contents, :message, :code, :rubygem, :body, :version, :version_id, :size + + def initialize(api_key, body, remote_ip = "") + @api_key = api_key + @owner = api_key.owner + @scoped_rubygem = api_key.rubygem @body = StringIO.new(body.read) @size = @body.size @remote_ip = remote_ip - @scoped_rubygem = scoped_rubygem end def process - trace("gemcutter.pusher.process", tags: { "gemcutter.user.id" => user.id }) do + trace("gemcutter.pusher.process", tags: { "gemcutter.api_key.owner" => owner.to_gid }) do pull_spec && find && authorize && verify_gem_scope && verify_mfa_requirement && validate && save end end def authorize - rubygem.pushable? || rubygem.owned_by?(user) || notify_unauthorized + (rubygem.pushable? && api_key.user?) || owner.owns_gem?(rubygem) || notify_unauthorized end def verify_gem_scope @@ -41,7 +33,7 @@ def verify_gem_scope end def verify_mfa_requirement - user.mfa_enabled? || !(version_mfa_required? || rubygem.metadata_mfa_required?) || + api_key.mfa_enabled? || !(version_mfa_required? || rubygem.metadata_mfa_required?) || notify("Rubygem requires owners to enable MFA. You must enable MFA before pushing new version.", 403) end @@ -67,11 +59,11 @@ def save write_gem @body, @spec_contents end rescue ArgumentError => e - @version.destroy + @version&.destroy Rails.error.report(e, handled: true) notify("There was a problem saving your gem. #{e}", 400) rescue StandardError => e - @version.destroy + @version&.destroy Rails.error.report(e, handled: true) notify("There was a problem saving your gem. Please try again.", 500) else @@ -94,7 +86,7 @@ def pull_spec MSG end - def find + def find # rubocop:disable Metrics/AbcSize name = spec.name.to_s set_tag "gemcutter.rubygem.name", name @@ -124,7 +116,7 @@ def find size: size, sha256: sha256, spec_sha256: spec_sha256, - pusher: user, + pusher: api_key.user, pusher_api_key: api_key, cert_chain: spec.cert_chain @@ -136,7 +128,7 @@ def find # Overridden so we don't get megabytes of the raw data printing out def inspect - attrs = %i[@rubygem @user @message @code].map do |attr| + attrs = %i[@rubygem @owner @message @code].map do |attr| "#{attr}=#{instance_variable_get(attr).inspect}" end "" @@ -147,7 +139,7 @@ def inspect def after_write @version_id = version.id version.rubygem.push_notifiable_owners.each do |notified_user| - Mailer.gem_pushed(user.id, @version_id, notified_user.id).deliver_later + Mailer.gem_pushed(owner, @version_id, notified_user.id).deliver_later end Indexer.perform_later UploadVersionsFileJob.perform_later @@ -156,18 +148,18 @@ def after_write ReindexRubygemJob.perform_later(rubygem:) GemCachePurger.call(rubygem.name) StoreVersionContentsJob.perform_later(version:) if ld_variation(key: "gemcutter.pusher.store_version_contents", default: false) - RackAttackReset.gem_push_backoff(@remote_ip, @user.display_id) if @remote_ip.present? + RackAttackReset.gem_push_backoff(@remote_ip, owner.to_gid) if @remote_ip.present? StatsD.increment "push.success" end def ld_variation(key:, default:) Rails.configuration.launch_darkly_client.variation( - key, user.ld_context, default + key, owner.ld_context, default ) end def notify(message, code) - logger.info { { message:, code:, user: user.id, api_key: api_key&.id, rubygem: rubygem&.name, version: version&.full_name } } + logger.info { { message:, code:, owner: owner, api_key: api_key&.id, rubygem: rubygem&.name, version: version&.full_name } } @message = message @code = code @@ -177,7 +169,7 @@ def notify(message, code) def update rubygem.disown if rubygem.versions.indexed.count.zero? rubygem.update_attributes_from_gem_specification!(version, spec) - rubygem.create_ownership(user) + rubygem.create_ownership(owner) set_info_checksum true @@ -197,18 +189,20 @@ def republish_notification(version) "Please bump the version number and push a new different release.\n" \ "See also `gem yank` if you want to unpublish the bad release.", 409) else - different_owner = "pushed by a previous owner of this gem " unless version.rubygem.owners.include?(@user) + different_owner = "pushed by a previous owner of this gem " unless owner.owns_gem?(version.rubygem) notify("A yanked version #{different_owner}already exists (#{version.full_name}).\n" \ "Repushing of gem versions is not allowed. Please use a new version and retry", 409) end end def notify_unauthorized - if rubygem.unconfirmed_ownership?(user) + if !api_key.user? + notify("You are not allowed to push this gem.", 403) + elsif rubygem.unconfirmed_ownership?(owner) notify("You do not have permission to push to this gem. " \ - "Please confirm the ownership by clicking on the confirmation link sent your email #{user.email}", 403) + "Please confirm the ownership by clicking on the confirmation link sent your email #{owner.email}", 403) else - notify("You do not have permission to push to this gem. Ask an owner to add you with: gem owner #{rubygem.name} --add #{user.email}", 403) + notify("You do not have permission to push to this gem. Ask an owner to add you with: gem owner #{rubygem.name} --add #{owner.email}", 403) end end @@ -264,7 +258,7 @@ def log_pushing } end - { message: "Pushing gem", version:, rubygem: @version.rubygem.name, pusher: user.as_json } + { message: "Pushing gem", version:, rubygem: @version.rubygem.name, pusher: owner.as_json } end end diff --git a/app/models/user.rb b/app/models/user.rb index 6ab389e51b7..5b76ef72312 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,7 +33,7 @@ class User < ApplicationRecord # used for deleting unconfirmed ownerships as well on user destroy has_many :unconfirmed_ownerships, -> { unconfirmed }, dependent: :destroy, inverse_of: :user, class_name: "Ownership" - has_many :api_keys, dependent: :destroy + has_many :api_keys, dependent: :destroy, inverse_of: :owner, as: :owner has_many :ownership_calls, -> { opened }, dependent: :destroy, inverse_of: :user has_many :ownership_requests, -> { opened }, dependent: :destroy, inverse_of: :user @@ -249,6 +249,10 @@ def can_request_ownership?(rubygem) !rubygem.owned_by?(self) && rubygem.ownership_requestable? end + def owns_gem?(rubygem) + rubygem.owned_by?(self) + end + def ld_context LaunchDarkly::LDContext.create( key: "user-key-#{id}", diff --git a/app/policies/api_key_policy.rb b/app/policies/api_key_policy.rb index bb391458a03..b99a290268c 100644 --- a/app/policies/api_key_policy.rb +++ b/app/policies/api_key_policy.rb @@ -6,7 +6,7 @@ def resolve end def avo_show? - Pundit.policy!(user, record.user).avo_show? + Pundit.policy!(user, record.owner).avo_show? end has_association :api_key_rubygem_scope diff --git a/app/tasks/maintenance/backfill_api_key_owners_task.rb b/app/tasks/maintenance/backfill_api_key_owners_task.rb deleted file mode 100644 index ae6d13aee61..00000000000 --- a/app/tasks/maintenance/backfill_api_key_owners_task.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class Maintenance::BackfillApiKeyOwnersTask < MaintenanceTasks::Task - def collection - ApiKey.where(owner_id: nil).or(ApiKey.where(owner_type: nil)) - end - - def process(api_key) - api_key.owner ||= api_key.user - api_key.save!(validate: false) - end -end diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index fe6f9000289..536b04dbf02 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -90,6 +90,14 @@ def self.api_hashed_key(req) ApiKey.find_by_hashed_key(hashed_key) end + def self.api_key_owner_id(req) + api_key = api_hashed_key(req) + return unless api_key + + URI::GID.build(app: GlobalID.app, + model_name: api_key.association(:owner).klass.name, model_id: api_key.owner_id).to_s + end + safelist("assets path") do |req| req.path.starts_with?("/assets") && req.request_method == "GET" end @@ -128,7 +136,7 @@ def self.api_hashed_key(req) ########################### rate limit per api key ########################### throttle("api/key/#{level}", limit: EXP_BASE_REQUEST_LIMIT * level, period: (EXP_BASE_LIMIT_PERIOD**level).seconds) do |req| - api_hashed_key(req)&.user&.display_id.presence if protected_route?(protected_api_mfa_actions, req.path, req.request_method) + api_key_owner_id(req) if protected_route?(protected_api_mfa_actions, req.path, req.request_method) end end @@ -144,7 +152,7 @@ def self.api_hashed_key(req) end throttle("#{PUSH_THROTTLE_PER_USER_KEY}/#{level}", limit: EXP_BASE_REQUEST_LIMIT * level, period: (EXP_BASE_LIMIT_PERIOD**level).seconds) do |req| - api_hashed_key(req)&.user&.display_id.presence if protected_route?(protected_push_action, req.path, req.request_method) + api_key_owner_id(req) if protected_route?(protected_push_action, req.path, req.request_method) end end diff --git a/db/migrate/20231120032536_change_api_key_user_id_to_null.rb b/db/migrate/20231120032536_change_api_key_user_id_to_null.rb new file mode 100644 index 00000000000..7afcb3d87b6 --- /dev/null +++ b/db/migrate/20231120032536_change_api_key_user_id_to_null.rb @@ -0,0 +1,5 @@ +class ChangeApiKeyUserIdToNull < ActiveRecord::Migration[7.0] + def change + change_column_null :api_keys, :user_id, true + end +end diff --git a/db/migrate/20231120033231_change_api_key_owner_to_not_null.rb b/db/migrate/20231120033231_change_api_key_owner_to_not_null.rb new file mode 100644 index 00000000000..a3cb7d498ca --- /dev/null +++ b/db/migrate/20231120033231_change_api_key_owner_to_not_null.rb @@ -0,0 +1,6 @@ +class ChangeApiKeyOwnerToNotNull < ActiveRecord::Migration[7.0] + def change + add_check_constraint :api_keys, "owner_id IS NOT NULL", name: "api_keys_owner_id_null", validate: false + add_check_constraint :api_keys, "owner_type IS NOT NULL", name: "api_keys_owner_type_null", validate: false + end +end diff --git a/db/migrate/20231120033411_validate_change_api_key_owner_to_not_null.rb b/db/migrate/20231120033411_validate_change_api_key_owner_to_not_null.rb new file mode 100644 index 00000000000..a4529a44f31 --- /dev/null +++ b/db/migrate/20231120033411_validate_change_api_key_owner_to_not_null.rb @@ -0,0 +1,6 @@ +class ValidateChangeApiKeyOwnerToNotNull < ActiveRecord::Migration[7.0] + def change + validate_check_constraint :api_keys, name: "api_keys_owner_id_null" + validate_check_constraint :api_keys, name: "api_keys_owner_type_null" + end +end diff --git a/db/schema.rb b/db/schema.rb index aa18aa8c2e8..83bb90d4f2d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_11_02_190427) do +ActiveRecord::Schema[7.0].define(version: 2023_11_20_033411) do # These are extensions that must be enabled in order to support this database enable_extension "hstore" enable_extension "pgcrypto" @@ -37,7 +37,7 @@ end create_table "api_keys", force: :cascade do |t| - t.bigint "user_id", null: false + t.bigint "user_id" t.string "name", null: false t.string "hashed_key", null: false t.boolean "index_rubygems", default: false, null: false @@ -59,6 +59,8 @@ t.index ["hashed_key"], name: "index_api_keys_on_hashed_key", unique: true t.index ["owner_type", "owner_id"], name: "index_api_keys_on_owner" t.index ["user_id"], name: "index_api_keys_on_user_id" + t.check_constraint "owner_id IS NOT NULL", name: "api_keys_owner_id_null" + t.check_constraint "owner_type IS NOT NULL", name: "api_keys_owner_type_null" end create_table "audits", force: :cascade do |t| diff --git a/db/seeds.rb b/db/seeds.rb index 96df8d24be7..5066bec2218 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -233,7 +233,6 @@ end author.api_keys.find_or_create_by!( - user: author, hashed_key: "unexpiredmanualhashedkey", name: "Manual", push_rubygem: true, diff --git a/test/factories/api_key.rb b/test/factories/api_key.rb index 3895329b825..6d3a35bfc5f 100644 --- a/test/factories/api_key.rb +++ b/test/factories/api_key.rb @@ -2,7 +2,7 @@ factory :api_key do transient { key { "12345" } } - user + association :owner, factory: :user name { "ci-key" } # enabled by default. disabled when show_dashboard is enabled. diff --git a/test/functional/api/v1/api_keys_controller_test.rb b/test/functional/api/v1/api_keys_controller_test.rb index 1ee90dc5b1c..8a03c0268c9 100644 --- a/test/functional/api/v1/api_keys_controller_test.rb +++ b/test/functional/api/v1/api_keys_controller_test.rb @@ -158,7 +158,7 @@ def self.should_expect_otp_for_update context "with correct OTP" do setup do @request.env["HTTP_OTP"] = ROTP::TOTP.new(@user.totp_seed).now - @api_key = create(:api_key, user: @user, key: "12345", push_rubygem: true) + @api_key = create(:api_key, owner: @user, key: "12345", push_rubygem: true) put :update, params: { api_key: "12345", index_rubygems: "true" } @api_key.reload @@ -536,7 +536,7 @@ def self.should_expect_otp_for_update context "with correct credentials" do setup do - @api_key = create(:api_key, user: @user, key: "12345", push_rubygem: true) + @api_key = create(:api_key, owner: @user, key: "12345", push_rubygem: true) authorize_with("#{@user.email}:#{@user.password}") end diff --git a/test/functional/api/v1/deletions_controller_test.rb b/test/functional/api/v1/deletions_controller_test.rb index f7f2fd90e75..6d9ca36e69f 100644 --- a/test/functional/api/v1/deletions_controller_test.rb +++ b/test/functional/api/v1/deletions_controller_test.rb @@ -136,7 +136,7 @@ class Api::V1::DeletionsControllerTest < ActionController::TestCase context "with api key gem scoped" do setup do - @api_key = create(:api_key, name: "gem-scoped-delete-key", key: "123456", yank_rubygem: true, user: @user, rubygem_id: @rubygem.id) + @api_key = create(:api_key, name: "gem-scoped-delete-key", key: "123456", yank_rubygem: true, owner: @user, rubygem_id: @rubygem.id) @request.env["HTTP_AUTHORIZATION"] = "123456" end diff --git a/test/functional/api/v1/owners_controller_test.rb b/test/functional/api/v1/owners_controller_test.rb index 5f2d14f059c..e979c4c3f53 100644 --- a/test/functional/api/v1/owners_controller_test.rb +++ b/test/functional/api/v1/owners_controller_test.rb @@ -107,7 +107,7 @@ def self.should_respond_to(format) @second_user = create(:user) @third_user = create(:user) @ownership = create(:ownership, rubygem: @rubygem, user: @user) - @api_key = create(:api_key, key: "12334", add_owner: true, user: @user) + @api_key = create(:api_key, key: "12334", add_owner: true, owner: @user) @request.env["HTTP_AUTHORIZATION"] = "12334" end @@ -549,7 +549,7 @@ def self.should_respond_to(format) @ownership = create(:ownership, rubygem: @rubygem, user: @user) @ownership = create(:ownership, rubygem: @rubygem, user: @second_user) - @api_key = create(:api_key, key: "12223", remove_owner: true, user: @user) + @api_key = create(:api_key, key: "12223", remove_owner: true, owner: @user) @request.env["HTTP_AUTHORIZATION"] = "12223" end diff --git a/test/functional/api/v1/rubygems_controller_test.rb b/test/functional/api/v1/rubygems_controller_test.rb index a165f53424b..f26ed302993 100644 --- a/test/functional/api/v1/rubygems_controller_test.rb +++ b/test/functional/api/v1/rubygems_controller_test.rb @@ -696,7 +696,7 @@ def self.should_respond_to(format) context "to a gem with ownership removed" do setup do ownership = create(:ownership, user: create(:user), rubygem: create(:rubygem, name: "test-gem123")) - @api_key = create(:api_key, key: "12343", user: ownership.user, ownership: ownership, push_rubygem: true) + @api_key = create(:api_key, key: "12343", owner: ownership.user, ownership: ownership, push_rubygem: true) ownership.destroy! @request.env["HTTP_AUTHORIZATION"] = "12343" @@ -713,7 +713,7 @@ def self.should_respond_to(format) context "to a different gem" do setup do ownership = create(:ownership, user: create(:user), rubygem: create(:rubygem, name: "test-gem")) - create(:api_key, key: "12343", user: ownership.user, ownership: ownership, push_rubygem: true) + create(:api_key, key: "12343", owner: ownership.user, ownership: ownership, push_rubygem: true) @request.env["HTTP_AUTHORIZATION"] = "12343" post :create, body: gem_file("test-1.0.0.gem", &:read) @@ -729,7 +729,7 @@ def self.should_respond_to(format) context "to the gem being pushed" do setup do ownership = create(:ownership, user: create(:user), rubygem: create(:rubygem, name: "test")) - create(:api_key, key: "12343", user: ownership.user, ownership: ownership, push_rubygem: true) + create(:api_key, key: "12343", owner: ownership.user, ownership: ownership, push_rubygem: true) @request.env["HTTP_AUTHORIZATION"] = "12343" post :create, body: gem_file("test-1.0.0.gem", &:read) diff --git a/test/functional/api/v1/webauthn_verifications_controller_test.rb b/test/functional/api/v1/webauthn_verifications_controller_test.rb index d8af891a996..6e978ebc8c0 100644 --- a/test/functional/api/v1/webauthn_verifications_controller_test.rb +++ b/test/functional/api/v1/webauthn_verifications_controller_test.rb @@ -162,7 +162,7 @@ def self.should_respond_to_format(format) context "when authenticating with an api key" do setup do - create(:api_key, key: "12345", push_rubygem: true, user: @user) + create(:api_key, key: "12345", push_rubygem: true, owner: @user) @request.env["HTTP_AUTHORIZATION"] = "12345" get :status, params: { webauthn_token: @user.webauthn_verification.path_token, format: :json } end diff --git a/test/functional/api_keys_controller_test.rb b/test/functional/api_keys_controller_test.rb index 36df232f05a..4c1b1829402 100644 --- a/test/functional/api_keys_controller_test.rb +++ b/test/functional/api_keys_controller_test.rb @@ -71,7 +71,7 @@ class ApiKeysControllerTest < ActionController::TestCase context "api key exists" do setup do - @api_key = create(:api_key, user: @user) + @api_key = create(:api_key, owner: @user) get :index end @@ -162,7 +162,7 @@ class ApiKeysControllerTest < ActionController::TestCase context "on GET to edit" do setup do - @api_key = create(:api_key, user: @user) + @api_key = create(:api_key, owner: @user) get :edit, params: { id: @api_key.id } end @@ -183,7 +183,7 @@ class ApiKeysControllerTest < ActionController::TestCase end context "on PATCH to update" do - setup { @api_key = create(:api_key, user: @user) } + setup { @api_key = create(:api_key, owner: @user) } context "with successful save" do setup do @@ -251,7 +251,7 @@ class ApiKeysControllerTest < ActionController::TestCase context "on DELETE to destroy" do context "user is owner of key" do - setup { @api_key = create(:api_key, user: @user) } + setup { @api_key = create(:api_key, owner: @user) } context "with successful destroy" do setup { delete :destroy, params: { id: @api_key.id } } @@ -294,8 +294,8 @@ class ApiKeysControllerTest < ActionController::TestCase context "on DELETE to reset" do setup do - create(:api_key, key: "1234", user: @user) - create(:api_key, key: "2345", user: @user) + create(:api_key, key: "1234", owner: @user) + create(:api_key, key: "2345", owner: @user) delete :reset end @@ -368,7 +368,7 @@ class ApiKeysControllerTest < ActionController::TestCase context "on DELETE to reset" do setup do - create(:api_key, key: "1234", user: @user) + create(:api_key, key: "1234", owner: @user) delete :reset end @@ -401,7 +401,7 @@ class ApiKeysControllerTest < ActionController::TestCase context "on GET to edit" do setup do - @api_key = create(:api_key, user: @user) + @api_key = create(:api_key, owner: @user) get :edit, params: { id: @api_key.id } end @@ -414,7 +414,7 @@ class ApiKeysControllerTest < ActionController::TestCase context "on PATCH to update" do setup do - @api_key = create(:api_key, user: @user) + @api_key = create(:api_key, owner: @user) patch :update, params: { api_key: { name: "test", add_owner: true }, id: @api_key.id } @api_key.reload end @@ -424,7 +424,7 @@ class ApiKeysControllerTest < ActionController::TestCase context "on DELETE to destroy" do setup do - @api_key = create(:api_key, user: @user) + @api_key = create(:api_key, owner: @user) delete :destroy, params: { id: @api_key.id } end diff --git a/test/functional/passwords_controller_test.rb b/test/functional/passwords_controller_test.rb index b21799d579a..86057ab2a39 100644 --- a/test/functional/passwords_controller_test.rb +++ b/test/functional/passwords_controller_test.rb @@ -318,7 +318,7 @@ class PasswordsControllerTest < ActionController::TestCase setup do @user = create(:user) @api_key = @user.api_key - @new_api_key = create(:api_key, user: @user) + @new_api_key = create(:api_key, owner: @user) @old_encrypted_password = @user.encrypted_password end diff --git a/test/helpers/rate_limit_helpers.rb b/test/helpers/rate_limit_helpers.rb index 4b02e3d863b..fba31c815f2 100644 --- a/test/helpers/rate_limit_helpers.rb +++ b/test/helpers/rate_limit_helpers.rb @@ -73,30 +73,31 @@ def stay_under_exponential_limit(scope) Rack::Attack::EXP_BACKOFF_LEVELS.each do |level| under_backoff_limit = (Rack::Attack::EXP_BASE_REQUEST_LIMIT * level) - 1 throttle_level_key = "#{scope}/#{level}:#{@ip_address}" - under_backoff_limit.times { Rack::Attack.cache.count(throttle_level_key, exp_base_limit_period**level) } + update_limit_for(throttle_level_key, under_backoff_limit, exp_base_limit_period**level) end end def update_limit_for(key, limit, period = limit_period) + key = Rack::Attack.throttle_discriminator_normalizer.call(key) limit.times { Rack::Attack.cache.count(key, period) } end def exceed_exponential_limit_for(scope, level) expo_exceeding_limit = exceeding_exp_base_limit * level expo_limit_period = exp_base_limit_period**level - expo_exceeding_limit.times { Rack::Attack.cache.count("#{scope}:#{@ip_address}", expo_limit_period) } + update_limit_for("#{scope}:#{@ip_address}", expo_exceeding_limit, expo_limit_period) end def exceed_exponential_user_limit_for(scope, id, level) expo_exceeding_limit = exceeding_exp_base_limit * level expo_limit_period = exp_base_limit_period**level - expo_exceeding_limit.times { Rack::Attack.cache.count("#{scope}:#{id}", expo_limit_period) } + update_limit_for("#{scope}:#{id}", expo_exceeding_limit, expo_limit_period) end def exceed_exponential_api_key_limit_for(scope, user_display_id, level) expo_exceeding_limit = exceeding_exp_base_limit * level expo_limit_period = exp_base_limit_period**level - expo_exceeding_limit.times { Rack::Attack.cache.count("#{scope}:#{user_display_id}", expo_limit_period) } + update_limit_for("#{scope}:#{user_display_id}", expo_exceeding_limit, expo_limit_period) end def encode(username, password) diff --git a/test/integration/api/v1/github_secret_scanning_test.rb b/test/integration/api/v1/github_secret_scanning_test.rb index 71cab1abb74..5a69478a63a 100644 --- a/test/integration/api/v1/github_secret_scanning_test.rb +++ b/test/integration/api/v1/github_secret_scanning_test.rb @@ -130,7 +130,7 @@ class Api::V1::GitHubSecretScanningTest < ActionDispatch::IntegrationTest assert_equal "true_positive", json.last["label"] assert_equal @tokens.last["token"], json.last["token_raw"] - assert_raises(ActiveRecord::RecordNotFound) { @api_key.reload } + assert_predicate @api_key.reload, :expired? end should "delivers an email" do diff --git a/test/integration/api/v1/oidc/api_key_roles_test.rb b/test/integration/api/v1/oidc/api_key_roles_test.rb index 32db380c24f..1acdfd1895e 100644 --- a/test/integration/api/v1/oidc/api_key_roles_test.rb +++ b/test/integration/api/v1/oidc/api_key_roles_test.rb @@ -8,7 +8,7 @@ class Api::V1::OIDC::ApiKeyRolesTest < ActionDispatch::IntegrationTest @role = create(:oidc_api_key_role) @user = @role.user @user_api_key = "12323" - @api_key = create(:api_key, user: @user, key: @user_api_key) + @api_key = create(:api_key, owner: @user, key: @user_api_key) end should "return the user's roles" do @@ -47,7 +47,7 @@ class Api::V1::OIDC::ApiKeyRolesTest < ActionDispatch::IntegrationTest @role = create(:oidc_api_key_role) @user = @role.user @user_api_key = "12323" - @api_key = create(:api_key, user: @user, key: @user_api_key) + @api_key = create(:api_key, owner: @user, key: @user_api_key) end should "return the user's roles" do @@ -107,7 +107,7 @@ def jwt(claims = @claims, key: @pkey) "base_ref" => "", "head_ref" => "", "ref_type" => "branch", - "workflow" => ".github/workflows/token.yml", + "workflow" => "token", "event_name" => "push", "repository" => "segiddins/oidc-test", "run_number" => "4", diff --git a/test/integration/api/v1/oidc/id_tokens_test.rb b/test/integration/api/v1/oidc/id_tokens_test.rb index ecfab025740..60bd7154cc4 100644 --- a/test/integration/api/v1/oidc/id_tokens_test.rb +++ b/test/integration/api/v1/oidc/id_tokens_test.rb @@ -9,7 +9,7 @@ class Api::V1::OIDC::IdTokensTest < ActionDispatch::IntegrationTest @id_token = create(:oidc_id_token, user: @user, api_key_role: @role) @user_api_key = "12323" - @api_key = create(:api_key, user: @user, key: @user_api_key) + @api_key = create(:api_key, owner: @user, key: @user_api_key) end context "on GET to index" do diff --git a/test/integration/api/v1/oidc/providers_test.rb b/test/integration/api/v1/oidc/providers_test.rb index 9d85bbbcacb..41c9eac6540 100644 --- a/test/integration/api/v1/oidc/providers_test.rb +++ b/test/integration/api/v1/oidc/providers_test.rb @@ -8,7 +8,7 @@ class Api::V1::OIDC::ProvidersTest < ActionDispatch::IntegrationTest @user = create(:user) @user_api_key = "12323" - @api_key = create(:api_key, user: @user, key: @user_api_key) + @api_key = create(:api_key, owner: @user, key: @user_api_key) end context "on GET to index" do diff --git a/test/integration/api/v1/rubygems_test.rb b/test/integration/api/v1/rubygems_test.rb index b7761e55023..78ab0650b38 100644 --- a/test/integration/api/v1/rubygems_test.rb +++ b/test/integration/api/v1/rubygems_test.rb @@ -4,12 +4,12 @@ class Api::V1::RubygemsTest < ActionDispatch::IntegrationTest setup do @key = "12345" @user = create(:user) - create(:api_key, user: @user, key: @key, index_rubygems: true, push_rubygem: true) + create(:api_key, owner: @user, key: @key, index_rubygems: true, push_rubygem: true) end test "request has remote addr present" do ip_address = "1.2.3.4" - RackAttackReset.expects(:gem_push_backoff).with(ip_address, @user.display_id).once + RackAttackReset.expects(:gem_push_backoff).with(ip_address, @user.to_gid).once post "/api/v1/gems", params: gem_file("test-1.0.0.gem", &:read), diff --git a/test/integration/dashboard_test.rb b/test/integration/dashboard_test.rb index 3ac4f7c7d82..3570b196f1a 100644 --- a/test/integration/dashboard_test.rb +++ b/test/integration/dashboard_test.rb @@ -10,7 +10,7 @@ class DashboardTest < ActionDispatch::IntegrationTest test "request with array of api keys does not pass autorization" do delete sign_out_path - create(:api_key, user: @user, key: "1234", show_dashboard: true) + create(:api_key, owner: @user, key: "1234", show_dashboard: true) rubygem = create(:rubygem, name: "sandworm", number: "1.0.0") create(:subscription, rubygem: rubygem, user: @user) diff --git a/test/integration/push_test.rb b/test/integration/push_test.rb index 46576cc0f15..28115c8f150 100644 --- a/test/integration/push_test.rb +++ b/test/integration/push_test.rb @@ -7,7 +7,7 @@ class PushTest < ActionDispatch::IntegrationTest Dir.chdir(Dir.mktmpdir) @key = "12345" @user = create(:user) - create(:api_key, user: @user, key: @key, push_rubygem: true) + create(:api_key, owner: @user, key: @key, push_rubygem: true) end test "pushing a gem" do @@ -439,6 +439,26 @@ class PushTest < ActionDispatch::IntegrationTest assert_response :forbidden end + + should "fail when spec.date cannot Marshal.dump" do + build_gem_raw(file_name: "malicious.gem", spec: <<~YAML) + --- !ruby/object:Gem::Specification + specification_version: 100 + name: book + version: '1' + platform: ruby + summary: 'malicious' + authors: [test@example.com] + date: !ruby/object:Time + a: 1 + YAML + + capture_io do + push_gem "malicious.gem" + end + + assert_response :unprocessable_entity + end end def push_gem(path) diff --git a/test/integration/rack_attack_test.rb b/test/integration/rack_attack_test.rb index 392cfd36780..392e50827a0 100644 --- a/test/integration/rack_attack_test.rb +++ b/test/integration/rack_attack_test.rb @@ -87,7 +87,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest setup do @rubygem = create(:rubygem, name: "test", number: "0.0.1") create(:ownership, user: @user, rubygem: @rubygem) - create(:api_key, key: "12334", push_rubygem: true, user: @user) + create(:api_key, key: "12334", push_rubygem: true, owner: @user) end should "allow gem push by ip" do @@ -123,11 +123,11 @@ class RackAttackTest < ActionDispatch::IntegrationTest @push_exp_throttle_level_key = "#{Rack::Attack::PUSH_EXP_THROTTLE_KEY}/#{level}:#{@ip_address}" under_backoff_limit.times { Rack::Attack.cache.count(@push_exp_throttle_level_key, exp_base_limit_period**level) } - @push_throttle_per_user_key = "#{Rack::Attack::PUSH_THROTTLE_PER_USER_KEY}/#{level}:#{@user.display_id}" + @push_throttle_per_user_key = "#{Rack::Attack::PUSH_THROTTLE_PER_USER_KEY}/#{level}:#{@user.to_gid}" under_backoff_limit.times { Rack::Attack.cache.count(@push_throttle_per_user_key, exp_base_limit_period**level) } end - create(:api_key, key: "12334", push_rubygem: true, user: @user) + create(:api_key, key: "12334", push_rubygem: true, owner: @user) post "/api/v1/gems", params: gem_file("test-0.0.0.gem", &:read), headers: { REMOTE_ADDR: @ip_address, HTTP_AUTHORIZATION: "12334", CONTENT_TYPE: "application/octet-stream" } @@ -197,7 +197,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest @user.enable_totp!(ROTP::Base32.random_base32, :ui_and_api) stay_under_exponential_limit("api/ip") - create(:api_key, key: "12334", add_owner: true, yank_rubygem: true, remove_owner: true, user: @user) + create(:api_key, key: "12334", add_owner: true, yank_rubygem: true, remove_owner: true, owner: @user) @rubygem = create(:rubygem, name: "test", number: "0.0.1") create(:ownership, user: @user, rubygem: @rubygem) end @@ -419,7 +419,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest should "throttle gem push by ip" do exceed_push_limit_for("api/push/ip") - create(:api_key, key: "12334", push_rubygem: true, user: @user) + create(:api_key, key: "12334", push_rubygem: true, owner: @user) post "/api/v1/gems", params: gem_file("test-1.0.0.gem", &:read), @@ -434,7 +434,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest @mfa_max_period = { 1 => 300, 2 => 90_000 } @user.enable_totp!(ROTP::Base32.random_base32, :ui_only) @api_key = "12345" - create(:api_key, key: @api_key, user: @user) + create(:api_key, key: @api_key, owner: @user) end Rack::Attack::EXP_BACKOFF_LEVELS.each do |level| @@ -519,7 +519,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest should "throttle api key show by api key #{level}" do freeze_time do - exceed_exponential_api_key_limit_for("api/key/#{level}", @user.display_id, level) + exceed_exponential_api_key_limit_for("api/key/#{level}", @user.to_gid, level) get "/api/v1/api_key.json", headers: { HTTP_AUTHORIZATION: @api_key } assert_throttle_at(level) @@ -537,7 +537,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest should "throttle api key create by api key #{level}" do freeze_time do - exceed_exponential_api_key_limit_for("api/key/#{level}", @user.display_id, level) + exceed_exponential_api_key_limit_for("api/key/#{level}", @user.to_gid, level) post "/api/v1/api_key.json", headers: { HTTP_AUTHORIZATION: @api_key } assert_throttle_at(level) @@ -576,7 +576,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest should "throttle gem yank by api key #{level}" do freeze_time do - exceed_exponential_api_key_limit_for("api/key/#{level}", @user.display_id, level) + exceed_exponential_api_key_limit_for("api/key/#{level}", @user.to_gid, level) delete "/api/v1/gems/yank", headers: { HTTP_AUTHORIZATION: @api_key } assert_throttle_at(level) @@ -594,7 +594,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest should "throttle owner add by api key #{level}" do freeze_time do - exceed_exponential_api_key_limit_for("api/key/#{level}", @user.display_id, level) + exceed_exponential_api_key_limit_for("api/key/#{level}", @user.to_gid, level) post "/api/v1/gems/somegem/owners", headers: { HTTP_AUTHORIZATION: @api_key } assert_throttle_at(level) @@ -612,7 +612,7 @@ class RackAttackTest < ActionDispatch::IntegrationTest should "throttle owner remove by api key #{level}" do freeze_time do - exceed_exponential_api_key_limit_for("api/key/#{level}", @user.display_id, level) + exceed_exponential_api_key_limit_for("api/key/#{level}", @user.to_gid, level) delete "/api/v1/gems/somegem/owners", headers: { HTTP_AUTHORIZATION: @api_key } assert_throttle_at(level) diff --git a/test/integration/yank_test.rb b/test/integration/yank_test.rb index 5ebf973cf73..985ad770f62 100644 --- a/test/integration/yank_test.rb +++ b/test/integration/yank_test.rb @@ -7,7 +7,7 @@ class YankTest < SystemTest create(:ownership, user: @user, rubygem: @rubygem) @user_api_key = "12345" - create(:api_key, user: @user, key: @user_api_key, yank_rubygem: true) + create(:api_key, owner: @user, key: @user_api_key, yank_rubygem: true) Dir.chdir(Dir.mktmpdir) visit sign_in_path diff --git a/test/jobs/delete_user_job_test.rb b/test/jobs/delete_user_job_test.rb index b747538a526..3216f14edd9 100644 --- a/test/jobs/delete_user_job_test.rb +++ b/test/jobs/delete_user_job_test.rb @@ -23,7 +23,7 @@ class DeleteUserJobTest < ActiveJob::TestCase test "succeeds with api key" do user = create(:user) - create(:api_key, user:) + create(:api_key, owner: user) Mailer.expects(:deletion_complete).with(user.email).returns(mock(deliver_later: nil)) DeleteUserJob.perform_now(user:) @@ -31,7 +31,7 @@ class DeleteUserJobTest < ActiveJob::TestCase test "succeeds with api key used to push version" do user = create(:user) - api_key = create(:api_key, user:) + api_key = create(:api_key, owner: user) create(:version, pusher_api_key: api_key, pusher: user) Mailer.expects(:deletion_complete).with(user.email).returns(mock(deliver_later: nil)) diff --git a/test/jobs/store_version_contents_job_test.rb b/test/jobs/store_version_contents_job_test.rb index 586ac2ea6ab..42344c95da0 100644 --- a/test/jobs/store_version_contents_job_test.rb +++ b/test/jobs/store_version_contents_job_test.rb @@ -8,7 +8,7 @@ class StoreVersionContentsJobTest < ActiveJob::TestCase @gem = gem_file("bin_and_img-0.1.0.gem") @user = create(:user) - pusher = Pusher.new(create(:api_key, user: @user), @gem) + pusher = Pusher.new(create(:api_key, owner: @user), @gem) assert pusher.process, "gem should be pushed successfully: #{pusher.code} #{pusher.message}" @gem.rewind diff --git a/test/jobs/yank_version_contents_job_test.rb b/test/jobs/yank_version_contents_job_test.rb index 661b88f16f6..49e324fa1be 100644 --- a/test/jobs/yank_version_contents_job_test.rb +++ b/test/jobs/yank_version_contents_job_test.rb @@ -6,7 +6,8 @@ class YankVersionContentsJobTest < ActiveJob::TestCase RubygemFs.mock! @user = create(:user) - gem_file("bin_and_img-0.1.0.gem") { |gem| Pusher.new(@user, gem).process } + @api_key = create(:api_key, owner: @user) + gem_file("bin_and_img-0.1.0.gem") { |gem| Pusher.new(@api_key, gem).process } @version = Version.last StoreVersionContentsJob.perform_now(version: @version) @rubygem = @version.rubygem diff --git a/test/mailers/previews/mailer_preview.rb b/test/mailers/previews/mailer_preview.rb index 7ccdb7ba1a7..ad0dff8d879 100644 --- a/test/mailers/previews/mailer_preview.rb +++ b/test/mailers/previews/mailer_preview.rb @@ -30,7 +30,7 @@ def notifiers_changed def gem_pushed ownership = Ownership.where.not(user: nil).where(push_notifier: true).last - Mailer.gem_pushed(ownership.user_id, ownership.rubygem.versions.last.id, ownership.user_id) + Mailer.gem_pushed(ownership.user, ownership.rubygem.versions.last.id, ownership.user_id) end def mfa_notification @@ -84,7 +84,7 @@ def owner_added end def api_key_created - api_key = ApiKey.last + api_key = ApiKey.where(owner_type: "User").last Mailer.api_key_created(api_key.id) end @@ -94,7 +94,7 @@ def api_key_created_oidc_api_key_role end def api_key_revoked - api_key = ApiKey.last + api_key = ApiKey.where(owner_type: "User").last Mailer.api_key_revoked(api_key.user.id, api_key.name, api_key.enabled_scopes.join(", "), "https://example.com") end diff --git a/test/models/api_key_test.rb b/test/models/api_key_test.rb index 7f02a0f86cf..91ad24f188c 100644 --- a/test/models/api_key_test.rb +++ b/test/models/api_key_test.rb @@ -1,7 +1,6 @@ require "test_helper" class ApiKeyTest < ActiveSupport::TestCase - should belong_to :user should belong_to :owner should validate_presence_of(:name) should validate_presence_of(:hashed_key) @@ -54,12 +53,12 @@ class ApiKeyTest < ActiveSupport::TestCase context "gem scope" do setup do @ownership = create(:ownership) - @api_key = create(:api_key, push_rubygem: true, user: @ownership.user, ownership: @ownership) - @api_key_no_gem_scope = create(:api_key, key: SecureRandom.hex(24), index_rubygems: true, user: @ownership.user) + @api_key = create(:api_key, push_rubygem: true, owner: @ownership.user, ownership: @ownership) + @api_key_no_gem_scope = create(:api_key, key: SecureRandom.hex(24), index_rubygems: true, owner: @ownership.user) end should "be invalid if non applicable API scope is enabled" do - api_key = build(:api_key, index_rubygems: true, user: @ownership.user, ownership: @ownership) + api_key = build(:api_key, index_rubygems: true, owner: @ownership.user, ownership: @ownership) refute_predicate api_key, :valid? assert_contains api_key.errors[:rubygem], "scope can only be set for push/yank rubygem, and add/remove owner scopes" @@ -67,7 +66,7 @@ class ApiKeyTest < ActiveSupport::TestCase should "be valid if applicable API scope is enabled" do %i[push_rubygem yank_rubygem add_owner remove_owner].each do |scope| - api_key = build(:api_key, scope => true, user: @ownership.user, ownership: @ownership) + api_key = build(:api_key, scope => true, owner: @ownership.user, ownership: @ownership) assert_predicate api_key, :valid? end @@ -95,7 +94,7 @@ class ApiKeyTest < ActiveSupport::TestCase context "#rubygem_id=" do should "set ownership to a gem" do - api_key = create(:api_key, key: SecureRandom.hex(24), push_rubygem: true, user: @ownership.user, rubygem_id: @ownership.rubygem_id) + api_key = create(:api_key, key: SecureRandom.hex(24), push_rubygem: true, owner: @ownership.user, rubygem_id: @ownership.rubygem_id) assert_equal @ownership.rubygem_id, api_key.rubygem_id end @@ -107,7 +106,7 @@ class ApiKeyTest < ActiveSupport::TestCase end should "add error when id is not associated with the user" do - api_key = ApiKey.new(hashed_key: SecureRandom.hex(24), push_rubygem: true, user: @ownership.user, rubygem_id: -1) + api_key = ApiKey.new(hashed_key: SecureRandom.hex(24), push_rubygem: true, owner: @ownership.user, rubygem_id: -1) assert_contains api_key.errors[:rubygem], "must be a gem that you are an owner of" end @@ -119,7 +118,7 @@ class ApiKeyTest < ActiveSupport::TestCase :api_key, key: SecureRandom.hex(24), push_rubygem: true, - user: @ownership.user, + owner: @ownership.user, rubygem_name: @ownership.rubygem.name ) @@ -137,7 +136,7 @@ class ApiKeyTest < ActiveSupport::TestCase api_key = ApiKey.new( hashed_key: SecureRandom.hex(24), push_rubygem: true, - user: @ownership.user, + owner: @ownership.user, rubygem_name: rubygem.name ) @@ -148,7 +147,7 @@ class ApiKeyTest < ActiveSupport::TestCase api_key = ApiKey.new( hashed_key: SecureRandom.hex(24), push_rubygem: true, - user: @ownership.user, + owner: @ownership.user, rubygem_name: "invalid-gem-name" ) @@ -185,7 +184,7 @@ class ApiKeyTest < ActiveSupport::TestCase context "#soft_deleted_by_ownership?" do should "return true if soft deleted gem name is present" do ownership = create(:ownership) - api_key = create(:api_key, push_rubygem: true, user: ownership.user, ownership: ownership) + api_key = create(:api_key, push_rubygem: true, owner: ownership.user, ownership: ownership) api_key.soft_delete!(ownership: ownership) assert_predicate api_key, :soft_deleted_by_ownership? diff --git a/test/models/deletion_test.rb b/test/models/deletion_test.rb index e636cc7556f..1e28a38a99d 100644 --- a/test/models/deletion_test.rb +++ b/test/models/deletion_test.rb @@ -6,8 +6,9 @@ class DeletionTest < ActiveSupport::TestCase setup do @user = create(:user) + @api_key = create(:api_key, owner: @user) @gem_file = gem_file("test-0.0.0.gem") - Pusher.new(@user, @gem_file).process + Pusher.new(@api_key, @gem_file).process @gem_file.rewind @version = Version.last @spec_rz = RubygemFs.instance.get("quick/Marshal.4.8/#{@version.full_name}.gemspec.rz") diff --git a/test/models/oidc/api_key_role_test.rb b/test/models/oidc/api_key_role_test.rb index 4752a2d77af..790f6a1ee16 100644 --- a/test/models/oidc/api_key_role_test.rb +++ b/test/models/oidc/api_key_role_test.rb @@ -29,16 +29,16 @@ class OIDC::ApiKeyRoleTest < ActiveSupport::TestCase empty_gems = create(:oidc_api_key_role, api_key_permissions: { gems: [], scopes: ["push_rubygem"] }, user:) nil_gems = create(:oidc_api_key_role, api_key_permissions: { gems: nil, scopes: ["push_rubygem"] }, user:) - assert_equal [rubygem_role], OIDC::ApiKeyRole.for_rubygem(rubygem).to_a - assert_equal [@role, empty_gems, nil_gems], OIDC::ApiKeyRole.for_rubygem(nil).to_a + assert_same_elements [rubygem_role], OIDC::ApiKeyRole.for_rubygem(rubygem).to_a + assert_same_elements [@role, empty_gems, nil_gems], OIDC::ApiKeyRole.for_rubygem(nil).to_a end test "for_scope scope" do role1 = create(:oidc_api_key_role, api_key_permissions: { gems: [], scopes: %w[push_rubygem yank_rubygem] }) role2 = create(:oidc_api_key_role, api_key_permissions: { gems: [], scopes: ["push_rubygem"] }) - assert_equal [role1, role2], OIDC::ApiKeyRole.for_scope("push_rubygem").to_a - assert_equal [role1], OIDC::ApiKeyRole.for_scope("yank_rubygem").to_a + assert_same_elements [role1, role2], OIDC::ApiKeyRole.for_scope("push_rubygem").to_a + assert_same_elements [role1], OIDC::ApiKeyRole.for_scope("yank_rubygem").to_a assert_predicate OIDC::ApiKeyRole.for_scope("show_dashboard"), :none? end diff --git a/test/models/parallel_pusher_test.rb b/test/models/parallel_pusher_test.rb index b24b33b858c..249fa9561fc 100644 --- a/test/models/parallel_pusher_test.rb +++ b/test/models/parallel_pusher_test.rb @@ -8,6 +8,7 @@ class ParallelPusherTest < ActiveSupport::TestCase setup do @fs = RubygemFs.mock! @user = create(:user, email: "user@example.com") + @api_key = create(:api_key, owner: @user) end teardown do @@ -22,7 +23,7 @@ class ParallelPusherTest < ActiveSupport::TestCase Thread.new do gem_file("hola-0.0.0.gem") do |gem1| - Pusher.new(@user, gem1).process + Pusher.new(@api_key, gem1).process end ActiveRecord::Base.connection.close latch.count_down @@ -30,7 +31,7 @@ class ParallelPusherTest < ActiveSupport::TestCase Thread.new do gem_file("hola/hola-0.0.0.gem") do |gem2| - Pusher.new(@user, gem2).process + Pusher.new(@api_key, gem2).process end ActiveRecord::Base.connection.close latch.count_down diff --git a/test/models/pusher_test.rb b/test/models/pusher_test.rb index 23180b00935..e28ea19a702 100644 --- a/test/models/pusher_test.rb +++ b/test/models/pusher_test.rb @@ -5,8 +5,9 @@ class PusherTest < ActiveSupport::TestCase setup do @user = create(:user, email: "user@example.com") + @api_key = create(:api_key, owner: @user) @gem = gem_file - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) # Ensure we test #log_pushing @cutter.logger.level = :info @@ -18,7 +19,7 @@ class PusherTest < ActiveSupport::TestCase context "creating a new gemcutter" do should "have some state" do - assert_respond_to @cutter, :user + assert_respond_to @cutter, :owner assert_respond_to @cutter, :version assert_respond_to @cutter, :version_id assert_respond_to @cutter, :spec @@ -27,7 +28,7 @@ class PusherTest < ActiveSupport::TestCase assert_respond_to @cutter, :rubygem assert_respond_to @cutter, :body - assert_equal @user, @cutter.user + assert_equal @user, @cutter.owner end should "initialize size from the gem" do @@ -35,7 +36,7 @@ class PusherTest < ActiveSupport::TestCase end should "#inspect" do - assert_equal "", + assert_equal "", @cutter.inspect end @@ -133,7 +134,7 @@ class PusherTest < ActiveSupport::TestCase should "not be able to pull spec with metadata containing bad ruby objects" do @gem = gem_file("exploit.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) out, err = capture_io do @cutter.pull_spec end @@ -152,7 +153,7 @@ class PusherTest < ActiveSupport::TestCase # this isn't the kind of invalid that we're testing with this gem Gem::Specification.any_instance.stubs(:authors).returns(["user@example.com"]) @gem = gem_file("legit-gem-0.0.1.gem.fake") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) @cutter.stubs(:save).never @cutter.process @@ -164,7 +165,7 @@ class PusherTest < ActiveSupport::TestCase should "not be able to save a gem if the date is not valid" do @gem = gem_file("bad-date-1.0.0.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) out, err = capture_io do @cutter.process end @@ -241,7 +242,7 @@ class PusherTest < ActiveSupport::TestCase should "not be able to save a gem if it is signed and has been tampered with" do @gem = gem_file("valid_signature_tampered-0.0.1.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) @cutter.process assert_includes @cutter.message, %(missing signing certificate) @@ -250,7 +251,7 @@ class PusherTest < ActiveSupport::TestCase should "not be able to save a gem if it is signed with an expired signing certificate" do @gem = gem_file("expired_signature-0.0.0.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) @cutter.process assert_includes @cutter.message, %(not valid after 2021-07-08 08:21:01 UTC) @@ -269,7 +270,7 @@ class PusherTest < ActiveSupport::TestCase spec.cert_chain = two_cert_chain(signing_key: signing_key) end - @cutter = Pusher.new(@user, File.open(gem_file)) + @cutter = Pusher.new(@api_key, File.open(gem_file)) @cutter.process assert_equal 200, @cutter.code @@ -289,7 +290,7 @@ class PusherTest < ActiveSupport::TestCase Gem::Security::SigningPolicy.verify_root = old_verify_root_policy end - @cutter = Pusher.new(@user, File.open(gem_file)) + @cutter = Pusher.new(@api_key, File.open(gem_file)) @cutter.process assert_includes @cutter.message, %(CN=Root not valid after) @@ -304,7 +305,7 @@ class PusherTest < ActiveSupport::TestCase should "not be able to pull spec with metadata containing bad ruby symbols" do ["1.0.0", "2.0.0", "3.0.0", "4.0.0"].each do |version| @gem = gem_file("dos-#{version}.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) out, err = capture_io do @cutter.pull_spec end @@ -320,7 +321,7 @@ class PusherTest < ActiveSupport::TestCase should "be able to pull spec with metadata containing aliases" do @gem = gem_file("aliases-0.0.0.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) @cutter.pull_spec assert_not_nil @cutter.spec @@ -329,7 +330,7 @@ class PusherTest < ActiveSupport::TestCase should "not be able to pull spec when no data available" do @gem = gem_file("aliases-nodata-0.0.1.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) @cutter.pull_spec assert_includes @cutter.message, %{package content (data.tar.gz) is missing} @@ -495,6 +496,20 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: assert @cutter.authorize end + should "be false if rubygem is new and api key has unexpected owner type" do + @cutter.stubs(:rubygem).returns Rubygem.new + + owner = stub("owner") + @api_key.update_columns(owner_id: 0, owner_type: "stub") + @cutter.stubs(:owner).returns owner + owner.expects(:owns_gem?).with(@cutter.rubygem).returns(false) + + refute @cutter.authorize + assert_equal "You are not allowed to push this gem.", + @cutter.message + assert_equal 403, @cutter.code + end + context "with a existing rubygem" do setup do @rubygem = create(:rubygem, name: "the_gem_name") @@ -520,6 +535,18 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: assert_equal 403, @cutter.code end + should "be false if api key has unexpected owner type" do + owner = stub("owner") + @api_key.update_columns(owner_id: 0, owner_type: "stub") + @cutter.stubs(:owner).returns owner + owner.expects(:owns_gem?).with(@rubygem).returns(false) + + refute @cutter.authorize + assert_equal "You are not allowed to push this gem.", + @cutter.message + assert_equal 403, @cutter.code + end + should "be true if not owned by user but no indexed versions exist" do create(:version, rubygem: @rubygem, number: "0.1.1", indexed: false) @@ -680,7 +707,7 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: context "pushing to s3 fails" do setup do @gem = gem_file("test-1.0.0.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) @fs = RubygemFs.s3!("https://some.host") s3_exception = Aws::S3::Errors::ServiceError.new("stub raises", "something went wrong") Aws::S3::Client.any_instance.stubs(:put_object).with(any_parameters).raises(s3_exception) @@ -700,6 +727,23 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: end end + context "saving fails with ArgumentError" do + setup do + @gem = gem_file("test-1.0.0.gem") + @cutter = Pusher.new(@api_key, @gem) + @cutter.stubs(:update).raises(ArgumentError.new("some message")) + @cutter.process + end + + should "not create rubygem or version" do + rubygem = Rubygem.find_by(name: "test") + expected_message = "There was a problem saving your gem. some message" + + assert_equal expected_message, @cutter.message + assert_nil rubygem + end + end + context "has a scoped gem" do setup do @rubygem = create(:rubygem) @@ -707,7 +751,8 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: should "pushes gem if scoped to the same gem" do create(:version, rubygem: @rubygem, number: "0.1.1", indexed: false) - cutter = Pusher.new(@user, @gem, "", @rubygem) + @api_key.ownership = create(:ownership, rubygem: @rubygem, user: @user) + cutter = Pusher.new(@api_key, @gem, "") cutter.stubs(:rubygem).returns @rubygem assert cutter.verify_gem_scope @@ -715,7 +760,8 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: should "does not push gem if scoped to another gem" do create(:version, rubygem: @rubygem, number: "0.1.1", indexed: false) - cutter = Pusher.new(@user, @gem, "", create(:rubygem)) + @api_key.ownership = create(:ownership, rubygem: create(:rubygem), user: @user) + cutter = Pusher.new(@api_key, @gem, "") cutter.stubs(:rubygem).returns @rubygem refute cutter.verify_gem_scope @@ -725,7 +771,7 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: context "the gem has been signed and not tampered with" do setup do @gem = gem_file("valid_signature-0.0.0.gem") - @cutter = Pusher.new(@user, @gem) + @cutter = Pusher.new(@api_key, @gem) @cutter.process end diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 0c91fe0ffd6..cd417db467f 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -395,7 +395,7 @@ class UserTest < ActiveSupport::TestCase context "blocking user with api key" do setup do - api_key = create(:api_key, user: @user) + api_key = create(:api_key, owner: @user) # simulate gem pushed using api key to ensure # user with pushed gems can be blocked create(:version, pusher: @user, pusher_api_key: api_key) diff --git a/test/models/web_hook_test.rb b/test/models/web_hook_test.rb index 272f39eb663..87468c90ddf 100644 --- a/test/models/web_hook_test.rb +++ b/test/models/web_hook_test.rb @@ -181,7 +181,7 @@ class WebHookTest < ActiveSupport::TestCase should "include an Authorization header for a user with many API keys" do @hook.user.update(api_key: nil) - create(:api_key, user: @hook.user) + create(:api_key, owner: @hook.user) authorization = Digest::SHA2.hexdigest(@rubygem.name + @version.number + @hook.user.api_keys.first.hashed_key) stub_request(:post, @url).with(headers: { "Authorization" => authorization }) diff --git a/test/system/api_keys_test.rb b/test/system/api_keys_test.rb index ffb47eba928..55adbda6a62 100644 --- a/test/system/api_keys_test.rb +++ b/test/system/api_keys_test.rb @@ -29,7 +29,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "creating new api key from index" do - create(:api_key, user: @user) + create(:api_key, owner: @user) visit_profile_api_keys_path click_button "New API key" @@ -132,7 +132,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "update api key scope" do - api_key = create(:api_key, user: @user) + api_key = create(:api_key, owner: @user) visit_profile_api_keys_path click_button "Edit" @@ -148,7 +148,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "update api key gem scope" do - api_key = create(:api_key, push_rubygem: true, user: @user, ownership: @ownership) + api_key = create(:api_key, push_rubygem: true, owner: @user, ownership: @ownership) visit_profile_api_keys_path click_button "Edit" @@ -163,7 +163,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "update gem scoped api key with applicable scopes removed" do - api_key = create(:api_key, push_rubygem: true, user: @user, ownership: @ownership) + api_key = create(:api_key, push_rubygem: true, owner: @user, ownership: @ownership) visit_profile_api_keys_path click_button "Edit" @@ -179,7 +179,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "update gem scoped api key to another applicable scope" do - api_key = create(:api_key, push_rubygem: true, user: @user, ownership: @ownership) + api_key = create(:api_key, push_rubygem: true, owner: @user, ownership: @ownership) visit_profile_api_keys_path click_button "Edit" @@ -197,7 +197,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "update api key gem scope to a gem the user does not own" do - api_key = create(:api_key, push_rubygem: true, user: @user, ownership: @ownership) + api_key = create(:api_key, push_rubygem: true, owner: @user, ownership: @ownership) @another_ownership = create(:ownership, user: @user, rubygem: create(:rubygem, name: "another_gem")) visit_profile_api_keys_path @@ -218,7 +218,7 @@ class ApiKeysTest < ApplicationSystemTestCase test "update api key with MFA UI enabled" do @user.enable_totp!(ROTP::Base32.random_base32, :ui_only) - api_key = create(:api_key, user: @user) + api_key = create(:api_key, owner: @user) visit_profile_api_keys_path click_button "Edit" @@ -235,7 +235,7 @@ class ApiKeysTest < ApplicationSystemTestCase test "update api key with MFA UI and API enabled" do @user.enable_totp!(ROTP::Base32.random_base32, :ui_and_api) - api_key = create(:api_key, user: @user) + api_key = create(:api_key, owner: @user) visit_profile_api_keys_path click_button "Edit" @@ -251,7 +251,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "deleting api key" do - create(:api_key, user: @user) + create(:api_key, owner: @user) visit_profile_api_keys_path click_button "Delete" @@ -262,7 +262,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "deleting all api key" do - create(:api_key, user: @user) + create(:api_key, owner: @user) visit_profile_api_keys_path click_button "Reset" @@ -273,7 +273,7 @@ class ApiKeysTest < ApplicationSystemTestCase end test "gem ownership removed displays api key as invalid" do - api_key = create(:api_key, push_rubygem: true, user: @user, ownership: @ownership) + api_key = create(:api_key, push_rubygem: true, owner: @user, ownership: @ownership) visit_profile_api_keys_path refute page.has_css? ".owners__row__invalid" diff --git a/test/system/multifactor_auths_test.rb b/test/system/multifactor_auths_test.rb index 80f474bc63f..a53eec596bb 100644 --- a/test/system/multifactor_auths_test.rb +++ b/test/system/multifactor_auths_test.rb @@ -42,7 +42,7 @@ class MultifactorAuthsTest < ApplicationSystemTestCase end test "user with mfa disabled gets redirected back to profile api keys pages after setting up mfa" do - create(:api_key, push_rubygem: true, user: @user, ownership: @ownership) + create(:api_key, push_rubygem: true, owner: @user, ownership: @ownership) redirect_test_mfa_disabled(profile_api_keys_path) { verify_password } end @@ -75,7 +75,7 @@ class MultifactorAuthsTest < ApplicationSystemTestCase end test "user with weak level mfa gets redirected back to profile api keys pages after setting up mfa" do - create(:api_key, push_rubygem: true, user: @user, ownership: @ownership) + create(:api_key, push_rubygem: true, owner: @user, ownership: @ownership) redirect_test_mfa_weak_level(profile_api_keys_path) { verify_password } end diff --git a/test/system/webauthn_verification_test.rb b/test/system/webauthn_verification_test.rb index 979e37ba43a..f7ce6b42ac0 100644 --- a/test/system/webauthn_verification_test.rb +++ b/test/system/webauthn_verification_test.rb @@ -129,7 +129,7 @@ def assert_link_is_expired end def assert_poll_status(status) - @api_key ||= create(:api_key, key: "12345", push_rubygem: true, user: @user) + @api_key ||= create(:api_key, key: "12345", push_rubygem: true, owner: @user) Capybara.current_driver = :rack_test page.driver.header "AUTHORIZATION", "12345" diff --git a/test/tasks/maintenance/backfill_api_key_owners_task_test.rb b/test/tasks/maintenance/backfill_api_key_owners_task_test.rb deleted file mode 100644 index 1f833cf11b6..00000000000 --- a/test/tasks/maintenance/backfill_api_key_owners_task_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class Maintenance::BackfillApiKeyOwnersTaskTest < ActiveSupport::TestCase - test "#process performs a task iteration" do - element = create(:api_key) - element.update_columns(owner_id: nil, owner_type: nil) - - assert_nil element.reload.owner - - Maintenance::BackfillApiKeyOwnersTask.process(element) - - assert_equal element.reload.user, element.owner - end - - test "#collection returns the elements to process" do - create(:api_key) - nil_owner = create(:api_key, key: "other") - nil_owner.update_columns(owner_id: nil, owner_type: nil) - - assert_nil nil_owner.reload.owner - assert_same_elements [nil_owner], Maintenance::BackfillApiKeyOwnersTask.collection - end -end diff --git a/test/unit/helpers/api_keys_helper_test.rb b/test/unit/helpers/api_keys_helper_test.rb index 956ca2db665..e3ff6db5cad 100644 --- a/test/unit/helpers/api_keys_helper_test.rb +++ b/test/unit/helpers/api_keys_helper_test.rb @@ -4,7 +4,7 @@ class ApiKeysHelperTest < ActionView::TestCase context "gem_scope" do should "return gem name" do @ownership = create(:ownership) - @api_key = create(:api_key, push_rubygem: true, user: @ownership.user, ownership: @ownership) + @api_key = create(:api_key, push_rubygem: true, owner: @ownership.user, ownership: @ownership) assert_equal @ownership.rubygem.name, gem_scope(@api_key) end @@ -15,7 +15,7 @@ class ApiKeysHelperTest < ActionView::TestCase should "return error tooltip if key if gem ownership is removed" do @ownership = create(:ownership) - @api_key = create(:api_key, push_rubygem: true, user: @ownership.user, ownership: @ownership) + @api_key = create(:api_key, push_rubygem: true, owner: @ownership.user, ownership: @ownership) @ownership.destroy! rubygem_name = @ownership.rubygem.name From 1ca1e85618d9ab8e29de6bb01b9cb134055c68b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Mon, 27 Nov 2023 22:54:02 +0100 Subject: [PATCH 035/112] Upgrade to PostgreSQL 12. --- CONTRIBUTING.md | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d399e72976..ef54aa86624 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,7 +90,7 @@ Follow the instructions below on how to install Bundler and setup the database. * Note that `-e "xpack.security.enabled=false"` disables authentication. -* Install PostgreSQL (>= 11.13.x): `brew install postgres` +* Install PostgreSQL (>= 12.x): `brew install postgres` * Setup information: `brew info postgresql` * Install memcached: `brew install memcached` * Show all memcached options: `memcached -h` diff --git a/docker-compose.yml b/docker-compose.yml index 9eeba79be34..1f03dd2ce09 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: db: - image: postgres:11.13 + image: postgres:12.17 ports: - "5432:5432" environment: From 9fc2c35430e68984e3fa9f79ba0ca3d510a3ab36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Tue, 28 Nov 2023 00:09:54 +0100 Subject: [PATCH 036/112] Update staging DATABASE_URL to PostgreSQL 12 instance. --- config/deploy/staging/secrets.ejson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deploy/staging/secrets.ejson b/config/deploy/staging/secrets.ejson index b10ed78e960..5fc2c62e43d 100644 --- a/config/deploy/staging/secrets.ejson +++ b/config/deploy/staging/secrets.ejson @@ -5,7 +5,7 @@ "_type": "Opaque", "data": { "secret_key_base": "EJ[1:d8Qpc6QSyiCh7sBlZ3VzKenIldRrWaFh8nE/ThE6cio=:X57Fr3d7YC/hdbuJYu/xrbglh7C3g2eF:L0jHynvq98zSuGHlVsH2DTTxitG1enTc4T8N0qEIc+wP3cxG/dWVdo5kCRdeufJfi+B1rv38DkKWW4/Cc3QjJQzN9awHVOK9KZcSTUdUqFlm7Kk5fai4FsIPgHTyFQLyTta8iubIrDNuZgpw9t3NZ6KkRwAkFsLLy2TaoICcEEQFgesiyMWVoXhun1kJnbQp]", - "database_url": "EJ[1:pWdDjbKg+vvJNbzB01oQzYlmXDPVIxoPydn1NtUUPFI=:S/bKqCGClJWQeqxu2qbDKYeP7qwieuJR:L8/fR5V643iQa3LE2Z6YEmCIOELin0joj5OEWBpzSxJF1ItYO0sMlheg1BdNDDT9nxegFjsclScg8MgkM4dAoTv4A6CrnEdqT8Z3N6LPagIeDBG3Wr+GQ4QpO7GhdC1/eEwnnVbANUPjOCbcWMvP4/jg3OlsqGb8poovCnTJaRjTOMY1lPQZAHDsiMfHgHXL70Vc7rnlrpQJe38+ANqMx1HkTNR/na/nq/A=]", + "database_url": "EJ[1:G6ITVcjC1yGU1GiOCe5ttJMp2/f4xoLmfPb/NLqgAlg=:ezKujy7N9Evo9aY23beBi377UccDyBQh:RVTtlTpeEwxCJoBSA9kGC+Gw4HicjT7udL0KEq4yTbMYr6qjTxoMfUZS166uEuCZ02iqX1YReTtHfmbRHNzeZgSosJEvdIDZAIvUZNZjtC6U3qM4HOmBfcfbkN9pnwHpVtnJRO1CoJiUjt4ZlXM7hCDa0251NKf/oby27UPy9rrOyJLojwdHCFFDpLGIpyiivXo8+NIzn7gdlGkhelMZqdykn1OgaNbmXOIPgeE=]", "aws_access_key_id": "EJ[1:jnrbdGY2s+ZVCF8BStxF4ZGp1yhxk+x/sXxH8x32qmg=:OLx6cKpU43qYM+Fsa8FeC5Bnx0jbIB68:n9U9IUwaWpnbrLIkjwP7erHefKdX1/08kSTvSmBeuq9+0YQ4]", "aws_secret_access_key": "EJ[1:jnrbdGY2s+ZVCF8BStxF4ZGp1yhxk+x/sXxH8x32qmg=:uMtyxPC2DC0BgsYRhOWGCdaom6OroJqI:drTaFTjJ2uhfJn+aPgYTQ4hp1GdLS3Os2PfJVn1BevaukzzcsI9xVp2xS0umzutnQbmmdISOgYg=]", "honeybadger_api_key": "EJ[1:jnrbdGY2s+ZVCF8BStxF4ZGp1yhxk+x/sXxH8x32qmg=:TD1e42m2hCnraaRigNZZZao9WKF9S9Nf:Myj5m7maDkOC2GbolUskZ2F+zmRscHE8]", From 41c7296ab46476e1106b1687f285097709117c35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:07:25 +0000 Subject: [PATCH 037/112] Bump shoryuken from 6.1.0 to 6.1.1 Bumps [shoryuken](https://github.com/phstc/shoryuken) from 6.1.0 to 6.1.1. - [Changelog](https://github.com/ruby-shoryuken/shoryuken/blob/main/CHANGELOG.md) - [Commits](https://github.com/phstc/shoryuken/commits/v6.1.1) --- updated-dependencies: - dependency-name: shoryuken dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index da6544916e3..dc0aabe9028 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,7 +101,7 @@ GEM zeitwerk (>= 2.6.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.857.0) + aws-partitions (1.859.0) aws-sdk-core (3.188.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) @@ -584,7 +584,7 @@ GEM semantic (1.6.1) semantic_logger (4.15.0) concurrent-ruby (~> 1.0) - shoryuken (6.1.0) + shoryuken (6.1.1) aws-sdk-core (>= 2) concurrent-ruby thor From 923e3816bf2fd7893337300d714bfa54a259cdab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:07:52 +0000 Subject: [PATCH 038/112] Bump view_component from 3.7.0 to 3.8.0 Bumps [view_component](https://github.com/viewcomponent/view_component) from 3.7.0 to 3.8.0. - [Release notes](https://github.com/viewcomponent/view_component/releases) - [Changelog](https://github.com/ViewComponent/view_component/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/viewcomponent/view_component/compare/v3.7.0...v3.8.0) --- updated-dependencies: - dependency-name: view_component dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index ec864c28089..b1fd58ca421 100644 --- a/Gemfile +++ b/Gemfile @@ -56,7 +56,7 @@ gem "phlex-rails", "~> 1.1" # Admin dashboard gem "avo", "~> 2.45" -gem "view_component", "~> 3.6" +gem "view_component", "~> 3.8" gem "pundit", "~> 2.3" gem "chartkick", "~> 5.0" gem "groupdate", "~> 6.2" diff --git a/Gemfile.lock b/Gemfile.lock index da6544916e3..3ab01b7d3e1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -655,7 +655,7 @@ GEM validates_formatting_of (0.9.0) activemodel version_gem (1.1.1) - view_component (3.7.0) + view_component (3.8.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) @@ -781,7 +781,7 @@ DEPENDENCIES toxiproxy (~> 2.0) unpwn (~> 1.0) validates_formatting_of (~> 0.9) - view_component (~> 3.6) + view_component (~> 3.8) webauthn (~> 3.0) webmock (~> 3.18) xml-simple (~> 1.1) From 02b93dbe22d65dc4d2848ff10698a18af13d3ce3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:08:13 +0000 Subject: [PATCH 039/112] Bump terser from 1.1.19 to 1.1.20 Bumps [terser](https://github.com/ahorek/terser-ruby) from 1.1.19 to 1.1.20. - [Release notes](https://github.com/ahorek/terser-ruby/releases) - [Changelog](https://github.com/ahorek/terser-ruby/blob/master/CHANGELOG.md) - [Commits](https://github.com/ahorek/terser-ruby/compare/1.1.19...1.1.20) --- updated-dependencies: - dependency-name: terser dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index da6544916e3..2b5a1509365 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -621,7 +621,7 @@ GEM faraday-follow_redirects tailwindcss-rails (2.0.32) railties (>= 6.0.0) - terser (1.1.19) + terser (1.1.20) execjs (>= 0.3.0, < 3) thor (1.3.0) tilt (2.2.0) From c2f14b88524d4b671a45761e6963eb31a24fcd61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:30:39 +0100 Subject: [PATCH 040/112] Bump aws-sdk-s3 from 1.140.0 to 1.141.0 (#4252) --- Gemfile | 2 +- Gemfile.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index b1fd58ca421..0cb07c00a86 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" -gem "aws-sdk-s3", "~> 1.140" +gem "aws-sdk-s3", "~> 1.141" gem "aws-sdk-sqs", "~> 1.68" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" diff --git a/Gemfile.lock b/Gemfile.lock index 90f36e467ff..13d34ee4ab5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,23 +101,23 @@ GEM zeitwerk (>= 2.6.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.859.0) - aws-sdk-core (3.188.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (1.860.0) + aws-sdk-core (3.189.0) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.73.0) + aws-sdk-kms (1.74.0) aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.140.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-s3 (1.141.0) + aws-sdk-core (~> 3, >= 3.189.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) + aws-sigv4 (~> 1.8) aws-sdk-sqs (1.68.0) aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sigv4 (1.7.0) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) base64 (0.2.0) bcrypt (3.1.20) @@ -694,7 +694,7 @@ DEPENDENCIES amazing_print (~> 1.4) autoprefixer-rails (~> 10.4) avo (~> 2.45) - aws-sdk-s3 (~> 1.140) + aws-sdk-s3 (~> 1.141) aws-sdk-sqs (~> 1.68) bcrypt (~> 3.1) bootsnap (~> 1.16) From 2eaabe4526c3ef54f61138583d63a8104c183e54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:30:55 +0100 Subject: [PATCH 041/112] Bump searchkick from 5.3.0 to 5.3.1 (#4253) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 0cb07c00a86..408799ec651 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,7 @@ gem "shoryuken", "~> 6.1", require: false gem "statsd-instrument", "~> 3.5" gem "validates_formatting_of", "~> 0.9" gem "opensearch-ruby", "~> 3.0" -gem "searchkick", "~> 5.2" +gem "searchkick", "~> 5.3" gem "faraday_middleware-aws-sigv4", "~> 1.0" gem "xml-simple", "~> 1.1" gem "compact_index", "~> 0.14.0" diff --git a/Gemfile.lock b/Gemfile.lock index 13d34ee4ab5..a83a6715673 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -574,7 +574,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - searchkick (5.3.0) + searchkick (5.3.1) activemodel (>= 6.1) hashie selenium-webdriver (4.15.0) @@ -766,7 +766,7 @@ DEPENDENCIES rubocop-performance (~> 1.16) rubocop-rails (~> 2.18) ruby-magic (~> 0.6) - searchkick (~> 5.2) + searchkick (~> 5.3) selenium-webdriver (~> 4.8) shoryuken (~> 6.1) shoulda-context (~> 2.0) From a1bb567bdb13c58819763a530212c746c85f186d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:26:45 -0500 Subject: [PATCH 042/112] Bump aws-sdk-sqs from 1.68.0 to 1.69.0 (#4251) Bumps [aws-sdk-sqs](https://github.com/aws/aws-sdk-ruby) from 1.68.0 to 1.69.0. - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-sqs/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-sqs dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 408799ec651..a4c191b16e4 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" gem "aws-sdk-s3", "~> 1.141" -gem "aws-sdk-sqs", "~> 1.68" +gem "aws-sdk-sqs", "~> 1.69" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" gem "dalli", "~> 3.2" diff --git a/Gemfile.lock b/Gemfile.lock index a83a6715673..556d2078944 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,8 +101,8 @@ GEM zeitwerk (>= 2.6.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.860.0) - aws-sdk-core (3.189.0) + aws-partitions (1.861.0) + aws-sdk-core (3.190.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) @@ -114,7 +114,7 @@ GEM aws-sdk-core (~> 3, >= 3.189.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) - aws-sdk-sqs (1.68.0) + aws-sdk-sqs (1.69.0) aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) aws-sigv4 (1.8.0) @@ -695,7 +695,7 @@ DEPENDENCIES autoprefixer-rails (~> 10.4) avo (~> 2.45) aws-sdk-s3 (~> 1.141) - aws-sdk-sqs (~> 1.68) + aws-sdk-sqs (~> 1.69) bcrypt (~> 3.1) bootsnap (~> 1.16) brakeman (~> 6.0) From 5812b5e537c9f31f9fd18c3559f547c7ceafabb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Thu, 30 Nov 2023 03:01:14 +0100 Subject: [PATCH 043/112] Update production DATABASE_URL to PostgreSQL 12 instance. --- config/deploy/production/secrets.ejson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deploy/production/secrets.ejson b/config/deploy/production/secrets.ejson index ab6030524a9..4af4d82b5b7 100644 --- a/config/deploy/production/secrets.ejson +++ b/config/deploy/production/secrets.ejson @@ -5,7 +5,7 @@ "_type": "Opaque", "data": { "secret_key_base": "EJ[1:PdZreInTWXI1FLrFAIe4j7eLOfbTVh6EWWwBL89TMAk=:i2m4ZdL6cNaw89lU8r22s403O9eEiEDl:NY18SMzycBnJwDS/hYHI3GmxqKDpLgBxGJbFhf3GgRScY2J/QEHCHv+Fl+vahDSeYEbAmgJxTUS/mpeE++FuhnWrGua9dpQpar5hr12X4CzadkQN2ef+YadRmUr97IvD10VBCUn5zjBjZOYiJQdBqwhK73Cr4jf/hWH30RJn6UCuAiPGo5H+KsjlZ7voW/Ep]", - "database_url": "EJ[1:/aLhumlos/DZXUNQ/3BYiDp1wKyxZQc+Q0KICeK+QnA=:YxFjVTCJUyg0n0jiu0b3f043LJSXT2qk:AaPrjgyz3ZA3DkYNdTwddk8ig2ofmCNj2cmim6aACkHwV14/ujHV6efOslBO4NTX68HnLFhA39PkcyqTzuza/6zw2eJ5y+uL+5BXHcbpcJxl3Sw+e6ueIhDzMyyzltN219rR6ikhy16g0exuI/IteGNfZV5VLMuaYlKHPyGSRTdTge8jZWKym8N8y9+IdqRCVzo0pvvYE/ljViPh2Pre7fTva4kz9FoAA63u4RucRfhS+iQxJ1o=]", + "database_url": "EJ[1:uMpyU5gyTfgs0xJhbejdAgqyoj7aF7C2zVoSoa6xZW0=:bx5WnEVeg7OoFJXHH7hv7mJIXrZyZO7M:kwStYHCymICCmujukrkEIULaXy7tm8LMRsAYpEreEot9oGLXVjYqqfg9BI4LeDl/i9SgvQFLTmwsnf3Ujl4xQLFWdeM4yQbTEJI1VFaj3zkZM+eVGGiS+6m/bomqdK/XHcygWEr3q35mygAeo2WPC3j9sPMNJocWYZX5tJHKrUSZ6EHKnXQw7fISNBb5ZgLbI7Jb2q+hR9/zKVAhW74Wf+8pmQ74yovKIX9O7vegEF+2CUw=]", "aws_access_key_id": "EJ[1:PdZreInTWXI1FLrFAIe4j7eLOfbTVh6EWWwBL89TMAk=:KeGkWu/d8KKHzHzGpDncf6Nvp2NfVYRQ:YMhI+eSsVUJKoYJCM0zWenmcJ6CtVcLiyz6GHU2V63O6HPFx]", "aws_secret_access_key": "EJ[1:PdZreInTWXI1FLrFAIe4j7eLOfbTVh6EWWwBL89TMAk=:u6fuRpUNQaSoDWTFMn/Me+9Kr1Z+93hA:1HEeKUFo2+K5xaf9fjD6VmewC60GIaYZ93JESoOCqpGhybfk3UypbFhp0yNTsHVM7PhH1n1O56M=]", "honeybadger_api_key": "EJ[1:PdZreInTWXI1FLrFAIe4j7eLOfbTVh6EWWwBL89TMAk=:PvoZeAX52WmcFsCSWiVhSF86SNd3j9SF:z5I4XA0St0psAfod3SCcwPMWYl2d330p]", From 683edf7fc0b99da641d2e5e299f8377b5557c9dc Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 30 Nov 2023 10:23:15 -0800 Subject: [PATCH 044/112] Add version_id column to Deletions (#4254) So we can use a straightforward foreign key relationship between the version & deletion --- app/models/deletion.rb | 1 + app/models/user.rb | 1 + .../backfill_deletion_versions_task.rb | 13 +++++++++ ...31129233528_add_version_id_to_deletions.rb | 7 +++++ db/schema.rb | 4 ++- test/models/deletion_test.rb | 1 + test/system/avo/versions_test.rb | 3 ++- .../backfill_deletion_versions_task_test.rb | 27 +++++++++++++++++++ 8 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 app/tasks/maintenance/backfill_deletion_versions_task.rb create mode 100644 db/migrate/20231129233528_add_version_id_to_deletions.rb create mode 100644 test/tasks/maintenance/backfill_deletion_versions_task_test.rb diff --git a/app/models/deletion.rb b/app/models/deletion.rb index 0bc3d28a4d4..3bc19940805 100644 --- a/app/models/deletion.rb +++ b/app/models/deletion.rb @@ -47,6 +47,7 @@ def record_metadata self.rubygem = rubygem_name self.number = version.number self.platform = version.platform + self.version_id = version.id end def expire_cache diff --git a/app/models/user.rb b/app/models/user.rb index 5b76ef72312..e8b44ebee1d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,6 +27,7 @@ class User < ApplicationRecord has_many :subscribed_gems, -> { order("name ASC") }, through: :subscriptions, source: :rubygem has_many :pushed_versions, -> { by_created_at }, dependent: :nullify, inverse_of: :pusher, class_name: "Version", foreign_key: :pusher_id + has_many :yanked_versions, through: :deletions, source: :version has_many :deletions, dependent: :nullify has_many :web_hooks, dependent: :destroy diff --git a/app/tasks/maintenance/backfill_deletion_versions_task.rb b/app/tasks/maintenance/backfill_deletion_versions_task.rb new file mode 100644 index 00000000000..2dd74413217 --- /dev/null +++ b/app/tasks/maintenance/backfill_deletion_versions_task.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Maintenance::BackfillDeletionVersionsTask < MaintenanceTasks::Task + def collection + Deletion.all + end + + def process(deletion) + return if deletion.version_id? + + deletion.update!(version_id: deletion.version.id) + end +end diff --git a/db/migrate/20231129233528_add_version_id_to_deletions.rb b/db/migrate/20231129233528_add_version_id_to_deletions.rb new file mode 100644 index 00000000000..72737a60de4 --- /dev/null +++ b/db/migrate/20231129233528_add_version_id_to_deletions.rb @@ -0,0 +1,7 @@ +class AddVersionIdToDeletions < ActiveRecord::Migration[7.0] + disable_ddl_transaction! + + def change + add_reference :deletions, :version, index: {algorithm: :concurrently} + end +end diff --git a/db/schema.rb b/db/schema.rb index 83bb90d4f2d..e280f51d425 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_11_20_033411) do +ActiveRecord::Schema[7.0].define(version: 2023_11_29_233528) do # These are extensions that must be enabled in order to support this database enable_extension "hstore" enable_extension "pgcrypto" @@ -83,7 +83,9 @@ t.string "platform" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.bigint "version_id" t.index ["user_id"], name: "index_deletions_on_user_id" + t.index ["version_id"], name: "index_deletions_on_version_id" end create_table "dependencies", id: :serial, force: :cascade do |t| diff --git a/test/models/deletion_test.rb b/test/models/deletion_test.rb index 1e28a38a99d..4ea8feb8862 100644 --- a/test/models/deletion_test.rb +++ b/test/models/deletion_test.rb @@ -121,6 +121,7 @@ class DeletionTest < ActiveSupport::TestCase deletion.valid? assert_equal deletion.rubygem, @version.rubygem.name + assert_equal @version.id, deletion.version_id end context "with restored gem" do diff --git a/test/system/avo/versions_test.rb b/test/system/avo/versions_test.rb index dfc2dfa8246..c72c89a0cd8 100644 --- a/test/system/avo/versions_test.rb +++ b/test/system/avo/versions_test.rb @@ -112,7 +112,8 @@ def sign_in_as(user) "number" => [version_attributes[:number], nil], "platform" => ["ruby", nil], "created_at" => [deletion.created_at.as_json, nil], - "updated_at" => [deletion.updated_at.as_json, nil] + "updated_at" => [deletion.updated_at.as_json, nil], + "version_id" => [version.id, nil] }, "unchanged" => {} } diff --git a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb new file mode 100644 index 00000000000..748b6f01ca7 --- /dev/null +++ b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "test_helper" + +class Maintenance::BackfillDeletionVersionsTaskTest < ActiveSupport::TestCase + test "#collection" do + assert_equal Deletion.all, Maintenance::BackfillDeletionVersionsTask.collection + end + + test "#process" do + version = create(:version) + deletion = create(:deletion, version:) + + deletion.update_column(:version_id, nil) + + assert_nil deletion.version_id + + Maintenance::BackfillDeletionVersionsTask.process(deletion) + + assert_equal version.id, deletion.version_id + assert_equal version, deletion.version + + assert_no_changes -> { deletion.reload.as_json } do + Maintenance::BackfillDeletionVersionsTask.process(deletion) + end + end +end From b0d8c8a3088edc712cbbd11d3e03982fb4aa363c Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 30 Nov 2023 14:03:37 -0500 Subject: [PATCH 045/112] Fix deletion version_id backfill when user has been deleted (#4259) Fixes the failure observed in https://staging.rubygems.org/admin/maintenance_tasks/tasks/Maintenance::BackfillDeletionVersionsTask --- app/models/deletion.rb | 6 ++++-- test/models/deletion_test.rb | 2 +- .../backfill_deletion_versions_task_test.rb | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/models/deletion.rb b/app/models/deletion.rb index 3bc19940805..fe5151a31da 100644 --- a/app/models/deletion.rb +++ b/app/models/deletion.rb @@ -1,5 +1,6 @@ class Deletion < ApplicationRecord - belongs_to :user + # we nullify the user when they delete their account + belongs_to :user, optional: true belongs_to :version, ->(d) { joins(:rubygem).where(platform: d.platform, rubygem: { name: d.rubygem }) }, class_name: "Version", @@ -7,7 +8,8 @@ class Deletion < ApplicationRecord primary_key: :number, inverse_of: :deletion - validates :user, :rubygem, :number, presence: true + validates :user, presence: true, on: :create + validates :rubygem, :number, presence: true validates :version, presence: true validate :version_is_indexed, on: :create validate :metadata_matches_version diff --git a/test/models/deletion_test.rb b/test/models/deletion_test.rb index 4ea8feb8862..2c0a4ef89b0 100644 --- a/test/models/deletion_test.rb +++ b/test/models/deletion_test.rb @@ -29,7 +29,7 @@ class DeletionTest < ActiveSupport::TestCase context "association" do subject { Deletion.new(version: @version, user: @user) } - should belong_to :user + should belong_to(:user).without_validating_presence end context "with deleted gem" do diff --git a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb index 748b6f01ca7..242e926a561 100644 --- a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb +++ b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb @@ -24,4 +24,20 @@ class Maintenance::BackfillDeletionVersionsTaskTest < ActiveSupport::TestCase Maintenance::BackfillDeletionVersionsTask.process(deletion) end end + + test "#process with deleted user" do + version = create(:version) + deletion = create(:deletion, version:) + + deletion.update_column(:version_id, nil) + + assert_nil deletion.version_id + + deletion.user.destroy! + + Maintenance::BackfillDeletionVersionsTask.process(deletion.reload) + + assert_equal version.id, deletion.version_id + assert_equal version, deletion.version + end end From 88e90e9adc0957b4ce660c9faa5a9255bf5df12a Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 30 Nov 2023 16:56:36 -0500 Subject: [PATCH 046/112] Marshal.dump straight to gzip writer IO (#4257) --- app/jobs/indexer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/indexer.rb b/app/jobs/indexer.rb index 99698e11966..df939032cce 100644 --- a/app/jobs/indexer.rb +++ b/app/jobs/indexer.rb @@ -28,9 +28,9 @@ def perform private def stringify(value) - final = StringIO.new + final = ActiveSupport::Gzip::Stream.new gzip = Zlib::GzipWriter.new(final) - gzip.write(Marshal.dump(value)) + Marshal.dump(value, gzip) gzip.close final.string From 18fba9f386f659f742d3e263b8cc84be3cbe1b0c Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Fri, 1 Dec 2023 14:23:56 -0800 Subject: [PATCH 047/112] Fix deletion version_id backfill when gem name case has changed (#4261) Fixes the failure observed in https://staging.rubygems.org/admin/maintenance_tasks/tasks/Maintenance::BackfillDeletionVersionsTask --- app/models/deletion.rb | 2 +- .../backfill_deletion_versions_task_test.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/models/deletion.rb b/app/models/deletion.rb index fe5151a31da..69f2457debf 100644 --- a/app/models/deletion.rb +++ b/app/models/deletion.rb @@ -2,7 +2,7 @@ class Deletion < ApplicationRecord # we nullify the user when they delete their account belongs_to :user, optional: true - belongs_to :version, ->(d) { joins(:rubygem).where(platform: d.platform, rubygem: { name: d.rubygem }) }, + belongs_to :version, ->(d) { joins(:rubygem).where(platform: d.platform).where("UPPER(rubygems.name) = UPPER(?)", d.rubygem) }, class_name: "Version", foreign_key: :number, primary_key: :number, diff --git a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb index 242e926a561..69d53e88af0 100644 --- a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb +++ b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb @@ -40,4 +40,21 @@ class Maintenance::BackfillDeletionVersionsTaskTest < ActiveSupport::TestCase assert_equal version.id, deletion.version_id assert_equal version, deletion.version end + + test "#process with changed gem capitalization" do + rubygem = create(:rubygem, name: "rubygem0") + version = create(:version, rubygem:) + deletion = create(:deletion, version:) + + deletion.update_column(:version_id, nil) + + assert_nil deletion.version_id + + rubygem.update!(name: "Rubygem0") + + Maintenance::BackfillDeletionVersionsTask.process(deletion.reload) + + assert_equal version.id, deletion.version_id + assert_equal version, deletion.version + end end From 5c6e71f483a000a61cb5b46ffe2223ff95934194 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Fri, 1 Dec 2023 17:01:01 -0800 Subject: [PATCH 048/112] Fix deletion version_id backfill when version is missing entirely (#4263) Fixes the failure observed in https://staging.rubygems.org/admin/maintenance_tasks/tasks/Maintenance::BackfillDeletionVersionsTask --- .../maintenance/backfill_deletion_versions_task.rb | 8 +++++++- .../backfill_deletion_versions_task_test.rb | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/tasks/maintenance/backfill_deletion_versions_task.rb b/app/tasks/maintenance/backfill_deletion_versions_task.rb index 2dd74413217..e26b8db1da1 100644 --- a/app/tasks/maintenance/backfill_deletion_versions_task.rb +++ b/app/tasks/maintenance/backfill_deletion_versions_task.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Maintenance::BackfillDeletionVersionsTask < MaintenanceTasks::Task + include SemanticLogger::Loggable + def collection Deletion.all end @@ -8,6 +10,10 @@ def collection def process(deletion) return if deletion.version_id? - deletion.update!(version_id: deletion.version.id) + if deletion.version.blank? + logger.warn("Deletion does not have a matching version", deletion:) + else + deletion.update!(version_id: deletion.version.id) + end end end diff --git a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb index 69d53e88af0..65d066b904b 100644 --- a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb +++ b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb @@ -57,4 +57,15 @@ class Maintenance::BackfillDeletionVersionsTaskTest < ActiveSupport::TestCase assert_equal version.id, deletion.version_id assert_equal version, deletion.version end + + test "#process with missing version" do + Deletion.insert!({ rubygem: "missing", number: "1.0.0", platform: "ruby" }) + deletion = Deletion.where(rubygem: "missing", number: "1.0.0", platform: "ruby").sole + + assert_nil deletion.version_id + + Maintenance::BackfillDeletionVersionsTask.process(deletion) + + assert_nil deletion.version_id + end end From 3299b92c0a3221a0e49234b9426e714e027bb1d5 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sat, 2 Dec 2023 16:22:17 -0800 Subject: [PATCH 049/112] Validate spec checksums in verify gem contents task (#4262) --- .../verify_gem_contents_in_fs_task.rb | 27 +++++++++---------- .../verify_gem_contents_in_fs_task_test.rb | 19 ++++++++----- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/app/tasks/maintenance/verify_gem_contents_in_fs_task.rb b/app/tasks/maintenance/verify_gem_contents_in_fs_task.rb index 1558ed35509..1f5b9b7bf2d 100644 --- a/app/tasks/maintenance/verify_gem_contents_in_fs_task.rb +++ b/app/tasks/maintenance/verify_gem_contents_in_fs_task.rb @@ -26,21 +26,8 @@ def collection def process(version) logger.tagged(version_id: version.id, name: version.rubygem.name, number: version.number, platform: version.platform) do - gem_path = "gems/#{version.gem_file_name}" - spec_path = "quick/Marshal.4.8/#{version.full_name}.gemspec.rz" - - expected_checksum = version.sha256 - logger.warn "Version #{version.full_name} has no checksum" if expected_checksum.blank? - - gem_contents = RubygemFs.instance.get(gem_path) - logger.warn "Version #{version.full_name} is missing gem contents" if gem_contents.blank? - - logger.warn "#{spec_path} is missing" if RubygemFs.instance.head(spec_path).blank? - - return unless gem_contents.present? && expected_checksum.present? - - sha256 = Digest::SHA256.base64digest(gem_contents) - logger.error "#{gem_path} has incorrect checksum (expected #{expected_checksum}, got #{sha256})" if sha256 != expected_checksum + validate_checksum(version, "gem", "gems/#{version.gem_file_name}", version.sha256) + validate_checksum(version, "spec", "quick/Marshal.4.8/#{version.full_name}.gemspec.rz", version.spec_sha256) end end @@ -60,4 +47,14 @@ def patterns_are_valid def matches_regexp(collection, field, regexp) collection.where(collection.arel_table[field].matches_regexp(regexp)) end + + def validate_checksum(version, name, path, expected_checksum) + logger.warn "Version #{version.fullname} has no #{name} checksum" if expected_checksum.blank? + contents = RubygemFs.instance.get(path) + logger.warn "Version #{version.full_name} is missing #{name} contents (#{path})" if contents.blank? + + return unless contents.present? && expected_checksum.present? + sha256 = Digest::SHA256.base64digest(contents) + logger.error "#{path} has incorrect checksum (expected #{expected_checksum}, got #{sha256})" if sha256 != expected_checksum + end end diff --git a/test/tasks/maintenance/verify_gem_contents_in_fs_task_test.rb b/test/tasks/maintenance/verify_gem_contents_in_fs_task_test.rb index 71fa67ea6df..a4121ee024f 100644 --- a/test/tasks/maintenance/verify_gem_contents_in_fs_task_test.rb +++ b/test/tasks/maintenance/verify_gem_contents_in_fs_task_test.rb @@ -31,16 +31,18 @@ def task(**attrs) assert_semantic_logger_event( @task.logger.events[1], level: :warn, - message_includes: ".gemspec.rz is missing" + message_includes: "is missing spec contents" ) end should "not error when checksums match" do gem = "foo-1.0.0.gem" + gemspec = "#{gem}spec" sha256 = Digest::SHA256.base64digest(gem) - version = create(:version, sha256:) + spec_sha256 = Digest::SHA256.base64digest(gemspec) + version = create(:version, sha256:, spec_sha256:) RubygemFs.instance.store("gems/#{version.full_name}.gem", gem) - RubygemFs.instance.store("quick/Marshal.4.8/#{version.full_name}.gemspec.rz", "") + RubygemFs.instance.store("quick/Marshal.4.8/#{version.full_name}.gemspec.rz", gemspec) @task = task @task.process(version) @@ -49,19 +51,24 @@ def task(**attrs) end should "error when checksums do not match" do - version = create(:version, sha256: "abcd") + version = create(:version, sha256: "abcd", spec_sha256: "defg") RubygemFs.instance.store("gems/#{version.full_name}.gem", "abcd") - RubygemFs.instance.store("quick/Marshal.4.8/#{version.full_name}.gemspec.rz", "") + RubygemFs.instance.store("quick/Marshal.4.8/#{version.full_name}.gemspec.rz", "defg") @task = task @task.process(version) - assert_equal 1, @task.logger.events.size + assert_equal 2, @task.logger.events.size assert_semantic_logger_event( @task.logger.events[0], level: :error, message_includes: "has incorrect checksum (expected abcd, got #{Digest::SHA256.base64digest('abcd')})" ) + assert_semantic_logger_event( + @task.logger.events[1], + level: :error, + message_includes: "has incorrect checksum (expected defg, got #{Digest::SHA256.base64digest('defg')})" + ) end end From d0a496559f3bd49dd4d4814d06932f492a9137d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:56:43 +0000 Subject: [PATCH 050/112] Bump rbtrace from 0.4.14 to 0.5.0 Bumps [rbtrace](https://github.com/tmm1/rbtrace) from 0.4.14 to 0.5.0. - [Changelog](https://github.com/tmm1/rbtrace/blob/master/CHANGELOG) - [Commits](https://github.com/tmm1/rbtrace/compare/v0.4.14...v0.5.0) --- updated-dependencies: - dependency-name: rbtrace dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index a4c191b16e4..905c7a860a7 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,7 @@ gem "pg", "~> 1.4" gem "puma", "~> 6.4" gem "rack", "~> 2.2" gem "rack-utf8_sanitizer", "~> 1.8" -gem "rbtrace", "~> 0.4.8" +gem "rbtrace", "~> 0.5.0" gem "rdoc", "~> 6.5" gem "roadie-rails", "~> 3.0" gem "ruby-magic", "~> 0.6" diff --git a/Gemfile.lock b/Gemfile.lock index 556d2078944..b45a341f19d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -421,7 +421,7 @@ GEM openssl (3.1.0) openssl-signature_algorithm (1.3.0) openssl (> 2.0) - optimist (3.0.1) + optimist (3.1.0) pagy (6.2.0) parallel (1.22.1) parser (3.2.1.1) @@ -516,7 +516,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rbtrace (0.4.14) + rbtrace (0.5.0) ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) @@ -755,7 +755,7 @@ DEPENDENCIES rails-erd (~> 1.7) rails-i18n (~> 7.0) rails_semantic_logger (~> 4.14) - rbtrace (~> 0.4.8) + rbtrace (~> 0.5.0) rdoc (~> 6.5) roadie-rails (~> 3.0) rotp (~> 6.2) From afbce150a403e288922b182c57c7127bd056103a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:59:08 -0800 Subject: [PATCH 051/112] Bump selenium-webdriver from 4.15.0 to 4.16.0 (#4270) Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.15.0 to 4.16.0. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES) - [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.15.0...selenium-4.16.0) --- updated-dependencies: - dependency-name: selenium-webdriver dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 905c7a860a7..2f19b28edba 100644 --- a/Gemfile +++ b/Gemfile @@ -107,7 +107,7 @@ group :test do gem "mocha", "~> 2.0", require: false gem "shoulda-context", "~> 2.0" gem "shoulda-matchers", "~> 5.3" - gem "selenium-webdriver", "~> 4.8" + gem "selenium-webdriver", "~> 4.16" gem "webmock", "~> 3.18" gem "simplecov", "~> 0.22", require: false gem "simplecov-cobertura", "~> 2.1", require: false diff --git a/Gemfile.lock b/Gemfile.lock index b45a341f19d..6c4cdb40e97 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -577,7 +577,7 @@ GEM searchkick (5.3.1) activemodel (>= 6.1) hashie - selenium-webdriver (4.15.0) + selenium-webdriver (4.16.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -767,7 +767,7 @@ DEPENDENCIES rubocop-rails (~> 2.18) ruby-magic (~> 0.6) searchkick (~> 5.3) - selenium-webdriver (~> 4.8) + selenium-webdriver (~> 4.16) shoryuken (~> 6.1) shoulda-context (~> 2.0) shoulda-matchers (~> 5.3) From dc09001fe0097ec99eabf38a30570a430358cc64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:01:22 -0800 Subject: [PATCH 052/112] Bump brakeman from 6.0.1 to 6.1.0 (#4266) Bumps [brakeman](https://github.com/presidentbeef/brakeman) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/presidentbeef/brakeman/releases) - [Changelog](https://github.com/presidentbeef/brakeman/blob/main/CHANGES.md) - [Commits](https://github.com/presidentbeef/brakeman/compare/v6.0.1...v6.1.0) --- updated-dependencies: - dependency-name: brakeman dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 2f19b28edba..a4d6b5768e5 100644 --- a/Gemfile +++ b/Gemfile @@ -81,7 +81,7 @@ group :development, :test do gem "factory_bot_rails", "~> 6.4" gem "dotenv-rails", "~> 2.8" - gem "brakeman", "~> 6.0", require: false + gem "brakeman", "~> 6.1", require: false gem "rubocop", "~> 1.48", require: false gem "rubocop-rails", "~> 2.18", require: false gem "rubocop-performance", "~> 1.16", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 6c4cdb40e97..78dba7c0f77 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,7 +129,7 @@ GEM msgpack bootsnap (1.17.0) msgpack (~> 1.2) - brakeman (6.0.1) + brakeman (6.1.0) browser (5.3.1) builder (3.2.4) byebug (11.1.3) @@ -698,7 +698,7 @@ DEPENDENCIES aws-sdk-sqs (~> 1.69) bcrypt (~> 3.1) bootsnap (~> 1.16) - brakeman (~> 6.0) + brakeman (~> 6.1) browser (~> 5.3, >= 5.3.1) capybara (~> 3.38) chartkick (~> 5.0) From 0f45d96c52a6afeb21dbc4cf6e32445491532e7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 03:03:20 +0000 Subject: [PATCH 053/112] Bump honeybadger from 5.3.0 to 5.4.0 (#4269) Bumps [honeybadger](https://github.com/honeybadger-io/honeybadger-ruby) from 5.3.0 to 5.4.0. - [Release notes](https://github.com/honeybadger-io/honeybadger-ruby/releases) - [Changelog](https://github.com/honeybadger-io/honeybadger-ruby/blob/master/CHANGELOG.md) - [Commits](https://github.com/honeybadger-io/honeybadger-ruby/compare/v5.3.0...v5.4.0) --- updated-dependencies: - dependency-name: honeybadger dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index a4d6b5768e5..5a57046d8d7 100644 --- a/Gemfile +++ b/Gemfile @@ -15,7 +15,7 @@ gem "faraday", "~> 2.7" gem "good_job", "~> 3.21" gem "gravtastic", "~> 3.2" gem "high_voltage", "~> 3.1" -gem "honeybadger", "~> 5.2" +gem "honeybadger", "~> 5.4" gem "http_accept_language", "~> 2.1" gem "jquery-rails", "~> 4.5" gem "kaminari", "~> 1.2" diff --git a/Gemfile.lock b/Gemfile.lock index 78dba7c0f77..362634aa4cb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -257,7 +257,7 @@ GEM heapy (0.2.0) thor high_voltage (3.1.2) - honeybadger (5.3.0) + honeybadger (5.4.0) http (5.1.1) addressable (~> 2.8) http-cookie (~> 1.0) @@ -718,7 +718,7 @@ DEPENDENCIES gravtastic (~> 3.2) groupdate (~> 6.2) high_voltage (~> 3.1) - honeybadger (~> 5.2) + honeybadger (~> 5.4) http_accept_language (~> 2.1) jquery-rails (~> 4.5) kaminari (~> 1.2) From 6f6b4182486685527112e421279de39ff7abe9a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 03:04:53 +0000 Subject: [PATCH 054/112] Bump rdoc from 6.6.0 to 6.6.1 (#4268) Bumps [rdoc](https://github.com/ruby/rdoc) from 6.6.0 to 6.6.1. - [Release notes](https://github.com/ruby/rdoc/releases) - [Changelog](https://github.com/ruby/rdoc/blob/master/History.rdoc) - [Commits](https://github.com/ruby/rdoc/compare/v6.6.0...v6.6.1) --- updated-dependencies: - dependency-name: rdoc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 5a57046d8d7..7be955f05a2 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,7 @@ gem "puma", "~> 6.4" gem "rack", "~> 2.2" gem "rack-utf8_sanitizer", "~> 1.8" gem "rbtrace", "~> 0.5.0" -gem "rdoc", "~> 6.5" +gem "rdoc", "~> 6.6" gem "roadie-rails", "~> 3.0" gem "ruby-magic", "~> 0.6" gem "shoryuken", "~> 6.1", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 362634aa4cb..907cf4917eb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -520,7 +520,7 @@ GEM ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) - rdoc (6.6.0) + rdoc (6.6.1) psych (>= 4.0.0) regexp_parser (2.8.1) rexml (3.2.6) @@ -611,7 +611,7 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) statsd-instrument (3.6.1) - stringio (3.0.8) + stringio (3.1.0) strong_migrations (1.6.4) activerecord (>= 5.2) swd (2.0.2) @@ -756,7 +756,7 @@ DEPENDENCIES rails-i18n (~> 7.0) rails_semantic_logger (~> 4.14) rbtrace (~> 0.5.0) - rdoc (~> 6.5) + rdoc (~> 6.6) roadie-rails (~> 3.0) rotp (~> 6.2) rqrcode (~> 2.1) From 3bfc650ed889fffe2edfa6f71ded3faf32155b1d Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 6 Dec 2023 18:29:19 -0800 Subject: [PATCH 055/112] Fix pushing gems that require MFA when the api key owner has MFA enabled but the API key does not require MFA Fixes https://github.com/rubygems/rubygems.org/issues/4264 Verified these are the correct behaviors by testing against ea145d9~ --- app/models/pusher.rb | 2 +- test/models/pusher_test.rb | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/models/pusher.rb b/app/models/pusher.rb index d1800271043..3f84ac74a3c 100644 --- a/app/models/pusher.rb +++ b/app/models/pusher.rb @@ -33,7 +33,7 @@ def verify_gem_scope end def verify_mfa_requirement - api_key.mfa_enabled? || !(version_mfa_required? || rubygem.metadata_mfa_required?) || + owner.mfa_enabled? || !(version_mfa_required? || rubygem.metadata_mfa_required?) || notify("Rubygem requires owners to enable MFA. You must enable MFA before pushing new version.", 403) end diff --git a/test/models/pusher_test.rb b/test/models/pusher_test.rb index e28ea19a702..bfa0daeddd1 100644 --- a/test/models/pusher_test.rb +++ b/test/models/pusher_test.rb @@ -556,7 +556,7 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: context "version metadata has rubygems_mfa_required set" do setup do spec = mock - spec.expects(:metadata).returns({ "rubygems_mfa_required" => true }) + spec.stubs(:metadata).returns({ "rubygems_mfa_required" => true }) @cutter.stubs(:spec).returns spec metadata = { "rubygems_mfa_required" => "true" } @@ -566,6 +566,24 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: should "be false if user has no mfa setup" do refute @cutter.verify_mfa_requirement end + + should "be true if user has ui_and_api mfa but API key does not require MFA" do + @user.enable_totp!("abc123", User.mfa_levels["ui_and_api"]) + + assert_predicate @cutter, :verify_mfa_requirement + end + + should "be true if user has ui_only mfa but API key does not require MFA" do + @user.enable_totp!("abc123", User.mfa_levels["ui_only"]) + + assert_predicate @cutter, :verify_mfa_requirement + end + + should "be true if user has ui_and_gem_signin mfa but API key does not require MFA" do + @user.enable_totp!("abc123", User.mfa_levels["ui_and_gem_signin"]) + + assert_predicate @cutter, :verify_mfa_requirement + end end end end From 7e22438eab5a61a9b5326c0f8fcfe2d1af1ad81f Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 7 Dec 2023 11:21:19 -0800 Subject: [PATCH 056/112] Use Deletion#version_id for version association (#4273) The backfill has been run, results available at https://gist.github.com/segiddins/f1299ebf328a7a695d4c71c66a9cb5da --- app/models/deletion.rb | 7 +- app/models/user.rb | 2 +- app/models/version.rb | 10 +-- .../backfill_deletion_versions_task.rb | 19 ----- .../backfill_deletion_versions_task_test.rb | 71 ------------------- 5 files changed, 4 insertions(+), 105 deletions(-) delete mode 100644 app/tasks/maintenance/backfill_deletion_versions_task.rb delete mode 100644 test/tasks/maintenance/backfill_deletion_versions_task_test.rb diff --git a/app/models/deletion.rb b/app/models/deletion.rb index 69f2457debf..be28a194220 100644 --- a/app/models/deletion.rb +++ b/app/models/deletion.rb @@ -2,11 +2,7 @@ class Deletion < ApplicationRecord # we nullify the user when they delete their account belongs_to :user, optional: true - belongs_to :version, ->(d) { joins(:rubygem).where(platform: d.platform).where("UPPER(rubygems.name) = UPPER(?)", d.rubygem) }, - class_name: "Version", - foreign_key: :number, - primary_key: :number, - inverse_of: :deletion + belongs_to :version, inverse_of: :deletion validates :user, presence: true, on: :create validates :rubygem, :number, presence: true @@ -49,7 +45,6 @@ def record_metadata self.rubygem = rubygem_name self.number = version.number self.platform = version.platform - self.version_id = version.id end def expire_cache diff --git a/app/models/user.rb b/app/models/user.rb index e8b44ebee1d..e04e0711d0c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,7 +27,7 @@ class User < ApplicationRecord has_many :subscribed_gems, -> { order("name ASC") }, through: :subscriptions, source: :rubygem has_many :pushed_versions, -> { by_created_at }, dependent: :nullify, inverse_of: :pusher, class_name: "Version", foreign_key: :pusher_id - has_many :yanked_versions, through: :deletions, source: :version + has_many :yanked_versions, through: :deletions, source: :version, inverse_of: :yanker has_many :deletions, dependent: :nullify has_many :web_hooks, dependent: :destroy diff --git a/app/models/version.rb b/app/models/version.rb index e12c10467ab..89a483f21bf 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -9,10 +9,8 @@ class Version < ApplicationRecord # rubocop:disable Metrics/ClassLength has_one :gem_download, inverse_of: :version, dependent: :destroy belongs_to :pusher, class_name: "User", inverse_of: false, optional: true belongs_to :pusher_api_key, class_name: "ApiKey", inverse_of: :pushed_versions, optional: true - has_one :deletion, ->(v) { where(rubygem: v.rubygem.name, platform: v.platform) }, - dependent: :delete, inverse_of: :version, required: false, - primary_key: :number, - foreign_key: :number + has_one :deletion, dependent: :delete, inverse_of: :version, required: false + has_one :yanker, through: :deletion, source: :user, inverse_of: :yanked_versions, required: false before_validation :set_canonical_number, if: :number_changed? before_validation :full_nameify! @@ -396,10 +394,6 @@ def rubygems_metadata_mfa_required? ActiveRecord::Type::Boolean.new.cast(metadata["rubygems_mfa_required"]) end - def yanker - Deletion.find_by(rubygem: rubygem.name, number: number, platform: platform)&.user unless indexed - end - def prerelease !!to_gem_version.prerelease? end diff --git a/app/tasks/maintenance/backfill_deletion_versions_task.rb b/app/tasks/maintenance/backfill_deletion_versions_task.rb deleted file mode 100644 index e26b8db1da1..00000000000 --- a/app/tasks/maintenance/backfill_deletion_versions_task.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class Maintenance::BackfillDeletionVersionsTask < MaintenanceTasks::Task - include SemanticLogger::Loggable - - def collection - Deletion.all - end - - def process(deletion) - return if deletion.version_id? - - if deletion.version.blank? - logger.warn("Deletion does not have a matching version", deletion:) - else - deletion.update!(version_id: deletion.version.id) - end - end -end diff --git a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb b/test/tasks/maintenance/backfill_deletion_versions_task_test.rb deleted file mode 100644 index 65d066b904b..00000000000 --- a/test/tasks/maintenance/backfill_deletion_versions_task_test.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class Maintenance::BackfillDeletionVersionsTaskTest < ActiveSupport::TestCase - test "#collection" do - assert_equal Deletion.all, Maintenance::BackfillDeletionVersionsTask.collection - end - - test "#process" do - version = create(:version) - deletion = create(:deletion, version:) - - deletion.update_column(:version_id, nil) - - assert_nil deletion.version_id - - Maintenance::BackfillDeletionVersionsTask.process(deletion) - - assert_equal version.id, deletion.version_id - assert_equal version, deletion.version - - assert_no_changes -> { deletion.reload.as_json } do - Maintenance::BackfillDeletionVersionsTask.process(deletion) - end - end - - test "#process with deleted user" do - version = create(:version) - deletion = create(:deletion, version:) - - deletion.update_column(:version_id, nil) - - assert_nil deletion.version_id - - deletion.user.destroy! - - Maintenance::BackfillDeletionVersionsTask.process(deletion.reload) - - assert_equal version.id, deletion.version_id - assert_equal version, deletion.version - end - - test "#process with changed gem capitalization" do - rubygem = create(:rubygem, name: "rubygem0") - version = create(:version, rubygem:) - deletion = create(:deletion, version:) - - deletion.update_column(:version_id, nil) - - assert_nil deletion.version_id - - rubygem.update!(name: "Rubygem0") - - Maintenance::BackfillDeletionVersionsTask.process(deletion.reload) - - assert_equal version.id, deletion.version_id - assert_equal version, deletion.version - end - - test "#process with missing version" do - Deletion.insert!({ rubygem: "missing", number: "1.0.0", platform: "ruby" }) - deletion = Deletion.where(rubygem: "missing", number: "1.0.0", platform: "ruby").sole - - assert_nil deletion.version_id - - Maintenance::BackfillDeletionVersionsTask.process(deletion) - - assert_nil deletion.version_id - end -end From a0bcfeb097f05f84ac5b89be932f8fe68bfbd822 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:22:30 -0800 Subject: [PATCH 057/112] Bump ddtrace from 1.17.0 to 1.18.0 (#4274) Bumps [ddtrace](https://github.com/DataDog/dd-trace-rb) from 1.17.0 to 1.18.0. - [Release notes](https://github.com/DataDog/dd-trace-rb/releases) - [Changelog](https://github.com/DataDog/dd-trace-rb/blob/master/CHANGELOG.md) - [Commits](https://github.com/DataDog/dd-trace-rb/compare/v1.17.0...v1.18.0) --- updated-dependencies: - dependency-name: ddtrace dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 7be955f05a2..25b4e6c1112 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem "aws-sdk-sqs", "~> 1.69" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" gem "dalli", "~> 3.2" -gem "ddtrace", "~> 1.17", require: "ddtrace/auto_instrument" +gem "ddtrace", "~> 1.18", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" gem "google-protobuf", "~> 3.25" gem "faraday", "~> 2.7" diff --git a/Gemfile.lock b/Gemfile.lock index 907cf4917eb..38ffcddb7b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,17 +175,17 @@ GEM sprockets (> 3.0) sprockets-rails tilt - datadog-ci (0.4.1) + datadog-ci (0.5.0) msgpack date (3.3.4) - ddtrace (1.17.0) - datadog-ci (~> 0.4.0) - debase-ruby_core_source (= 3.2.2) + ddtrace (1.18.0) + datadog-ci (~> 0.5.0) + debase-ruby_core_source (= 3.2.3) libdatadog (~> 5.0.0.1.0) libddwaf (~> 1.14.0.0.0) msgpack dead_end (4.0.0) - debase-ruby_core_source (3.2.2) + debase-ruby_core_source (3.2.3) derailed_benchmarks (2.1.2) benchmark-ips (~> 2) dead_end @@ -706,7 +706,7 @@ DEPENDENCIES compact_index (~> 0.14.0) dalli (~> 3.2) dartsass-sprockets (~> 3.0) - ddtrace (~> 1.17) + ddtrace (~> 1.18) derailed_benchmarks (~> 2.1) dogstatsd-ruby (~> 5.5) dotenv-rails (~> 2.8) From 2db31009ae618849e16402498ff201fd313ed562 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:29:42 -0800 Subject: [PATCH 058/112] Bump avo from 2.45.0 to 2.46.0 (#4267) Bumps [avo](https://github.com/avo-hq/avo) from 2.45.0 to 2.46.0. - [Release notes](https://github.com/avo-hq/avo/releases) - [Changelog](https://github.com/avo-hq/avo/blob/main/RELEASE.MD) - [Commits](https://github.com/avo-hq/avo/compare/v2.45.0...v2.46.0) --- updated-dependencies: - dependency-name: avo dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 25b4e6c1112..161cdc49e42 100644 --- a/Gemfile +++ b/Gemfile @@ -55,7 +55,7 @@ gem "strong_migrations", "~> 1.6" gem "phlex-rails", "~> 1.1" # Admin dashboard -gem "avo", "~> 2.45" +gem "avo", "~> 2.46" gem "view_component", "~> 3.8" gem "pundit", "~> 2.3" gem "chartkick", "~> 5.0" diff --git a/Gemfile.lock b/Gemfile.lock index 38ffcddb7b0..00218879d10 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,7 +84,7 @@ GEM attr_required (1.0.1) autoprefixer-rails (10.4.16.0) execjs (~> 2) - avo (2.45.0) + avo (2.46.0) actionview (>= 6.0) active_link_to activerecord (>= 6.0) @@ -693,7 +693,7 @@ DEPENDENCIES aggregate_assertions (~> 0.2.0) amazing_print (~> 1.4) autoprefixer-rails (~> 10.4) - avo (~> 2.45) + avo (~> 2.46) aws-sdk-s3 (~> 1.141) aws-sdk-sqs (~> 1.69) bcrypt (~> 3.1) From 4c4bab4acb18b83f99dcf50b781070cc640be591 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:09:07 -0800 Subject: [PATCH 059/112] Bump chartkick from 5.0.4 to 5.0.5 (#4275) Bumps [chartkick](https://github.com/ankane/chartkick) from 5.0.4 to 5.0.5. - [Changelog](https://github.com/ankane/chartkick/blob/master/CHANGELOG.md) - [Commits](https://github.com/ankane/chartkick/compare/v5.0.4...v5.0.5) --- updated-dependencies: - dependency-name: chartkick dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 00218879d10..9139c1e7572 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,7 +144,7 @@ GEM xpath (~> 3.2) cbor (0.5.9.6) cgi (0.4.0) - chartkick (5.0.4) + chartkick (5.0.5) choice (0.2.0) chunky_png (1.4.0) clearance (2.6.1) From 05f6813718f65ebd05155df4f02131686e173e92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:10:02 -0800 Subject: [PATCH 060/112] Bump compact_index from 0.14.0 to 0.15.0 (#4276) Bumps [compact_index](https://github.com/rubygems/compact_index) from 0.14.0 to 0.15.0. - [Release notes](https://github.com/rubygems/compact_index/releases) - [Changelog](https://github.com/rubygems/compact_index/blob/master/CHANGELOG.md) - [Commits](https://github.com/rubygems/compact_index/compare/v0.14.0...v0.15.0) --- updated-dependencies: - dependency-name: compact_index dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 161cdc49e42..c5ea7a16ea6 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,7 @@ gem "opensearch-ruby", "~> 3.0" gem "searchkick", "~> 5.3" gem "faraday_middleware-aws-sigv4", "~> 1.0" gem "xml-simple", "~> 1.1" -gem "compact_index", "~> 0.14.0" +gem "compact_index", "~> 0.15.0" gem "sprockets-rails", "~> 3.4" gem "rack-attack", "~> 6.6" gem "rqrcode", "~> 2.1" diff --git a/Gemfile.lock b/Gemfile.lock index 9139c1e7572..5e3c05cbf70 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -156,7 +156,7 @@ GEM email_validator (~> 2.0) railties (>= 5.0) coderay (1.1.3) - compact_index (0.14.0) + compact_index (0.15.0) concurrent-ruby (1.2.2) cose (1.3.0) cbor (~> 0.5.9) @@ -703,7 +703,7 @@ DEPENDENCIES capybara (~> 3.38) chartkick (~> 5.0) clearance (~> 2.6) - compact_index (~> 0.14.0) + compact_index (~> 0.15.0) dalli (~> 3.2) dartsass-sprockets (~> 3.0) ddtrace (~> 1.18) From 080b8e3ab401a0d660a6f3ac35d34404bd75160c Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Mon, 11 Dec 2023 11:09:34 -0800 Subject: [PATCH 061/112] Add trusted publishers (#4239) --- .rubocop.yml | 2 +- app/avo/resources/api_key_resource.rb | 2 +- ...oidc_pending_trusted_publisher_resource.rb | 16 + app/avo/resources/oidc_provider_resource.rb | 3 - ...oidc_rubygem_trusted_publisher_resource.rb | 11 + ...rusted_publisher_github_action_resource.rb | 19 + app/avo/resources/rubygem_resource.rb | 3 + .../v1/oidc/trusted_publisher_controller.rb | 71 +++ ...c_pending_trusted_publishers_controller.rb | 4 + ...c_rubygem_trusted_publishers_controller.rb | 4 + ...ted_publisher_github_actions_controller.rb | 4 + .../concerns/trusted_publisher_creation.rb | 21 + .../pending_trusted_publishers_controller.rb | 65 +++ .../rubygem_trusted_publishers_controller.rb | 80 ++++ app/helpers/rubygems_helper.rb | 13 + app/mailers/mailer.rb | 12 + app/models/api_key.rb | 2 +- app/models/oidc/pending_trusted_publisher.rb | 38 ++ app/models/oidc/provider.rb | 7 + app/models/oidc/rubygem_trusted_publisher.rb | 12 + app/models/oidc/trusted_publisher.rb | 9 + .../oidc/trusted_publisher/github_action.rb | 162 +++++++ app/models/pusher.rb | 27 +- app/models/rubygem.rb | 6 +- app/models/user.rb | 4 +- .../oidc/pending_trusted_publisher_policy.rb | 13 + .../oidc/rubygem_trusted_publisher_policy.rb | 13 + .../trusted_publisher/github_action_policy.rb | 16 + app/policies/rubygem_policy.rb | 3 + app/views/application_view.rb | 4 + app/views/components/application_component.rb | 31 +- .../github_action/form_component.rb | 27 ++ .../github_action/table_component.rb | 20 + app/views/mailer/gem_pushed.html.erb | 7 + .../gem_trusted_publisher_added.html.erb | 53 +++ .../pending_trusted_publishers/index_view.rb | 67 +++ .../pending_trusted_publishers/new_view.rb | 37 ++ .../concerns/title.rb | 18 + .../rubygem_trusted_publishers/index_view.rb | 45 ++ .../rubygem_trusted_publishers/new_view.rb | 37 ++ .../github_actions/_github_action.html.erb | 1 + app/views/rubygems/_aside.html.erb | 1 + app/views/rubygems/_gem_members.html.erb | 5 + app/views/settings/edit.html.erb | 7 + config/brakeman.ignore | 58 ++- config/initializers/datadog.rb | 2 +- config/locales/de.yml | 53 +++ config/locales/en.yml | 60 +++ config/locales/es.yml | 434 ++++++++++++------ config/locales/fr.yml | 53 +++ config/locales/ja.yml | 55 ++- config/locales/nl.yml | 53 +++ config/locales/pt-BR.yml | 53 +++ config/locales/zh-CN.yml | 53 +++ config/locales/zh-TW.yml | 53 +++ config/routes.rb | 3 + ...e_oidc_trusted_publisher_github_actions.rb | 17 + ..._create_oidc_rubygem_trusted_publishers.rb | 14 + ..._create_oidc_pending_trusted_publishers.rb | 12 + db/schema.rb | 36 ++ db/seeds.rb | 31 ++ .../oidc/pending_trusted_publishers.rb | 8 + .../oidc/rubygem_trusted_publishers.rb | 6 + .../oidc/trusted_publisher/github_actions.rb | 9 + .../oidc/trusted_publisher_controller_test.rb | 208 +++++++++ test/integration/api/v1/owner_test.rb | 16 + ...ding_trusted_publishers_controller_test.rb | 27 ++ ...ygem_trusted_publishers_controller_test.rb | 27 ++ ...ublisher_github_actions_controller_test.rb | 25 + ...ding_trusted_publishers_controller_test.rb | 152 ++++++ ...ygem_trusted_publishers_controller_test.rb | 185 ++++++++ test/integration/push_test.rb | 70 +++ test/mailers/previews/mailer_preview.rb | 15 +- .../oidc/pending_trusted_publisher_test.rb | 29 ++ .../oidc/rubygem_trusted_publisher_test.rb | 13 + .../trusted_publisher/github_action_test.rb | 138 ++++++ .../pending_trusted_publisher_policy_test.rb | 42 ++ .../rubygem_trusted_publisher_policy_test.rb | 42 ++ .../github_action_policy_test.rb | 42 ++ test/system/oidc_test.rb | 138 ++++++ 80 files changed, 3067 insertions(+), 167 deletions(-) create mode 100644 app/avo/resources/oidc_pending_trusted_publisher_resource.rb create mode 100644 app/avo/resources/oidc_rubygem_trusted_publisher_resource.rb create mode 100644 app/avo/resources/oidc_trusted_publisher_github_action_resource.rb create mode 100644 app/controllers/api/v1/oidc/trusted_publisher_controller.rb create mode 100644 app/controllers/avo/oidc_pending_trusted_publishers_controller.rb create mode 100644 app/controllers/avo/oidc_rubygem_trusted_publishers_controller.rb create mode 100644 app/controllers/avo/oidc_trusted_publisher_github_actions_controller.rb create mode 100644 app/controllers/oidc/concerns/trusted_publisher_creation.rb create mode 100644 app/controllers/oidc/pending_trusted_publishers_controller.rb create mode 100644 app/controllers/oidc/rubygem_trusted_publishers_controller.rb create mode 100644 app/models/oidc/pending_trusted_publisher.rb create mode 100644 app/models/oidc/rubygem_trusted_publisher.rb create mode 100644 app/models/oidc/trusted_publisher.rb create mode 100644 app/models/oidc/trusted_publisher/github_action.rb create mode 100644 app/policies/oidc/pending_trusted_publisher_policy.rb create mode 100644 app/policies/oidc/rubygem_trusted_publisher_policy.rb create mode 100644 app/policies/oidc/trusted_publisher/github_action_policy.rb create mode 100644 app/views/components/oidc/trusted_publisher/github_action/form_component.rb create mode 100644 app/views/components/oidc/trusted_publisher/github_action/table_component.rb create mode 100644 app/views/mailer/gem_trusted_publisher_added.html.erb create mode 100644 app/views/oidc/pending_trusted_publishers/index_view.rb create mode 100644 app/views/oidc/pending_trusted_publishers/new_view.rb create mode 100644 app/views/oidc/rubygem_trusted_publishers/concerns/title.rb create mode 100644 app/views/oidc/rubygem_trusted_publishers/index_view.rb create mode 100644 app/views/oidc/rubygem_trusted_publishers/new_view.rb create mode 100644 app/views/oidc/trusted_publisher/github_actions/_github_action.html.erb create mode 100644 db/migrate/20231027190405_create_oidc_trusted_publisher_github_actions.rb create mode 100644 db/migrate/20231027191446_create_oidc_rubygem_trusted_publishers.rb create mode 100644 db/migrate/20231108205146_create_oidc_pending_trusted_publishers.rb create mode 100644 test/factories/oidc/pending_trusted_publishers.rb create mode 100644 test/factories/oidc/rubygem_trusted_publishers.rb create mode 100644 test/factories/oidc/trusted_publisher/github_actions.rb create mode 100644 test/integration/api/v1/oidc/trusted_publisher_controller_test.rb create mode 100644 test/integration/avo/oidc_pending_trusted_publishers_controller_test.rb create mode 100644 test/integration/avo/oidc_rubygem_trusted_publishers_controller_test.rb create mode 100644 test/integration/avo/oidc_trusted_publisher_github_actions_controller_test.rb create mode 100644 test/integration/oidc/pending_trusted_publishers_controller_test.rb create mode 100644 test/integration/oidc/rubygem_trusted_publishers_controller_test.rb create mode 100644 test/models/oidc/pending_trusted_publisher_test.rb create mode 100644 test/models/oidc/rubygem_trusted_publisher_test.rb create mode 100644 test/models/oidc/trusted_publisher/github_action_test.rb create mode 100644 test/policies/oidc/pending_trusted_publisher_policy_test.rb create mode 100644 test/policies/oidc/rubygem_trusted_publisher_policy_test.rb create mode 100644 test/policies/oidc/trusted_publisher/github_action_policy_test.rb diff --git a/.rubocop.yml b/.rubocop.yml index 32917f9d948..c963f80f35d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -70,7 +70,7 @@ Metrics/BlockLength: - config/environments/development.rb Metrics/ClassLength: - Max: 356 # TODO: Lower to 100 + Max: 357 # TODO: Lower to 100 Exclude: - test/**/* diff --git a/app/avo/resources/api_key_resource.rb b/app/avo/resources/api_key_resource.rb index 4843f45c154..70832a69514 100644 --- a/app/avo/resources/api_key_resource.rb +++ b/app/avo/resources/api_key_resource.rb @@ -12,7 +12,7 @@ class ExpiredFilter < ScopeBooleanFilter; end field :user, as: :belongs_to, visible: ->(_) { false } field :owner, as: :belongs_to, polymorphic_as: :owner, - types: [::User] + types: [::User, ::OIDC::TrustedPublisher::GitHubAction] field :last_accessed_at, as: :date_time field :soft_deleted_at, as: :date_time field :soft_deleted_rubygem_name, as: :text diff --git a/app/avo/resources/oidc_pending_trusted_publisher_resource.rb b/app/avo/resources/oidc_pending_trusted_publisher_resource.rb new file mode 100644 index 00000000000..f546d3a8df1 --- /dev/null +++ b/app/avo/resources/oidc_pending_trusted_publisher_resource.rb @@ -0,0 +1,16 @@ +class OIDCPendingTrustedPublisherResource < Avo::BaseResource + self.title = :id + self.includes = [] + self.model_class = ::OIDC::PendingTrustedPublisher + + class ExpiredFilter < ScopeBooleanFilter; end + filter ExpiredFilter, arguments: { default: { expired: false, unexpired: true } } + + field :id, as: :id + # Fields generated from the model + field :rubygem_name, as: :text + field :user, as: :belongs_to + field :trusted_publisher, as: :belongs_to, polymorphic_as: :trusted_publisher + field :expires_at, as: :date_time + # add fields here +end diff --git a/app/avo/resources/oidc_provider_resource.rb b/app/avo/resources/oidc_provider_resource.rb index 7f209c2c895..8a2fa0fe6d4 100644 --- a/app/avo/resources/oidc_provider_resource.rb +++ b/app/avo/resources/oidc_provider_resource.rb @@ -2,9 +2,6 @@ class OIDCProviderResource < Avo::BaseResource self.title = :issuer self.includes = [] self.model_class = ::OIDC::Provider - # self.search_query = -> do - # scope.ransack(id_eq: params[:q], m: "or").result(distinct: false) - # end action RefreshOIDCProvider diff --git a/app/avo/resources/oidc_rubygem_trusted_publisher_resource.rb b/app/avo/resources/oidc_rubygem_trusted_publisher_resource.rb new file mode 100644 index 00000000000..96c63c43096 --- /dev/null +++ b/app/avo/resources/oidc_rubygem_trusted_publisher_resource.rb @@ -0,0 +1,11 @@ +class OIDCRubygemTrustedPublisherResource < Avo::BaseResource + self.title = :id + self.includes = [:trusted_publisher] + self.model_class = ::OIDC::RubygemTrustedPublisher + + field :id, as: :id + # Fields generated from the model + field :rubygem, as: :belongs_to + field :trusted_publisher, as: :belongs_to, polymorphic_as: :trusted_publisher + # add fields here +end diff --git a/app/avo/resources/oidc_trusted_publisher_github_action_resource.rb b/app/avo/resources/oidc_trusted_publisher_github_action_resource.rb new file mode 100644 index 00000000000..0ea00fd83e4 --- /dev/null +++ b/app/avo/resources/oidc_trusted_publisher_github_action_resource.rb @@ -0,0 +1,19 @@ +class OIDCTrustedPublisherGitHubActionResource < Avo::BaseResource + self.title = :name + self.includes = [] + self.model_class = ::OIDC::TrustedPublisher::GitHubAction + + field :id, as: :id + # Fields generated from the model + field :repository_owner, as: :text + field :repository_name, as: :text + field :repository_owner_id, as: :text + field :workflow_filename, as: :text + field :environment, as: :text + # add fields here + # + field :rubygem_trusted_publishers, as: :has_many + field :pending_trusted_publishers, as: :has_many + field :rubygems, as: :has_many, through: :rubygem_trusted_publishers + field :api_keys, as: :has_many, inverse_of: :owner +end diff --git a/app/avo/resources/rubygem_resource.rb b/app/avo/resources/rubygem_resource.rb index 8de4d3be225..e942e207847 100644 --- a/app/avo/resources/rubygem_resource.rb +++ b/app/avo/resources/rubygem_resource.rb @@ -38,6 +38,9 @@ class IndexedFilter < ScopeBooleanFilter; end field :linkset, as: :has_one field :gem_download, as: :has_one + field :link_verifications, as: :has_many + field :oidc_rubygem_trusted_publishers, as: :has_many + field :audits, as: :has_many end end diff --git a/app/controllers/api/v1/oidc/trusted_publisher_controller.rb b/app/controllers/api/v1/oidc/trusted_publisher_controller.rb new file mode 100644 index 00000000000..d5046822924 --- /dev/null +++ b/app/controllers/api/v1/oidc/trusted_publisher_controller.rb @@ -0,0 +1,71 @@ +class Api::V1::OIDC::TrustedPublisherController < Api::BaseController + include ApiKeyable + + before_action :decode_jwt + before_action :find_provider + before_action :verify_signature + before_action :find_trusted_publisher + before_action :validate_claims + + class UnsupportedIssuer < StandardError; end + class UnverifiedJWT < StandardError; end + + rescue_from( + UnsupportedIssuer, UnverifiedJWT, + JSON::JWT::VerificationFailed, JSON::JWK::Set::KidNotFound, + OIDC::AccessPolicy::AccessError, + with: :render_not_found + ) + + def exchange_token + key = generate_unique_rubygems_key + iat = Time.at(@jwt[:iat].to_i, in: "UTC") + api_key = @trusted_publisher.api_keys.create!( + hashed_key: hashed_key(key), + name: "#{@trusted_publisher.name} #{iat.iso8601}", + push_rubygem: true, + expires_at: 15.minutes.from_now + ) + + render json: { + rubygems_api_key: key, + name: api_key.name, + scopes: api_key.enabled_scopes, + gem: api_key.rubygem, + expires_at: api_key.expires_at + }.compact, status: :created + end + + private + + def decode_jwt + @jwt = JSON::JWT.decode_compact_serialized(params.require(:jwt), :skip_verification) + rescue JSON::JWT::InvalidFormat, JSON::ParserError, ArgumentError + # invalid base64 raises ArgumentError + render_bad_request + end + + def find_provider + @provider = OIDC::Provider.find_by!(issuer: @jwt[:iss]) + end + + def verify_signature + raise UnverifiedJWT, "Invalid time" unless (@jwt["nbf"]..@jwt["exp"]).cover?(Time.now.to_i) + @jwt.verify!(@provider.jwks) + end + + def find_trusted_publisher + unless (trusted_publisher_class = @provider.trusted_publisher_class) + raise UnsupportedIssuer, "Unsuported issuer for trusted publishing" + end + @trusted_publisher = trusted_publisher_class.for_claims(@jwt) + end + + def validate_claims + @trusted_publisher.to_access_policy(@jwt).verify_access!(@jwt) + end + + def render_bad_request + render json: { error: "Bad Request" }, status: :bad_request + end +end diff --git a/app/controllers/avo/oidc_pending_trusted_publishers_controller.rb b/app/controllers/avo/oidc_pending_trusted_publishers_controller.rb new file mode 100644 index 00000000000..44059a5649f --- /dev/null +++ b/app/controllers/avo/oidc_pending_trusted_publishers_controller.rb @@ -0,0 +1,4 @@ +# This controller has been generated to enable Rails' resource routes. +# More information on https://docs.avohq.io/2.0/controllers.html +class Avo::OIDCPendingTrustedPublishersController < Avo::ResourcesController +end diff --git a/app/controllers/avo/oidc_rubygem_trusted_publishers_controller.rb b/app/controllers/avo/oidc_rubygem_trusted_publishers_controller.rb new file mode 100644 index 00000000000..a9559a522d4 --- /dev/null +++ b/app/controllers/avo/oidc_rubygem_trusted_publishers_controller.rb @@ -0,0 +1,4 @@ +# This controller has been generated to enable Rails' resource routes. +# More information on https://docs.avohq.io/2.0/controllers.html +class Avo::OIDCRubygemTrustedPublishersController < Avo::ResourcesController +end diff --git a/app/controllers/avo/oidc_trusted_publisher_github_actions_controller.rb b/app/controllers/avo/oidc_trusted_publisher_github_actions_controller.rb new file mode 100644 index 00000000000..f35ba803f21 --- /dev/null +++ b/app/controllers/avo/oidc_trusted_publisher_github_actions_controller.rb @@ -0,0 +1,4 @@ +# This controller has been generated to enable Rails' resource routes. +# More information on https://docs.avohq.io/2.0/controllers.html +class Avo::OIDCTrustedPublisherGitHubActionsController < Avo::ResourcesController +end diff --git a/app/controllers/oidc/concerns/trusted_publisher_creation.rb b/app/controllers/oidc/concerns/trusted_publisher_creation.rb new file mode 100644 index 00000000000..480c4e5c9f8 --- /dev/null +++ b/app/controllers/oidc/concerns/trusted_publisher_creation.rb @@ -0,0 +1,21 @@ +module OIDC::Concerns::TrustedPublisherCreation + extend ActiveSupport::Concern + + included do + include SessionVerifiable + verify_session_before + + before_action :set_trusted_publisher_type, only: %i[create] + before_action :create_params, only: %i[create] + before_action :set_page, only: :index + end + + def set_trusted_publisher_type + trusted_publisher_type = params.permit(create_params_key => :trusted_publisher_type).require(create_params_key).require(:trusted_publisher_type) + + @trusted_publisher_type = OIDC::TrustedPublisher.all.find { |type| type.polymorphic_name == trusted_publisher_type } + + return if @trusted_publisher_type + redirect_back fallback_location: root_path, flash: { error: t("oidc.trusted_publisher.unsupported_type") } + end +end diff --git a/app/controllers/oidc/pending_trusted_publishers_controller.rb b/app/controllers/oidc/pending_trusted_publishers_controller.rb new file mode 100644 index 00000000000..b1e5f10c0b9 --- /dev/null +++ b/app/controllers/oidc/pending_trusted_publishers_controller.rb @@ -0,0 +1,65 @@ +class OIDC::PendingTrustedPublishersController < ApplicationController + include OIDC::Concerns::TrustedPublisherCreation + + before_action :find_pending_trusted_publisher, only: %i[destroy] + + def index + trusted_publishers = current_user + .oidc_pending_trusted_publishers.unexpired.includes(:trusted_publisher) + .order(:rubygem_name, :created_at).page(@page).strict_loading + render OIDC::PendingTrustedPublishers::IndexView.new( + trusted_publishers: + ) + end + + def new + pending_trusted_publisher = current_user.oidc_pending_trusted_publishers.new(trusted_publisher: OIDC::TrustedPublisher::GitHubAction.new) + render OIDC::PendingTrustedPublishers::NewView.new( + pending_trusted_publisher: + ) + end + + def create + trusted_publisher = current_user.oidc_pending_trusted_publishers.new( + create_params.merge( + expires_at: 12.hours.from_now + ) + ) + + if trusted_publisher.save + redirect_to profile_oidc_pending_trusted_publishers_path, flash: { notice: t(".success") } + else + flash.now[:error] = trusted_publisher.errors.full_messages.to_sentence + render OIDC::PendingTrustedPublishers::NewView.new( + pending_trusted_publisher: trusted_publisher + ), status: :unprocessable_entity + end + end + + def destroy + if @pending_trusted_publisher.destroy + redirect_to profile_oidc_pending_trusted_publishers_path, flash: { notice: t(".success") } + else + redirect_back fallback_location: profile_oidc_pending_trusted_publishers_path, + flash: { error: @pending_trusted_publisher.errors.full_messages.to_sentence } + end + end + + private + + def create_params + params.permit( + create_params_key => [ + :rubygem_name, + :trusted_publisher_type, + { trusted_publisher_attributes: @trusted_publisher_type.permitted_attributes } + ] + ).require(create_params_key) + end + + def create_params_key = :oidc_pending_trusted_publisher + + def find_pending_trusted_publisher + @pending_trusted_publisher = current_user.oidc_pending_trusted_publishers.find(params.require(:id)) + end +end diff --git a/app/controllers/oidc/rubygem_trusted_publishers_controller.rb b/app/controllers/oidc/rubygem_trusted_publishers_controller.rb new file mode 100644 index 00000000000..15761d1aa80 --- /dev/null +++ b/app/controllers/oidc/rubygem_trusted_publishers_controller.rb @@ -0,0 +1,80 @@ +class OIDC::RubygemTrustedPublishersController < ApplicationController + include OIDC::Concerns::TrustedPublisherCreation + + before_action :find_rubygem + before_action :render_forbidden, unless: :owner? + before_action :find_rubygem_trusted_publisher, except: %i[index new create] + + def index + render OIDC::RubygemTrustedPublishers::IndexView.new( + rubygem: @rubygem, + trusted_publishers: @rubygem.oidc_rubygem_trusted_publishers.includes(:trusted_publisher).page(@page).strict_loading + ) + end + + def new + render OIDC::RubygemTrustedPublishers::NewView.new( + rubygem_trusted_publisher: @rubygem.oidc_rubygem_trusted_publishers.new(trusted_publisher: gh_actions_trusted_publisher) + ) + end + + def create + trusted_publisher = @rubygem.oidc_rubygem_trusted_publishers.new( + create_params + ) + + if trusted_publisher.save + redirect_to rubygem_trusted_publishers_path(@rubygem.slug), flash: { notice: t(".success") } + else + flash.now[:error] = trusted_publisher.errors.full_messages.to_sentence + render OIDC::RubygemTrustedPublishers::NewView.new( + rubygem_trusted_publisher: trusted_publisher + ), status: :unprocessable_entity + end + end + + def destroy + if @rubygem_trusted_publisher.destroy + redirect_to rubygem_trusted_publishers_path(@rubygem.slug), flash: { notice: t(".success") } + else + redirect_back fallback_location: rubygem_trusted_publishers_path(@rubygem.slug), + flash: { error: @rubygem_trusted_publisher.errors.full_messages.to_sentence } + end + end + + private + + def create_params + params.permit( + create_params_key => [ + :trusted_publisher_type, + { trusted_publisher_attributes: @trusted_publisher_type.permitted_attributes } + ] + ).require(create_params_key) + end + + def create_params_key = :oidc_rubygem_trusted_publisher + + def find_rubygem_trusted_publisher + @rubygem_trusted_publisher = @rubygem.oidc_rubygem_trusted_publishers.find(params.require(:id)) + end + + def gh_actions_trusted_publisher + github_params = helpers.github_params(@rubygem) + + publisher = OIDC::TrustedPublisher::GitHubAction.new + if github_params + publisher.repository_owner = github_params[:user] + publisher.repository_name = github_params[:repo] + publisher.workflow_filename = workflow_filename(publisher.repository) + end + publisher + end + + def workflow_filename(repo) + paths = Octokit.contents(repo, path: ".github/workflows").lazy.select { _1.type == "file" }.map(&:name).grep(/\.ya?ml\z/) + paths.max_by { |path| [path.include?("release"), path.include?("push")].map! { (_1 && 1) || 0 } } + rescue Octokit::NotFound + nil + end +end diff --git a/app/helpers/rubygems_helper.rb b/app/helpers/rubygems_helper.rb index 960adedfa2b..c0caeeff740 100644 --- a/app/helpers/rubygems_helper.rb +++ b/app/helpers/rubygems_helper.rb @@ -93,6 +93,10 @@ def ownership_link(rubygem) link_to I18n.t("rubygems.aside.links.ownership"), rubygem_owners_path(rubygem.slug), class: "gem__link t-list__item" end + def rubygem_trusted_publishers_link(rubygem) + link_to t("rubygems.aside.links.trusted_publishers"), rubygem_trusted_publishers_path(rubygem.slug), class: "gem__link t-list__item" + end + def oidc_api_key_role_links(rubygem) roles = current_user.oidc_api_key_roles.for_rubygem(rubygem) @@ -135,6 +139,15 @@ def link_to_user(user) alt: user.display_handle, title: user.display_handle end + def link_to_pusher(api_key_owner) + case api_key_owner + when OIDC::TrustedPublisher::GitHubAction + image_tag "github_icon.png", width: 48, height: 48, theme: :light, alt: "GitHub", title: api_key_owner.name + else + raise ArgumentError, "unknown api_key_owner type #{api_key_owner.class}" + end + end + def nice_date_for(time) time.to_date.to_fs(:long) end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index c9b46dd0db1..fb59eefd5d5 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -67,6 +67,18 @@ def gem_pushed(pushed_by, version_id, notified_user_id) default: "Gem %{gem} pushed to RubyGems.org") end + def gem_trusted_publisher_added(rubygem_trusted_publisher, created_by_user, notified_user) + @rubygem_trusted_publisher = rubygem_trusted_publisher + @created_by_user = created_by_user + @notified_user = notified_user + + mail to: notified_user.email, + subject: I18n.t("mailer.gem_trusted_publisher_added.subject", + gem: @rubygem_trusted_publisher.rubygem.name, + host: Gemcutter::HOST_DISPLAY, + default: "Trusted publisher added to %{gem} on RubyGems.org") + end + def mfa_notification(user_id) @user = User.find(user_id) diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 72aec9c6472..bcf4e219d1c 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -9,7 +9,7 @@ class ApiKey < ApplicationRecord has_one :api_key_rubygem_scope, dependent: :destroy has_one :ownership, through: :api_key_rubygem_scope has_one :oidc_id_token, class_name: "OIDC::IdToken", dependent: :restrict_with_error - has_one :oidc_api_key_role, through: :oidc_id_token, source: :api_key_role, inverse_of: :api_keys + has_one :oidc_api_key_role, class_name: "OIDC::ApiKeyRole", through: :oidc_id_token, source: :api_key_role, inverse_of: :api_keys has_many :pushed_versions, class_name: "Version", inverse_of: :pusher_api_key, foreign_key: :pusher_api_key_id, dependent: :nullify before_validation :set_owner_from_user diff --git a/app/models/oidc/pending_trusted_publisher.rb b/app/models/oidc/pending_trusted_publisher.rb new file mode 100644 index 00000000000..024a11a8c85 --- /dev/null +++ b/app/models/oidc/pending_trusted_publisher.rb @@ -0,0 +1,38 @@ +class OIDC::PendingTrustedPublisher < ApplicationRecord + belongs_to :user + belongs_to :trusted_publisher, polymorphic: true, optional: false + + accepts_nested_attributes_for :trusted_publisher + + validates :rubygem_name, + length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, + presence: true, + name_format: true, + uniqueness: { case_sensitive: false, scope: %i[trusted_publisher_id trusted_publisher_type], conditions: -> { unexpired } } + + validate :available_rubygem_name, on: :create + + scope :unexpired, -> { where(arel_table[:expires_at].eq(nil).or(arel_table[:expires_at].gt(Time.now.utc))) } + scope :expired, -> { where(arel_table[:expires_at].lteq(Time.now.utc)) } + + scope :rubygem_name_is, lambda { |name| + sensitive = where(rubygem_name: name.strip).limit(1) + return sensitive unless sensitive.empty? + + where("UPPER(rubygem_name) = UPPER(?)", name.strip).limit(1) + } + + def build_trusted_publisher(params) + self.trusted_publisher = trusted_publisher_type.constantize.build_trusted_publisher(params) + end + + private + + def available_rubygem_name + return if rubygem_name.blank? + rubygem = Rubygem.name_is(rubygem_name).first + return if rubygem.nil? || rubygem.pushable? + + errors.add(:rubygem_name, :unavailable) + end +end diff --git a/app/models/oidc/provider.rb b/app/models/oidc/provider.rb index 52cc1a6e942..04361fff603 100644 --- a/app/models/oidc/provider.rb +++ b/app/models/oidc/provider.rb @@ -39,6 +39,13 @@ def valid? attribute :jwks, Types::JsonDeserializable.new(JSON::JWK::Set) + def trusted_publisher_class + case issuer + when GITHUB_ACTIONS_ISSUER + OIDC::TrustedPublisher::GitHubAction + end + end + private def issuer_match diff --git a/app/models/oidc/rubygem_trusted_publisher.rb b/app/models/oidc/rubygem_trusted_publisher.rb new file mode 100644 index 00000000000..055bf6a6238 --- /dev/null +++ b/app/models/oidc/rubygem_trusted_publisher.rb @@ -0,0 +1,12 @@ +class OIDC::RubygemTrustedPublisher < ApplicationRecord + belongs_to :rubygem + belongs_to :trusted_publisher, polymorphic: true, optional: false + + accepts_nested_attributes_for :trusted_publisher + + validates :rubygem, uniqueness: { scope: %i[trusted_publisher_id trusted_publisher_type] } + + def build_trusted_publisher(params) + self.trusted_publisher = trusted_publisher_type.constantize.build_trusted_publisher(params) + end +end diff --git a/app/models/oidc/trusted_publisher.rb b/app/models/oidc/trusted_publisher.rb new file mode 100644 index 00000000000..ad9a68da98a --- /dev/null +++ b/app/models/oidc/trusted_publisher.rb @@ -0,0 +1,9 @@ +module OIDC::TrustedPublisher + def self.table_name_prefix + "oidc_trusted_publisher_" + end + + def self.all + [GitHubAction] + end +end diff --git a/app/models/oidc/trusted_publisher/github_action.rb b/app/models/oidc/trusted_publisher/github_action.rb new file mode 100644 index 00000000000..29873689123 --- /dev/null +++ b/app/models/oidc/trusted_publisher/github_action.rb @@ -0,0 +1,162 @@ +class OIDC::TrustedPublisher::GitHubAction < ApplicationRecord + has_many :rubygem_trusted_publishers, class_name: "OIDC::RubygemTrustedPublisher", as: :trusted_publisher, dependent: :destroy, + inverse_of: :trusted_publisher + has_many :pending_trusted_publishers, class_name: "OIDC::PendingTrustedPublisher", as: :trusted_publisher, dependent: :destroy, + inverse_of: :trusted_publisher + has_many :rubygems, through: :rubygem_trusted_publishers + has_many :api_keys, dependent: :destroy, inverse_of: :owner, as: :owner + + before_validation :find_github_repository_owner_id + + validates :repository_owner, presence: true + validates :repository_name, presence: true + validates :workflow_filename, presence: true + validates :environment, presence: true, allow_nil: true + validates :repository_owner_id, presence: true + + validate :unique_publisher + validate :workflow_filename_format + + def self.for_claims(claims) + repository = claims.fetch(:repository) + repository_owner, repository_name = repository.split("/", 2) + workflow_prefix = "#{repository}/.github/workflows/" + workflow_ref = claims.fetch(:job_workflow_ref).delete_prefix(workflow_prefix) + workflow_filename = workflow_ref.sub(/@[^@]+\z/, "") + + required = { + repository_owner:, repository_name:, workflow_filename:, + repository_owner_id: claims.fetch(:repository_owner_id) + } + + base = where(required) + if (env = claims[:environment]) + base.where(environment: env).or(base.where(environment: nil)).order(environment: :asc) # NULLS LAST by default for asc + else + base.where(environment: nil) + end.first! + end + + def self.permitted_attributes + %i[repository_owner repository_name workflow_filename environment] + end + + def self.build_trusted_publisher(params) + params.delete(:environment) if params[:environment].blank? + params = params.reverse_merge(repository_owner_id: nil, repository_name: nil, workflow_filename: nil, environment: nil) + find_or_initialize_by(params) + end + + def self.publisher_name = "GitHub Actions" + + def repository_condition + OIDC::AccessPolicy::Statement::Condition.new( + operator: "string_equals", + claim: "repository", + value: [repository_owner, repository_name].join("/") + ) + end + + def environment_condition + return if environment.blank? + OIDC::AccessPolicy::Statement::Condition.new( + operator: "string_equals", + claim: "environment", + value: environment + ) + end + + def repository_owner_id_condition + OIDC::AccessPolicy::Statement::Condition.new( + operator: "string_equals", + claim: "repository_owner_id", + value: repository_owner_id + ) + end + + def audience_condition + OIDC::AccessPolicy::Statement::Condition.new( + operator: "string_equals", + claim: "aud", + value: Gemcutter::HOST + ) + end + + def job_workflow_ref_condition(ref) + OIDC::AccessPolicy::Statement::Condition.new( + operator: "string_equals", + claim: "job_workflow_ref", + value: "#{repository}/#{workflow_slug}@#{ref}" + ) + end + + def to_access_policy(jwt) + common_conditions = [repository_condition, environment_condition, repository_owner_id_condition, audience_condition].compact + refs = [jwt.fetch(:ref), jwt.fetch(:sha)].compact_blank + raise OIDC::AccessPolicy::AccessError, "ref and sha are both missing" if refs.empty? + OIDC::AccessPolicy.new( + statements: refs.map do |ref| + OIDC::AccessPolicy::Statement.new( + effect: "allow", + principal: OIDC::AccessPolicy::Statement::Principal.new( + oidc: OIDC::Provider::GITHUB_ACTIONS_ISSUER + ), + conditions: common_conditions + [job_workflow_ref_condition(ref)] + ) + end + ) + end + + def name + name = "#{self.class.publisher_name} #{repository_owner}/#{repository_name} @ #{workflow_slug}" + name << " (#{environment})" if environment? + name + end + + def repository = "#{repository_owner}/#{repository_name}" + + def workflow_slug = ".github/workflows/#{workflow_filename}" + + def owns_gem?(rubygem) = rubygem_trusted_publishers.exists?(rubygem: rubygem) + + def ld_context + LaunchDarkly::LDContext.create( + key: "#{model_name.singular}-key-#{id}", + kind: "trusted_publisher", + name: name + ) + end + + private + + def find_github_repository_owner_id + return if repository_owner.blank? + return if repository_owner_id.present? + + self.repository_owner_id = + begin + Octokit::Client.new.user(repository_owner).id + rescue Octokit::NotFound + nil + end + end + + def unique_publisher + return unless self.class.exists?( + repository_owner: repository_owner, + repository_name: repository_name, + repository_owner_id: repository_owner_id, + workflow_filename: workflow_filename, + environment: environment + ) + + errors.add(:base, "publisher already exists") + end + + def workflow_filename_format + return if workflow_filename.blank? + + errors.add(:workflow_filename, "must end with .yml or .yaml") unless /\.ya?ml\z/.match?(workflow_filename) + errors.add(:workflow_filename, "must be a filename only, without directories") if workflow_filename.include?("/") + end +end diff --git a/app/models/pusher.rb b/app/models/pusher.rb index 3f84ac74a3c..6d7922a6076 100644 --- a/app/models/pusher.rb +++ b/app/models/pusher.rb @@ -23,7 +23,7 @@ def process end def authorize - (rubygem.pushable? && api_key.user?) || owner.owns_gem?(rubygem) || notify_unauthorized + (rubygem.pushable? && (api_key.user? || find_pending_trusted_publisher)) || owner.owns_gem?(rubygem) || notify_unauthorized end def verify_gem_scope @@ -33,7 +33,7 @@ def verify_gem_scope end def verify_mfa_requirement - owner.mfa_enabled? || !(version_mfa_required? || rubygem.metadata_mfa_required?) || + (!api_key.user? || owner.mfa_enabled?) || !(version_mfa_required? || rubygem.metadata_mfa_required?) || notify("Rubygem requires owners to enable MFA. You must enable MFA before pushing new version.", 403) end @@ -169,7 +169,23 @@ def notify(message, code) def update rubygem.disown if rubygem.versions.indexed.count.zero? rubygem.update_attributes_from_gem_specification!(version, spec) - rubygem.create_ownership(owner) + + if rubygem.unowned? + case owner + when User + rubygem.create_ownership(owner) + else + pending_publisher = find_pending_trusted_publisher + return notify_unauthorized if pending_publisher.blank? + + rubygem.transaction do + logger.info { "Reifying pending publisher" } + rubygem.create_ownership(pending_publisher.user) + owner.rubygem_trusted_publishers.create!(rubygem: rubygem) + end + end + end + set_info_checksum true @@ -289,4 +305,9 @@ def serialize_spec @spec_contents = Gem.deflate(Marshal.dump(spec)) true end + + def find_pending_trusted_publisher + return unless owner.class.module_parent_name == "OIDC::TrustedPublisher" + owner.pending_trusted_publishers.unexpired.rubygem_name_is(rubygem.name).first + end end diff --git a/app/models/rubygem.rb b/app/models/rubygem.rb index efcd9d54578..69cb7ae936c 100644 --- a/app/models/rubygem.rb +++ b/app/models/rubygem.rb @@ -20,6 +20,7 @@ class Rubygem < ApplicationRecord has_many :ownership_requests, -> { opened }, dependent: :destroy, inverse_of: :rubygem has_many :audits, as: :auditable, inverse_of: :auditable has_many :link_verifications, as: :linkable, inverse_of: :linkable, dependent: :destroy + has_many :oidc_rubygem_trusted_publishers, class_name: "OIDC::RubygemTrustedPublisher", inverse_of: :rubygem, dependent: :destroy validates :name, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, @@ -315,8 +316,11 @@ def refresh_indexed! end def disown - ownerships_including_unconfirmed.each(&:delete) + ownerships_including_unconfirmed.find_each(&:delete) ownerships_including_unconfirmed.clear + + oidc_rubygem_trusted_publishers.find_each(&:delete) + oidc_rubygem_trusted_publishers.clear end def find_version_from_spec(spec) diff --git a/app/models/user.rb b/app/models/user.rb index e04e0711d0c..b0f8dc05c15 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -43,7 +43,9 @@ class User < ApplicationRecord has_many :oidc_api_key_roles, dependent: :nullify, class_name: "OIDC::ApiKeyRole", inverse_of: :user has_many :oidc_id_tokens, through: :oidc_api_key_roles, class_name: "OIDC::IdToken", inverse_of: :user, source: :id_tokens - has_many :oidc_providers, through: :oidc_api_key_roles, class_name: "OIDC::Provider", inverse_of: :users, source: :providers + has_many :oidc_providers, through: :oidc_api_key_roles, class_name: "OIDC::Provider", inverse_of: :users, source: :provider + has_many :oidc_pending_trusted_publishers, class_name: "OIDC::PendingTrustedPublisher", inverse_of: :user, dependent: :destroy + has_many :oidc_rubygem_trusted_publishers, through: :rubygems, class_name: "OIDC::RubygemTrustedPublisher" validates :email, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, format: { with: URI::MailTo::EMAIL_REGEXP }, presence: true validates :unconfirmed_email, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true diff --git a/app/policies/oidc/pending_trusted_publisher_policy.rb b/app/policies/oidc/pending_trusted_publisher_policy.rb new file mode 100644 index 00000000000..0c1b670ff8d --- /dev/null +++ b/app/policies/oidc/pending_trusted_publisher_policy.rb @@ -0,0 +1,13 @@ +class OIDC::PendingTrustedPublisherPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end + + def avo_index? = rubygems_org_admin? + def avo_show? = rubygems_org_admin? + + has_association :rubygem + has_association :trusted_publisher +end diff --git a/app/policies/oidc/rubygem_trusted_publisher_policy.rb b/app/policies/oidc/rubygem_trusted_publisher_policy.rb new file mode 100644 index 00000000000..4aced2a2386 --- /dev/null +++ b/app/policies/oidc/rubygem_trusted_publisher_policy.rb @@ -0,0 +1,13 @@ +class OIDC::RubygemTrustedPublisherPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end + + def avo_index? = rubygems_org_admin? + def avo_show? = rubygems_org_admin? + + has_association :rubygem + has_association :trusted_publisher +end diff --git a/app/policies/oidc/trusted_publisher/github_action_policy.rb b/app/policies/oidc/trusted_publisher/github_action_policy.rb new file mode 100644 index 00000000000..155fbfb8bcf --- /dev/null +++ b/app/policies/oidc/trusted_publisher/github_action_policy.rb @@ -0,0 +1,16 @@ +class OIDC::TrustedPublisher::GitHubActionPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end + + def avo_index? = rubygems_org_admin? + def avo_show? = rubygems_org_admin? + + has_association :trusted_publishers + has_association :rubygem_trusted_publishers + has_association :pending_trusted_publishers + has_association :rubygems + has_association :api_keys +end diff --git a/app/policies/rubygem_policy.rb b/app/policies/rubygem_policy.rb index 9347951cfcd..73f4fe46ecf 100644 --- a/app/policies/rubygem_policy.rb +++ b/app/policies/rubygem_policy.rb @@ -33,4 +33,7 @@ def act_on? has_association :linkset has_association :gem_download has_association :audits + has_association :link_verifications + + has_association :oidc_rubygem_trusted_publishers end diff --git a/app/views/application_view.rb b/app/views/application_view.rb index a46f657d1bd..42743b7431a 100644 --- a/app/views/application_view.rb +++ b/app/views/application_view.rb @@ -10,4 +10,8 @@ class ApplicationView < ApplicationComponent def title=(title) @_view_context.instance_variable_set :@title, title end + + def title_for_header_only=(title) + @_view_context.instance_variable_set :@title_for_header_only, title + end end diff --git a/app/views/components/application_component.rb b/app/views/components/application_component.rb index b32deb27a7f..8c29603a93c 100644 --- a/app/views/components/application_component.rb +++ b/app/views/components/application_component.rb @@ -2,7 +2,28 @@ class ApplicationComponent < Phlex::HTML include Phlex::Rails::Helpers::Routes - include ActionView::Helpers::TranslationHelper + + class TranslationHelper + include ActionView::Helpers::TranslationHelper + + def initialize(translation_path:) + @translation_path = translation_path + end + + private + + def scope_key_by_partial(key) + return key unless key&.start_with?(".") + + "#{@translation_path}#{key}" + end + end + + delegate :t, to: "self.class.translation_helper" + + def self.translation_helper + @translation_helper ||= TranslationHelper.new(translation_path: translation_path) + end def self.translation_path @translation_path ||= name&.dup.tap do |n| @@ -12,12 +33,4 @@ def self.translation_path n.downcase! end end - - private - - def scope_key_by_partial(key) - return key unless key&.start_with?(".") - - "#{self.class.translation_path}#{key}" - end end diff --git a/app/views/components/oidc/trusted_publisher/github_action/form_component.rb b/app/views/components/oidc/trusted_publisher/github_action/form_component.rb new file mode 100644 index 00000000000..a33c72406b7 --- /dev/null +++ b/app/views/components/oidc/trusted_publisher/github_action/form_component.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class OIDC::TrustedPublisher::GitHubAction::FormComponent < ApplicationComponent + extend Dry::Initializer + + option :github_action_form + + def template + github_action_form.fields_for :trusted_publisher do |trusted_publisher_form| + field trusted_publisher_form, :text_field, :repository_owner, autocomplete: :off + field trusted_publisher_form, :text_field, :repository_name, autocomplete: :off + field trusted_publisher_form, :text_field, :workflow_filename, autocomplete: :off + field trusted_publisher_form, :text_field, :environment, autocomplete: :off, optional: true + end + end + + private + + def field(form, type, name, optional: false, **opts) + form.label name, class: "form__label" do + plain form.object.class.human_attribute_name(name) + span(class: "t-text--s") { " (#{t('form.optional')})" } if optional + end + form.send type, name, class: helpers.class_names("form__input", "tw-border tw-border-red-500" => form.object.errors.include?(name)), **opts + p(class: "form__field__instructions") { t("oidc.trusted_publisher.github_actions.#{name}_help_html") } + end +end diff --git a/app/views/components/oidc/trusted_publisher/github_action/table_component.rb b/app/views/components/oidc/trusted_publisher/github_action/table_component.rb new file mode 100644 index 00000000000..0b9b5f9a990 --- /dev/null +++ b/app/views/components/oidc/trusted_publisher/github_action/table_component.rb @@ -0,0 +1,20 @@ +class OIDC::TrustedPublisher::GitHubAction::TableComponent < ApplicationComponent + extend Dry::Initializer + + option :github_action + + def template + dl(class: "tw-flex tw-flex-col sm:tw-grid sm:tw-grid-cols-2 tw-items-baseline tw-gap-4 full-width overflow-wrap") do + dt(class: "adoption__heading ") { "GitHub Repository" } + dd { code { github_action.repository } } + + dt(class: "adoption__heading ") { "Workflow Filename" } + dd { code { github_action.workflow_filename } } + + if github_action.environment? + dt(class: "adoption__heading") { "Environment" } + dd { code { github_action.environment } } + end + end + end +end diff --git a/app/views/mailer/gem_pushed.html.erb b/app/views/mailer/gem_pushed.html.erb index d42fb8cf1fc..f922d42bd8f 100644 --- a/app/views/mailer/gem_pushed.html.erb +++ b/app/views/mailer/gem_pushed.html.erb @@ -15,10 +15,17 @@

Gem: <%= link_to @version.to_title, rubygem_version_url(@version.rubygem.slug, @version.slug), target: "_blank" %>
+ <% if @pushed_by_user.is_a?(User) %> Pushed by user: <%= link_to @pushed_by_user.handle, profile_url(@pushed_by_user.display_id), target: "_blank" %> <%= mail_to(@pushed_by_user.email) if @pushed_by_user.public_email? %> + <% else %> + Pushed by trusted publisher: + + <%= link_to @pushed_by_user.name, rubygem_trusted_publishers_url(@version.rubygem.slug), target: "_blank" %> + + <% end %>
Pushed at: <%= @version.created_at.to_formatted_s(:rfc822) %>

diff --git a/app/views/mailer/gem_trusted_publisher_added.html.erb b/app/views/mailer/gem_trusted_publisher_added.html.erb new file mode 100644 index 00000000000..1d844c4782e --- /dev/null +++ b/app/views/mailer/gem_trusted_publisher_added.html.erb @@ -0,0 +1,53 @@ +<% @title = t(".title") %> +<% @sub_title = @rubygem_trusted_publisher.rubygem.name %> + + + + + + + + +
+
 
+ +
+

+ A gem you have push access to has recently added a new trusted publisher. +

+

+ Gem: <%= link_to @rubygem_trusted_publisher.rubygem.name, rubygem_url(@rubygem_trusted_publisher.rubygem.slug), target: "_blank" %> +
+ Trusted publisher: <%= link_to @rubygem_trusted_publisher.trusted_publisher.name, rubygem_trusted_publishers_url(@rubygem_trusted_publisher.rubygem.slug), target: "_blank" %> +
+ Added by: + + <%= link_to @created_by_user.display_handle, profile_url(@created_by_user.display_id), target: "_blank" %> <%= mail_to(@created_by_user.email) if @created_by_user.public_email? %> + +
+ Added at: <%= @rubygem_trusted_publisher.created_at.to_formatted_s(:rfc822) %> +

+
+

If this new trusted publisher is expected, you do not need to take further action.

+

+ Only if this change is unexpected + please take immediate steps to secure your account and gems: +

+ + <%= render "compromised_instructions" do %> +
  • Remove the trusted publisher reported in this email
  • + <% end %> + +

    + + To stop receiving these messages, update your <%= link_to("email notification settings", notifier_url) %>. + +

    +
    + +
     
    + +
     
    + +
    + diff --git a/app/views/oidc/pending_trusted_publishers/index_view.rb b/app/views/oidc/pending_trusted_publishers/index_view.rb new file mode 100644 index 00000000000..eb25c6fbfa7 --- /dev/null +++ b/app/views/oidc/pending_trusted_publishers/index_view.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +class OIDC::PendingTrustedPublishers::IndexView < ApplicationView + include Phlex::Rails::Helpers::ButtonTo + include Phlex::Rails::Helpers::ContentFor + include Phlex::Rails::Helpers::DistanceOfTimeInWordsToNow + include Phlex::Rails::Helpers::LinkTo + extend Dry::Initializer + + option :trusted_publishers + + def template + title_content + + div(class: "tw-space-y-2 t-body") do + p do + t(".description_html") + end + + p do + button_to t(".create"), new_profile_oidc_pending_trusted_publisher_path, class: "form__submit", method: :get + end + + header(class: "gems__header push--s") do + p(class: "gems__meter l-mb-0") { plain helpers.page_entries_info(trusted_publishers) } + end + + div(class: "tw-divide-y") do + trusted_publishers.each do |pending_trusted_publisher| + trusted_publisher_section(pending_trusted_publisher) + end + end + + plain helpers.paginate(trusted_publishers) + end + end + + def title_content + self.title_for_header_only = t(".title") + content_for :title do + h1(class: "t-display page__heading page__heading-small") do + plain t(".title") + end + end + end + + def trusted_publisher_section(pending_trusted_publisher) + div(class: "tw-border-solid tw-my-4 tw-space-y-2 tw-flex tw-flex-col") do + div(class: "sm:tw-flex sm:tw-flex-row tw-gap-4 tw-mt-2") do + h3(class: "!tw-mb-0") { pending_trusted_publisher.rubygem_name } + button_to(t(".delete"), profile_oidc_pending_trusted_publisher_path(pending_trusted_publisher), + method: :delete, class: "form__submit form__submit--small") + end + + div(class: "sm:tw-flex sm:tw-flex-row tw-gap-4") do + p(class: "!tw-mb-0") { pending_trusted_publisher.trusted_publisher.class.publisher_name } + p(class: "!tw-mb-0") do + t(".valid_for_html", + time_html: helpers.time_tag(pending_trusted_publisher.expires_at, +distance_of_time_in_words_to_now(pending_trusted_publisher.expires_at))) + end + end + + render pending_trusted_publisher.trusted_publisher + end + end +end diff --git a/app/views/oidc/pending_trusted_publishers/new_view.rb b/app/views/oidc/pending_trusted_publishers/new_view.rb new file mode 100644 index 00000000000..5456de9567c --- /dev/null +++ b/app/views/oidc/pending_trusted_publishers/new_view.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class OIDC::PendingTrustedPublishers::NewView < ApplicationView + include Phlex::Rails::Helpers::LinkTo + include Phlex::Rails::Helpers::SelectTag + include Phlex::Rails::Helpers::OptionsForSelect + include Phlex::Rails::Helpers::FormWith + + extend Dry::Initializer + + option :pending_trusted_publisher + + def template + self.title = t(".title") + + div(class: "t-body") do + form_with( + model: pending_trusted_publisher, + url: profile_oidc_pending_trusted_publishers_path + ) do |f| + f.label :rubygem_name, class: "form__label" + f.text_field :rubygem_name, class: "form__input", autocomplete: :off + p(class: "form__field__instructions") { t("oidc.trusted_publisher.pending.rubygem_name_help_html") } + + f.label :trusted_publisher_type, class: "form__label" + f.select :trusted_publisher_type, OIDC::TrustedPublisher.all.map { |type| + [type.publisher_name, type.polymorphic_name] + }, {}, class: "form__input form__select" + + render OIDC::TrustedPublisher::GitHubAction::FormComponent.new( + github_action_form: f + ) + f.submit class: "form__submit" + end + end + end +end diff --git a/app/views/oidc/rubygem_trusted_publishers/concerns/title.rb b/app/views/oidc/rubygem_trusted_publishers/concerns/title.rb new file mode 100644 index 00000000000..56643847482 --- /dev/null +++ b/app/views/oidc/rubygem_trusted_publishers/concerns/title.rb @@ -0,0 +1,18 @@ +module OIDC::RubygemTrustedPublishers::Concerns::Title + extend ActiveSupport::Concern + + included do + def title_content + self.title_for_header_only = t(".title") + content_for :title do + h1(class: "t-display page__heading page__heading-small") do + plain t(".title") + + i(class: "page__subheading page__subheading--block") do + t(".subtitle_owner_html", gem_html: helpers.link_to(rubygem.name, rubygem_path(rubygem.slug), class: "t-link t-underline")) + end + end + end + end + end +end diff --git a/app/views/oidc/rubygem_trusted_publishers/index_view.rb b/app/views/oidc/rubygem_trusted_publishers/index_view.rb new file mode 100644 index 00000000000..c3b4246fcf4 --- /dev/null +++ b/app/views/oidc/rubygem_trusted_publishers/index_view.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class OIDC::RubygemTrustedPublishers::IndexView < ApplicationView + include Phlex::Rails::Helpers::ButtonTo + include Phlex::Rails::Helpers::LinkTo + include Phlex::Rails::Helpers::ContentFor + include OIDC::RubygemTrustedPublishers::Concerns::Title + extend Dry::Initializer + + option :rubygem + option :trusted_publishers + + def template + title_content + + div(class: "tw-space-y-2 t-body") do + p do + t(".description_html") + end + + p do + button_to t(".create"), new_rubygem_trusted_publisher_path(rubygem.slug), class: "form__submit", method: :get + end + + header(class: "gems__header push--s") do + p(class: "gems__meter l-mb-0") { plain helpers.page_entries_info(trusted_publishers) } + end + + div(class: "tw-divide-y") do + trusted_publishers.each do |rubygem_trusted_publisher| + div(class: "tw-border-solid tw-my-4 tw-space-y-4 tw-flex tw-flex-col") do + div(class: "sm:tw-flex sm:tw-items-baseline tw-mt-4 tw-gap-2") do + h4 { rubygem_trusted_publisher.trusted_publisher.class.publisher_name } + button_to(t(".delete"), rubygem_trusted_publisher_path(rubygem.slug, rubygem_trusted_publisher), + method: :delete, class: "form__submit form__submit--small") + end + render rubygem_trusted_publisher.trusted_publisher + end + end + end + + plain helpers.paginate(trusted_publishers) + end + end +end diff --git a/app/views/oidc/rubygem_trusted_publishers/new_view.rb b/app/views/oidc/rubygem_trusted_publishers/new_view.rb new file mode 100644 index 00000000000..d664c691ca4 --- /dev/null +++ b/app/views/oidc/rubygem_trusted_publishers/new_view.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class OIDC::RubygemTrustedPublishers::NewView < ApplicationView + include Phlex::Rails::Helpers::ContentFor + include Phlex::Rails::Helpers::FormWith + include Phlex::Rails::Helpers::LinkTo + include Phlex::Rails::Helpers::OptionsForSelect + include Phlex::Rails::Helpers::SelectTag + include OIDC::RubygemTrustedPublishers::Concerns::Title + + extend Dry::Initializer + + option :rubygem_trusted_publisher + + def template + title_content + + div(class: "t-body") do + form_with( + model: rubygem_trusted_publisher, + url: rubygem_trusted_publishers_path(rubygem_trusted_publisher.rubygem.slug) + ) do |f| + f.label :trusted_publisher_type, class: "form__label" + f.select :trusted_publisher_type, OIDC::TrustedPublisher.all.map { |type| + [type.publisher_name, type.polymorphic_name] + }, {}, class: "form__input form__select" + + render OIDC::TrustedPublisher::GitHubAction::FormComponent.new( + github_action_form: f + ) + f.submit class: "form__submit" + end + end + end + + delegate :rubygem, to: :rubygem_trusted_publisher +end diff --git a/app/views/oidc/trusted_publisher/github_actions/_github_action.html.erb b/app/views/oidc/trusted_publisher/github_actions/_github_action.html.erb new file mode 100644 index 00000000000..f54667d34a5 --- /dev/null +++ b/app/views/oidc/trusted_publisher/github_actions/_github_action.html.erb @@ -0,0 +1 @@ +<%= render OIDC::TrustedPublisher::GitHubAction::TableComponent.new(github_action:) %> \ No newline at end of file diff --git a/app/views/rubygems/_aside.html.erb b/app/views/rubygems/_aside.html.erb index 80040810a4b..e4949661b11 100644 --- a/app/views/rubygems/_aside.html.erb +++ b/app/views/rubygems/_aside.html.erb @@ -67,6 +67,7 @@ <%= report_abuse_link(@rubygem) %> <%= reverse_dependencies_link(@rubygem) %> <%= ownership_link(@rubygem) if @rubygem.owned_by?(current_user) %> + <%= rubygem_trusted_publishers_link(@rubygem) if @rubygem.owned_by?(current_user) %> <%= oidc_api_key_role_links(@rubygem) if @rubygem.owned_by?(current_user) %> <%= resend_owner_confirmation_link(@rubygem) if @rubygem.unconfirmed_ownership?(current_user) %> <%= rubygem_adoptions_link(@rubygem) if @rubygem.owned_by?(current_user) || @rubygem.ownership_requestable?%> diff --git a/app/views/rubygems/_gem_members.html.erb b/app/views/rubygems/_gem_members.html.erb index 38a936d189e..90f823be220 100644 --- a/app/views/rubygems/_gem_members.html.erb +++ b/app/views/rubygems/_gem_members.html.erb @@ -32,6 +32,11 @@
    <%= link_to_user(latest_version.pusher) %>
    + <% elsif latest_version.pusher_api_key&.owner.present? %> +

    <%= t '.pushed_by' %>:

    +
    + <%= link_to_pusher(latest_version.pusher_api_key.owner) %> +
    <% end %> <% if latest_version.yanker.present? %> diff --git a/app/views/settings/edit.html.erb b/app/views/settings/edit.html.erb index 81d239543f9..066b24bd8c0 100644 --- a/app/views/settings/edit.html.erb +++ b/app/views/settings/edit.html.erb @@ -69,6 +69,13 @@

    <%= link_to t('api_keys.index.api_keys'), profile_api_keys_path %>

    +<% if @user.oidc_pending_trusted_publishers.any? %> +
    +

    <%= link_to t('oidc.pending_trusted_publishers.index.title'), profile_oidc_pending_trusted_publishers_path %>

    + Pending trusted publishers allow you to configure trusted publishing before you have pushed the first version of a gem. For more information about how to set up trusted publishing, see the trusted publishing documentation. +
    +<% end %> + <% if @user.oidc_api_key_roles.any? %>

    <%= link_to t('oidc.api_key_roles.index.api_key_roles'), profile_oidc_api_key_roles_path %>

    diff --git a/config/brakeman.ignore b/config/brakeman.ignore index ab7ee81ab1a..a09e512efd8 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -7,7 +7,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/controllers/oidc/id_tokens_controller.rb", - "line": 17, + "line": 16, "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => OIDC::IdTokens::IndexView.new(:id_tokens => current_user.oidc_id_tokens.includes(:api_key, :api_key_role, :provider).page(params[:page].to_i).strict_loading), {})", "render_path": null, @@ -30,7 +30,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/oidc/api_key_roles/show.html.erb", - "line": 20, + "line": 25, "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => current_user.oidc_api_key_roles.includes(:provider).find_by!(:token => params.require(:token)).access_policy, {})", "render_path": [ @@ -57,6 +57,29 @@ ], "note": "" }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "175ed1fa3ddae92efe03e70a1fea7df153ac9bd53edf617e0c70f420f4ac6781", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/controllers/oidc/rubygem_trusted_publishers_controller.rb", + "line": 11, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => OIDC::RubygemTrustedPublishers::IndexView.new(:rubygem => Rubygem.find_by_name((params[:rubygem_id] or params[:id])), :trusted_publishers => Rubygem.find_by_name((params[:rubygem_id] or params[:id])).oidc_rubygem_trusted_publishers.includes(:trusted_publisher).page(params[:page].to_i).strict_loading), {})", + "render_path": null, + "location": { + "type": "method", + "class": "OIDC::RubygemTrustedPublishersController", + "method": "index" + }, + "user_input": "params[:page]", + "confidence": "Weak", + "cwe_id": [ + 22 + ], + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -110,7 +133,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/controllers/oidc/providers_controller.rb", - "line": 13, + "line": 12, "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => OIDC::Providers::IndexView.new(:providers => OIDC::Provider.all.strict_loading.page(params[:page].to_i)), {})", "render_path": null, @@ -126,6 +149,29 @@ ], "note": "" }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "c120032e149023b5d6bb95739c27f2d47bfdb64f78b9bccb2e92c7707bd92a78", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/controllers/oidc/pending_trusted_publishers_controller.rb", + "line": 11, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => OIDC::PendingTrustedPublishers::IndexView.new(:trusted_publishers => current_user.oidc_pending_trusted_publishers.unexpired.includes(:trusted_publisher).order(:rubygem_name, :created_at).page(params[:page].to_i).strict_loading), {})", + "render_path": null, + "location": { + "type": "method", + "class": "OIDC::PendingTrustedPublishersController", + "method": "index" + }, + "user_input": "params[:page]", + "confidence": "Weak", + "cwe_id": [ + 22 + ], + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -133,7 +179,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/oidc/api_key_roles/show.html.erb", - "line": 16, + "line": 21, "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => current_user.oidc_api_key_roles.includes(:provider).find_by!(:token => params.require(:token)).api_key_permissions, {})", "render_path": [ @@ -167,7 +213,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/controllers/oidc/providers_controller.rb", - "line": 17, + "line": 16, "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => OIDC::Providers::ShowView.new(:provider => OIDC::Provider.find(params.require(:id))), {})", "render_path": null, @@ -184,6 +230,6 @@ "note": "" } ], - "updated": "2023-10-17 10:36:55 -0700", + "updated": "2023-11-24 13:29:48 -0600", "brakeman_version": "6.0.1" } diff --git a/config/initializers/datadog.rb b/config/initializers/datadog.rb index 67a755a616a..2e44a0f4b13 100644 --- a/config/initializers/datadog.rb +++ b/config/initializers/datadog.rb @@ -9,7 +9,7 @@ # Enabling datadog functionality - enabled = (Rails.env.production? || Rails.env.staging?) && ENV["DD_AGENT_HOST"].present? && !defined?(Rails::Console) + enabled = !(Rails.env.development? || Rails.env.test?) && ENV["DD_AGENT_HOST"].present? && !defined?(Rails::Console) c.runtime_metrics.enabled = enabled c.profiling.enabled = enabled c.tracing.enabled = enabled diff --git a/config/locales/de.yml b/config/locales/de.yml index 93455bda012..b2ca1fe7f70 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -57,6 +57,10 @@ de: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: unpwn: @@ -73,6 +77,14 @@ de: taken: full_name: taken: + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: activemodel: @@ -317,6 +329,8 @@ de: global_html: gem_text: gem_html: + gem_trusted_publisher_added: + title: news: show: title: @@ -580,6 +594,7 @@ de: api_key_role: name: new: + trusted_publishers: reserved: reserved_namespace: dependencies: @@ -783,6 +798,42 @@ de: title: show: title: + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: @@ -796,3 +847,5 @@ de: seconds: other: one: + form: + optional: diff --git a/config/locales/en.yml b/config/locales/en.yml index 72e1c61f935..06802c17602 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -67,6 +67,10 @@ en: api_key_role: API Key Role oidc/api_key_role: api_key_permissions: API Key Permissions + oidc/trusted_publisher/github_action: + repository_owner_id: GitHub Repository Owner ID + oidc/pending_trusted_publisher: + rubygem_name: RubyGem name errors: messages: unpwn: has previously appeared in a data breach and should not be used @@ -83,6 +87,14 @@ en: taken: "%{value} already exists" full_name: taken: "%{value} already exists" + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: "has already been configured with this trusted publisher" + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: "is already in use" models: user: User activemodel: @@ -331,6 +343,8 @@ en: global_html: This webhook was previously called when any gem was pushed. gem_text: This webhook was previously called when %{gem} was pushed. gem_html: This webhook was previously called when %{gem} was pushed. + gem_trusted_publisher_added: + title: TRUSTED PUBLISHER ADDED news: show: title: New Releases — All Gems @@ -580,6 +594,7 @@ en: api_key_role: name: "OIDC: %{name}" new: "OIDC: Create" + trusted_publishers: Trusted publishers reserved: reserved_namespace: This namespace is reserved by rubygems.org. dependencies: @@ -784,6 +799,49 @@ en: title: "OIDC ID Tokens" show: title: "OIDC ID Token" + rubygem_trusted_publishers: + index: + title: Trusted Publishers + subtitle_owner_html: "Trusted publishers for %{gem_html}" + delete: Delete + create: Create + description_html: | + Trusted publishers allow you to push gems from CI without storing any long-lived sensitive credentials. + For more information about how to set up trusted publishing, see the trusted publishing documentation. + destroy: + success: "Trusted Publisher deleted" + create: + success: "Trusted Publisher created" + new: + title: "New Trusted Publisher" + subtitle_owner_html: "Add a trusted publisher for %{gem_html}" + pending_trusted_publishers: + index: + title: Pending Trusted Publishers + valid_for_html: "Valid for %{time_html}" + delete: Delete + create: Create + description_html: | + Pending trusted publishers allow you to configure trusted publishing before you have pushed the first version of a gem. + For more information about how to set up trusted publishing, see the trusted publishing documentation. + destroy: + success: "Pending Trusted Publisher deleted" + create: + success: "Pending Trusted Publisher created" + new: + title: "New Pending Trusted Publisher" + trusted_publisher: + unsupported_type: "Unsupported trusted publisher type" + github_actions: + repository_owner_help_html: "The GitHub organization name or GitHub username that owns the repository" + repository_name_help_html: "The name of the GitHub repository that contains the publishing workflow" + workflow_filename_help_html: "The filename of the publishing workflow.
    This file should exist in the .github/workflows/ directory in the repository configured above." + environment_help_html: | + The name of the GitHub Actions environment that the above workflow uses for publishing.
    + This should be configured under the repository's settings.
    + While not required, a dedicated publishing environment is strongly encouraged, especially if your repository has maintainers with commit access who shouldn't have RubyGems.org gem push access. + pending: + rubygem_name_help_html: "The gem (on RubyGems.org) that will be created when this publisher is used" duration: minutes: other: "%{count} minutes" @@ -797,3 +855,5 @@ en: seconds: other: "%{count} seconds" one: "1 second" + form: + optional: optional diff --git a/config/locales/es.yml b/config/locales/es.yml index 0946aba85e2..fa0c414f637 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -68,10 +68,16 @@ es: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: - unpwn: ha aparecido con anterioridad en una filtración de datos y no se debería usar - blocked: "El dominio '%{domain}' ha sido bloqueado por spam. Por favor utiliza un email personal válido." + unpwn: ha aparecido con anterioridad en una filtración de datos y no se debería + usar + blocked: El dominio '%{domain}' ha sido bloqueado por spam. Por favor utiliza + un email personal válido. models: ownership: attributes: @@ -84,6 +90,14 @@ es: taken: "%{value} ya existe" full_name: taken: "%{value} ya existe" + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: Usuario activemodel: @@ -96,15 +110,16 @@ es: oidc/api_key_permissions: attributes: valid_for: - inclusion: "%{value} segundos debe estar entre 5 minutos (300 segundos) y 1 día (86.400 segundos)" + inclusion: "%{value} segundos debe estar entre 5 minutos (300 segundos) + y 1 día (86.400 segundos)" gems: - too_long: "como mucho puede incluir una gema" + too_long: como mucho puede incluir una gema api_keys: create: - success: "Nueva clave de API creada" + success: Nueva clave de API creada invalid_gem: La gema seleccionada no se puede incluir en esta clave destroy: - success: "Clave de API eliminada con éxito: %{name}" + success: 'Clave de API eliminada con éxito: %{name}' index: api_keys: Claves de API name: Nombre @@ -125,28 +140,34 @@ es: access_webhooks: Acceso a webhooks show_dashboard: Mostrar dashboard reset: Restablecer - save_key: "Ten en cuenta que no se volverá a mostrar la clave de API. Nueva clave de API:" + save_key: 'Ten en cuenta que no se volverá a mostrar la clave de API. Nueva + clave de API:' mfa: AMF new: new_api_key: Nueva clave de API multifactor_auth: Autenticación multifactor enable_mfa: Activar AMF rubygem_scope: Ámbito de aplicación - rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas y añadir/eliminar propietarios a una gema específica. + rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas + y añadir/eliminar propietarios a una gema específica. reset: - success: "Borradas todas las claves de API" + success: Borradas todas las claves de API update: - success: "Clave de API actualizada con éxito" - invalid_gem: La gema seleccionada no se puede incluir en el alcance de esta clave + success: Clave de API actualizada con éxito + invalid_gem: La gema seleccionada no se puede incluir en el alcance de esta + clave edit: edit_api_key: Editar clave de API multifactor_auth: Autenticación de múltiples factores enable_mfa: Activar AMF rubygem_scope: Ámbito de aplicación - rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas y añadir/eliminar propietarios a una gema específica. - invalid_key: No se puede editar una clave de API inválida. Por favor bórrala y crea una nueva. + rubygem_scope_info: Este alcance restringe los comandos de añadir/eliminar gemas + y añadir/eliminar propietarios a una gema específica. + invalid_key: No se puede editar una clave de API inválida. Por favor bórrala + y crea una nueva. all_gems: Todas las gemas - gem_ownership_removed: Se ha eliminado la propiedad de %{rubygem_name} tras haber sido añadida a esta clave. + gem_ownership_removed: Se ha eliminado la propiedad de %{rubygem_name} tras haber + sido añadida a esta clave. clearance_mailer: change_password: title: CAMBIAR CONTRASEÑA @@ -212,7 +233,7 @@ es: uptime: Uptime verified_by: Verificado por secured_by: Protegido por - looking_for_maintainers: "Se buscan mantenedores/as" + looking_for_maintainers: Se buscan mantenedores/as header: dashboard: Dashboard settings: Configuración @@ -225,18 +246,21 @@ es: mailer: confirm_your_email: Por favor confirma tu dirección de correo con el enlace enviado. confirmation_subject: Por favor confirma tu dirección de correo con %{host} - link_expiration_explanation_html: Por favor ten en cuenta que este enlace es válido solo durante 3 horas. Puedes solicitar un enlace actualizado usando la página de reenvío del correo de confirmación. + link_expiration_explanation_html: Por favor ten en cuenta que este enlace es válido + solo durante 3 horas. Puedes solicitar un enlace actualizado usando la página + de reenvío del correo + de confirmación. email_confirmation: title: CONFIRMACIÓN DE EMAIL subtitle: "¡Último paso!" confirmation_link: Confirma la dirección de correo - welcome_message: Bienvenidos a %{host}! Haz clic en el enlace de abajo - para verificar tu dirección de correo. + welcome_message: Bienvenidos a %{host}! Haz clic en el enlace de abajo para + verificar tu dirección de correo. email_reset: title: CAMBIO DE EMAIL subtitle: "¡Hola %{handle}!" - visit_link_instructions: Cambiaste tu dirección de correo en %{host}. Por - favor visita el siguiente enlace para reactivar tu cuenta. + visit_link_instructions: Cambiaste tu dirección de correo en %{host}. Por favor + visita el siguiente enlace para reactivar tu cuenta. deletion_complete: title: ELIMINACIÓN COMPLETADA subtitle: "¡Adiós!" @@ -247,15 +271,15 @@ es: title: ELIMINACIÓN FALLIDA subtitle: "¡Lo sentimos!" subject: Tu solicitud para eliminar tu cuenta de %{host} ha fallado - body_html: Has solicitado eliminar tu cuenta de %{host}. Lamentablemente, - no hemos podido procesar tu pedido. Por favor inténtalo de nuevo más adelante + body_html: Has solicitado eliminar tu cuenta de %{host}. Lamentablemente, no + hemos podido procesar tu pedido. Por favor inténtalo de nuevo más adelante o %{contact} si el problema persiste. notifiers_changed: subject: Cambiaste la configuración de tus notificaciones por email de %{host} title: NOTIFICACIONES POR EMAIL subtitle: "¡Hola %{handle}!" - "on": "ON" - off_html: OFF + 'on': 'ON' + off_html: "OFF" gem_pushed: subject: Gema %{gem} subida a %{host} title: GEMA SUBIDA @@ -263,7 +287,7 @@ es: subject: Gema %{gem} eliminada de %{host} title: GEMA ELIMINADA reset_api_key: - subject: "Clave de API de %{host} restablecida" + subject: Clave de API de %{host} restablecida title: CLAVE DE API RESTABLECIDA subtitle: "¡Hola %{handle}!" webauthn_credential_created: @@ -289,29 +313,44 @@ es: subject: Por favor confirma la propiedad de la gema %{gem} en %{host} title: CONFIMACIÓN DE PROPIEDAD subtitle: "¡Hola %{handle}!" - body_text: Has sido añadido/a como propietario/a de la gema %{gem} por %{authorizer}. Por favor visita el enlace siguiente para confirmarlo. - body_html: Has sido añadido/a como propietario/a de la gema %{gem} por %{authorizer}. Por favor visita el enlace siguiente para confirmarlo. - link_expiration_explanation_html: Ten en cuenta que este enlace es válido solo durante %{expiry_hours}. Puedes reenviar el correo de confirmación desde la página de la gema %{gem} una vez autenticado. + body_text: Has sido añadido/a como propietario/a de la gema %{gem} por %{authorizer}. + Por favor visita el enlace siguiente para confirmarlo. + body_html: Has sido añadido/a como propietario/a de la gema %{gem} + por %{authorizer}. Por favor visita el enlace siguiente para + confirmarlo. + link_expiration_explanation_html: Ten en cuenta que este enlace es válido solo + durante %{expiry_hours}. Puedes reenviar el correo de confirmación desde la + página de la gema %{gem} + una vez autenticado. owner_added: subject_self: Has sido añadido/a como propietario/a de la gema %{gem} - subject_others: El usuario %{owner_handle} ha sido añadido/a como propietario/a de la gema %{gem} + subject_others: El usuario %{owner_handle} ha sido añadido/a como propietario/a + de la gema %{gem} title: PROPIETARIO AÑADIDO subtitle: "¡Hola %{handle}!" - body_self_html: Has sido añadido/a como propietario/a de la gema %{gem} en %{host}. - body_others_html: El usuario %{owner_handle} ha sido añadido/a como propietario/a de la gema %{gem} por %{authorizer}. Recibes esta notificación por ser propietario de %{gem}. + body_self_html: Has sido añadido/a como propietario/a de la gema %{gem} en %{host}. + body_others_html: El usuario %{owner_handle} ha sido añadido/a como propietario/a + de la gema %{gem} + por %{authorizer}. Recibes esta notificación por ser propietario de + %{gem}. owner_removed: subject: Has sido eliminado como propietario/a de la gema %{gem} title: PROPIETARIO ELIMINADO subtitle: "¡Hola %{handle}!" - body_html: Has sido eliminado como propietario/a de la gema %{gem} en %{host} por %{remover}. + body_html: Has sido eliminado como propietario/a de la gema %{gem} + en %{host} por %{remover}. ownerhip_request_closed: title: CANDIDATURA A PROPIETARIO subtitle: "¡Hola %{handle}!" - body_html: Gracias por proponerte como propietario para %{gem}. Lamentamos informarte de que el dueño de la gema ha cerrado tu solicitud. + body_html: Gracias por proponerte como propietario para %{gem}. + Lamentamos informarte de que el dueño de la gema ha cerrado tu solicitud. ownerhip_request_approved: - body_html: ¡Enhorabuena! Tu candidatura a propietario de %{gem} ha sido aprobada. Se te ha añadido a la lista de propietarios de la gema. + body_html: "¡Enhorabuena! Tu candidatura a propietario de %{gem} + ha sido aprobada. Se te ha añadido a la lista de propietarios de la gema." new_opwnership_requests: - body_html: Hay %{count} nuevas candidaturas a propietario para %{gem}. Por favor haz click en el botón siguiente para ver todas las candidaturas. + body_html: Hay %{count} nuevas candidaturas a propietario para %{gem}. + Por favor haz click en el botón siguiente para ver todas las candidaturas. button: CANDIDATURAS A PROPIETARIO disable_notifications: Para dejar de recibir estos mensajes actualiza tus owners_page: PROPIETARIOS @@ -319,10 +358,13 @@ es: title: WEBHOOK ELIMINADO subject: Se ha borrado tu webhook en %{host} subtitle: "¡Hola %{handle}!" - body_text: Tu webhook que enviaba peticiones POST a %{url} ha sido eliminado tras %{failures} fallos. - body_html: Tu webhook que enviaba peticiones POST a %{url} ha sido eliminado tras %{failures} fallos + body_text: Tu webhook que enviaba peticiones POST a %{url} ha sido eliminado + tras %{failures} fallos. + body_html: Tu webhook que enviaba peticiones POST a %{url} + ha sido eliminado tras %{failures} fallos global_text: Este webhook se ejecutaba antes cuando se subía cualquier gema. - global_html: Este webhook se ejecutaba antes cuando se subía cualquier gema. + global_html: Este webhook se ejecutaba antes cuando se subía cualquier + gema. gem_text: Este webhook se ejecutaba antes cuando se subía %{gem}. gem_html: Este webhook se ejecutaba antes cuando se subía %{gem}. web_hook_disabled: @@ -338,9 +380,12 @@ es:

    La última que se ejecutó con éxito fue en %{last_success} y desde entonces ha fallado %{failures_since_last_success} veces.

    Puedes borrar este webhook usando el comando %{delete_command}.

    global_text: Este webhook se ejecutaba antes cuando se subía cualquier gema. - global_html: Este webhook se ejecutaba antes cuando se subía cualquier gema. + global_html: Este webhook se ejecutaba antes cuando se subía cualquier + gema. gem_text: Este webhook se ejecutaba antes cuando se subía %{gem}. gem_html: Este webhook se ejecutaba antes cuando se subía %{gem}. + gem_trusted_publisher_added: + title: news: show: title: Nuevos lanzamientos — Todas las Gemas @@ -351,11 +396,12 @@ es: pages: about: contributors_amount: "%{count} Rubystas" - downloads_amount: "millones de descargas de gemas" - checkout_code: "por favor echa un ojo al código" - mit_licensed: "MIT" + downloads_amount: millones de descargas de gemas + checkout_code: por favor echa un ojo al código + mit_licensed: MIT logo_header: "¿Buscas nuestro logo?" - logo_details: Usa el botón de descarga y obtendrás tres archivos .PNG y un .SVG del logo de RubyGems. + logo_details: Usa el botón de descarga y obtendrás tres archivos .PNG y un .SVG + del logo de RubyGems. founding_html: El proyecto comenzó en abril del 2009 por %{founder}, y desde entonces ha crecido para incluir las contribuciones de %{contributors} y %{downloads}. A partir de RubyGems 1.3.6 el sitio se renombró de Gemcutter a %{title} para @@ -398,26 +444,41 @@ es: new: submit: Restablecer contraseña title: Cambiar tu contraseña - will_email_notice: Te enviaremos el enlace para cambiar tu contraseña por correo electrónico. + will_email_notice: Te enviaremos el enlace para cambiar tu contraseña por correo + electrónico. multifactor_auths: incorrect_otp: Tu código OTP no es correcto. session_expired: Ha expirado tu sesión en la página de acceso. - require_totp_disabled: La autenticación de múltiples factores basada en OTP ya está activa. Para reconfigurarla debes primero eliminarla. - require_mfa_enabled: No se ha activado la autenticación de múltiples factores. Primero tienes que activarla. - require_totp_enabled: No tienes aplicación de autenticación activa. Debes activarla primero. - require_webauthn_enabled: No tienes ningún dispositivo de seguridad activado. Primero debes asociar un dispositivo a tu cuenta. - setup_required_html: Por la seguridad de tu cuenta y de tus gemas se te requiere activar la autenticación de múltiples factores. - Lee por favor el artículo en nuestro blog para saber más detalles. - setup_recommended: Por la seguridad de tu cuenta y de tus gemas te animamos a configurar la autenticación de múltiples factores. En el futuro será obligatorio tener AMF activada en tu cuenta. - strong_mfa_level_required_html: Por la seguridad de tu cuenta y de tus gemas es necesario que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas" o "Interfaz de usuario y API". - Lee por favor el artículo en nuestro blog para saber más detalles. - strong_mfa_level_recommended: Por la seguridad de tu cuenta y de tus gemas te recomendamos que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas" o "Interfaz de usuario y API". En el futuro será obligatorio tener AMF configurada en alguno de esos niveles. - setup_webauthn_html: 🎉 ¡Ahora soportamos dispositivos de seguridad! Aumenta la seguridad de tu cuenta configurando un nuevo dispositivo. + require_totp_disabled: La autenticación de múltiples factores basada en OTP ya + está activa. Para reconfigurarla debes primero eliminarla. + require_mfa_enabled: No se ha activado la autenticación de múltiples factores. + Primero tienes que activarla. + require_totp_enabled: No tienes aplicación de autenticación activa. Debes activarla + primero. + require_webauthn_enabled: No tienes ningún dispositivo de seguridad activado. + Primero debes asociar un dispositivo a tu cuenta. + setup_required_html: Por la seguridad de tu cuenta y de tus gemas se te requiere + activar la autenticación de múltiples factores. Lee por favor el artículo + en nuestro blog para saber más detalles. + setup_recommended: Por la seguridad de tu cuenta y de tus gemas te animamos a + configurar la autenticación de múltiples factores. En el futuro será obligatorio + tener AMF activada en tu cuenta. + strong_mfa_level_required_html: Por la seguridad de tu cuenta y de tus gemas es + necesario que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas" + o "Interfaz de usuario y API". Lee por favor el artículo + en nuestro blog para saber más detalles. + strong_mfa_level_recommended: Por la seguridad de tu cuenta y de tus gemas te + recomendamos que cambies el nivel de AMF a "Interfaz de usuario y firma de gemas" + o "Interfaz de usuario y API". En el futuro será obligatorio tener AMF configurada + en alguno de esos niveles. + setup_webauthn_html: "\U0001F389 ¡Ahora soportamos dispositivos de seguridad! + Aumenta la seguridad de tu cuenta configurando + un nuevo dispositivo." new: title: Activando autenticación de múltiples factores scan_prompt: Por favor escanea el código QR con tu aplicación de autenticación. - Si no puedes escanear el código, agrega manualmente la información siguiente a tu - aplicación. + Si no puedes escanear el código, agrega manualmente la información siguiente + a tu aplicación. otp_prompt: Escribe el código de la aplicación de autenticación para continuar. confirm: He mantenido seguros mis códigos de recuperación. enable: Activar @@ -425,8 +486,8 @@ es: key: 'Clave: %{key}' time_based: 'Basado en tiempo: Sí' create: - qrcode_expired: El código QR y la clave han vencido. Por favor intenta otra vez - registrar un nuevo dispositivo. + qrcode_expired: El código QR y la clave han vencido. Por favor intenta otra + vez registrar un nuevo dispositivo. success: Has activado con éxito la autenticación de múltiples factores. recovery: copied: "[ copiado ]" @@ -434,7 +495,10 @@ es: title: Códigos de recuperación copy: "[ copiar ]" saved: Declaro haber guardado mis códigos de recuperación. - note_html: "Por favor copia y guarda estos códigos de recuperación. Puedes usar estos códigos para acceder y restablecer tu autenticación de múltiples factores si pierdes tu dispositivo. Cada código se puede usar una sola vez." + note_html: Por favor copia y guarda + estos códigos de recuperación. Puedes usar estos códigos para acceder y restablecer + tu autenticación de múltiples factores si pierdes tu dispositivo. Cada código + se puede usar una sola vez. already_generated: Ya deberías haber guardado tus códigos de recuperación. destroy: success: Has desactivado exitosamente la autenticación de múltiples factores. @@ -442,22 +506,26 @@ es: invalid_level: Nivel de AMF inválido. success: Has actualizado exitosamente la autenticación de múltiples factores. prompt: - webauthn_credential_note: Autentícate con un dispositivo de seguridad como Touch Id, YubiKey, etc. + webauthn_credential_note: Autentícate con un dispositivo de seguridad como Touch + Id, YubiKey, etc. sign_in_with_webauthn_credential: Autenticar con dispositivo de seguridad otp_code: Código OTP otp_or_recovery: OTP o código de recuperación recovery_code: Código de recuperación - recovery_code_html: 'Puedes utilizar un código de recuperación válido si has perdido el acceso a tu dispositivo de seguridad o de autenticación de múltiples factores.' + recovery_code_html: Puedes utilizar un código de recuperación válido si has perdido el acceso + a tu dispositivo de seguridad o de autenticación de múltiples factores. security_device: Dispositivo de seguridad verify_code: Verificar código notifiers: update: - success: Has actualizado exitosamente la configuración de tus notificaciones por correo. + success: Has actualizado exitosamente la configuración de tus notificaciones + por correo. show: - info: - Para ayudar a detectar cambios no autorizados en gemas o en propietarios, te enviamos un correo electrónico - cada vez que se sube o se elimina una versión de cualquiera de tus gemas o se le añade un nuevo propietario. - Recibiendo y leyendo esos mensajes ayudas al ecosistema de Ruby. + info: Para ayudar a detectar cambios no autorizados en gemas o en propietarios, + te enviamos un correo electrónico cada vez que se sube o se elimina una versión + de cualquiera de tus gemas o se le añade un nuevo propietario. Recibiendo + y leyendo esos mensajes ayudas al ecosistema de Ruby. 'on': Activado 'off': Desactivado recommended: recomendado @@ -467,7 +535,8 @@ es: owner_request_heading: Notificaciones de solicitud de propietarios push_heading: Notificaciones Push webauthn_verifications: - expired_or_already_used: El token del enlace utilizado ha expirado o ya ha sido utilizado. + expired_or_already_used: El token del enlace utilizado ha expirado o ya ha sido + utilizado. no_port: No se especifica el puerto. Por favor inténtalo de nuevo. pending: La autenticación del dispositivo de seguridad está pendiente todavía. prompt: @@ -483,11 +552,12 @@ es: close_browser: Por favor cierra este navegador e inténtalo de nuevo. owners: confirm: - confirmed_email: "Has sido añadido/a como propietario de la gema %{gem}" - token_expired: El token de confirmación ha expirado. Por favor intenta reenviar el token desde la página de la gema. + confirmed_email: Has sido añadido/a como propietario de la gema %{gem} + token_expired: El token de confirmación ha expirado. Por favor intenta reenviar + el token desde la página de la gema. index: add_owner: AÑADIR PROPIETARIO - name: "PROPIETARIO/A" + name: PROPIETARIO/A mfa: ESTADO DE AMF status: ESTADO confirmed_at: CONFIRMADO @@ -498,21 +568,25 @@ es: info: añadir o eliminar propietarios confirmed: Confirmado pending: Pendiente - confirm_remove: ¿Seguro que quieres eliminar a este usuario de los propietarios? + confirm_remove: "¿Seguro que quieres eliminar a este usuario de los propietarios?" resend_confirmation: resent_notice: Se ha reenviado un mensaje de confirmación a tu correo electrónico create: - success_notice: "Se ha añadido a %{handle} como propietario sin confirmar. Su acceso como propietario se activará cuando haga click en el mensaje de confirmación que se le ha enviado a su correo" + success_notice: Se ha añadido a %{handle} como propietario sin confirmar. Su + acceso como propietario se activará cuando haga click en el mensaje de confirmación + que se le ha enviado a su correo destroy: removed_notice: "%{owner_name} eliminado con éxito de la lista de propietarios" failed_notice: No se puede eliminar al único propietario de una gema - mfa_required: La gema tiene activado el requerimiento de AMF, configura AMF en tu cuenta por favor. + mfa_required: La gema tiene activado el requerimiento de AMF, configura AMF en + tu cuenta por favor. settings: edit: title: Editar configuración webauthn_credentials: Dispositivo de seguridad no_webauthn_credentials: No tienes dispositivos de seguridad - webauthn_credential_note: Un dispositivo de seguridad puede ser cualquier dispositivo que cumpla el estándar FIDO2 como las llaves biométrica y de seguridad. + webauthn_credential_note: Un dispositivo de seguridad puede ser cualquier dispositivo + que cumpla el estándar FIDO2 como las llaves biométrica y de seguridad. otp_code: Código OTP o código de recuperación api_access: confirm_reset: "¿Seguro? Este cambio no puede deshacerse." @@ -529,10 +603,16 @@ es: mfa: multifactor_auth: Autenticación de múltiples factores otp: Aplicación de autenticación - disabled_html: No has activado todavía la autenticación de múltiples factores basada en OTP. Por favor lee la guía sobre AMF para informarte sobre los distintos niveles de AMF. + disabled_html: No has activado todavía la autenticación de múltiples factores + basada en OTP. Por favor lee la guía + sobre AMF para informarte sobre los distintos niveles de AMF. go_settings: Registrar un nuevo dispositivo - level_html: Has activado la autenticación de múltiples factores. Pincha en "Actualizar" para modificar el nivel de AMF. Por favor lee la guía sobre AMF para informarte sobre los distintos niveles de AMF. - enabled_note: Has activado la autenticación de múltiples factores. Para desactivarla usa tu OTP o uno de tus códigos de recuperación activos. + level_html: Has activado la autenticación de múltiples factores. Pincha en + "Actualizar" para modificar el nivel de AMF. Por favor lee la guía + sobre AMF para informarte sobre los distintos niveles de AMF. + enabled_note: Has activado la autenticación de múltiples factores. Para desactivarla + usa tu OTP o uno de tus códigos de recuperación activos. update: Actualizar disable: Desactivar enabled: Activado @@ -545,17 +625,25 @@ es: ui_and_gem_signin: Interfaz de Usuario y firma de gemas profiles: adoptions: - no_ownership_calls: No has creado llamadas a ser propietario para ninguna de tus gemas + no_ownership_calls: No has creado llamadas a ser propietario para ninguna de + tus gemas no_ownership_requests: No has creado ninguna petición para ser propietario title: Adopción - subtitle_html: Pide nuevos responsables de mantenimiento o solicita propietarios (leer más) + subtitle_html: Pide nuevos responsables de mantenimiento o solicita propietarios + (leer + más) edit: change_avatar: Cambiar avatar - disabled_avatar_html: Se usa un avatar por defecto debido a la configuración de privacidad del email. Para usar un Gravatar personalizado activa la opción 'Mostrar correo electrónico en perfil público'. Ten en cuenta que esto hará público tu correo." - email_awaiting_confirmation: Por favor confirma tu nueva dirección de correo %{unconfirmed_email} + disabled_avatar_html: Se usa un avatar por defecto debido a la configuración + de privacidad del email. Para usar un Gravatar + personalizado activa la opción 'Mostrar correo electrónico en perfil público'. + Ten en cuenta que esto hará público tu correo." + email_awaiting_confirmation: Por favor confirma tu nueva dirección de correo + %{unconfirmed_email} enter_password: Por favor introduce tu contraseña optional_full_name: Opcional. Será mostrado en tu perfil público - optional_twitter_username: Usuario de X opcional. Será mostrado en tu perfil público + optional_twitter_username: Usuario de X opcional. Será mostrado en tu perfil + público title: Editar perfil delete: delete: Eliminar @@ -614,17 +702,21 @@ es: ownership: Propietarios oidc: api_key_role: - name: "OIDC: %{name}" - new: "OIDC: Crear" + name: 'OIDC: %{name}' + new: 'OIDC: Crear' + trusted_publishers: reserved: reserved_namespace: Este namespace está reservado por rubygems.org. dependencies: header: dependencias de %{title} gem_members: authors_header: Autores - self_no_mfa_warning_html: Considera por favor activar la autenticación de múltiples factores (AMF) para mantener tu cuenta segura. - not_using_mfa_warning_show: "* Algunos propietarios no están usando AMF. Haga click para ver la lista completa." - not_using_mfa_warning_hide: "* Los siguientes propietarios no están usando AMF. Haga click para ocultar." + self_no_mfa_warning_html: Considera por favor activar + la autenticación de múltiples factores (AMF) para mantener tu cuenta segura. + not_using_mfa_warning_show: "* Algunos propietarios no están usando AMF. Haga + click para ver la lista completa." + not_using_mfa_warning_hide: "* Los siguientes propietarios no están usando AMF. + Haga click para ocultar." owners_header: Propietarios pushed_by: Subida por using_mfa_info: "* Todos los propietarios están usando AMF." @@ -633,7 +725,7 @@ es: signature_period: Periodo de validez de la firma expired: Expirado version_navigation: - previous_version: ← Versión anterior + previous_version: "← Versión anterior" next_version: Siguiente versión → index: downloads: Descargas @@ -671,7 +763,7 @@ es: reverse_dependencies: index: title: Dependencias inversas para %{name} - subtitle: "La última versión de las siguientes gemas requieren %{name}" + subtitle: La última versión de las siguientes gemas requieren %{name} no_reverse_dependencies: Esta gema no tiene dependencias inversas search: search_reverse_dependencies_html: Buscar dependencias inversas… @@ -699,7 +791,8 @@ es: confirm: Confirmar notice: Por favor confirma tu contraseña para continuar. create: - account_blocked: Tu cuenta ha sido bloqueada por el equipo de rubygems. Para recuperar tu cuenta envía un mensaje a support@rubygems.org, por favor. + account_blocked: Tu cuenta ha sido bloqueada por el equipo de rubygems. Para + recuperar tu cuenta envía un mensaje a support@rubygems.org, por favor. stats: index: title: Estadísticas @@ -709,7 +802,8 @@ es: total_users: Usuarios totales users: create: - email_sent: Se ha enviado un correo de confirmación a tu dirección de correo electrónico. + email_sent: Se ha enviado un correo de confirmación a tu dirección de correo + electrónico. new: have_account: "¿Ya tienes una cuenta?" versions: @@ -717,16 +811,23 @@ es: not_hosted_notice: Esta gema no está alojada actualmente en RubyGems.org. title: Todas las versiones de %{name} versions_since: "%{count} versiones desde %{since}" - imported_gem_version_notice: "Esta versión de la gema se importó a RubyGems.org el %{import_date}. La fecha que se muestra fue especificada por el autor en el archivo gemspec." + imported_gem_version_notice: Esta versión de la gema se importó a RubyGems.org + el %{import_date}. La fecha que se muestra fue especificada por el autor en + el archivo gemspec. version: yanked: borrada adoptions: index: title: Adopciones - subtitle_owner_html: Solicita nuevos responsables de mantenimiento para %{gem} (leer más) - subtitle_user_html: Solicita ser propietario de %{gem} (leer más) + subtitle_owner_html: Solicita nuevos responsables de mantenimiento para %{gem} + (leer + más) + subtitle_user_html: Solicita ser propietario de %{gem} (leer + más) ownership_calls: Solicitud de propietarios - no_ownership_calls: No hay convocatorias de propietarios para %{gem}. Los dueños de la gema no están buscando nuevos responsables de mantenimiento. + no_ownership_calls: No hay convocatorias de propietarios para %{gem}. Los dueños + de la gema no están buscando nuevos responsables de mantenimiento. ownership_calls: update: success_notice: Convocatoria para propietarios de %{gem} cerrada. @@ -734,14 +835,17 @@ es: success_notice: Creada convocatoria para propietarios de %{gem}. index: title: Se buscan responsables de mantenimiento - subtitle_html: Gemas que buscan nuevos responsables de mantenimiento (leer más) + subtitle_html: Gemas que buscan nuevos responsables de mantenimiento (leer + más) share_requirements: Por favor especifica en que areas necesitas ayuda - note_for_applicants: "Nota para candidatos:" + note_for_applicants: 'Nota para candidatos:' created_by: Creado por details: Detalles apply: Proponte close: Cerrar - markup_supported_html: Etiquetas Rdoc soportadas + markup_supported_html: Etiquetas + Rdoc soportadas create_call: Crear convocatoria para propietarios ownership_requests: create: @@ -752,28 +856,33 @@ es: close: success_notice: Se han cerrado todas las candidaturas a propietario de %{gem}. ownership_requests: Candidaturas a propietario - note_for_owners: "Nota para propietarios:" + note_for_owners: 'Nota para propietarios:' your_ownership_requests: Tus candidaturas a propietario close_all: Cerrar todas approve: Aprobar gems_published: Gemas publicadas created_at: Creado el - no_ownership_requests: Las peticiones para unirse a tu proyecto aparecerán aquí. Todavia no hay candidaturas a propietario para %{gem}. + no_ownership_requests: Las peticiones para unirse a tu proyecto aparecerán aquí. + Todavia no hay candidaturas a propietario para %{gem}. create_req: Crea una candidatura a propietario - signin_to_create_html: Por favor accede para crear una candidatura a propietario. + signin_to_create_html: Por favor accede + para crear una candidatura a propietario. webauthn_credentials: callback: success: Has dado de alta con éxito un dispositivo de seguridad. recovery: continue: Continuar title: Has añadido con éxito un dispositivo de seguridad - notice_html: 'Por favor copia y guarda estos códigos de recuperación. Puedes utilizar estos códigos para acceder si pierdes tu dispositivo de seguridad. Cada código solo se puede usar una vez.' + notice_html: Por favor copia y guarda + estos códigos de recuperación. Puedes utilizar estos códigos para acceder + si pierdes tu dispositivo de seguridad. Cada código solo se puede usar una + vez. copied: "[ copiado ]" copy: "[ copiar ]" saved: Declaro haber guardado mis códigos de recuperación. webauthn_credential: - confirm_delete: "Credencial borrada" - delete_failed: "No se pudo borrar la credencial" + confirm_delete: Credencial borrada + delete_failed: No se pudo borrar la credencial delete: Borrar confirm: "¿Seguro que quieres borrar esta credencial?" saved: Dispositivo de seguridad creado con éxito @@ -787,61 +896,110 @@ es: api_key_roles: Roles de clave API OIDC new_role: Crear rol de clave API show: - api_key_role_name: "Rol de clave API %{name}" - automate_gh_actions_publishing: "Automatizar publicación de gemas con GitHub Actions" - view_provider: "Ver proveedor %{issuer}" - edit_role: "Editar rol de clave API" - delete_role: "Borrar rol de clave API" + api_key_role_name: Rol de clave API %{name} + automate_gh_actions_publishing: Automatizar publicación de gemas con GitHub + Actions + view_provider: Ver proveedor %{issuer} + edit_role: Editar rol de clave API + delete_role: Borrar rol de clave API confirm_delete: "¿Seguro que quieres borrar este rol?" - deleted_at_html: "Este rol se borró hace %{time_html} y ya no puede usarse." + deleted_at_html: Este rol se borró hace %{time_html} y ya no puede usarse. edit: - edit_role: "Editar rol de clave API" + edit_role: Editar rol de clave API git_hub_actions_workflow: - title: "OIDC GitHub Actions Workflow para subir gema" - configured_for_html: "Este rol de clave API OIDC está configurado para permitir subir %{link_html} desde GitHub Actions." - to_automate_html: "Para automatizar lanzar %{link_html} cuando se suba una nueva etiqueta, añade el siguiente workflow a tu repositorio." - not_github: "Este rol de clave API OIDC no está configurado para usar GitHub Actions." - not_push: "Este rol de clave API OIDC no está configurado para permitir subir gemas." + title: OIDC GitHub Actions Workflow para subir gema + configured_for_html: Este rol de clave API OIDC está configurado para permitir + subir %{link_html} desde GitHub Actions. + to_automate_html: Para automatizar lanzar %{link_html} cuando se suba una + nueva etiqueta, añade el siguiente workflow a tu repositorio. + not_github: Este rol de clave API OIDC no está configurado para usar GitHub + Actions. + not_push: Este rol de clave API OIDC no está configurado para permitir subir + gemas. copy_to_clipboard: Copiar al portapapeles - copied: ¡Copiado! + copied: "¡Copiado!" a_gem: una gema - instructions_html: | - Para lanzar una gema, crea la nueva versión y genera una etiqueta nueva (usando rake release:source_control_push) a GitHub. El workflow empaquetará y subirá automáticamente la gema a RubyGems.org. + instructions_html: 'Para lanzar una gema, crea la nueva versión y genera una etiqueta nueva (usando + rake release:source_control_push) a GitHub. El workflow empaquetará + y subirá automáticamente la gema a RubyGems.org. + + ' new: - title: "Nuevo rol de clave API OIDC" + title: Nuevo rol de clave API OIDC update: - success: "Rol de clave API OIDC actualizado" + success: Rol de clave API OIDC actualizado create: - success: "Rol de clave API OIDC creado" + success: Rol de clave API OIDC creado destroy: - success: "Rol de clave API OIDC borrado" + success: Rol de clave API OIDC borrado form: - add_condition: "Añadir condición" - remove_condition: "Eliminar condición!" - add_statement: "Añadir declaración" - remove_statement: "Eliminar declaración" - deleted: "El rol se ha eliminado." + add_condition: Añadir condición + remove_condition: Eliminar condición! + add_statement: Añadir declaración + remove_statement: Eliminar declaración + deleted: El rol se ha eliminado. providers: index: - title: "Proveedores de OIDC" - description_html: "Estos son los proveedores de OIDC que están configurados para RubyGems.org.
    Por favor, contacta con soporte si necesitas añadir otro proveedor OIDC." + title: Proveedores de OIDC + description_html: Estos son los proveedores de OIDC que están configurados + para RubyGems.org.
    Por favor, contacta con soporte si necesitas añadir + otro proveedor OIDC. show: - title: "Proveedor de OIDC" + title: Proveedor de OIDC id_tokens: index: - title: "Tokens OIDC ID" + title: Tokens OIDC ID show: - title: "Token OIDC ID" + title: Token OIDC ID + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: "%{count} minutos" - one: "1 minuto" + one: 1 minuto hours: other: "%{count} horas" - one: "1 hora" + one: 1 hora days: other: "%{count} días" - one: "1 día" + one: 1 día seconds: other: "%{count} segundos" - one: "1 segundo" + one: 1 segundo + form: + optional: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 118c3fe286e..06dfcaeb775 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -68,6 +68,10 @@ fr: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: unpwn: @@ -84,6 +88,14 @@ fr: taken: full_name: taken: + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: activemodel: @@ -337,6 +349,8 @@ fr: global_html: gem_text: gem_html: + gem_trusted_publisher_added: + title: news: show: title: Nouvelles Versions - Toutes les Gems @@ -617,6 +631,7 @@ fr: api_key_role: name: new: + trusted_publishers: reserved: reserved_namespace: Ce nom est réservé par rubygems.org. dependencies: @@ -833,6 +848,42 @@ fr: title: show: title: + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: @@ -846,3 +897,5 @@ fr: seconds: other: one: + form: + optional: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 84fdf96ce7d..56f812722ba 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -61,6 +61,10 @@ ja: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: unpwn: 過去にデータ侵害を受けたためお使いになれません @@ -77,6 +81,14 @@ ja: taken: "%{value}は既に存在します" full_name: taken: "%{value}は既に存在します" + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: ユーザー activemodel: @@ -331,6 +343,8 @@ ja: global_html: このwebhookは以前何らかのgemがプッシュされたときに呼ばれました。 gem_text: このwebhookは以前%{gem}がプッシュされたときに呼ばれました。 gem_html: このwebhookは以前%{gem}がプッシュされたときに呼ばれました。 + gem_trusted_publisher_added: + title: news: show: title: 新しいリリース - 全てのgem @@ -341,7 +355,7 @@ ja: pages: about: contributors_amount: "%{count}人以上のRubyist" - downloads_amount: '何億回ものgemダウンロード' + downloads_amount: 何億回ものgemダウンロード checkout_code: mit_licensed: logo_header: @@ -583,6 +597,7 @@ ja: api_key_role: name: new: + trusted_publishers: reserved: reserved_namespace: この名前空間はrubygems.orgにより予約されています。 dependencies: @@ -792,6 +807,42 @@ ja: title: show: title: + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: @@ -805,3 +856,5 @@ ja: seconds: other: one: + form: + optional: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 2bedc1881ce..9251d3293fb 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -60,6 +60,10 @@ nl: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: unpwn: @@ -76,6 +80,14 @@ nl: taken: full_name: taken: + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: activemodel: @@ -322,6 +334,8 @@ nl: global_html: gem_text: gem_html: + gem_trusted_publisher_added: + title: news: show: title: @@ -584,6 +598,7 @@ nl: api_key_role: name: new: + trusted_publishers: reserved: reserved_namespace: dependencies: @@ -787,6 +802,42 @@ nl: title: show: title: + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: @@ -800,3 +851,5 @@ nl: seconds: other: one: + form: + optional: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index b325ff3e11e..104b5feedc2 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -67,6 +67,10 @@ pt-BR: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: unpwn: já apareceu anteriormente em um vazamento de dados e não deve ser utilizada @@ -83,6 +87,14 @@ pt-BR: taken: full_name: taken: + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: Usuário activemodel: @@ -334,6 +346,8 @@ pt-BR: global_html: gem_text: gem_html: + gem_trusted_publisher_added: + title: news: show: title: Novos Releases - Todas as Gems @@ -595,6 +609,7 @@ pt-BR: api_key_role: name: new: + trusted_publishers: reserved: reserved_namespace: This namespace is reserved by rubygems.org. dependencies: @@ -810,6 +825,42 @@ pt-BR: title: show: title: + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: @@ -823,3 +874,5 @@ pt-BR: seconds: other: one: + form: + optional: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 567acc7ad11..377abc7a339 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -62,6 +62,10 @@ zh-CN: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: unpwn: 曾出现过数据泄露,不应该再使用 @@ -78,6 +82,14 @@ zh-CN: taken: full_name: taken: + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: 用户 activemodel: @@ -336,6 +348,8 @@ zh-CN: gem_text: 这个 webhook 在 %{gem} 被推送时被调用。 gem_html: 这个 webhook 在 %{gem} 被推送时被调用。 + gem_trusted_publisher_added: + title: news: show: title: 新的发布 — 所有 Gem @@ -591,6 +605,7 @@ zh-CN: api_key_role: name: new: + trusted_publishers: reserved: reserved_namespace: 该命名空间由 RubyGems.org 保留。 dependencies: @@ -800,6 +815,42 @@ zh-CN: title: show: title: + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: @@ -813,3 +864,5 @@ zh-CN: seconds: other: one: + form: + optional: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 2418a316566..2199807cae3 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -57,6 +57,10 @@ zh-TW: api_key_role: oidc/api_key_role: api_key_permissions: + oidc/trusted_publisher/github_action: + repository_owner_id: + oidc/pending_trusted_publisher: + rubygem_name: errors: messages: unpwn: @@ -73,6 +77,14 @@ zh-TW: taken: full_name: taken: + oidc/rubygem_trusted_publisher: + attributes: + rubygem: + taken: + oidc/pending_trusted_publisher: + attributes: + rubygem_name: + unavailable: models: user: activemodel: @@ -316,6 +328,8 @@ zh-TW: global_html: gem_text: gem_html: + gem_trusted_publisher_added: + title: news: show: title: 最新發佈 @@ -567,6 +581,7 @@ zh-TW: api_key_role: name: new: + trusted_publishers: reserved: reserved_namespace: dependencies: @@ -770,6 +785,42 @@ zh-TW: title: show: title: + rubygem_trusted_publishers: + index: + title: + subtitle_owner_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + subtitle_owner_html: + pending_trusted_publishers: + index: + title: + valid_for_html: + delete: + create: + description_html: + destroy: + success: + create: + success: + new: + title: + trusted_publisher: + unsupported_type: + github_actions: + repository_owner_help_html: + repository_name_help_html: + workflow_filename_help_html: + environment_help_html: + pending: + rubygem_name_help_html: duration: minutes: other: @@ -783,3 +834,5 @@ zh-TW: seconds: other: one: + form: + optional: diff --git a/config/routes.rb b/config/routes.rb index d6c2d56e9c7..24a6c63a690 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -113,6 +113,7 @@ resources :timeframe_versions, only: :index namespace :oidc do + post 'trusted_publisher/exchange_token' resources :api_key_roles, only: %i[index show], param: :token, format: 'json', defaults: { format: :json } do member do post :assume_role @@ -182,6 +183,7 @@ resources :api_key_roles, param: :token, only: %i[show], constraints: { format: :json } resources :id_tokens, only: %i[index show] resources :providers, only: %i[index show] + resources :pending_trusted_publishers, except: %i[show edit update] end end resources :stats, only: :index @@ -214,6 +216,7 @@ patch 'close_all', to: 'ownership_requests#close_all', as: :close_all, on: :collection end resources :adoptions, only: %i[index] + resources :trusted_publishers, controller: 'oidc/rubygem_trusted_publishers', only: %i[index create destroy new] end resources :ownership_calls, only: :index diff --git a/db/migrate/20231027190405_create_oidc_trusted_publisher_github_actions.rb b/db/migrate/20231027190405_create_oidc_trusted_publisher_github_actions.rb new file mode 100644 index 00000000000..61109730267 --- /dev/null +++ b/db/migrate/20231027190405_create_oidc_trusted_publisher_github_actions.rb @@ -0,0 +1,17 @@ +class CreateOIDCTrustedPublisherGitHubActions < ActiveRecord::Migration[7.0] + def change + create_table :oidc_trusted_publisher_github_actions do |t| + t.string :repository_owner, null: false + t.string :repository_name, null: false + t.string :repository_owner_id, null: false + t.string :workflow_filename, null: false + t.string :environment, null: true + + t.timestamps + end + + add_index :oidc_trusted_publisher_github_actions, + [:repository_owner, :repository_name, :repository_owner_id, :workflow_filename, :environment], + unique: true, name: "index_oidc_trusted_publisher_github_actions_claims" + end +end diff --git a/db/migrate/20231027191446_create_oidc_rubygem_trusted_publishers.rb b/db/migrate/20231027191446_create_oidc_rubygem_trusted_publishers.rb new file mode 100644 index 00000000000..1a9f2506b25 --- /dev/null +++ b/db/migrate/20231027191446_create_oidc_rubygem_trusted_publishers.rb @@ -0,0 +1,14 @@ +class CreateOIDCRubygemTrustedPublishers < ActiveRecord::Migration[7.0] + def change + create_table :oidc_rubygem_trusted_publishers do |t| + t.references :rubygem, null: false, foreign_key: true + t.references :trusted_publisher, polymorphic: true, null: false + + t.timestamps + end + + add_index :oidc_rubygem_trusted_publishers, + [:rubygem_id, :trusted_publisher_id, :trusted_publisher_type], + unique: true, name: "index_oidc_rubygem_trusted_publishers_unique" + end +end diff --git a/db/migrate/20231108205146_create_oidc_pending_trusted_publishers.rb b/db/migrate/20231108205146_create_oidc_pending_trusted_publishers.rb new file mode 100644 index 00000000000..903524f64e3 --- /dev/null +++ b/db/migrate/20231108205146_create_oidc_pending_trusted_publishers.rb @@ -0,0 +1,12 @@ +class CreateOIDCPendingTrustedPublishers < ActiveRecord::Migration[7.0] + def change + create_table :oidc_pending_trusted_publishers do |t| + t.string :rubygem_name + t.references :user, null: false, foreign_key: true + t.references :trusted_publisher, null: false, polymorphic: true + t.timestamp :expires_at, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e280f51d425..9166a238a6e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -284,6 +284,18 @@ t.index ["oidc_api_key_role_id"], name: "index_oidc_id_tokens_on_oidc_api_key_role_id" end + create_table "oidc_pending_trusted_publishers", force: :cascade do |t| + t.string "rubygem_name" + t.bigint "user_id", null: false + t.string "trusted_publisher_type", null: false + t.bigint "trusted_publisher_id", null: false + t.datetime "expires_at", precision: nil, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["trusted_publisher_type", "trusted_publisher_id"], name: "index_oidc_pending_trusted_publishers_on_trusted_publisher" + t.index ["user_id"], name: "index_oidc_pending_trusted_publishers_on_user_id" + end + create_table "oidc_providers", force: :cascade do |t| t.text "issuer" t.jsonb "configuration" @@ -293,6 +305,28 @@ t.index ["issuer"], name: "index_oidc_providers_on_issuer", unique: true end + create_table "oidc_rubygem_trusted_publishers", force: :cascade do |t| + t.bigint "rubygem_id", null: false + t.string "trusted_publisher_type", null: false + t.bigint "trusted_publisher_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["rubygem_id", "trusted_publisher_id", "trusted_publisher_type"], name: "index_oidc_rubygem_trusted_publishers_unique", unique: true + t.index ["rubygem_id"], name: "index_oidc_rubygem_trusted_publishers_on_rubygem_id" + t.index ["trusted_publisher_type", "trusted_publisher_id"], name: "index_oidc_rubygem_trusted_publishers_on_trusted_publisher" + end + + create_table "oidc_trusted_publisher_github_actions", force: :cascade do |t| + t.string "repository_owner", null: false + t.string "repository_name", null: false + t.string "repository_owner_id", null: false + t.string "workflow_filename", null: false + t.string "environment" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["repository_owner", "repository_name", "repository_owner_id", "workflow_filename", "environment"], name: "index_oidc_trusted_publisher_github_actions_claims", unique: true + end + create_table "ownership_calls", force: :cascade do |t| t.bigint "rubygem_id" t.bigint "user_id" @@ -495,6 +529,8 @@ add_foreign_key "oidc_api_key_roles", "users" add_foreign_key "oidc_id_tokens", "api_keys" add_foreign_key "oidc_id_tokens", "oidc_api_key_roles" + add_foreign_key "oidc_pending_trusted_publishers", "users" + add_foreign_key "oidc_rubygem_trusted_publishers", "rubygems" add_foreign_key "ownerships", "users", on_delete: :cascade add_foreign_key "versions", "api_keys", column: "pusher_api_key_id" add_foreign_key "webauthn_credentials", "users" diff --git a/db/seeds.rb b/db/seeds.rb index 5066bec2218..a141704fc2d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -259,6 +259,37 @@ last_verified_at: 10.years.since, ).find_or_create_by!(uri: "https://example.com/rubygem0/code") +trusted_publisher = OIDC::TrustedPublisher::GitHubAction.find_or_create_by!( + repository_owner: "example", + repository_name: "rubygem0", + repository_owner_id: "1234567890", + workflow_filename: "push_gem.yml", + environment: nil +) +trusted_publisher.rubygem_trusted_publishers.find_or_create_by!(rubygem: rubygem0).trusted_publisher.api_keys.find_or_create_by!( + name: "GitHub Actions something", + hashed_key: "securehashedkey-tp", + push_rubygem: true, +).pushed_versions.create_with(indexed: true).find_or_create_by!( + rubygem: rubygem0, number: "0.1.0", platform: "ruby", gem_platform: "ruby" +) +trusted_publisher.rubygem_trusted_publishers.find_or_create_by!(rubygem: rubygem1) + +OIDC::TrustedPublisher::GitHubAction.find_or_create_by!( + repository_owner: "example", + repository_name: "rubygem0", + repository_owner_id: "1234567890", + workflow_filename: "push_gem2.yml", + environment: "deploy" +).rubygem_trusted_publishers.find_or_create_by!(rubygem: rubygem0) + +author.oidc_pending_trusted_publishers.create_with( + expires_at: 100.years.from_now +).find_or_create_by!( + trusted_publisher: trusted_publisher, + rubygem_name: "pending-trusted-publisher-rubygem" +) + puts <<~MESSAGE # rubocop:disable Rails/Output Four users were created, you can login with following combinations: - email: #{author.email}, password: #{password} -> gem author owning few example gems diff --git a/test/factories/oidc/pending_trusted_publishers.rb b/test/factories/oidc/pending_trusted_publishers.rb new file mode 100644 index 00000000000..02ba468adcf --- /dev/null +++ b/test/factories/oidc/pending_trusted_publishers.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :oidc_pending_trusted_publisher, class: "OIDC::PendingTrustedPublisher" do + sequence(:rubygem_name) { |n| "pending-rubygem#{n}" } + user + association :trusted_publisher, factory: :oidc_trusted_publisher_github_action + expires_at { 7.days.from_now } + end +end diff --git a/test/factories/oidc/rubygem_trusted_publishers.rb b/test/factories/oidc/rubygem_trusted_publishers.rb new file mode 100644 index 00000000000..94c1f5cd751 --- /dev/null +++ b/test/factories/oidc/rubygem_trusted_publishers.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :oidc_rubygem_trusted_publisher, class: "OIDC::RubygemTrustedPublisher" do + rubygem + association :trusted_publisher, factory: :oidc_trusted_publisher_github_action + end +end diff --git a/test/factories/oidc/trusted_publisher/github_actions.rb b/test/factories/oidc/trusted_publisher/github_actions.rb new file mode 100644 index 00000000000..4fa51fdd885 --- /dev/null +++ b/test/factories/oidc/trusted_publisher/github_actions.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :oidc_trusted_publisher_github_action, class: "OIDC::TrustedPublisher::GitHubAction" do + repository_owner { "example" } + sequence(:repository_name) { |n| "rubygem#{n}" } + repository_owner_id { "123456" } + workflow_filename { "push_gem.yml" } + environment { nil } + end +end diff --git a/test/integration/api/v1/oidc/trusted_publisher_controller_test.rb b/test/integration/api/v1/oidc/trusted_publisher_controller_test.rb new file mode 100644 index 00000000000..69aa29a89da --- /dev/null +++ b/test/integration/api/v1/oidc/trusted_publisher_controller_test.rb @@ -0,0 +1,208 @@ +require "test_helper" + +class Api::V1::OIDC::TrustedPublisherControllerTest < ActionDispatch::IntegrationTest + setup do + @pkey = OpenSSL::PKey::RSA.generate(2048) + create(:oidc_provider, issuer: OIDC::Provider::GITHUB_ACTIONS_ISSUER, pkey: @pkey) + + @claims = { + "aud" => Gemcutter::HOST, + "exp" => 1_680_020_837, + "iat" => 1_680_020_537, + "iss" => "https://token.actions.githubusercontent.com", + "jti" => "79685b65-945d-450a-a3d8-a36bcf72c23d", + "nbf" => 1_680_019_937, + "ref" => "refs/heads/main", + "sha" => "04de3558bc5861874a86f8fcd67e516554101e71", + "sub" => "repo:segiddins/oidc-test:ref:refs/heads/main", + "actor" => "segiddins", + "run_id" => "4545231084", + "actor_id" => "1946610", + "base_ref" => "", + "head_ref" => "", + "ref_type" => "branch", + "workflow" => "token", + "event_name" => "push", + "repository" => "segiddins/oidc-test", + "run_number" => "4", + "run_attempt" => "1", + "workflow_ref" => "segiddins/oidc-test/.github/workflows/token.yml@refs/heads/main", + "workflow_sha" => "04de3558bc5861874a86f8fcd67e516554101e71", + "repository_id" => "620393838", + "job_workflow_ref" => "segiddins/oidc-test/.github/workflows/token.yml@refs/heads/main", + "job_workflow_sha" => "04de3558bc5861874a86f8fcd67e516554101e71", + "repository_owner" => "segiddins", + "runner_environment" => "github-hosted", + "repository_owner_id" => "1946610", + "repository_visibility" => "public" + } + + travel_to Time.zone.at(1_680_020_830) # after the JWT iat, before the exp + end + + def jwt(claims = @claims, key: @pkey) + JSON::JWT.new(claims).sign(key.to_jwk) + end + + context "POST exchange_token" do + should "return not found with no matching trusted publisher" do + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "return not found when owner has changed" do + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "123", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "return not found with an unknown issuer" do + @claims["iss"] = "https://unknown.example.com" + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "1946610", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "return not found with an unsupported issuer" do + @claims["iss"] = "https://unknown.example.com" + create(:oidc_provider, issuer: @claims["iss"], pkey: @pkey) + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "1946610", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "return bad request with an invalid JWT" do + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: "invalid" } + + assert_response :bad_request + end + + should "return bad request with invalid JSON" do + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: "a.a.a" } + + assert_response :bad_request + end + + should "return not found when time is before nbf" do + @claims["nbf"] += 1_000_000 + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "1946610", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "return not found when time is after exp" do + @claims["exp"] -= 1_000_000 + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "1946610", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "return not found when signature validation fails" do + @claims["exp"] -= 1_000_000 + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "1946610", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt(key: OpenSSL::PKey::RSA.generate(2048)).to_s } + + assert_response :not_found + end + + should "return not found when workflow is from a different ref" do + @claims["job_workflow_ref"] = "segiddins/oidc-test/.github/workflows/token.yml@refs/heads/other" + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "1946610", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "return not found when audience is wrong" do + @claims["aud"] = "other.com" + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "123", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :not_found + end + + should "succeed with matching trusted publisher" do + trusted_publisher = build(:oidc_trusted_publisher_github_action, + repository_name: "oidc-test", + repository_owner_id: "1946610", + workflow_filename: "token.yml") + trusted_publisher.repository_owner = "segiddins" + trusted_publisher.save! + post api_v1_oidc_trusted_publisher_exchange_token_path, + params: { jwt: jwt.to_s } + + assert_response :success + + resp = response.parsed_body + + assert_match(/^rubygems_/, resp["rubygems_api_key"]) + assert_equal({ + "rubygems_api_key" => resp["rubygems_api_key"], + "name" => "GitHub Actions segiddins/oidc-test @ .github/workflows/token.yml 2023-03-28T16:22:17Z", + "scopes" => ["push_rubygem"], + "expires_at" => 15.minutes.from_now + }, resp) + + api_key = trusted_publisher.api_keys.sole + + assert_equal api_key.owner, trusted_publisher + end + end +end diff --git a/test/integration/api/v1/owner_test.rb b/test/integration/api/v1/owner_test.rb index 550fbb613a4..ef95c7e8574 100644 --- a/test/integration/api/v1/owner_test.rb +++ b/test/integration/api/v1/owner_test.rb @@ -9,6 +9,10 @@ class Api::V1::OwnerTest < ActionDispatch::IntegrationTest @other_user = create(:api_key, key: @other_user_api_key, add_owner: true, remove_owner: true).user post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD }) + @trusted_publisher_api_key = "12325" + @trusted_publisher = create(:oidc_trusted_publisher_github_action) + create(:api_key, key: @trusted_publisher_api_key, owner: @trusted_publisher) + @rubygem = create(:rubygem, number: "1.0.0") create(:ownership, user: @user, rubygem: @rubygem) end @@ -66,5 +70,17 @@ class Api::V1::OwnerTest < ActionDispatch::IntegrationTest headers: { "HTTP_AUTHORIZATION" => @other_user_api_key } assert_response :unauthorized + + post api_v1_rubygem_owners_path(@rubygem.slug), + params: { email: @other_user.email }, + headers: { "HTTP_AUTHORIZATION" => @trusted_publisher_api_key } + + assert_response :forbidden + + delete api_v1_rubygem_owners_path(@rubygem.slug), + params: { email: @other_user.email }, + headers: { "HTTP_AUTHORIZATION" => @trusted_publisher_api_key } + + assert_response :forbidden end end diff --git a/test/integration/avo/oidc_pending_trusted_publishers_controller_test.rb b/test/integration/avo/oidc_pending_trusted_publishers_controller_test.rb new file mode 100644 index 00000000000..46193b3da45 --- /dev/null +++ b/test/integration/avo/oidc_pending_trusted_publishers_controller_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class Avo::OIDCPendingTrustedPublishersControllerTest < ActionDispatch::IntegrationTest + include AdminHelpers + + test "getting pending trusted publishers as admin" do + admin_sign_in_as create(:admin_github_user, :is_admin) + + get avo.resources_oidc_pending_trusted_publishers_path + + assert_response :success + + oidc_pending_trusted_publisher = create(:oidc_pending_trusted_publisher) + + get avo.resources_oidc_pending_trusted_publishers_path + + assert_response :success + page.assert_text oidc_pending_trusted_publisher.rubygem_name + page.assert_text oidc_pending_trusted_publisher.trusted_publisher.name + + get avo.resources_oidc_pending_trusted_publisher_path(oidc_pending_trusted_publisher) + + assert_response :success + page.assert_text oidc_pending_trusted_publisher.rubygem_name + page.assert_text oidc_pending_trusted_publisher.trusted_publisher.name + end +end diff --git a/test/integration/avo/oidc_rubygem_trusted_publishers_controller_test.rb b/test/integration/avo/oidc_rubygem_trusted_publishers_controller_test.rb new file mode 100644 index 00000000000..7a9a643cd03 --- /dev/null +++ b/test/integration/avo/oidc_rubygem_trusted_publishers_controller_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class Avo::OIDCRubygemTrustedPublishersControllerTest < ActionDispatch::IntegrationTest + include AdminHelpers + + test "getting rubygem trusted publishers as admin" do + admin_sign_in_as create(:admin_github_user, :is_admin) + + get avo.resources_oidc_rubygem_trusted_publishers_path + + assert_response :success + + oidc_rubygem_trusted_publisher = create(:oidc_rubygem_trusted_publisher) + + get avo.resources_oidc_rubygem_trusted_publishers_path + + assert_response :success + page.assert_text oidc_rubygem_trusted_publisher.rubygem.name + page.assert_text oidc_rubygem_trusted_publisher.trusted_publisher.name + + get avo.resources_oidc_rubygem_trusted_publisher_path(oidc_rubygem_trusted_publisher) + + assert_response :success + page.assert_text oidc_rubygem_trusted_publisher.rubygem.name + page.assert_text oidc_rubygem_trusted_publisher.trusted_publisher.name + end +end diff --git a/test/integration/avo/oidc_trusted_publisher_github_actions_controller_test.rb b/test/integration/avo/oidc_trusted_publisher_github_actions_controller_test.rb new file mode 100644 index 00000000000..981aa0c9782 --- /dev/null +++ b/test/integration/avo/oidc_trusted_publisher_github_actions_controller_test.rb @@ -0,0 +1,25 @@ +require "test_helper" + +class Avo::OIDCTrustedPublisherGitHubActionsControllerTest < ActionDispatch::IntegrationTest + include AdminHelpers + + test "getting github actions trusted publishers as admin" do + admin_sign_in_as create(:admin_github_user, :is_admin) + + get avo.resources_oidc_trusted_publisher_github_actions_path + + assert_response :success + + oidc_trusted_publisher_github_action = create(:oidc_trusted_publisher_github_action) + + get avo.resources_oidc_trusted_publisher_github_actions_path + + assert_response :success + page.assert_text oidc_trusted_publisher_github_action.repository_owner + + get avo.resources_oidc_trusted_publisher_github_action_path(oidc_trusted_publisher_github_action) + + assert_response :success + page.assert_text oidc_trusted_publisher_github_action.repository_owner + end +end diff --git a/test/integration/oidc/pending_trusted_publishers_controller_test.rb b/test/integration/oidc/pending_trusted_publishers_controller_test.rb new file mode 100644 index 00000000000..0f11ef9e26d --- /dev/null +++ b/test/integration/oidc/pending_trusted_publishers_controller_test.rb @@ -0,0 +1,152 @@ +require "test_helper" + +class OIDC::PendingTrustedPublishersControllerTest < ActionDispatch::IntegrationTest + setup do + @user = create(:user, remember_token_expires_at: Gemcutter::REMEMBER_FOR.from_now) + post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD }) + + @trusted_publisher = create(:oidc_pending_trusted_publisher, user: @user) + end + + context "with a verified session" do + setup do + post(authenticate_session_path(verify_password: { password: PasswordHelpers::SECURE_TEST_PASSWORD })) + end + + should "get index" do + get profile_oidc_pending_trusted_publishers_url + + assert_response :success + end + + should "get new" do + get new_profile_oidc_pending_trusted_publisher_url + + assert_response :success + end + + should "create trusted publisher" do + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" }) + + assert_difference("OIDC::PendingTrustedPublisher.count") do + trusted_publisher = build(:oidc_pending_trusted_publisher) + post profile_oidc_pending_trusted_publishers_url, params: { + oidc_pending_trusted_publisher: { + rubygem_name: trusted_publisher.rubygem_name, + trusted_publisher_type: trusted_publisher.trusted_publisher_type, + trusted_publisher_attributes: trusted_publisher.trusted_publisher.as_json + } + } + end + + assert_redirected_to profile_oidc_pending_trusted_publishers_url + end + + should "error creating trusted publisher with type" do + assert_no_difference("OIDC::PendingTrustedPublisher.count") do + post profile_oidc_pending_trusted_publishers_url, params: { + oidc_pending_trusted_publisher: { + rubygem_name: "rubygem1", + trusted_publisher_type: "Hash", + trusted_publisher_attributes: { repository_owner: "example" } + } + } + + assert_response :redirect + assert_equal "Unsupported trusted publisher type", flash[:error] + end + end + + should "error creating trusted publisher with unknown repository owner" do + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 404, body: { message: "Not Found" }.to_json, headers: { "Content-Type" => "application/json" }) + + assert_no_difference("OIDC::PendingTrustedPublisher.count") do + post profile_oidc_pending_trusted_publishers_url, params: { + oidc_pending_trusted_publisher: { + rubygem_name: "rubygem1", + trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name, + trusted_publisher_attributes: { repository_owner: "example" } + } + } + + assert_response :unprocessable_entity + assert_equal [ + "Trusted publisher repository name can't be blank", + "Trusted publisher workflow filename can't be blank", + "Trusted publisher repository owner can't be blank" + ].to_sentence, flash[:error] + end + end + + should "error creating invalid trusted publisher" do + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" }) + + assert_no_difference("OIDC::PendingTrustedPublisher.count") do + post profile_oidc_pending_trusted_publishers_url, params: { + oidc_pending_trusted_publisher: { + rubygem_name: "rubygem1", + trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name, + trusted_publisher_attributes: { repository_name: "rubygem1", repository_owner: "example", workflow_filename: "ci.NO" } + } + } + + assert_response :unprocessable_entity + assert_equal ["Trusted publisher workflow filename must end with .yml or .yaml"].to_sentence, flash[:error] + end + end + + should "destroy trusted publisher" do + assert_difference("OIDC::PendingTrustedPublisher.count", -1) do + delete profile_oidc_pending_trusted_publisher_url(@trusted_publisher) + end + + assert_redirected_to profile_oidc_pending_trusted_publishers_url + + assert_raises ActiveRecord::RecordNotFound do + @trusted_publisher.reload + end + end + + should "return not found on destroy for other users trusted publisher" do + trusted_publisher = create(:oidc_pending_trusted_publisher) + assert_no_difference("OIDC::PendingTrustedPublisher.count") do + delete profile_oidc_pending_trusted_publisher_url(trusted_publisher) + + assert_response :not_found + end + end + end + + context "without a verified session" do + should "redirect index to verify" do + get profile_oidc_pending_trusted_publishers_url + + assert_response :redirect + assert_redirected_to verify_session_path + end + + should "redirect new to verify" do + get new_profile_oidc_pending_trusted_publisher_url + + assert_response :redirect + assert_redirected_to verify_session_path + end + + should "redirect create to verify" do + post profile_oidc_pending_trusted_publishers_url + + assert_response :redirect + assert_redirected_to verify_session_path + end + + should "redirect destroy to verify" do + delete new_profile_oidc_pending_trusted_publisher_url + + assert_response :redirect + assert_redirected_to verify_session_path + end + end +end diff --git a/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb new file mode 100644 index 00000000000..d695739859e --- /dev/null +++ b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb @@ -0,0 +1,185 @@ +require "test_helper" + +class OIDC::RubygemTrustedPublishersControllerTest < ActionDispatch::IntegrationTest + setup do + @user = create(:user, remember_token_expires_at: Gemcutter::REMEMBER_FOR.from_now) + post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD }) + + @rubygem = create(:rubygem, owners: [@user]) + create(:version, rubygem: @rubygem) + @trusted_publisher = create(:oidc_rubygem_trusted_publisher, rubygem: @rubygem) + end + + context "with a verified session" do + setup do + post(authenticate_session_path(verify_password: { password: PasswordHelpers::SECURE_TEST_PASSWORD })) + end + + should "respond forbidden for non-owner" do + @rubygem.disown + + get rubygem_trusted_publishers_url(@rubygem.slug) + + assert_response :forbidden + end + + should "get index" do + create(:oidc_rubygem_trusted_publisher, rubygem: @rubygem, + trusted_publisher: create(:oidc_trusted_publisher_github_action, environment: "production")) + get rubygem_trusted_publishers_url(@rubygem.slug) + + assert_response :success + end + + should "get new" do + get new_rubygem_trusted_publisher_url(@rubygem.slug) + + assert_response :success + end + + should "get new for a github rubygem" do + stub_request(:get, "https://api.github.com/repos/example/rubygem1/contents/.github/workflows") + .to_return(status: 200, body: [ + { name: "ci.yml", type: "file" }, + { name: "push_rubygem.yml", type: "file" }, + { name: "push_README.md", type: "file" }, + { name: "push.yml", type: "directory" } + ].to_json, headers: { "Content-Type" => "application/json" }) + + create(:version, rubygem: @rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem1" }) + + get new_rubygem_trusted_publisher_url(@rubygem.slug) + + assert_response :success + + page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_owner]'][value='example']") + page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_name]'][value='rubygem1']") + page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][workflow_filename]'][value='push_rubygem.yml']") + end + + should "get new for a github rubygem with no found workflows" do + stub_request(:get, "https://api.github.com/repos/example/rubygem1/contents/.github/workflows") + .to_return(status: 404, body: { message: "Not Found" }.to_json, headers: { "Content-Type" => "application/json" }) + + create(:version, rubygem: @rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem1" }) + + get new_rubygem_trusted_publisher_url(@rubygem.slug) + + assert_response :success + + page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_owner]'][value='example']") + page.assert_selector("input[name='oidc_rubygem_trusted_publisher[trusted_publisher_attributes][repository_name]'][value='rubygem1']") + end + + should "create trusted publisher" do + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" }) + + assert_difference("OIDC::RubygemTrustedPublisher.count") do + trusted_publisher = build(:oidc_rubygem_trusted_publisher, rubygem: @rubygem) + post rubygem_trusted_publishers_url(@rubygem.slug), params: { + oidc_rubygem_trusted_publisher: { + trusted_publisher_type: trusted_publisher.trusted_publisher_type, + trusted_publisher_attributes: trusted_publisher.trusted_publisher.as_json + } + } + end + + assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug) + end + + should "error creating trusted publisher with type" do + assert_no_difference("OIDC::RubygemTrustedPublisher.count") do + post rubygem_trusted_publishers_url(@rubygem.slug), params: { + oidc_rubygem_trusted_publisher: { + trusted_publisher_type: "Hash", + trusted_publisher_attributes: { repository_owner: "example" } + } + } + + assert_response :redirect + assert_equal "Unsupported trusted publisher type", flash[:error] + end + end + + should "error creating trusted publisher with unknown repository owner" do + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 404, body: { message: "Not Found" }.to_json, headers: { "Content-Type" => "application/json" }) + + assert_no_difference("OIDC::RubygemTrustedPublisher.count") do + post rubygem_trusted_publishers_url(@rubygem.slug), params: { + oidc_rubygem_trusted_publisher: { + trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name, + trusted_publisher_attributes: { repository_owner: "example" } + } + } + + assert_response :unprocessable_entity + assert_equal [ + "Trusted publisher repository name can't be blank", + "Trusted publisher workflow filename can't be blank", + "Trusted publisher repository owner can't be blank" + ].to_sentence, flash[:error] + end + end + + should "error creating invalid trusted publisher" do + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" }) + + assert_no_difference("OIDC::RubygemTrustedPublisher.count") do + post rubygem_trusted_publishers_url(@rubygem.slug), params: { + oidc_rubygem_trusted_publisher: { + trusted_publisher_type: OIDC::TrustedPublisher::GitHubAction.polymorphic_name, + trusted_publisher_attributes: { repository_name: "rubygem1", repository_owner: "example", workflow_filename: "ci.NO" } + } + } + + assert_response :unprocessable_entity + assert_equal ["Trusted publisher workflow filename must end with .yml or .yaml"].to_sentence, flash[:error] + end + end + + should "destroy trusted publisher" do + assert_difference("OIDC::RubygemTrustedPublisher.count", -1) do + delete rubygem_trusted_publisher_url(@rubygem.slug, @trusted_publisher) + end + + assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug) + + assert_raises ActiveRecord::RecordNotFound do + @trusted_publisher.reload + end + end + end + + context "without a verified session" do + should "redirect index to verify" do + get rubygem_trusted_publishers_url(@rubygem.slug) + + assert_response :redirect + assert_redirected_to verify_session_path + end + + should "redirect new to verify" do + get new_rubygem_trusted_publisher_url(@rubygem.slug) + + assert_response :redirect + assert_redirected_to verify_session_path + end + + should "redirect create to verify" do + post rubygem_trusted_publishers_url(@rubygem.slug) + + assert_response :redirect + assert_redirected_to verify_session_path + end + + should "redirect destroy to verify" do + delete new_rubygem_trusted_publisher_url(@rubygem.slug) + + assert_response :redirect + assert_redirected_to verify_session_path + end + end +end diff --git a/test/integration/push_test.rb b/test/integration/push_test.rb index 28115c8f150..af733526118 100644 --- a/test/integration/push_test.rb +++ b/test/integration/push_test.rb @@ -59,6 +59,76 @@ class PushTest < ActionDispatch::IntegrationTest assert page.has_content?("2.0.0") end + test "pushing a new version of a gem with a trusted publisher" do + rubygem = create(:rubygem, name: "sandworm", number: "1.0.0") + create(:ownership, rubygem: rubygem, user: @user) + + rubygem_trusted_publisher = create(:oidc_rubygem_trusted_publisher, rubygem: rubygem) + + @key = "543321" + create(:api_key, owner: rubygem_trusted_publisher.trusted_publisher, key: @key, push_rubygem: true) + + build_gem "sandworm", "2.0.0" + + push_gem "sandworm-2.0.0.gem" + + assert_response :success + + get rubygem_path("sandworm") + + assert_response :success + page.assert_text("Pushed by") + page.assert_selector(:xpath, ".//img[@title=#{rubygem_trusted_publisher.trusted_publisher.name.inspect}]") + end + + test "pushing a new gem with a pending trusted publisher" do + pending_trusted_publisher = create(:oidc_pending_trusted_publisher, rubygem_name: "sandworm", user: @user) + + @key = "543321" + create(:api_key, owner: pending_trusted_publisher.trusted_publisher, key: @key, push_rubygem: true) + + build_gem "sandworm", "2.0.0" + + push_gem "sandworm-2.0.0.gem" + + assert_response :success + + get rubygem_path("sandworm") + + assert_response :success + page.assert_text("Pushed by") + page.assert_selector(:xpath, ".//img[@title=#{pending_trusted_publisher.trusted_publisher.name.inspect}]") + + rubygem = Rubygem.find_by!(name: "sandworm") + + assert rubygem.owned_by?(@user) + assert rubygem.oidc_rubygem_trusted_publishers.exists?(trusted_publisher: pending_trusted_publisher.trusted_publisher) + end + + test "pushing a new gem with a pending trusted publisher case insensitive" do + pending_trusted_publisher = create(:oidc_pending_trusted_publisher, rubygem_name: "SaNdWoRm", user: @user) + + @key = "543321" + create(:api_key, owner: pending_trusted_publisher.trusted_publisher, key: @key, push_rubygem: true) + + build_gem "sandworm", "2.0.0" + + push_gem "sandworm-2.0.0.gem" + + assert_response :success + + get rubygem_path("sandworm") + + assert_response :success + page.assert_text("Pushed by") + page.assert_selector(:xpath, ".//img[@title=#{pending_trusted_publisher.trusted_publisher.name.inspect}]") + + rubygem = Rubygem.find_by!(name: "sandworm") + + assert rubygem.owned_by?(@user) + assert rubygem.oidc_rubygem_trusted_publishers.exists?(trusted_publisher: pending_trusted_publisher.trusted_publisher) + end + test "pushing a gem with a known dependency" do rubygem = create(:rubygem, name: "crysknife", number: "1.0.0") diff --git a/test/mailers/previews/mailer_preview.rb b/test/mailers/previews/mailer_preview.rb index ad0dff8d879..ff7e7bf7945 100644 --- a/test/mailers/previews/mailer_preview.rb +++ b/test/mailers/previews/mailer_preview.rb @@ -33,6 +33,19 @@ def gem_pushed Mailer.gem_pushed(ownership.user, ownership.rubygem.versions.last.id, ownership.user_id) end + def gem_pushed_by_trusted_publisher + ownership = Ownership.where.not(user: nil).where(push_notifier: true).last + + Mailer.gem_pushed(OIDC::RubygemTrustedPublisher.first.trusted_publisher, ownership.rubygem.versions.last.id, ownership.user_id) + end + + def gem_trusted_publisher_added + rubygem_trusted_publisher = OIDC::RubygemTrustedPublisher.last + created_by_user = User.last + notified_user = User.first + Mailer.gem_trusted_publisher_added(rubygem_trusted_publisher, created_by_user, notified_user) + end + def mfa_notification Mailer.mfa_notification(User.last.id) end @@ -89,7 +102,7 @@ def api_key_created end def api_key_created_oidc_api_key_role - api_key = OIDC::IdToken.last.api_key + api_key = OIDC::IdToken.where.not(api_key_role: nil).last.api_key Mailer.api_key_created(api_key.id) end diff --git a/test/models/oidc/pending_trusted_publisher_test.rb b/test/models/oidc/pending_trusted_publisher_test.rb new file mode 100644 index 00000000000..c1c6a79d603 --- /dev/null +++ b/test/models/oidc/pending_trusted_publisher_test.rb @@ -0,0 +1,29 @@ +require "test_helper" + +class OIDC::PendingTrustedPublisherTest < ActiveSupport::TestCase + setup do + @pending_trusted_publisher = build(:oidc_pending_trusted_publisher) + end + subject { @pending_trusted_publisher } + + should belong_to(:trusted_publisher) + should belong_to(:user) + + should validate_presence_of(:rubygem_name) + should validate_uniqueness_of(:rubygem_name).scoped_to(:trusted_publisher_id, :trusted_publisher_type).case_insensitive + + test "validates rubygem name is available" do + publisher = build(:oidc_pending_trusted_publisher, rubygem_name: "foo") + + assert_predicate publisher, :valid? + + rubygem = create(:rubygem, name: "foo") + + assert_predicate publisher, :valid? + + create(:version, rubygem: rubygem) + + refute_predicate publisher, :valid? + assert_equal ["is already in use"], publisher.errors[:rubygem_name] + end +end diff --git a/test/models/oidc/rubygem_trusted_publisher_test.rb b/test/models/oidc/rubygem_trusted_publisher_test.rb new file mode 100644 index 00000000000..57e3f51c209 --- /dev/null +++ b/test/models/oidc/rubygem_trusted_publisher_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class OIDC::RubygemTrustedPublisherTest < ActiveSupport::TestCase + setup do + @rubygem_trusted_publisher = build(:oidc_rubygem_trusted_publisher) + end + subject { @rubygem_trusted_publisher } + + should belong_to(:rubygem) + should belong_to(:trusted_publisher) + + should validate_uniqueness_of(:rubygem).scoped_to(:trusted_publisher_id, :trusted_publisher_type) +end diff --git a/test/models/oidc/trusted_publisher/github_action_test.rb b/test/models/oidc/trusted_publisher/github_action_test.rb new file mode 100644 index 00000000000..b87e8e63e8f --- /dev/null +++ b/test/models/oidc/trusted_publisher/github_action_test.rb @@ -0,0 +1,138 @@ +require "test_helper" + +class OIDC::TrustedPublisher::GitHubActionTest < ActiveSupport::TestCase + make_my_diffs_pretty! + + should have_many(:rubygems) + should have_many(:rubygem_trusted_publishers) + should have_many(:api_keys).inverse_of(:owner) + + should validate_presence_of(:repository_owner) + should validate_presence_of(:repository_name) + should validate_presence_of(:workflow_filename) + should validate_presence_of(:repository_owner_id) + + test "validates publisher uniqueness" do + publisher = create(:oidc_trusted_publisher_github_action) + assert_raises(ActiveRecord::RecordInvalid) do + create(:oidc_trusted_publisher_github_action, repository_owner: publisher.repository_owner, + repository_name: publisher.repository_name, workflow_filename: publisher.workflow_filename, + repository_owner_id: publisher.repository_owner_id, environment: publisher.environment) + end + end + + test ".for_claims" do + bar_other_owner_id = create(:oidc_trusted_publisher_github_action, repository_name: "bar") + bar_other_owner_id.update!(repository_owner_id: "654321") + bar = create(:oidc_trusted_publisher_github_action, repository_name: "bar") + bar_test = create(:oidc_trusted_publisher_github_action, repository_name: "bar", environment: "test") + _bar_dev = create(:oidc_trusted_publisher_github_action, repository_name: "bar", environment: "dev") + create(:oidc_trusted_publisher_github_action, repository_name: "foo") + + claims = { + repository: "example/bar", + job_workflow_ref: "example/bar/.github/workflows/push_gem.yml@refs/heads/main", + ref: "refs/heads/main", + sha: "04de3558bc5861874a86f8fcd67e516554101e71", + repository_owner_id: "123456" + } + + assert_equal bar, OIDC::TrustedPublisher::GitHubAction.for_claims(claims) + assert_equal bar, OIDC::TrustedPublisher::GitHubAction.for_claims(claims.merge(environment: nil)) + assert_equal bar, OIDC::TrustedPublisher::GitHubAction.for_claims(claims.merge(environment: "other")) + assert_equal bar_test, OIDC::TrustedPublisher::GitHubAction.for_claims(claims.merge(environment: "test")) + end + + test "#name" do + publisher = create(:oidc_trusted_publisher_github_action, repository_name: "bar") + + assert_equal "GitHub Actions example/bar @ .github/workflows/push_gem.yml", publisher.name + + publisher.update!(environment: "test") + + assert_equal "GitHub Actions example/bar @ .github/workflows/push_gem.yml (test)", publisher.name + end + + test "#owns_gem?" do + rubygem1 = create(:rubygem) + rubygem2 = create(:rubygem) + + publisher = create(:oidc_trusted_publisher_github_action) + create(:oidc_rubygem_trusted_publisher, trusted_publisher: publisher, rubygem: rubygem1) + + assert publisher.owns_gem?(rubygem1) + refute publisher.owns_gem?(rubygem2) + end + + test "#to_access_policy" do + publisher = create(:oidc_trusted_publisher_github_action, repository_name: "rubygem1") + + assert_equal( + { + statements: [ + { + effect: "allow", + principal: { + oidc: "https://token.actions.githubusercontent.com" + }, + conditions: [ + { operator: "string_equals", claim: "repository", value: "example/rubygem1" }, + { operator: "string_equals", claim: "repository_owner_id", value: "123456" }, + { operator: "string_equals", claim: "aud", value: Gemcutter::HOST }, + { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@ref" } + ] + }, + { + effect: "allow", + principal: { + oidc: "https://token.actions.githubusercontent.com" + }, + conditions: [ + { operator: "string_equals", claim: "repository", value: "example/rubygem1" }, + { operator: "string_equals", claim: "repository_owner_id", value: "123456" }, + { operator: "string_equals", claim: "aud", value: Gemcutter::HOST }, + { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@sha" } + ] + } + ] + }.deep_stringify_keys, + publisher.to_access_policy({ ref: "ref", sha: "sha" }).as_json + ) + + publisher.update!(environment: "test") + + assert_equal( + { + statements: [ + { + effect: "allow", + principal: { + oidc: "https://token.actions.githubusercontent.com" + }, + conditions: [ + { operator: "string_equals", claim: "repository", value: "example/rubygem1" }, + { operator: "string_equals", claim: "environment", value: "test" }, + { operator: "string_equals", claim: "repository_owner_id", value: "123456" }, + { operator: "string_equals", claim: "aud", value: Gemcutter::HOST }, + { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@ref" } + ] + }, + { + effect: "allow", + principal: { + oidc: "https://token.actions.githubusercontent.com" + }, + conditions: [ + { operator: "string_equals", claim: "repository", value: "example/rubygem1" }, + { operator: "string_equals", claim: "environment", value: "test" }, + { operator: "string_equals", claim: "repository_owner_id", value: "123456" }, + { operator: "string_equals", claim: "aud", value: Gemcutter::HOST }, + { operator: "string_equals", claim: "job_workflow_ref", value: "example/rubygem1/.github/workflows/push_gem.yml@sha" } + ] + } + ] + }.deep_stringify_keys, + publisher.to_access_policy({ ref: "ref", sha: "sha" }).as_json + ) + end +end diff --git a/test/policies/oidc/pending_trusted_publisher_policy_test.rb b/test/policies/oidc/pending_trusted_publisher_policy_test.rb new file mode 100644 index 00000000000..74c921e3bdd --- /dev/null +++ b/test/policies/oidc/pending_trusted_publisher_policy_test.rb @@ -0,0 +1,42 @@ +require "test_helper" + +class OIDC::PendingTrustedPublisherPolicyTest < ActiveSupport::TestCase + setup do + @pending_trusted_publisher = create(:oidc_pending_trusted_publisher) + + @admin = create(:admin_github_user, :is_admin) + @non_admin = create(:admin_github_user) + end + + def test_scope + assert_equal [@pending_trusted_publisher], Pundit.policy_scope!( + @admin, + OIDC::PendingTrustedPublisher + ).to_a + end + + def test_avo_index + assert_predicate Pundit.policy!(@admin, OIDC::PendingTrustedPublisher), :avo_index? + refute_predicate Pundit.policy!(@non_admin, OIDC::PendingTrustedPublisher), :avo_index? + end + + def test_avo_show + assert_predicate Pundit.policy!(@admin, @pending_trusted_publisher), :avo_show? + refute_predicate Pundit.policy!(@non_admin, @pending_trusted_publisher), :avo_show? + end + + def test_avo_create + refute_predicate Pundit.policy!(@admin, OIDC::PendingTrustedPublisher), :avo_create? + refute_predicate Pundit.policy!(@non_admin, OIDC::PendingTrustedPublisher), :avo_create? + end + + def test_avo_update + refute_predicate Pundit.policy!(@admin, @pending_trusted_publisher), :avo_update? + refute_predicate Pundit.policy!(@non_admin, @pending_trusted_publisher), :avo_update? + end + + def test_avo_destroy + refute_predicate Pundit.policy!(@admin, @pending_trusted_publisher), :avo_destroy? + refute_predicate Pundit.policy!(@non_admin, @pending_trusted_publisher), :avo_destroy? + end +end diff --git a/test/policies/oidc/rubygem_trusted_publisher_policy_test.rb b/test/policies/oidc/rubygem_trusted_publisher_policy_test.rb new file mode 100644 index 00000000000..1ec4e6c33cc --- /dev/null +++ b/test/policies/oidc/rubygem_trusted_publisher_policy_test.rb @@ -0,0 +1,42 @@ +require "test_helper" + +class OIDC::RubygemTrustedPublisherPolicyTest < ActiveSupport::TestCase + setup do + @rubygem_trusted_publisher = create(:oidc_rubygem_trusted_publisher) + + @admin = create(:admin_github_user, :is_admin) + @non_admin = create(:admin_github_user) + end + + def test_scope + assert_equal [@rubygem_trusted_publisher], Pundit.policy_scope!( + @admin, + OIDC::RubygemTrustedPublisher + ).to_a + end + + def test_avo_index + assert_predicate Pundit.policy!(@admin, OIDC::RubygemTrustedPublisher), :avo_index? + refute_predicate Pundit.policy!(@non_admin, OIDC::RubygemTrustedPublisher), :avo_index? + end + + def test_avo_show + assert_predicate Pundit.policy!(@admin, @rubygem_trusted_publisher), :avo_show? + refute_predicate Pundit.policy!(@non_admin, @rubygem_trusted_publisher), :avo_show? + end + + def test_avo_create + refute_predicate Pundit.policy!(@admin, OIDC::RubygemTrustedPublisher), :avo_create? + refute_predicate Pundit.policy!(@non_admin, OIDC::RubygemTrustedPublisher), :avo_create? + end + + def test_avo_update + refute_predicate Pundit.policy!(@admin, @rubygem_trusted_publisher), :avo_update? + refute_predicate Pundit.policy!(@non_admin, @rubygem_trusted_publisher), :avo_update? + end + + def test_avo_destroy + refute_predicate Pundit.policy!(@admin, @rubygem_trusted_publisher), :avo_destroy? + refute_predicate Pundit.policy!(@non_admin, @rubygem_trusted_publisher), :avo_destroy? + end +end diff --git a/test/policies/oidc/trusted_publisher/github_action_policy_test.rb b/test/policies/oidc/trusted_publisher/github_action_policy_test.rb new file mode 100644 index 00000000000..4d00f78ab02 --- /dev/null +++ b/test/policies/oidc/trusted_publisher/github_action_policy_test.rb @@ -0,0 +1,42 @@ +require "test_helper" + +class OIDC::TrustedPublisher::GitHubActionPolicyTest < ActiveSupport::TestCase + setup do + @trusted_publisher_github_action = create(:oidc_trusted_publisher_github_action) + + @admin = create(:admin_github_user, :is_admin) + @non_admin = create(:admin_github_user) + end + + def test_scope + assert_equal [@trusted_publisher_github_action], Pundit.policy_scope!( + @admin, + OIDC::TrustedPublisher::GitHubAction + ).to_a + end + + def test_avo_index + assert_predicate Pundit.policy!(@admin, OIDC::TrustedPublisher::GitHubAction), :avo_index? + refute_predicate Pundit.policy!(@non_admin, OIDC::TrustedPublisher::GitHubAction), :avo_index? + end + + def test_avo_show + assert_predicate Pundit.policy!(@admin, @trusted_publisher_github_action), :avo_show? + refute_predicate Pundit.policy!(@non_admin, @trusted_publisher_github_action), :avo_show? + end + + def test_avo_create + refute_predicate Pundit.policy!(@admin, OIDC::TrustedPublisher::GitHubAction), :avo_create? + refute_predicate Pundit.policy!(@non_admin, OIDC::TrustedPublisher::GitHubAction), :avo_create? + end + + def test_avo_update + refute_predicate Pundit.policy!(@admin, @trusted_publisher_github_action), :avo_update? + refute_predicate Pundit.policy!(@non_admin, @trusted_publisher_github_action), :avo_update? + end + + def test_avo_destroy + refute_predicate Pundit.policy!(@admin, @trusted_publisher_github_action), :avo_destroy? + refute_predicate Pundit.policy!(@non_admin, @trusted_publisher_github_action), :avo_destroy? + end +end diff --git a/test/system/oidc_test.rb b/test/system/oidc_test.rb index 7c4b17cbff4..2f42f6eb0d5 100644 --- a/test/system/oidc_test.rb +++ b/test/system/oidc_test.rb @@ -214,4 +214,142 @@ def verify_session # rubocop:disable Minitest/TestMethodName } ), role.reload.as_json.slice(*expected.keys)) end + + test "creating rubygem trusted publishers" do + rubygem = create(:rubygem, name: "rubygem0") + create(:version, rubygem: rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem0" }) + + visit new_rubygem_trusted_publisher_path(rubygem.slug) + + assert_text "Please sign in to continue." + + sign_in + visit new_rubygem_trusted_publisher_path(rubygem.slug) + verify_session + + assert_text "forbidden" + + create(:ownership, rubygem: rubygem, user: @user) + + visit rubygem_trusted_publishers_path(rubygem.slug) + + page.assert_selector "h1", text: "Trusted Publishers" + page.assert_text("Trusted publishers for rubygem0") + page.assert_text "NO RUBYGEM TRUSTED PUBLISHERS FOUND" + + stub_request(:get, "https://api.github.com/repos/example/rubygem0/contents/.github/workflows") + .to_return(status: 200, body: [ + { name: "ci.yml", type: "file" }, + { name: "push_rubygem.yml", type: "file" }, + { name: "push_README.md", type: "file" }, + { name: "push.yml", type: "directory" } + ].to_json, headers: { "Content-Type" => "application/json" }) + + click_button "Create" + + page.assert_selector "h1", text: "New Trusted Publisher" + + assert_field "Repository owner", with: "example" + assert_field "Repository name", with: "rubygem0" + assert_field "Workflow filename", with: "push_rubygem.yml" + assert_field "Environment", with: "" + + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" }) + + click_button "Create Rubygem trusted publisher" + + page.assert_text "Trusted Publisher created" + page.assert_selector "h1", text: "Trusted Publishers" + page.assert_text("Trusted publishers for rubygem0") + page.assert_text "GitHub Actions\nDelete\nGitHub Repository\nexample/rubygem0\nWorkflow Filename\npush_rubygem.yml" + end + + test "deleting rubygem trusted publishers" do + rubygem = create(:rubygem, owners: [@user]) + create(:oidc_rubygem_trusted_publisher, rubygem:) + create(:version, rubygem:) + + sign_in + visit rubygem_trusted_publishers_path(rubygem.slug) + verify_session + + click_button "Delete" + + page.assert_text "Trusted Publisher deleted" + page.assert_text "NO RUBYGEM TRUSTED PUBLISHERS FOUND" + end + + test "creating pending trusted publishers" do + rubygem = create(:rubygem, name: "rubygem0") + create(:version, rubygem: rubygem, metadata: { "source_code_uri" => "https://github.com/example/rubygem0" }) + + visit profile_oidc_pending_trusted_publishers_path + + assert_text "Please sign in to continue." + + sign_in + visit profile_oidc_pending_trusted_publishers_path + verify_session + click_button "Create" + + page.assert_selector "h1", text: "New Pending Trusted Publisher" + + click_button "Create" + + page.assert_text "can't be blank" + page.assert_selector "h1", text: "New Pending Trusted Publisher" + + page.fill_in "RubyGem name", with: "rubygem0" + page.fill_in "Repository owner", with: "example" + page.fill_in "Repository name", with: "rubygem1" + page.fill_in "Workflow filename", with: "push_rubygem.yml" + page.fill_in "Environment", with: "prod" + + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 200, body: { id: "54321" }.to_json, headers: { "Content-Type" => "application/json" }) + + click_button "Create" + + page.assert_text "RubyGem name is already in use" + page.assert_selector "h1", text: "New Pending Trusted Publisher" + + assert_field "RubyGem name", with: "rubygem0" + assert_field "Repository owner", with: "example" + assert_field "Repository name", with: "rubygem1" + assert_field "Workflow filename", with: "push_rubygem.yml" + assert_field "Environment", with: "prod" + + page.fill_in "RubyGem name", with: "rubygem1" + + click_button "Create Pending trusted publisher" + + page.assert_text "Pending Trusted Publisher created" + page.assert_selector "h1", text: "Pending Trusted Publishers" + page.assert_text <<~TEXT + rubygem1 + Delete + GitHub Actions + Valid for about 12 hours + GitHub Repository + example/rubygem1 + Workflow Filename + push_rubygem.yml + Environment + prod + TEXT + end + + test "deleting pending trusted publishers" do + create(:oidc_pending_trusted_publisher, user: @user) + + sign_in + visit profile_oidc_pending_trusted_publishers_path + verify_session + + click_button "Delete" + + page.assert_text "Pending Trusted Publisher deleted" + page.assert_text "NO PENDING TRUSTED PUBLISHERS FOUND" + end end From 92757a7ed486b29995e095ce61a72df7544fcdd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:21:24 -0800 Subject: [PATCH 062/112] Bump tailwindcss-rails from 2.0.32 to 2.0.33 (#4280) Bumps [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) from 2.0.32 to 2.0.33. - [Release notes](https://github.com/rails/tailwindcss-rails/releases) - [Changelog](https://github.com/rails/tailwindcss-rails/blob/main/CHANGELOG.md) - [Commits](https://github.com/rails/tailwindcss-rails/compare/v2.0.32...v2.0.33) --- updated-dependencies: - dependency-name: tailwindcss-rails dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5e3c05cbf70..0f60547d34a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -619,7 +619,7 @@ GEM attr_required (>= 0.0.5) faraday (~> 2.0) faraday-follow_redirects - tailwindcss-rails (2.0.32) + tailwindcss-rails (2.0.33) railties (>= 6.0.0) terser (1.1.20) execjs (>= 0.3.0, < 3) From 2cf9d8822ce3b2fd21685c569765cdc9f19aa60a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:21:43 -0800 Subject: [PATCH 063/112] Bump good_job from 3.21.2 to 3.21.3 (#4279) Bumps [good_job](https://github.com/bensheldon/good_job) from 3.21.2 to 3.21.3. - [Release notes](https://github.com/bensheldon/good_job/releases) - [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md) - [Commits](https://github.com/bensheldon/good_job/compare/v3.21.2...v3.21.3) --- updated-dependencies: - dependency-name: good_job dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0f60547d34a..7175ad204d4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,7 +241,7 @@ GEM ffi (~> 1.0) globalid (1.2.1) activesupport (>= 6.1) - good_job (3.21.2) + good_job (3.21.3) activejob (>= 6.0.0) activerecord (>= 6.0.0) concurrent-ruby (>= 1.0.2) From fdbbcb591181717449ac80901e5641cc2c0f7e19 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Mon, 11 Dec 2023 12:21:12 -0800 Subject: [PATCH 064/112] Fix creating rubygem trusted publisher when gh action exists (#4282) Fix is removing repository_owner_id from search params --- .../oidc/trusted_publisher/github_action.rb | 3 ++- ...ygem_trusted_publishers_controller_test.rb | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/models/oidc/trusted_publisher/github_action.rb b/app/models/oidc/trusted_publisher/github_action.rb index 29873689123..0a3280ee41c 100644 --- a/app/models/oidc/trusted_publisher/github_action.rb +++ b/app/models/oidc/trusted_publisher/github_action.rb @@ -42,8 +42,9 @@ def self.permitted_attributes end def self.build_trusted_publisher(params) - params.delete(:environment) if params[:environment].blank? params = params.reverse_merge(repository_owner_id: nil, repository_name: nil, workflow_filename: nil, environment: nil) + params.delete(:environment) if params[:environment].blank? + params.delete(:repository_owner_id) find_or_initialize_by(params) end diff --git a/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb index d695739859e..4f8ab977973 100644 --- a/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb +++ b/test/integration/oidc/rubygem_trusted_publishers_controller_test.rb @@ -88,6 +88,25 @@ class OIDC::RubygemTrustedPublishersControllerTest < ActionDispatch::Integration assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug) end + should "create rubygem trusted publisher when trusted publisher already exists" do + stub_request(:get, "https://api.github.com/users/example") + .to_return(status: 200, body: { id: "123456" }.to_json, headers: { "Content-Type" => "application/json" }) + + github_action_trusted_publisher = create(:oidc_trusted_publisher_github_action) + + assert_difference("OIDC::RubygemTrustedPublisher.count") do + post rubygem_trusted_publishers_url(@rubygem.slug), params: { + oidc_rubygem_trusted_publisher: { + trusted_publisher_type: github_action_trusted_publisher.class.polymorphic_name, + trusted_publisher_attributes: github_action_trusted_publisher.as_json + .slice("workflow_filename", "repository_owner", "repository_name").merge("environment" => "") + } + } + end + + assert_redirected_to rubygem_trusted_publishers_url(@rubygem.slug) + end + should "error creating trusted publisher with type" do assert_no_difference("OIDC::RubygemTrustedPublisher.count") do post rubygem_trusted_publishers_url(@rubygem.slug), params: { From 64b78de6ff766772cef911399172e4eb609891f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:14:22 -0800 Subject: [PATCH 065/112] Bump ruby/setup-ruby from 1.161.0 to 1.162.0 (#4288) --- .github/workflows/lint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e1f94598ee4..4e8bbee38b5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 + - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 with: bundler-cache: true - name: Rubocop @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 + - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 with: bundler-cache: true - name: Brakeman @@ -41,7 +41,7 @@ jobs: - name: login to Github Packages run: echo "${{ github.token }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 + - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 with: bundler-cache: true - name: krane render From 11f7a16bf28951ec85bd31d9a316568091a27b4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:14:58 -0800 Subject: [PATCH 066/112] Bump good_job from 3.21.3 to 3.21.5 (#4287) --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7175ad204d4..5113d6bde0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,7 +241,7 @@ GEM ffi (~> 1.0) globalid (1.2.1) activesupport (>= 6.1) - good_job (3.21.3) + good_job (3.21.5) activejob (>= 6.0.0) activerecord (>= 6.0.0) concurrent-ruby (>= 1.0.2) From bc0a321250d21ef66446cd29901899cb53678c19 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 12 Dec 2023 16:40:45 -0800 Subject: [PATCH 067/112] Make pending publisher link visible to everyone on settings#edit --- app/views/settings/edit.html.erb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/settings/edit.html.erb b/app/views/settings/edit.html.erb index 066b24bd8c0..15f23bb0ee4 100644 --- a/app/views/settings/edit.html.erb +++ b/app/views/settings/edit.html.erb @@ -69,12 +69,10 @@

    <%= link_to t('api_keys.index.api_keys'), profile_api_keys_path %>

    -<% if @user.oidc_pending_trusted_publishers.any? %> -
    -

    <%= link_to t('oidc.pending_trusted_publishers.index.title'), profile_oidc_pending_trusted_publishers_path %>

    - Pending trusted publishers allow you to configure trusted publishing before you have pushed the first version of a gem. For more information about how to set up trusted publishing, see the trusted publishing documentation. -
    -<% end %> +
    +

    <%= link_to t('oidc.pending_trusted_publishers.index.title'), profile_oidc_pending_trusted_publishers_path %>

    + Pending trusted publishers allow you to configure trusted publishing before you have pushed the first version of a gem. For more information about how to set up trusted publishing, see the trusted publishing documentation. +
    <% if @user.oidc_api_key_roles.any? %>
    From 04066fdd3a81ecabc5820b1dc5de2b836afe0ce0 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Thu, 14 Dec 2023 00:43:24 +0800 Subject: [PATCH 068/112] Add a /profile/me action that redirects to the profile for the logged-in user (#4291) Allows linking directly to the users profile from other sites, e.g. the guides --- app/controllers/profiles_controller.rb | 4 ++++ config/routes.rb | 1 + test/functional/profiles_controller_test.rb | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 50f7929d87c..166cacdbd85 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -14,6 +14,10 @@ def show @extra_rubygems = rubygems end + def me + redirect_to profile_path(current_user.display_id) + end + def edit @user = current_user end diff --git a/config/routes.rb b/config/routes.rb index 24a6c63a690..920758bd6ca 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -157,6 +157,7 @@ end resource :dashboard, only: :show, constraints: { format: /html|atom/ } resources :profiles, only: :show + get "profile/me", to: "profiles#me", as: :my_profile resource :multifactor_auth, only: %i[new create update destroy] do get 'recovery' post 'otp_update', to: 'multifactor_auths#otp_update', as: :otp_update diff --git a/test/functional/profiles_controller_test.rb b/test/functional/profiles_controller_test.rb index ce2e5eb8548..0d88590b941 100644 --- a/test/functional/profiles_controller_test.rb +++ b/test/functional/profiles_controller_test.rb @@ -25,6 +25,13 @@ class ProfilesControllerTest < ActionController::TestCase end end + context "on GET to me" do + setup { get :me } + + should respond_with :redirect + should redirect_to("the sign in path") { sign_in_path } + end + context "on GET to show when hide email" do setup do @user.update(public_email: false) @@ -75,6 +82,15 @@ class ProfilesControllerTest < ActionController::TestCase end end + context "on GET to me" do + setup do + get :me + end + + should respond_with :redirect + should redirect_to("the user's profile page") { profile_path(@user.handle) } + end + context "on GET to delete" do setup do get :delete From 3113886f07cd49dd68d657757cad3774145eb280 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 01:07:41 +0800 Subject: [PATCH 069/112] Bump opensearch-ruby from 3.0.1 to 3.1.0 (#4292) Bumps [opensearch-ruby](https://github.com/opensearch-project/opensearch-ruby) from 3.0.1 to 3.1.0. - [Release notes](https://github.com/opensearch-project/opensearch-ruby/releases) - [Changelog](https://github.com/opensearch-project/opensearch-ruby/blob/main/CHANGELOG.md) - [Commits](https://github.com/opensearch-project/opensearch-ruby/compare/3.0.1...3.1.0) --- updated-dependencies: - dependency-name: opensearch-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c5ea7a16ea6..e4d4cdf8e11 100644 --- a/Gemfile +++ b/Gemfile @@ -37,7 +37,7 @@ gem "ruby-magic", "~> 0.6" gem "shoryuken", "~> 6.1", require: false gem "statsd-instrument", "~> 3.5" gem "validates_formatting_of", "~> 0.9" -gem "opensearch-ruby", "~> 3.0" +gem "opensearch-ruby", "~> 3.1" gem "searchkick", "~> 5.3" gem "faraday_middleware-aws-sigv4", "~> 1.0" gem "xml-simple", "~> 1.1" diff --git a/Gemfile.lock b/Gemfile.lock index 5113d6bde0b..a39a935a02e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -415,7 +415,7 @@ GEM validate_email validate_url webfinger (~> 2.0) - opensearch-ruby (3.0.1) + opensearch-ruby (3.1.0) faraday (>= 1.0, < 3) multi_json (>= 1.0) openssl (3.1.0) @@ -740,7 +740,7 @@ DEPENDENCIES omniauth-github (~> 2.0) omniauth-rails_csrf_protection (~> 1.0) openid_connect (~> 2.2) - opensearch-ruby (~> 3.0) + opensearch-ruby (~> 3.1) pg (~> 1.4) phlex-rails (~> 1.1) pry-byebug (~> 3.10) From b1bb04e43f656ffc9cf11f46f1e31712d22d41e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Thu, 14 Dec 2023 19:50:10 -0800 Subject: [PATCH 070/112] Add locales for rubygems.aside.links.funding (#4289) --- config/locales/de.yml | 1 + config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + config/locales/ja.yml | 1 + config/locales/nl.yml | 1 + config/locales/pt-BR.yml | 1 + config/locales/zh-CN.yml | 1 + config/locales/zh-TW.yml | 1 + 9 files changed, 9 insertions(+) diff --git a/config/locales/de.yml b/config/locales/de.yml index b2ca1fe7f70..9e6df4cee0f 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -578,6 +578,7 @@ de: code: Quellcode docs: Dokumentation download: Download + funding: header: Links home: Homepage mail: Mailingliste diff --git a/config/locales/en.yml b/config/locales/en.yml index 06802c17602..9e16b317646 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -578,6 +578,7 @@ en: code: Source Code docs: Documentation download: Download + funding: Funding header: Links home: Homepage mail: Mailing List diff --git a/config/locales/es.yml b/config/locales/es.yml index fa0c414f637..f4e3a95a71a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -688,6 +688,7 @@ es: code: Código fuente docs: Documentación download: Descarga + funding: Financiación header: Enlace home: Página mail: Lista de Correo diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 06dfcaeb775..eed71b4557c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -615,6 +615,7 @@ fr: code: Code Source docs: Documentation download: Télécharger + funding: header: Liens home: Page d'accueil mail: Liste de diffusion diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 56f812722ba..61cac4f0ac6 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -581,6 +581,7 @@ ja: code: ソースコード docs: ドキュメント download: ダウンロード + funding: 寄付 header: リンク home: ホームページ mail: メーリングリスト diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 9251d3293fb..b7b2207b357 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -582,6 +582,7 @@ nl: code: Broncode docs: Documentatie download: Download + funding: header: Links home: Startpagina mail: Mailing-list diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 104b5feedc2..37379ea5ff2 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -593,6 +593,7 @@ pt-BR: code: Código Fonte docs: Documentação download: Download + funding: header: Links home: Homepage mail: Lista de Emails diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 377abc7a339..f1147b99310 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -589,6 +589,7 @@ zh-CN: code: 源代码 docs: 文档 download: 下载 + funding: 募集资金 header: 链接 home: 主页 mail: 邮件列表 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 2199807cae3..0e023a49a58 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -565,6 +565,7 @@ zh-TW: code: 原始碼 docs: 文件 download: 下載 + funding: header: 相關連結 home: 首頁 mail: 郵件群組 From 5ccdd508c5c4209f853f725d1b62c1fe8b632ac6 Mon Sep 17 00:00:00 2001 From: Martin Emde Date: Fri, 15 Dec 2023 21:02:37 -0800 Subject: [PATCH 071/112] Update to bundler 2.5.1 and add CHECKSUMS (#4296) * Update to bundler 2.5.1 and add CHEKCSUMS * Remove require "rubygems/indexer" which is not in rubygems anymore * Commit checksums foor current versions * Update to RubyGems 3.5.1 --------- Co-authored-by: Samuel Giddins --- .github/workflows/docker.yml | 2 +- .github/workflows/test.yml | 2 +- Gemfile.lock | 265 +++++++++++++++++++++++++++++++- config/initializers/requires.rb | 1 - 4 files changed, 266 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9dcafb91b2f..2001fa58559 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,7 +14,7 @@ jobs: name: Docker build (and optional push) runs-on: ubuntu-22.04 env: - RUBYGEMS_VERSION: 3.4.21 + RUBYGEMS_VERSION: 3.5.1 RUBY_VERSION: 3.2.2 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a86ea77ab4..b996e5ab12e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: matrix: rubygems: - name: locked - version: "3.4.21" + version: "3.5.1" - name: latest version: latest ruby_version: ["3.2.2"] diff --git a/Gemfile.lock b/Gemfile.lock index a39a935a02e..30be27b26a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -786,5 +786,268 @@ DEPENDENCIES webmock (~> 3.18) xml-simple (~> 1.1) +CHECKSUMS + actioncable (7.0.8) sha256=1f504ddb4ab6a34f7c52e9df924441a403e9f358bace330c36dcca6358ecfb84 + actionmailbox (7.0.8) sha256=9420037b801e44aa4e36cf113f4bd6eb25c17eb1b84d9c8865e8abf8846c14e5 + actionmailer (7.0.8) sha256=22574f270ed80bcd158f16b99068fad7772173e21c4332504238dae58fdccf70 + actionpack (7.0.8) sha256=2b998c6f6540ec07ad2e16b39f9acae22c8c4fda6b377417c2cfddf8c04d61d0 + actiontext (7.0.8) sha256=f7966296cec0a48e8644b59de2bfc8b7847d43a7809dfe040015a32aecc88744 + actionview (7.0.8) sha256=a22d692b9a6422f36882425301a4043fbe078a66e94a909a60a6a216246fd776 + active_link_to (1.0.5) sha256=4830847b3d14589df1e9fc62038ceec015257fce975ec1c2a77836c461b139ba + activejob (7.0.8) sha256=cb63d6a9f9af3379b7927bcb09a453d63db66ba9ec681018a8b21c5a0f8bc1b2 + activemodel (7.0.8) sha256=95beb8a2f6d1e0c7b4e3c0f17771b3a3024a25ad8c6e9d2d357e3cf1d5479c00 + activerecord (7.0.8) sha256=f236255235ab8c15f7a7bea3b77a35377801827e24d6e536dc776080f4dd8a13 + activestorage (7.0.8) sha256=8c2cae8de321ec899c7e7c4655331714fdd57f0966215286330f5c4d95a9db34 + activesupport (7.0.8) sha256=458316bb5098211ba9436d3c64d883177f09c49d1e29aa00f970d160275f13a1 + addressable (2.8.5) sha256=63f0fbcde42edf116d6da98a9437f19dd1692152f1efa3fcc4741e443c772117 + aes_key_wrap (1.1.0) sha256=b935f4756b37375895db45669e79dfcdc0f7901e12d4e08974d5540c8e0776a5 + aggregate_assertions (0.2.0) sha256=9bc51a48323a8e7b82f47cc38d48132817247345e5a8713686c9d65b25daca9e + amazing_print (1.5.0) sha256=f9f411b37257333a0f0cc16ce6520b2217a6f0b5a9f35656e1d403cd5e0c3362 + android_key_attestation (0.3.0) sha256=467eb01a99d2bb48ef9cf24cc13712669d7056cba5a52d009554ff037560570b + ansi (1.5.0) sha256=5408253274e33d9d27d4a98c46d2998266fd51cba58a7eb9d08f50e57ed23592 + argon2 (2.1.1) sha256=0fd6b50051102026e8b776b55fe5096065d84c214f837c94cede890370d3983c + ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12 + attr_required (1.0.1) sha256=024e10393bd30901e1adf6769bd756b873a5ef7da60f86f8f11066116b5742bc + autoprefixer-rails (10.4.16.0) sha256=40c4b14d6f26f66026cd0d4631baf18d6c56aab425b36059c8abbda17f19a706 + avo (2.46.0) sha256=bec3d981a5e6f3e324da1aaf5379b8f6efc26f3eff5aa38c043fdb2aa1e002eb + awrence (1.2.1) sha256=dd1d214c12a91f449d1ef81d7ee3babc2816944e450752e7522c65521872483e + aws-eventstream (1.3.0) sha256=f1434cc03ab2248756eb02cfa45e900e59a061d7fbdc4a9fd82a5dd23d796d3f + aws-partitions (1.861.0) sha256=47dc9d492feb3669dc725ce597351e09e45511d7b2cd99fee467c26cd464026b + aws-sdk-core (3.190.0) sha256=a3455fb3fc1691dd5331282ff16cb0b2ef136a5b63ed68b77e9fda447ea7cfa6 + aws-sdk-kms (1.74.0) sha256=b2537b21d051f6112c3bd71d2b60783435d08b152c2873d4593af93f539059c7 + aws-sdk-s3 (1.141.0) sha256=cadb88497af6736e86a4a1fc8eb42333fb27ae85901686334252c50862bdd02e + aws-sdk-sqs (1.69.0) sha256=142a748b303af9a6e9e54be122ac429cfb59c528656ed477bacae9f3042659db + aws-sigv4 (1.8.0) sha256=84dd99768b91b93b63d1d8e53ee837cfd06ab402812772a7899a78f9f9117cbc + base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 + bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 + benchmark-ips (2.12.0) sha256=09dd4d5be05db42470e7e7b01be7310564073a054e35d9d9ec7840c523f3dbcb + bindata (2.4.15) sha256=e567e4278223e041caf4e623de870b2df8a93479d8f13e2b478bad45e0fbc413 + bitarray (1.2.0) sha256=7f9f31fadbd87bf51544cf13058e81cd6ec408ff40f127902cef3d6767b23f11 + bloomer (1.0.0) sha256=57a0d3a78628db9a92c6723f06c67697e420abcdb05aa757c6dfae607251d272 + bootsnap (1.17.0) sha256=6b0ea4dd68f0d424968dcd13953c3f04b13a19a8761c540d3af13507fcfa1347 + brakeman (6.1.0) sha256=0d4066936dd58f0fe757d0ff1ec0744479be9ff06c771be4b581bdf0cb8d7403 + browser (5.3.1) sha256=62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c + builder (3.2.4) sha256=99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10 + byebug (11.1.3) sha256=2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b + capybara (3.39.2) sha256=d6f0ca5f30897e64789428d4b047a0df105815a302069913578ac35d5ca99884 + cbor (0.5.9.6) sha256=434a147658dd1df24ec9e7b3297c1fd4f8a691c97d0e688b3049df8e728b2114 + cgi (0.4.0) sha256=20a62f3b05234e09e141d461ad4de057c4aea699d72a958b3a3bf00680a49d48 + chartkick (5.0.5) sha256=62f096048bc5c7cdab00a0f125042863eb7832ab70ec317002484b6e35d3d8f2 + choice (0.2.0) sha256=a19617f7dfd4921b38a85d0616446620de685a113ec6d1ecc85bdb67bf38c974 + chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe + clearance (2.6.1) sha256=20593dfbe647d063b950f27bf5deca6f2090ea04db6728a403dd973f50d7ffca + coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b + compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b + concurrent-ruby (1.2.2) sha256=3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f + cose (1.3.0) sha256=63247c66a5bc76e53926756574fe3724cc0a88707e358c90532ae2a320e98601 + crack (0.4.5) sha256=798416fb29b8c9f655d139d5559169b39c4a0a3b8f8f39b7f670eec1af9b21b3 + crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d + css_parser (1.16.0) sha256=f70fb492254418522ea77c01d57bf64452d6c7465001926c3620d0b50289b1a2 + dalli (3.2.6) sha256=879092a248bc0c8ff4b40ff233686d794422e8af4f3a5395c7bdc7432de0fd8c + dartsass-ruby (3.0.1) sha256=e6929ed3b4059236b10766dfb4218a04e0fe708ee2a8c85fb24fb6edad6e6f7c + dartsass-sprockets (3.0.0) sha256=9dd57f8b2471356cb25ed0a3069c6242433eaac80affcf89113f4693629982a0 + datadog-ci (0.5.0) sha256=34da9a0e56888568f4ab0b92249be874cf9121af54a78be5e6105e4df65b044d + date (3.3.4) sha256=971f2cb66b945bcbea4ddd9c7908c9400b31a71bc316833cb42fa584b59d3291 + ddtrace (1.18.0) sha256=92539e16d6251c77cc91ac75021ac27507ac5a8242e686614935d9884a8dc038 + dead_end (4.0.0) sha256=695c8438993bb4c5415b1618a1b6e0afcae849ef2812fb8cb3846723904307eb + debase-ruby_core_source (3.2.3) sha256=bec157b6c01a07fdcaa09c42f6c6015bc8941a85e18ec6b9cd122c263437c912 + derailed_benchmarks (2.1.2) sha256=eaadc6206ceeb5538ff8f5e04a0023d54ebdd95d04f33e8960fb95a5f189a14f + docile (1.4.0) sha256=5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3 + dogstatsd-ruby (5.6.1) sha256=615dd1328c57ab66fb48cbf83b38ce2060d8353707be0bc3deb0ae960a659982 + domain_name (0.5.20190701) sha256=000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851 + dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 + dotenv-rails (2.8.1) sha256=b05b81b7f4e51d1359e218d92279db1d84b12440f7118c9df19b5dfffb620f6c + dry-initializer (3.1.1) sha256=4d267dea367ccabe498b259c62b909b99d577d6db547d9510561999403546dec + email_validator (2.2.3) sha256=4ff52cb6e6f90f898c66de66a221e69b3104767eeea46e7f4c2deab73cbbe399 + erb (4.0.3) sha256=351a013ffee7e4e40b593df76dd4d0b2bd612e14dbff1d66273221d0cff6a49a + erubi (1.12.0) sha256=27bedb74dfb1e04ff60674975e182d8ca787f2224f2e8143268c7696f42e4723 + et-orbi (1.2.7) sha256=3b693d47f94a4060ccc07e60adda488759b1e8b9228a633ebbad842dfc245fb4 + execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb + factory_bot (6.4.2) sha256=f236ff24212b721ef3794cf6a33c6c1f562da8a778264b907d58ab090fae6466 + factory_bot_rails (6.4.2) sha256=0c384f626f9eb8afa50b50f4c2b3d70769e53b54cda4101e4da11f187be8e2e0 + faraday (2.7.12) sha256=ed38dcd396d2defcf8a536bbf7ef45e6385392ab815fe087df46777be3a781a7 + faraday-follow_redirects (0.3.0) sha256=d92d975635e2c7fe525dd494fcd4b9bb7f0a4a0ec0d5f4c15c729530fdb807f9 + faraday-net_http (3.0.2) sha256=6882929abed8094e1ee30344a3369e856fe34530044630d1f652bf70ebd87e8d + faraday_middleware-aws-sigv4 (1.0.1) sha256=a001ea4f687ca1c60bad8f2a627196905ce3dbf285e461dc153240e92eaabe8f + ffi (1.16.3) sha256=6d3242ff10c87271b0675c58d68d3f10148fabc2ad6da52a18123f06078871fb + ffi-compiler (1.0.1) sha256=019f389b078a2fec9de7f4f65771095f80a447e34436b4588bcb629e2a564c30 + fugit (1.9.0) sha256=e92ae18828a094afdd753e42ded0b83ad3449d79adddc5d1ab88e28dbfedd221 + get_process_mem (0.2.7) sha256=4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba + globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9 + good_job (3.21.5) sha256=a1ca5decb3036306b4b968fee3a9a7321a23d25dc31372ed501193e06e3512d3 + google-protobuf (3.25.1) sha256=e740e099193f8dc4db638326e23868d6c799dbd5ae2fd7565e78d1530cc6d1a3 + gravtastic (3.2.6) sha256=ef98abcecf7c402b61cff1ae7c50a2c6d97dd22bac21ea9b421ce05bc03d734f + groupdate (6.4.0) sha256=65940645bf2a48f9b2d10ab7a1d19bdc78f3c89559d8fce39cea3448a15aec54 + hashdiff (1.0.1) sha256=2cd4d04f5080314ecc8403c4e2e00dbaa282dff395e2d031bc16c8d501bdd6db + hashie (5.0.0) sha256=9d6c4e51f2a36d4616cbc8a322d619a162d8f42815a792596039fc95595603da + heapy (0.2.0) sha256=74141e845d61ffc7c1e8bf8b127c8cf94544ec7a1181aec613288682543585ea + high_voltage (3.1.2) sha256=7786716114cc21b3754e537818a6d9b2c91aa1126aa77272977d50598e556c2f + honeybadger (5.4.0) sha256=b2ce46319887aafe053bad167fa1722244d48eaa1e8282d5af5b0ae0c8c9dd35 + http (5.1.1) sha256=fcaec14a4f82de6d2f9cb978c07326814c6c2b42b8974f6ec166ff19c645ebaf + http-cookie (1.0.5) sha256=73756d46c7dbdc7023deecdb8a171348ea95a1b99810b31cfe8b4fb4e9a6318f + http-form_data (2.3.0) sha256=cc4eeb1361d9876821e31d7b1cf0b68f1cf874b201d27903480479d86448a5f3 + http_accept_language (2.1.1) sha256=0043f0d55a148cf45b604dbdd197cb36437133e990016c68c892d49dbea31634 + httparty (0.21.0) sha256=00ef7bf9a71f30a3bff88edeb5b16a34bea883ab67c246b3f0db2d6794fe1214 + i18n (1.14.1) sha256=9d03698903547c060928e70a9bc8b6b87fda674453cda918fc7ab80235ae4a61 + inline_svg (1.9.0) sha256=f44c5e3d2e401fd619ad3047b7c8cee384517d855edb1d1fb1a248d3cae535d6 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + job-iteration (1.4.1) sha256=7243c40e4decc3d49529867e9c504afaea332976c967ffdebed9ff863c6424af + jquery-rails (4.6.0) sha256=3c4e6bf47274340b44d836b8aa1b5472c6d451e2739af5ec094421f39025a7e2 + json (2.6.3) sha256=86aaea16adf346a2b22743d88f8dcceeb1038843989ab93cda44b5176c845459 + json-jwt (1.16.3) sha256=51c971248913ad82428ab8f7c6dca8b8320455fbe941437ed197444e1b6065d9 + jwt (2.7.0) sha256=b7e0b8669170abfe90cae8b6534d3d05d60fa3f785f0f005e44a41abcb1fd227 + kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e + kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 + kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 + kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff + launchdarkly-server-sdk (8.0.0) sha256=c5b0a0df6a87535037486390489bd18815690c285a8378f810a3c7519e894e55 + launchy (2.5.2) sha256=8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b + ld-eventsource (2.2.2) sha256=5ea087a6f06bbd8e325d2c1aaead50f37f13d025b952985739e9380a78a96beb + letter_opener (1.8.1) sha256=2d2841ec4767786996498f3f71e591458d1d0ba14d7df5162a5b044a6e24adf8 + letter_opener_web (2.0.0) sha256=33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f + libdatadog (5.0.0.1.0) sha256=c61b02584c02447089ad8e71b7aa8e5a660fd675b727635c1a2eaec59b1840e8 + libddwaf (1.14.0.0.0) sha256=b91ea9675f7d79d1cd10dd6513e3706760ac442cb8902164fbcef79b7082a8fd + listen (3.8.0) sha256=9679040ac6e7845ad9f19cf59ecde60861c78e2fae57a5c20fe35e94959b2f8f + llhttp-ffi (0.4.0) sha256=e5f7327db3cf8007e648342ef76347d6e0ae545a8402e519cca9c886eb37b001 + loofah (2.22.0) sha256=10d76e070c86b12fec74b6a9515fd1940f4459198b991342d0a7897d86c372fe + mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad + maintenance_tasks (2.3.3) sha256=c27ccee383140dbe73921ea66488b5546e35781edc424b7772abf848981811eb + marcel (1.0.2) sha256=a013b677ef46cbcb49fd5c59b3d35803d2ee04dd75d8bfdc43533fc5a31f7e4e + matrix (0.4.2) sha256=71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0 + memory_profiler (1.0.1) sha256=38cdb42f22d9100df2eba0365c199724b58b05c38e765cd764a07392916901b1 + meta-tags (2.19.0) sha256=7e7f78d430dfbf5efaff97d02609766e6a0296ea82db3bba776af9f8ca41e2ef + method_source (1.0.0) sha256=d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede + mini_histogram (0.3.1) sha256=6a114b504e4618b0e076cc672996036870f7cc6f16b8e5c25c0c637726d2dd94 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + mini_portile2 (2.8.5) sha256=7a37db8ae758086c3c3ac3a59c036704d331e965d5e106635e4a42d6e66089ce + minitest (5.20.0) sha256=a3faf26a757ced073aaae0bd10481340f53e221a4f50d8a6033591555374752e + minitest-gcstats (1.3.0) sha256=c1bef6374d53771a6f7776f7a9c9fac5d1231fc83dfff5acea16fed66b886afd + minitest-profile (0.0.2) sha256=caf5306217b59ea2854d75ff6b312e2c139f2948f2b6a39486677b8317b7048e + minitest-reporters (1.6.1) sha256=f8fe74e46ab40dada29402f55ca236368d0af65afc410db4219189b7a1c0fc38 + mocha (2.1.0) sha256=f98757589e417b492383592ca6a3d7a98ed367a2289c797d13c120b522a25453 + msgpack (1.7.2) sha256=59ab62fd8a4d0dfbde45009f87eb6f158ab2628a7c48886b0256f175166baaa8 + multi_json (1.15.0) sha256=1fd04138b6e4a90017e8d1b804c039031399866ff3fbabb7822aea367c78615d + multi_xml (0.6.0) sha256=d24393cf958adb226db884b976b007914a89c53ad88718e25679d7008823ad52 + net-imap (0.4.6) sha256=31975db12d9f7979338ad29934ded585a1fdc881b096dc72b35596a563281a3e + net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 + net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 + net-smtp (0.4.0) sha256=2d7eb8de289ba8dce3f0d436ee40b9366bea28354c5ba183c8ab2ec05139a3e7 + nio4r (2.6.1) sha256=284aae4adc431498a8f2a8e6027da72bca5f2ea8134d770ffc6f8e45bf6b29f9 + nokogiri (1.15.5) sha256=22448ca35dbcbdcec60dbe25ccf452b685a5436c28f21b2fec2e20917aba9100 + oauth2 (2.0.9) sha256=b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb + octokit (8.0.0) sha256=c362be3ee7cd2f01f5af98c06b182587fbe4ae57c2385cdf94a1b04154d8d085 + omniauth (2.1.1) sha256=f2e43fc889fd915838bd1c812569e7e8211b7a1a53d1e0dbb9f694a163d7b297 + omniauth-github (2.0.1) sha256=8ff8e70ac6d6db9d52485eef52cfa894938c941496e66b52b5e2773ade3ccad4 + omniauth-oauth2 (1.8.0) sha256=b2f8e9559cc7e2d4efba57607691d6d2b634b879fc5b5b6ccfefa3da85089e78 + omniauth-rails_csrf_protection (1.0.1) sha256=fc546aeb7d43b7b9d7737051c380156e61c8f080b898cd4934d523eaa7e59acf + openid_connect (2.2.0) sha256=45b4c7621be512c8bd3abde88587c2639d03f6371f6b3f3d9b5a98c377e029c8 + opensearch-ruby (3.1.0) sha256=fc6147c682be51356140a8163e46713018b5220bde3f80ba0b36a0fd1feffdb6 + openssl (3.1.0) sha256=e3a01279e918a7a5cf741db69b124864878b1a9783b1f2d34854bc1d444ac430 + openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80 + optimist (3.1.0) sha256=81886f53ee8919f330aa30076d320d88eef9bc85aae2275376b4afb007c69260 + pagy (6.2.0) sha256=6dd5a71077faaefee1d79c757e8ed974c6eba4303a2d743f46d06abd6e8f876f + parallel (1.22.1) sha256=ebdf1f0c51f182df38522f70ba770214940bef998cdb6e00f36492b29699761f + parser (3.2.1.1) sha256=2c3fe2cbc54c90b74f2de92e61bc533090bc6eb84290db03a9dd1eee902b5ba8 + pg (1.5.4) sha256=04f7b247151c639a0b955d8e5a9a41541343f4640aa3c2bdf749a872c339d25d + phlex (1.9.0) sha256=75b06a334833542594ef65d0532c6c964c78648f459d855cb54cd8c0ddcdb549 + phlex-rails (1.1.1) sha256=b0e82d0ba541eca55ca39051de8be2817a7ed400f8a630e21b261239c8f812d0 + pry (0.14.1) sha256=99b6df0665875dd5a39d85e0150aa5a12e2bb4fef401b6c4f64d32ee502f8454 + pry-byebug (3.10.1) sha256=c8f975c32255bfdb29e151f5532130be64ff3d0042dc858d0907e849125581f8 + psych (5.1.1.1) sha256=44b0d1823629ac815f1f470af642dc7261489d67feb622a3f5573aa9f5cc5f72 + public_suffix (5.0.4) sha256=35cd648e0d21d06b8dce9331d19619538d1d898ba6d56a6f2258409d2526d1ae + puma (6.4.0) sha256=d5dda11362744df9f4694708a62e3cfddf72eba7498c16016ebbb30f106712f9 + pundit (2.3.1) sha256=5c62a2e7c65278828d51fb921a3e608472a262a39a046d53d0e78588a556b181 + pwned (2.3.0) sha256=63f5a9576f109203684e9dd053f815649fd5bc0a0348b7190568272641b22353 + raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 + racc (1.7.3) sha256=b785ab8a30ec43bce073c51dbbe791fd27000f68d1c996c95da98bf685316905 + rack (2.2.8) sha256=7b83a1f1304a8f5554c67bc83632d29ecd2ed1daeb88d276b7898533fde22d97 + rack-attack (6.7.0) sha256=3ca47e8f66cd33b2c96af53ea4754525cd928ed3fa8da10ee6dad0277791d77c + rack-oauth2 (2.2.0) sha256=a03f6790350b29b0ace98f66eb65ec15adc4193c6799e9d4de76445864f70737 + rack-protection (3.0.5) sha256=3a428f9de18ee2a4080e2fab308f20f9e98d74dcbe06ed407a8035b46ba822a8 + rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb + rack-utf8_sanitizer (1.9.1) sha256=6414b70172f5678e23044abf1d00f6a32e62a335507c9548bc5caf9e3bff6da0 + rails (7.0.8) sha256=8e43af921acf766fb429126f020ec90c3b25809631f8fbdff95c3553828d5867 + rails-controller-testing (1.0.5) sha256=741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94 + rails-dom-testing (2.2.0) sha256=e515712e48df1f687a1d7c380fd7b07b8558faa26464474da64183a7426fa93b + rails-erd (1.7.2) sha256=0b17d0fba25d319d8da8af7a3e5e2149d02d6187cc7351e8be43423f07c48bcd + rails-html-sanitizer (1.6.0) sha256=86e9f19d2e6748890dcc2633c8945ca45baa08a1df9d8c215ce17b3b0afaa4de + rails-i18n (7.0.8) sha256=ee9ff92bc4734085aaf234157a7c1c795a29bf3f7edeede1e14e3d4247dd12cd + rails_semantic_logger (4.14.0) sha256=738ca601d544108765bb0c9ea45d5ef7967777fecc5bba83bc9c2d86ac4a127f + railties (7.0.8) sha256=12325c3933efd33f8ead640197dec3b8c27c8d45607dd68b7b925896bf09cc69 + rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a + rake (13.1.0) sha256=be6a3e1aa7f66e6c65fa57555234eb75ce4cf4ada077658449207205474199c6 + rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe + rb-inotify (0.10.1) sha256=050062d4f31d307cca52c3f6a7f4b946df8de25fc4bd373e1a5142e41034a7ca + rbtrace (0.5.0) sha256=85e7743d591b77a9b134b286e2d90c47e99995ee72f7503e0ba957d396c66167 + rdoc (6.6.1) sha256=d9d46f8f9bef79cfe45d9bea2b6b7f4d0a1c02bc9582135175faf6d5afcc4afd + regexp_parser (2.8.1) sha256=83f63e2bfae3db38f988c66f114485140ff1791321fd827480bc75aa42cacb8c + rexml (3.2.6) sha256=e0669a2d4e9f109951cb1fde723d8acd285425d81594a2ea929304af50282816 + roadie (5.2.0) sha256=f4dc8e41ccc331ebe4833cfa8e7495446db8bfd725c1a9b480147cb53c4945e3 + roadie-rails (3.1.0) sha256=5a45e1a7eb2f7cac29325ef8be64684c9d67cec608b235a69e38a7b79c64de21 + rotp (6.3.0) sha256=75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854 + rqrcode (2.2.0) sha256=23eea88bb44c7ee6d6cab9354d08c287f7ebcdc6112e1fe7bcc2d010d1ffefc1 + rqrcode_core (1.2.0) sha256=cf4989dc82d24e2877984738c4ee569308625fed2a810960f1b02d68d0308d1a + rubocop (1.48.0) sha256=2a90d242c2155c6d72cfaaf86d68bbbe58a6816cc8b192ac8c6702466c40c231 + rubocop-ast (1.27.0) sha256=0a88b64598ce7a7422579a11f419e4424fb2b10537b34b1a1b107f88f95c2a9a + rubocop-capybara (2.17.1) sha256=d648a6efd9607da494e379cdeb8d4569f88eb0e6c0a35ba964af2f0c6815e5df + rubocop-minitest (0.29.0) sha256=e815a04e68bffe10701c7537d388cbfef85b44d445d2651b7db9db8b70448ab6 + rubocop-performance (1.16.0) sha256=cc764f84bf37c6ef269973e686c3b33f258ffd612130e40856b25103de06efd8 + rubocop-rails (2.18.0) sha256=a646566f436b18f66e0b89a23df87ec4eb5318a74c9e6044271aca126b03fd8d + ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 + ruby-magic (0.6.0) sha256=7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101 + ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 + ruby-statistics (3.0.2) sha256=fb53e7a9f9681dac144c02539d3535fb2e8fae626f78b907219b0586ff53ec20 + ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef + rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f + safety_net_attestation (0.4.0) sha256=96be2d74e7ed26453a51894913449bea0e072f44490021545ac2d1c38b0718ce + sass-embedded (1.66.1) sha256=5e7660b520109584f441d60bfa9d349884dab6be70aa4392a389eb82d7fc0e0d + sawyer (0.9.2) sha256=fa3a72d62a4525517b18857ddb78926aab3424de0129be6772a8e2ba240e7aca + searchkick (5.3.1) sha256=dc1181543f6a68354e380651f235fa7f3df6a09e4cd67fc284dc293fa9860f57 + selenium-webdriver (4.16.0) sha256=237013649ea52435fe386cf4069b56d3f64c127b05af5f4d5c059bd71ee4c3e3 + semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 + semantic_logger (4.15.0) sha256=ec4f56122b5d2e2117d148b86c69fb62c1194a2b01a271be04ba8678a85f81ff + shoryuken (6.1.1) sha256=394affa8aca9160c72e44a0a2f89b3bab635bf217d4cb1c26f1fc5371e4a1277 + shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34 + shoulda-matchers (5.3.0) sha256=f6ba863b8752bb5956aaa73b046d5df5ecfbe9a7acb61f31bf853613e0932f86 + simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5 + simplecov-cobertura (2.1.0) sha256=2c6532e34df2e38a379d72cef9a05c3b16c64ce90566beebc6887801c4ad3f02 + simplecov-html (0.12.3) sha256=4b1aad33259ffba8b29c6876c12db70e5750cb9df829486e4c6e5da4fa0aa07b + simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 + snaky_hash (2.0.1) sha256=1ac87ec157fcfe7a460e821e0cd48ae1e6f5e3e082ab520f03f31a9259dbdc31 + sprockets (4.0.2) sha256=68d44758ae3da4f172c80abeff323100b4c4bb2f0ff6e1a3cb6e6c69e8e26f46 + sprockets-rails (3.4.2) sha256=36d6327757ccf7460a00d1d52b2d5ef0019a4670503046a129fa1fb1300931ad + statsd-instrument (3.6.1) sha256=fdaf73665c9a4d99aeddcda2e70fc266935919225dc0bf01257234f59f8f55df + stringio (3.1.0) sha256=c1f6263ae03a15025e51194ab19b06b15e06adcaaedb7f5f6c06ab60f5d67718 + strong_migrations (1.6.4) sha256=0e67822fc47e778835339d10955d687d71db7f36a89e76d399542661e20e8761 + swd (2.0.2) sha256=ea6201e0b4477973388ceba7c028a417d6bf1096e542bb1109b8b349ccdf216b + tailwindcss-rails (2.0.33) sha256=27541d2091a3cdb67653bb0e89c6d881c6ca55f92434c41109c172d633f01ea6 + terser (1.1.20) sha256=4921b2f9daf4d830eb4af90d692b5d73e899352f9eec77dc345da5717e479e2e + thor (1.3.0) sha256=1adc7f9e5b3655a68c71393fee8bd0ad088d14ee8e83a0b73726f23cbb3ca7c3 + tilt (2.2.0) sha256=e76f850e611128a87992bb13ba74807624a9b8ec748e2c2ea7139580f67ab22e + timeout (0.4.1) sha256=6f1f4edd4bca28cffa59501733a94215407c6960bd2107331f0280d4abdebb9a + toxiproxy (2.0.2) sha256=2e3b53604fb921d40da3db8f78a52b3133fcae33e93d440725335b15974e440a + tpm-key_attestation (0.12.0) sha256=e133d80cf24fef0e7a7dfad00fd6aeff01fc79875fbfc66cd8537bbd622b1e6d + turbo-rails (1.5.0) sha256=b426cc762fb0940277729b3f1751a9f0bd269f5613c1d62ac73e5f0be7c7a83e + turbo_power (0.5.0) sha256=869726c3a4308b038d6244f7a80eb1331bdd3171fa320dddcff71899fcae24fb + tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b + unf (0.1.4) sha256=4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e + unf_ext (0.0.8.2) sha256=90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa + unicode-display_width (2.4.2) sha256=6a10205d1a19ca790c4e53064ba93f09d9eb234bf6bd135d9deb6001c21428be + unpwn (1.0.0) sha256=6239d17d46a882b3719b24fb79c78a34caff89d57ab0f5e546be5b5c882bc7d3 + validate_email (0.1.6) sha256=9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4 + validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 + validates_formatting_of (0.9.0) sha256=139590a4b87596dbfb04d93e897bd2e6d30fb849d04fab0343e71ed2ca856e7e + version_gem (1.1.1) sha256=3c2da6ded29045ddcc0387e152dc634e1f0c490b7128dce0697ccc1cf0915b6c + view_component (3.8.0) sha256=3ec17fe3b56e0d679064b27dde796d7d4254d7bf8110594de85471b86ac0c5f6 + webauthn (3.0.0) sha256=3f77d422c2a8a4b31e56cf42f83414bd066e0506e9896936e1730262dc4a20e6 + webfinger (2.1.2) sha256=d2549726f327b2a3dba4496b5430a18ef638cd51b856aafb4d39691d45a4617c + webmock (3.19.1) sha256=eae7eee33989478188451f1fda4224d7fbe097c5c14e96b40b57347ef2d5d16d + websocket (1.2.10) sha256=2cc1a4a79b6e63637b326b4273e46adcddf7871caa5dc5711f2ca4061a629fa8 + websocket-driver (0.7.6) sha256=f69400be7bc197879726ad8e6f5869a61823147372fd8928836a53c2c741d0db + websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 + xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d + xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e + zeitwerk (2.6.12) sha256=561e12975d0332fd3b62cc859aff3bab432e5f320689c8a10cd4674b5c0439be + BUNDLED WITH - 2.4.21 + 2.5.1 diff --git a/config/initializers/requires.rb b/config/initializers/requires.rb index 2103f334848..f238c49c1c6 100644 --- a/config/initializers/requires.rb +++ b/config/initializers/requires.rb @@ -1,5 +1,4 @@ require 'rubygems/package' -require 'rubygems/indexer' require 'rdoc/markup' require 'rdoc/markup/to_html' require 'patterns' From 81f537ecbfa802dfc2f02e218a5a950638b1a509 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sat, 16 Dec 2023 15:00:41 +0800 Subject: [PATCH 072/112] Allow using webauthn in places that currently require a password (#4271) i.e. allow using passkeys as a single auth factor --- Gemfile.lock | 2 +- app/assets/javascripts/webauthn.js | 111 ++++++++++-------- app/avo/resources/user_resource.rb | 5 +- .../resources/webauthn_credential_resource.rb | 13 ++ .../webauthn_verification_resource.rb | 13 ++ .../avo/webauthn_credentials_controller.rb | 4 + .../avo/webauthn_verifications_controller.rb | 4 + .../concerns/webauthn_verifiable.rb | 23 +++- app/controllers/sessions_controller.rb | 63 ++++++++-- app/models/concerns/user_webauthn_methods.rb | 2 +- app/policies/user_policy.rb | 3 +- app/policies/webauthn_credential_policy.rb | 13 ++ app/policies/webauthn_verification_policy.rb | 13 ++ .../_webauthn_prompt.html.erb | 13 ++ app/views/multifactor_auths/prompt.html.erb | 14 +-- app/views/sessions/new.html.erb | 2 + app/views/sessions/verify.html.erb | 3 + config/initializers/webauthn.rb | 9 +- config/routes.rb | 2 + .../20231208004220_index_users_webauthn_id.rb | 7 ++ db/schema.rb | 3 +- db/seeds.rb | 9 +- .../concerns/webauthn_verifiable_test.rb | 2 +- .../email_confirmations_controller_test.rb | 2 +- .../multifactor_auths_controller_test.rb | 4 +- test/functional/passwords_controller_test.rb | 2 +- test/functional/sessions_controller_test.rb | 2 +- .../webauthn_credentials_controller_test.rb | 8 +- .../webauthn_verifications_controller_test.rb | 12 +- .../webauthn_credentials_controller_test.rb | 16 +++ .../webauthn_verifications_controller_test.rb | 16 +++ test/integration/email_confirmation_test.rb | 2 - test/integration/owner_test.rb | 45 +++++++ test/integration/password_reset_test.rb | 2 - .../webauthn_credential_policy_test.rb | 41 +++++++ .../webauthn_verification_policy_test.rb | 41 +++++++ test/system/sign_in_webauthn_test.rb | 72 ++++++++++-- test/system/webauthn_verification_test.rb | 10 +- test/test_helper.rb | 15 ++- 39 files changed, 502 insertions(+), 121 deletions(-) create mode 100644 app/avo/resources/webauthn_credential_resource.rb create mode 100644 app/avo/resources/webauthn_verification_resource.rb create mode 100644 app/controllers/avo/webauthn_credentials_controller.rb create mode 100644 app/controllers/avo/webauthn_verifications_controller.rb create mode 100644 app/policies/webauthn_credential_policy.rb create mode 100644 app/policies/webauthn_verification_policy.rb create mode 100644 app/views/multifactor_auths/_webauthn_prompt.html.erb create mode 100644 db/migrate/20231208004220_index_users_webauthn_id.rb create mode 100644 test/integration/avo/webauthn_credentials_controller_test.rb create mode 100644 test/integration/avo/webauthn_verifications_controller_test.rb create mode 100644 test/policies/webauthn_credential_policy_test.rb create mode 100644 test/policies/webauthn_verification_policy_test.rb diff --git a/Gemfile.lock b/Gemfile.lock index 30be27b26a1..ca67b187bb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -522,7 +522,7 @@ GEM optimist (>= 3.0.0) rdoc (6.6.1) psych (>= 4.0.0) - regexp_parser (2.8.1) + regexp_parser (2.8.3) rexml (3.2.6) roadie (5.2.0) css_parser (~> 1.4) diff --git a/app/assets/javascripts/webauthn.js b/app/assets/javascripts/webauthn.js index 94a19edc8ff..30aff380ac9 100644 --- a/app/assets/javascripts/webauthn.js +++ b/app/assets/javascripts/webauthn.js @@ -1,16 +1,16 @@ (function() { - var handleEvent = function(event) { + const handleEvent = function(event) { event.preventDefault(); return event.target; }; - var setError = function(submit, error, message) { + const setError = function(submit, error, message) { submit.attr("disabled", false); error.attr("hidden", false); error.text(message); }; - var handleHtmlResponse = function(submit, responseError, response) { + const handleHtmlResponse = function(submit, responseError, response) { if (response.redirected) { window.location.href = response.url; } else { @@ -22,7 +22,7 @@ } }; - var credentialsToBase64 = function(credentials) { + const credentialsToBase64 = function(credentials) { return { type: credentials.type, id: credentials.id, @@ -32,12 +32,13 @@ authenticatorData: bufferToBase64url(credentials.response.authenticatorData), attestationObject: bufferToBase64url(credentials.response.attestationObject), clientDataJSON: bufferToBase64url(credentials.response.clientDataJSON), - signature: bufferToBase64url(credentials.response.signature) + signature: bufferToBase64url(credentials.response.signature), + userHandle: bufferToBase64url(credentials.response.userHandle), }, }; }; - var credentialsToBuffer = function(credentials) { + const credentialsToBuffer = function(credentials) { return credentials.map(function(credential) { return { id: base64urlToBuffer(credential.id), @@ -46,15 +47,32 @@ }); }; + const parseCreationOptionsFromJSON = function(json) { + return { + ...json, + challenge: base64urlToBuffer(json.challenge), + user: { ...json.user, id: base64urlToBuffer(json.user.id) }, + excludeCredentials: credentialsToBuffer(json.excludeCredentials), + }; + }; + + const parseRequestOptionsFromJSON = function(json) { + return { + ...json, + challenge: base64urlToBuffer(json.challenge), + allowCredentials: credentialsToBuffer(json.allowCredentials), + }; + }; + $(function() { - var credentialForm = $(".js-new-webauthn-credential--form"); - var credentialError = $(".js-new-webauthn-credential--error"); - var credentialSubmit = $(".js-new-webauthn-credential--submit"); - var csrfToken = $("[name='csrf-token']").attr("content"); + const credentialForm = $(".js-new-webauthn-credential--form"); + const credentialError = $(".js-new-webauthn-credential--error"); + const credentialSubmit = $(".js-new-webauthn-credential--submit"); + const csrfToken = $("[name='csrf-token']").attr("content"); credentialForm.submit(function(event) { - var form = handleEvent(event); - var nickname = $(".js-new-webauthn-credential--nickname").val(); + const form = handleEvent(event); + const nickname = $(".js-new-webauthn-credential--nickname").val(); fetch(form.action + ".json", { method: "POST", @@ -63,11 +81,8 @@ }).then(function (response) { return response.json(); }).then(function (json) { - json.user.id = base64urlToBuffer(json.user.id); - json.challenge = base64urlToBuffer(json.challenge); - json.excludeCredentials = credentialsToBuffer(json.excludeCredentials); return navigator.credentials.create({ - publicKey: json + publicKey: parseCreationOptionsFromJSON(json) }); }).then(function (credentials) { return fetch(form.action + "/callback.json", { @@ -98,36 +113,33 @@ }); }); - var getCredentials = function(event, csrfToken) { - var form = handleEvent(event); - var options = JSON.parse(form.dataset.options); - options.challenge = base64urlToBuffer(options.challenge); - options.allowCredentials = credentialsToBuffer(options.allowCredentials); - return navigator.credentials.get({ - publicKey: options - }).then(function (credentials) { - return fetch(form.action, { - method: "POST", - credentials: "same-origin", - redirect: "follow", - headers: { - "X-CSRF-Token": csrfToken, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - credentials: credentialsToBase64(credentials) - }) - }); - }) + const getCredentials = async function(event, csrfToken) { + const form = handleEvent(event); + const options = JSON.parse(form.dataset.options); + const credentials = await navigator.credentials.get({ + publicKey: parseRequestOptionsFromJSON(options) + }); + return await fetch(form.action, { + method: "POST", + credentials: "same-origin", + redirect: "follow", + headers: { + "X-CSRF-Token": csrfToken, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + credentials: credentialsToBase64(credentials), + }) + }); }; $(function() { - var cliSessionForm = $(".js-webauthn-session-cli--form"); - var cliSessionError = $(".js-webauthn-session-cli--error"); - var csrfToken = $("[name='csrf-token']").attr("content"); + const cliSessionForm = $(".js-webauthn-session-cli--form"); + const cliSessionError = $(".js-webauthn-session-cli--error"); + const csrfToken = $("[name='csrf-token']").attr("content"); function failed_verification_url(message) { - var url = new URL(`${location.origin}/webauthn_verification/failed_verification`); + const url = new URL(`${location.origin}/webauthn_verification/failed_verification`); url.searchParams.append("error", message); return url.href; }; @@ -148,17 +160,18 @@ }); $(function() { - var sessionForm = $(".js-webauthn-session--form"); - var sessionSubmit = $(".js-webauthn-session--submit"); - var sessionError = $(".js-webauthn-session--error"); - var csrfToken = $("[name='csrf-token']").attr("content"); + const sessionForm = $(".js-webauthn-session--form"); + const sessionSubmit = $(".js-webauthn-session--submit"); + const sessionError = $(".js-webauthn-session--error"); + const csrfToken = $("[name='csrf-token']").attr("content"); - sessionForm.submit(function(event) { - getCredentials(event, csrfToken).then(function (response) { + sessionForm.submit(async function(event) { + try { + const response = await getCredentials(event, csrfToken); handleHtmlResponse(sessionSubmit, sessionError, response); - }).catch(function (error) { + } catch (error) { setError(sessionSubmit, sessionError, error); - }); + } }); }); })(); diff --git a/app/avo/resources/user_resource.rb b/app/avo/resources/user_resource.rb index d76eacea65a..71c93636dc0 100644 --- a/app/avo/resources/user_resource.rb +++ b/app/avo/resources/user_resource.rb @@ -42,8 +42,7 @@ class UserResource < Avo::BaseResource field :mfa_level, as: :select, enum: ::User.mfa_levels field :mfa_recovery_codes, as: :text, visible: ->(_) { false } field :mfa_hashed_recovery_codes, as: :text, visible: ->(_) { false } - field :webauthn_id, as: :text, visible: ->(_) { false } - field :webauthn_credentials, as: :has_many, visible: ->(_) { false } + field :webauthn_id, as: :text field :remember_token_expires_at, as: :date_time field :api_key, as: :text, visible: ->(_) { false } field :confirmation_token, as: :text, visible: ->(_) { false } @@ -64,6 +63,8 @@ class UserResource < Avo::BaseResource field :ownership_requests, as: :has_many field :pushed_versions, as: :has_many field :oidc_api_key_roles, as: :has_many + field :webauthn_credentials, as: :has_many + field :webauthn_verification, as: :has_one field :audits, as: :has_many end diff --git a/app/avo/resources/webauthn_credential_resource.rb b/app/avo/resources/webauthn_credential_resource.rb new file mode 100644 index 00000000000..b6711b63eab --- /dev/null +++ b/app/avo/resources/webauthn_credential_resource.rb @@ -0,0 +1,13 @@ +class WebauthnCredentialResource < Avo::BaseResource + self.title = :id + self.includes = [] + + field :id, as: :id + # Fields generated from the model + field :external_id, as: :text + field :public_key, as: :text + field :nickname, as: :text + field :sign_count, as: :number + field :user, as: :belongs_to + # add fields here +end diff --git a/app/avo/resources/webauthn_verification_resource.rb b/app/avo/resources/webauthn_verification_resource.rb new file mode 100644 index 00000000000..5834540ac89 --- /dev/null +++ b/app/avo/resources/webauthn_verification_resource.rb @@ -0,0 +1,13 @@ +class WebauthnVerificationResource < Avo::BaseResource + self.title = :id + self.includes = [] + + field :id, as: :id + # Fields generated from the model + field :path_token, as: :text + field :path_token_expires_at, as: :date_time + field :otp, as: :text + field :otp_expires_at, as: :date_time + field :user, as: :belongs_to + # add fields here +end diff --git a/app/controllers/avo/webauthn_credentials_controller.rb b/app/controllers/avo/webauthn_credentials_controller.rb new file mode 100644 index 00000000000..f16d7df74bc --- /dev/null +++ b/app/controllers/avo/webauthn_credentials_controller.rb @@ -0,0 +1,4 @@ +# This controller has been generated to enable Rails' resource routes. +# More information on https://docs.avohq.io/2.0/controllers.html +class Avo::WebauthnCredentialsController < Avo::ResourcesController +end diff --git a/app/controllers/avo/webauthn_verifications_controller.rb b/app/controllers/avo/webauthn_verifications_controller.rb new file mode 100644 index 00000000000..16817b148b2 --- /dev/null +++ b/app/controllers/avo/webauthn_verifications_controller.rb @@ -0,0 +1,4 @@ +# This controller has been generated to enable Rails' resource routes. +# More information on https://docs.avohq.io/2.0/controllers.html +class Avo::WebauthnVerificationsController < Avo::ResourcesController +end diff --git a/app/controllers/concerns/webauthn_verifiable.rb b/app/controllers/concerns/webauthn_verifiable.rb index 3bd4241b0f7..956d8c699b9 100644 --- a/app/controllers/concerns/webauthn_verifiable.rb +++ b/app/controllers/concerns/webauthn_verifiable.rb @@ -15,6 +15,11 @@ def setup_webauthn_authentication(form_url:, session_options: {}) def webauthn_credential_verified? @credential = WebAuthn::Credential.from_get(credential_params) + unless user_webauthn_credential + @webauthn_error = t("credentials_required") + return false + end + @credential.verify( challenge, public_key: user_webauthn_credential.public_key, @@ -22,6 +27,11 @@ def webauthn_credential_verified? ) user_webauthn_credential.update!(sign_count: @credential.sign_count) + if @credential.user_handle.present? && @credential.user_handle != user_webauthn_credential.user.webauthn_id + @webauthn_error = t("credentials_required") + return false + end + true rescue WebAuthn::Error => e @webauthn_error = e.message @@ -35,8 +45,16 @@ def webauthn_credential_verified? private + def webauthn_credential_scope + if @user.present? + @user.webauthn_credentials + else + User.find_by(webauthn_id: @credential.user_handle)&.webauthn_credentials || WebauthnCredential.none + end + end + def user_webauthn_credential - @user_webauthn_credential ||= @user.webauthn_credentials.find_by( + @user_webauthn_credential ||= webauthn_credential_scope.find_by( external_id: @credential.id ) end @@ -50,7 +68,8 @@ def credential_params :id, :type, :rawId, - response: %i[authenticatorData attestationObject clientDataJSON signature] + :authenticatorAttachment, + response: %i[authenticatorData attestationObject clientDataJSON signature userHandle] ) end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 2077962bd6e..03262e3fdc5 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -2,11 +2,13 @@ class SessionsController < Clearance::SessionsController include MfaExpiryMethods include WebauthnVerifiable - before_action :redirect_to_signin, unless: :signed_in?, only: %i[verify authenticate] - before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled?, only: %i[verify authenticate] - before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled?, only: %i[verify authenticate] - before_action :ensure_not_blocked, only: :create - after_action :delete_mfa_session, only: %i[webauthn_create otp_create] + before_action :redirect_to_signin, unless: :signed_in?, only: %i[verify webauthn_authenticate authenticate] + before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled?, only: %i[verify webauthn_authenticate authenticate] + before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled?, only: %i[verify webauthn_authenticate authenticate] + before_action :ensure_not_blocked, only: %i[create webauthn_full_create] + before_action :webauthn_new_setup, only: :new + after_action :delete_mfa_session, only: %i[webauthn_create webauthn_full_create otp_create] + after_action :delete_session_verification, only: :destroy def create @user = find_user @@ -39,6 +41,14 @@ def webauthn_create do_login end + def webauthn_full_create + return login_failure(@webauthn_error) unless webauthn_credential_verified? + + @user = user_webauthn_credential.user + + do_login + end + def otp_create @user = User.find(session[:mfa_user]) @@ -54,21 +64,40 @@ def otp_create end def verify + @user = current_user + setup_webauthn_authentication(form_url: webauthn_authenticate_session_path) end def authenticate + @user = current_user if verify_user - session[:verified_user] = current_user.id - session[:verification] = Time.current + Gemcutter::PASSWORD_VERIFICATION_EXPIRY - redirect_to session.delete(:redirect_uri) || root_path + mark_verified else flash.now[:alert] = t("profiles.request_denied") + setup_webauthn_authentication(form_url: webauthn_authenticate_session_path) + render :verify, status: :unauthorized + end + end + + def webauthn_authenticate + @user = current_user + if webauthn_credential_verified? + mark_verified + else + flash.now[:alert] = @webauthn_error + setup_webauthn_authentication(form_url: webauthn_authenticate_session_path) render :verify, status: :unauthorized end end private + def mark_verified + session[:verified_user] = current_user.id + session[:verification] = Gemcutter::PASSWORD_VERIFICATION_EXPIRY.from_now + redirect_to session.delete(:redirect_uri) || root_path + end + def verify_user current_user.authenticated? verify_password_params[:password] end @@ -92,6 +121,7 @@ def do_login def login_failure(message) StatsD.increment "login.failure" flash.now.notice = message + webauthn_new_setup render "sessions/new", status: :unauthorized end @@ -134,6 +164,7 @@ def ensure_not_blocked return unless user&.blocked_email flash.now.alert = t(".account_blocked") + webauthn_new_setup render template: "sessions/new", status: :unauthorized end @@ -154,4 +185,20 @@ def record_mfa_login_duration(mfa_type:) StatsD.distribution("login.mfa.#{mfa_type}.duration", duration) end + + def webauthn_new_setup + @webauthn_options = WebAuthn::Credential.options_for_get( + user_verification: "discouraged" + ) + + @webauthn_verification_url = webauthn_full_create_session_path + + session[:webauthn_authentication] = { + "challenge" => @webauthn_options.challenge + } + end + + def delete_session_verification + session[:verified_user] = session[:verification] = nil + end end diff --git a/app/models/concerns/user_webauthn_methods.rb b/app/models/concerns/user_webauthn_methods.rb index fccd9d046cc..add3d95930a 100644 --- a/app/models/concerns/user_webauthn_methods.rb +++ b/app/models/concerns/user_webauthn_methods.rb @@ -17,7 +17,7 @@ def webauthn_options_for_create name: display_id }, exclude: webauthn_credentials.pluck(:external_id), - authenticator_selection: { user_verification: "discouraged" } + authenticator_selection: { user_verification: "discouraged", resident_key: "preferred" } ) end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 36c16110a43..c1496a8dbe1 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -18,7 +18,6 @@ def act_on? rubygems_org_admin? end - has_association :webauthn_credentials has_association :ownerships has_association :rubygems has_association :subscriptions @@ -32,4 +31,6 @@ def act_on? has_association :pushed_versions has_association :audits has_association :oidc_api_key_roles + has_association :webauthn_credentials + has_association :webauthn_verification end diff --git a/app/policies/webauthn_credential_policy.rb b/app/policies/webauthn_credential_policy.rb new file mode 100644 index 00000000000..88c5442f864 --- /dev/null +++ b/app/policies/webauthn_credential_policy.rb @@ -0,0 +1,13 @@ +class WebauthnCredentialPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end + + def avo_show? + Pundit.policy!(user, record.user).avo_show? + end + + has_association :user +end diff --git a/app/policies/webauthn_verification_policy.rb b/app/policies/webauthn_verification_policy.rb new file mode 100644 index 00000000000..bb4b1d36554 --- /dev/null +++ b/app/policies/webauthn_verification_policy.rb @@ -0,0 +1,13 @@ +class WebauthnVerificationPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end + + def avo_show? + Pundit.policy!(user, record.user).avo_show? + end + + has_association :user +end diff --git a/app/views/multifactor_auths/_webauthn_prompt.html.erb b/app/views/multifactor_auths/_webauthn_prompt.html.erb new file mode 100644 index 00000000000..cd3b6ae7735 --- /dev/null +++ b/app/views/multifactor_auths/_webauthn_prompt.html.erb @@ -0,0 +1,13 @@ +
    +

    <%= t("multifactor_auths.prompt.security_device") %>

    +
    +

    <%= t("multifactor_auths.prompt.webauthn_credential_note") %>

    +
    + <%= form_tag @webauthn_verification_url, method: :post, class: "js-webauthn-session--form", data: { options: @webauthn_options.to_json } do %> +
    + + + <%= submit_tag t("multifactor_auths.prompt.sign_in_with_webauthn_credential"), class: 'js-webauthn-session--submit form__submit form__submit--no-hover' %> +
    + <% end %> +
    diff --git a/app/views/multifactor_auths/prompt.html.erb b/app/views/multifactor_auths/prompt.html.erb index d9d532793b4..2c8c89707e2 100644 --- a/app/views/multifactor_auths/prompt.html.erb +++ b/app/views/multifactor_auths/prompt.html.erb @@ -2,19 +2,7 @@
    <% if @user.webauthn_enabled?%> -
    -

    <%= t(".security_device") %>

    -
    -

    <%= t(".webauthn_credential_note") %>

    -
    - <%= form_tag @webauthn_verification_url, method: :post, class: "js-webauthn-session--form", data: { options: @webauthn_options.to_json } do %> -
    - - - <%= submit_tag t(".sign_in_with_webauthn_credential"), class: 'js-webauthn-session--submit form__submit form__submit--no-hover' %> -
    - <% end %> -
    + <%= render "multifactor_auths/webauthn_prompt" %> <% end %> <% if @user.totp_enabled? || @user.webauthn_only_with_recovery? %> diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 5081036d195..099daa2ab84 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -20,3 +20,5 @@ <%= form.submit t('sign_in'), :data => {:disable_with => t('form_disable_with')}, :class => 'form__submit' %>
    <% end %> + +<%= render "multifactor_auths/webauthn_prompt" %> diff --git a/app/views/sessions/verify.html.erb b/app/views/sessions/verify.html.erb index c9fd01b4d54..490a28fecf1 100644 --- a/app/views/sessions/verify.html.erb +++ b/app/views/sessions/verify.html.erb @@ -13,3 +13,6 @@ <%= form.submit t(".confirm"), data: {disable_with: t("form_disable_with")}, class: "form__submit" %>
    <% end %> +<% if @user.webauthn_enabled?%> + <%= render "multifactor_auths/webauthn_prompt" %> +<% end %> diff --git a/config/initializers/webauthn.rb b/config/initializers/webauthn.rb index 7c930a29733..59a710c0c86 100644 --- a/config/initializers/webauthn.rb +++ b/config/initializers/webauthn.rb @@ -1,8 +1,11 @@ WebAuthn.configure do |config| - config.origin = if Rails.env.development? || Rails.env.test? + config.origin = if Rails.env.development? ENV.fetch("WEBAUTHN_ORIGIN", "http://localhost:3000") + elsif Rails.env.test? + "#{Gemcutter::PROTOCOL}://#{Gemcutter::HOST}:31337" else - "#{Rails.application.config.rubygems.protocol}://#{Rails.application.config.rubygems.host}" + "#{Gemcutter::PROTOCOL}://#{Gemcutter::HOST}" end - config.rp_name = "RubyGems.org" + config.rp_name = Gemcutter::HOST_DISPLAY + # config.rp_id = Gemcutter::HOST end diff --git a/config/routes.rb b/config/routes.rb index 920758bd6ca..5aa65e781db 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -243,8 +243,10 @@ resource :session, only: %i[create destroy] do post 'otp_create', to: 'sessions#otp_create', as: :otp_create post 'webauthn_create', to: 'sessions#webauthn_create', as: :webauthn_create + post 'webauthn_full_create', to: 'sessions#webauthn_full_create', as: :webauthn_full_create get 'verify', to: 'sessions#verify', as: :verify post 'authenticate', to: 'sessions#authenticate', as: :authenticate + post 'webauthn_authenticate', to: 'sessions#webauthn_authenticate', as: :webauthn_authenticate end resources :users, only: %i[new create] do diff --git a/db/migrate/20231208004220_index_users_webauthn_id.rb b/db/migrate/20231208004220_index_users_webauthn_id.rb new file mode 100644 index 00000000000..45a286b3070 --- /dev/null +++ b/db/migrate/20231208004220_index_users_webauthn_id.rb @@ -0,0 +1,7 @@ +class IndexUsersWebauthnId < ActiveRecord::Migration[7.0] + disable_ddl_transaction! + + def change + add_index :users, :webauthn_id, unique: true, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index 9166a238a6e..cf39d481311 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_11_29_233528) do +ActiveRecord::Schema[7.0].define(version: 2023_12_08_004220) do # These are extensions that must be enabled in order to support this database enable_extension "hstore" enable_extension "pgcrypto" @@ -434,6 +434,7 @@ t.index ["id", "token"], name: "index_users_on_id_and_token" t.index ["remember_token"], name: "index_users_on_remember_token" t.index ["token"], name: "index_users_on_token" + t.index ["webauthn_id"], name: "index_users_on_webauthn_id", unique: true end create_table "versions", id: :serial, force: :cascade do |t| diff --git a/db/seeds.rb b/db/seeds.rb index a141704fc2d..9424f679d1d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -3,7 +3,8 @@ author = User.create_with( handle: "gem-author", password: password, - email_confirmed: true + email_confirmed: true, + webauthn_id: "a1TLW3o1W18mTuDBfDALHhL2tZ1_E-2B03Fqsdu8Rv05V4tSsRzepe-L7Uprg356dw1tktXXcTI9TIRaK4gM-A" ).find_or_create_by!(email: "gem-author@example.com") maintainer = User.create_with( @@ -290,6 +291,12 @@ rubygem_name: "pending-trusted-publisher-rubygem" ) +author.webauthn_credentials.create_with(nickname: "segiddins development") + .find_or_create_by!( + external_id: "QdfU3FxkjNpPqfjC4uTuNA", + public_key: "pQECAyYgASFYIKMIHolehDjslWQ6oOVP1-R8OR6LXEBdDfqxhjgtiiDEIlgg1RgUq_AJFT-cSMo-xP_9XxGIbBsQDEj8253QPwc8-88", + ) + puts <<~MESSAGE # rubocop:disable Rails/Output Four users were created, you can login with following combinations: - email: #{author.email}, password: #{password} -> gem author owning few example gems diff --git a/test/functional/concerns/webauthn_verifiable_test.rb b/test/functional/concerns/webauthn_verifiable_test.rb index 2b76e596e8a..79dcf0b4f0b 100644 --- a/test/functional/concerns/webauthn_verifiable_test.rb +++ b/test/functional/concerns/webauthn_verifiable_test.rb @@ -78,7 +78,7 @@ class WebauthnVerifiableTest < ActionController::TestCase setup do get :prompt, params: { user_id: @user.id } @challenge = session[:webauthn_authentication]["challenge"] - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( diff --git a/test/functional/email_confirmations_controller_test.rb b/test/functional/email_confirmations_controller_test.rb index f9b75f0bf3a..d8679c4a0ff 100644 --- a/test/functional/email_confirmations_controller_test.rb +++ b/test/functional/email_confirmations_controller_test.rb @@ -226,7 +226,7 @@ class EmailConfirmationsControllerTest < ActionController::TestCase @user = create(:user) @webauthn_credential = create(:webauthn_credential, user: @user) get :update, params: { token: @user.confirmation_token, user_id: @user.id } - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) end diff --git a/test/functional/multifactor_auths_controller_test.rb b/test/functional/multifactor_auths_controller_test.rb index 92a173544a8..18c0c708e90 100644 --- a/test/functional/multifactor_auths_controller_test.rb +++ b/test/functional/multifactor_auths_controller_test.rb @@ -408,7 +408,7 @@ class MultifactorAuthsControllerTest < ActionController::TestCase context "on POST to webauthn_update" do setup do - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( @@ -818,7 +818,7 @@ class MultifactorAuthsControllerTest < ActionController::TestCase context "on POST to webauthn_update" do setup do - origin = "http://localhost:3000" + origin = WebAuthn.configuration.origin @rp_id = URI.parse(origin).host @client = WebAuthn::FakeClient.new(origin, encoding: false) WebauthnHelpers.create_credential( diff --git a/test/functional/passwords_controller_test.rb b/test/functional/passwords_controller_test.rb index 86057ab2a39..9328fe8391c 100644 --- a/test/functional/passwords_controller_test.rb +++ b/test/functional/passwords_controller_test.rb @@ -194,7 +194,7 @@ class PasswordsControllerTest < ActionController::TestCase @user = create(:user) @webauthn_credential = create(:webauthn_credential, user: @user) get :edit, params: { token: @user.confirmation_token, user_id: @user.id } - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) end diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb index 49805ad601e..dad172a95c2 100644 --- a/test/functional/sessions_controller_test.rb +++ b/test/functional/sessions_controller_test.rb @@ -776,7 +776,7 @@ def login_to_session_with_webauthn params: { session: { who: @user.handle, password: @user.password } } ) @challenge = session[:webauthn_authentication]["challenge"] - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( diff --git a/test/functional/webauthn_credentials_controller_test.rb b/test/functional/webauthn_credentials_controller_test.rb index 4dead63a2c9..7f251296a58 100644 --- a/test/functional/webauthn_credentials_controller_test.rb +++ b/test/functional/webauthn_credentials_controller_test.rb @@ -89,7 +89,7 @@ class WebauthnCredentialsControllerTest < ActionController::TestCase setup do @nickname = SecureRandom.hex challenge = JSON.parse(response.body)["challenge"] - origin = "http://localhost:3000" + origin = WebAuthn.configuration.origin client = WebAuthn::FakeClient.new(origin, encoding: false) perform_enqueued_jobs only: ActionMailer::MailDeliveryJob do @@ -148,7 +148,7 @@ class WebauthnCredentialsControllerTest < ActionController::TestCase setup do @nickname = "" challenge = JSON.parse(response.body)["challenge"] - origin = "http://localhost:3000" + origin = WebAuthn.configuration.origin client = WebAuthn::FakeClient.new(origin, encoding: false) post( :callback, @@ -170,7 +170,7 @@ class WebauthnCredentialsControllerTest < ActionController::TestCase setup do @nickname = SecureRandom.hex challenge = SecureRandom.hex - origin = "http://localhost:3000" + origin = WebAuthn.configuration.origin client = WebAuthn::FakeClient.new(origin, encoding: false) post( :callback, @@ -197,7 +197,7 @@ class WebauthnCredentialsControllerTest < ActionController::TestCase @nickname = SecureRandom.hex @challenge = JSON.parse(response.body)["challenge"] - origin = "http://localhost:3000" + origin = WebAuthn.configuration.origin @client = WebAuthn::FakeClient.new(origin, encoding: false) end diff --git a/test/functional/webauthn_verifications_controller_test.rb b/test/functional/webauthn_verifications_controller_test.rb index cf41b7786da..49ac1361fc0 100644 --- a/test/functional/webauthn_verifications_controller_test.rb +++ b/test/functional/webauthn_verifications_controller_test.rb @@ -105,7 +105,7 @@ class WebauthnVerificationsControllerTest < ActionController::TestCase context "when verifying the challenge" do setup do @challenge = session[:webauthn_authentication]["challenge"] - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( @@ -138,7 +138,7 @@ class WebauthnVerificationsControllerTest < ActionController::TestCase context "when verifying the challenge with safari" do setup do @challenge = session[:webauthn_authentication]["challenge"] - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( @@ -197,7 +197,7 @@ class WebauthnVerificationsControllerTest < ActionController::TestCase context "when providing wrong credentials" do setup do @wrong_challenge = "16b8e11ea1b46abc64aea3ecdac1c418" - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( @@ -228,7 +228,7 @@ class WebauthnVerificationsControllerTest < ActionController::TestCase setup do @wrong_webauthn_token = "pRpwn2mTH2D18t58" @challenge = session[:webauthn_authentication]["challenge"] - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( @@ -252,7 +252,7 @@ class WebauthnVerificationsControllerTest < ActionController::TestCase context "when the webauthn token has expired" do setup do @challenge = session[:webauthn_authentication]["challenge"] - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( @@ -277,7 +277,7 @@ class WebauthnVerificationsControllerTest < ActionController::TestCase setup do @challenge = session[:webauthn_authentication]["challenge"] session[:webauthn_authentication]["port"] = nil - @origin = "http://localhost:3000" + @origin = WebAuthn.configuration.origin @rp_id = URI.parse(@origin).host @client = WebAuthn::FakeClient.new(@origin, encoding: false) WebauthnHelpers.create_credential( diff --git a/test/integration/avo/webauthn_credentials_controller_test.rb b/test/integration/avo/webauthn_credentials_controller_test.rb new file mode 100644 index 00000000000..f79ed681ebd --- /dev/null +++ b/test/integration/avo/webauthn_credentials_controller_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class Avo::WebauthnCredentialsControllerTest < ActionDispatch::IntegrationTest + include AdminHelpers + + test "getting webauthn credentials as admin" do + admin_sign_in_as create(:admin_github_user, :is_admin) + + webauthn_credential = create(:webauthn_credential) + + get avo.resources_webauthn_credential_path(webauthn_credential) + + assert_response :success + page.assert_text webauthn_credential.external_id + end +end diff --git a/test/integration/avo/webauthn_verifications_controller_test.rb b/test/integration/avo/webauthn_verifications_controller_test.rb new file mode 100644 index 00000000000..f0111e28288 --- /dev/null +++ b/test/integration/avo/webauthn_verifications_controller_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class Avo::WebauthnVerificationsControllerTest < ActionDispatch::IntegrationTest + include AdminHelpers + + test "getting webauthn verifications as admin" do + admin_sign_in_as create(:admin_github_user, :is_admin) + + webauthn_verification = create(:webauthn_verification) + + get avo.resources_webauthn_verification_path(webauthn_verification) + + assert_response :success + page.assert_text webauthn_verification.path_token + end +end diff --git a/test/integration/email_confirmation_test.rb b/test/integration/email_confirmation_test.rb index 0c03d672e81..275ea33e32d 100644 --- a/test/integration/email_confirmation_test.rb +++ b/test/integration/email_confirmation_test.rb @@ -91,8 +91,6 @@ def request_confirmation_mail(email) assert page.has_content? "Multi-factor authentication" assert page.has_content? "Security Device" - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true - click_on "Authenticate with security device" find(:css, ".header__popup-link").click diff --git a/test/integration/owner_test.rb b/test/integration/owner_test.rb index 4ef8d97114a..d7aa11e91bb 100644 --- a/test/integration/owner_test.rb +++ b/test/integration/owner_test.rb @@ -114,6 +114,43 @@ class OwnerTest < SystemTest assert_no_emails end + test "verify using webauthn" do + create_webauthn_credential + + visit sign_in_path + click_button "Authenticate with security device" + find(:css, ".header__popup-link") + + visit rubygem_path(@rubygem.slug) + click_link "Ownership" + + assert page.has_css? "#verify_password_password" + + click_button "Authenticate with security device" + + page.assert_text "add or remove owners" + end + + test "verify failure using webauthn shows error" do + create_webauthn_credential + + visit sign_in_path + click_button "Authenticate with security device" + find(:css, ".header__popup-link") + + visit rubygem_path(@rubygem.slug) + click_link "Ownership" + + assert page.has_css? "#verify_password_password" + + @user.webauthn_credentials.find_each { |c| c.update!(external_id: "a") } + + click_button "Authenticate with security device" + + page.assert_text "Credentials required" + assert page.has_css? "#verify_password_password" + end + test "verify password again after 10 minutes" do visit_ownerships_page travel 15.minutes @@ -219,6 +256,12 @@ class OwnerTest < SystemTest refute page.has_content? @other_user.handle end + teardown do + @authenticator&.remove! + Capybara.reset_sessions! + Capybara.use_default_driver + end + private def owner_row(owner) @@ -247,5 +290,7 @@ def sign_in_as(user) fill_in "Email or Username", with: user.email fill_in "Password", with: PasswordHelpers::SECURE_TEST_PASSWORD click_button "Sign in" + + find(:css, ".header__popup-link") end end diff --git a/test/integration/password_reset_test.rb b/test/integration/password_reset_test.rb index ea3fd87ea3a..8ed199be1ee 100644 --- a/test/integration/password_reset_test.rb +++ b/test/integration/password_reset_test.rb @@ -124,8 +124,6 @@ def forgot_password_with(email) assert page.has_content? "Security Device" assert_not_nil page.find(".js-webauthn-session--form")[:action] - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true - click_on "Authenticate with security device" fill_in "Password", with: PasswordHelpers::SECURE_TEST_PASSWORD diff --git a/test/policies/webauthn_credential_policy_test.rb b/test/policies/webauthn_credential_policy_test.rb new file mode 100644 index 00000000000..0d947926379 --- /dev/null +++ b/test/policies/webauthn_credential_policy_test.rb @@ -0,0 +1,41 @@ +require "test_helper" + +class WebauthnCredentialPolicyTest < ActiveSupport::TestCase + setup do + @webauthn_credential = FactoryBot.create(:webauthn_credential) + @admin = FactoryBot.create(:admin_github_user, :is_admin) + @non_admin = FactoryBot.create(:admin_github_user) + end + + def test_scope + assert_equal [@webauthn_credential], Pundit.policy_scope!( + @admin, + WebauthnCredential + ).to_a + end + + def test_avo_index + refute_predicate Pundit.policy!(@admin, WebauthnCredential), :avo_index? + refute_predicate Pundit.policy!(@non_admin, WebauthnCredential), :avo_index? + end + + def test_avo_show + assert_predicate Pundit.policy!(@admin, @webauthn_credential), :avo_show? + refute_predicate Pundit.policy!(@non_admin, @webauthn_credential), :avo_show? + end + + def test_avo_create + refute_predicate Pundit.policy!(@admin, WebauthnCredential), :avo_create? + refute_predicate Pundit.policy!(@non_admin, WebauthnCredential), :avo_create? + end + + def test_avo_update + refute_predicate Pundit.policy!(@admin, @webauthn_credential), :avo_update? + refute_predicate Pundit.policy!(@non_admin, @webauthn_credential), :avo_update? + end + + def test_avo_destroy + refute_predicate Pundit.policy!(@admin, @webauthn_credential), :avo_destroy? + refute_predicate Pundit.policy!(@non_admin, @webauthn_credential), :avo_destroy? + end +end diff --git a/test/policies/webauthn_verification_policy_test.rb b/test/policies/webauthn_verification_policy_test.rb new file mode 100644 index 00000000000..3afef2dc596 --- /dev/null +++ b/test/policies/webauthn_verification_policy_test.rb @@ -0,0 +1,41 @@ +require "test_helper" + +class WebauthnVerificationPolicyTest < ActiveSupport::TestCase + setup do + @webauthn_verification = FactoryBot.create(:webauthn_verification) + @admin = FactoryBot.create(:admin_github_user, :is_admin) + @non_admin = FactoryBot.create(:admin_github_user) + end + + def test_scope + assert_equal [@webauthn_verification], Pundit.policy_scope!( + @admin, + WebauthnVerification + ).to_a + end + + def test_avo_index + refute_predicate Pundit.policy!(@admin, WebauthnVerification), :avo_index? + refute_predicate Pundit.policy!(@non_admin, WebauthnVerification), :avo_index? + end + + def test_avo_show + assert_predicate Pundit.policy!(@admin, @webauthn_verification), :avo_show? + refute_predicate Pundit.policy!(@non_admin, @webauthn_verification), :avo_show? + end + + def test_avo_create + refute_predicate Pundit.policy!(@admin, WebauthnVerification), :avo_create? + refute_predicate Pundit.policy!(@non_admin, WebauthnVerification), :avo_create? + end + + def test_avo_update + refute_predicate Pundit.policy!(@admin, @webauthn_verification), :avo_update? + refute_predicate Pundit.policy!(@non_admin, @webauthn_verification), :avo_update? + end + + def test_avo_destroy + refute_predicate Pundit.policy!(@admin, @webauthn_verification), :avo_destroy? + refute_predicate Pundit.policy!(@non_admin, @webauthn_verification), :avo_destroy? + end +end diff --git a/test/system/sign_in_webauthn_test.rb b/test/system/sign_in_webauthn_test.rb index 4152278e90c..c1540f3e861 100644 --- a/test/system/sign_in_webauthn_test.rb +++ b/test/system/sign_in_webauthn_test.rb @@ -12,10 +12,12 @@ class SignInWebauthnTest < ApplicationSystemTestCase end teardown do - @authenticator.remove! + @authenticator&.remove! + Capybara.reset_sessions! + Capybara.use_default_driver end - test "sign in with webauthn" do + test "sign in with webauthn mfa" do visit sign_in_path fill_in "Email or Username", with: @user.email @@ -25,15 +27,13 @@ class SignInWebauthnTest < ApplicationSystemTestCase assert page.has_content? "Multi-factor authentication" assert page.has_content? "Security Device" - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true - click_on "Authenticate with security device" assert page.has_content? "Dashboard" refute page.has_content? "We now support security devices!" end - test "sign in with webauthn but it expired" do + test "sign in with webauthn mfa but it expired" do visit sign_in_path fill_in "Email or Username", with: @user.email @@ -43,8 +43,6 @@ class SignInWebauthnTest < ApplicationSystemTestCase assert page.has_content? "Multi-factor authentication" assert page.has_content? "Security Device" - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true - travel 30.minutes do click_on "Authenticate with security device" @@ -53,7 +51,25 @@ class SignInWebauthnTest < ApplicationSystemTestCase end end - test "sign in with webauthn using recovery codes" do + test "sign in with webauthn mfa wrong user handle" do + visit sign_in_path + + fill_in "Email or Username", with: @user.email + fill_in "Password", with: @user.password + click_button "Sign in" + + assert page.has_content? "Multi-factor authentication" + assert page.has_content? "Security Device" + + @user.update!(webauthn_id: "a") + + click_on "Authenticate with security device" + + refute page.has_content? "Dashboard" + assert page.has_content? "Sign in" + end + + test "sign in with webauthn mfa using recovery codes" do visit sign_in_path fill_in "Email or Username", with: @user.email @@ -68,4 +84,44 @@ class SignInWebauthnTest < ApplicationSystemTestCase assert page.has_content? "Dashboard" end + + test "sign in with webauthn" do + visit sign_in_path + + click_on "Authenticate with security device" + + assert page.has_content? "Dashboard" + refute page.has_content? "We now support security devices!" + end + + test "sign in with webauthn failure" do + visit sign_in_path + + @user.webauthn_credentials.find_each { |c| c.update!(external_id: "a") } + + click_on "Authenticate with security device" + + refute page.has_content? "Dashboard" + end + + test "sign in with webauthn user_handle changed failure" do + visit sign_in_path + + @user.update!(webauthn_id: "a") + + click_on "Authenticate with security device" + + refute page.has_content? "Dashboard" + assert page.has_content? "Sign in" + end + + test "sign in with webauthn does not expire" do + visit sign_in_path + + travel 30.minutes do + click_on "Authenticate with security device" + + assert page.has_content? "Dashboard" + end + end end diff --git a/test/system/webauthn_verification_test.rb b/test/system/webauthn_verification_test.rb index f7ce6b42ac0..ea559350d10 100644 --- a/test/system/webauthn_verification_test.rb +++ b/test/system/webauthn_verification_test.rb @@ -11,7 +11,6 @@ class WebAuthnVerificationTest < ApplicationSystemTestCase test "when verifying webauthn credential" do visit webauthn_verification_path(webauthn_token: @verification.path_token, params: { port: @port }) - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true assert_match "Authenticate with Security Device", page.html assert_match "Authenticating as #{@user.handle}", page.html @@ -28,7 +27,6 @@ class WebAuthnVerificationTest < ApplicationSystemTestCase test "when verifying webauthn credential on safari" do assert_poll_status("pending") visit webauthn_verification_path(webauthn_token: @verification.path_token, params: { port: @port }) - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true assert_match "Authenticate with Security Device", page.html assert_match "Authenticating as #{@user.handle}", page.html @@ -47,7 +45,6 @@ class WebAuthnVerificationTest < ApplicationSystemTestCase test "when client closes connection during verification" do visit webauthn_verification_path(webauthn_token: @verification.path_token, params: { port: @port }) - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true assert_match "Authenticate with Security Device", page.html assert_match "Authenticating as #{@user.handle}", page.html @@ -66,7 +63,6 @@ class WebAuthnVerificationTest < ApplicationSystemTestCase test "when port given does not match the client port" do wrong_port = 1111 visit webauthn_verification_path(webauthn_token: @verification.path_token, params: { port: wrong_port }) - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true assert_match "Authenticate with Security Device", page.html assert_match "Authenticating as #{@user.handle}", page.html @@ -84,7 +80,6 @@ class WebAuthnVerificationTest < ApplicationSystemTestCase test "when there is a client error" do @mock_client.response = @mock_client.bad_request_response visit webauthn_verification_path(webauthn_token: @verification.path_token, params: { port: @port }) - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true assert_match "Authenticate with Security Device", page.html assert_match "Authenticating as #{@user.handle}", page.html @@ -100,7 +95,6 @@ class WebAuthnVerificationTest < ApplicationSystemTestCase test "when webauthn verification is expired during verification" do visit webauthn_verification_path(webauthn_token: @verification.path_token, params: { port: @port }) - WebAuthn::AuthenticatorAssertionResponse.any_instance.stubs(:verify).returns true travel 3.minutes do assert_match "Authenticate with Security Device", page.html @@ -117,7 +111,9 @@ class WebAuthnVerificationTest < ApplicationSystemTestCase def teardown @mock_client.kill_server - @authenticator.remove! + @authenticator&.remove! + Capybara.reset_sessions! + Capybara.use_default_driver end private diff --git a/test/test_helper.rb b/test/test_helper.rb index 6198b1c8083..561e63bff5a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -150,9 +150,12 @@ def create_webauthn_credential click_button "Sign in" visit edit_settings_path - options = ::Selenium::WebDriver::VirtualAuthenticatorOptions.new + options = ::Selenium::WebDriver::VirtualAuthenticatorOptions.new( + resident_key: true, + user_verification: true, + user_verified: true + ) @authenticator = page.driver.browser.add_virtual_authenticator(options) - WebAuthn::PublicKeyCredentialWithAttestation.any_instance.stubs(:verify).returns true credential_nickname = "new cred" fill_in "Nickname", with: credential_nickname @@ -180,14 +183,18 @@ class ActionDispatch::IntegrationTest Gemcutter::Application.load_tasks +# Force loading of ActionDispatch::SystemTesting::* helpers +_ = ActionDispatch::SystemTestCase + class SystemTest < ActionDispatch::IntegrationTest include Capybara::DSL + include Capybara::Minitest::Assertions + include ActionDispatch::SystemTesting::TestHelpers::ScreenshotHelper + include ActionDispatch::SystemTesting::TestHelpers::SetupAndTeardown setup do Capybara.current_driver = :rack_test end - - teardown { reset_session! } end Shoulda::Matchers.configure do |config| From 8b0600e7bb8ab25ef3616a1b336f9c9511e528e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 14:34:22 +0000 Subject: [PATCH 073/112] Bump rdoc from 6.6.1 to 6.6.2 Bumps [rdoc](https://github.com/ruby/rdoc) from 6.6.1 to 6.6.2. - [Release notes](https://github.com/ruby/rdoc/releases) - [Changelog](https://github.com/ruby/rdoc/blob/master/History.rdoc) - [Commits](https://github.com/ruby/rdoc/compare/v6.6.1...v6.6.2) --- updated-dependencies: - dependency-name: rdoc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 265 +-------------------------------------------------- 1 file changed, 1 insertion(+), 264 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ca67b187bb6..c0457782425 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -520,7 +520,7 @@ GEM ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) - rdoc (6.6.1) + rdoc (6.6.2) psych (>= 4.0.0) regexp_parser (2.8.3) rexml (3.2.6) @@ -786,268 +786,5 @@ DEPENDENCIES webmock (~> 3.18) xml-simple (~> 1.1) -CHECKSUMS - actioncable (7.0.8) sha256=1f504ddb4ab6a34f7c52e9df924441a403e9f358bace330c36dcca6358ecfb84 - actionmailbox (7.0.8) sha256=9420037b801e44aa4e36cf113f4bd6eb25c17eb1b84d9c8865e8abf8846c14e5 - actionmailer (7.0.8) sha256=22574f270ed80bcd158f16b99068fad7772173e21c4332504238dae58fdccf70 - actionpack (7.0.8) sha256=2b998c6f6540ec07ad2e16b39f9acae22c8c4fda6b377417c2cfddf8c04d61d0 - actiontext (7.0.8) sha256=f7966296cec0a48e8644b59de2bfc8b7847d43a7809dfe040015a32aecc88744 - actionview (7.0.8) sha256=a22d692b9a6422f36882425301a4043fbe078a66e94a909a60a6a216246fd776 - active_link_to (1.0.5) sha256=4830847b3d14589df1e9fc62038ceec015257fce975ec1c2a77836c461b139ba - activejob (7.0.8) sha256=cb63d6a9f9af3379b7927bcb09a453d63db66ba9ec681018a8b21c5a0f8bc1b2 - activemodel (7.0.8) sha256=95beb8a2f6d1e0c7b4e3c0f17771b3a3024a25ad8c6e9d2d357e3cf1d5479c00 - activerecord (7.0.8) sha256=f236255235ab8c15f7a7bea3b77a35377801827e24d6e536dc776080f4dd8a13 - activestorage (7.0.8) sha256=8c2cae8de321ec899c7e7c4655331714fdd57f0966215286330f5c4d95a9db34 - activesupport (7.0.8) sha256=458316bb5098211ba9436d3c64d883177f09c49d1e29aa00f970d160275f13a1 - addressable (2.8.5) sha256=63f0fbcde42edf116d6da98a9437f19dd1692152f1efa3fcc4741e443c772117 - aes_key_wrap (1.1.0) sha256=b935f4756b37375895db45669e79dfcdc0f7901e12d4e08974d5540c8e0776a5 - aggregate_assertions (0.2.0) sha256=9bc51a48323a8e7b82f47cc38d48132817247345e5a8713686c9d65b25daca9e - amazing_print (1.5.0) sha256=f9f411b37257333a0f0cc16ce6520b2217a6f0b5a9f35656e1d403cd5e0c3362 - android_key_attestation (0.3.0) sha256=467eb01a99d2bb48ef9cf24cc13712669d7056cba5a52d009554ff037560570b - ansi (1.5.0) sha256=5408253274e33d9d27d4a98c46d2998266fd51cba58a7eb9d08f50e57ed23592 - argon2 (2.1.1) sha256=0fd6b50051102026e8b776b55fe5096065d84c214f837c94cede890370d3983c - ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12 - attr_required (1.0.1) sha256=024e10393bd30901e1adf6769bd756b873a5ef7da60f86f8f11066116b5742bc - autoprefixer-rails (10.4.16.0) sha256=40c4b14d6f26f66026cd0d4631baf18d6c56aab425b36059c8abbda17f19a706 - avo (2.46.0) sha256=bec3d981a5e6f3e324da1aaf5379b8f6efc26f3eff5aa38c043fdb2aa1e002eb - awrence (1.2.1) sha256=dd1d214c12a91f449d1ef81d7ee3babc2816944e450752e7522c65521872483e - aws-eventstream (1.3.0) sha256=f1434cc03ab2248756eb02cfa45e900e59a061d7fbdc4a9fd82a5dd23d796d3f - aws-partitions (1.861.0) sha256=47dc9d492feb3669dc725ce597351e09e45511d7b2cd99fee467c26cd464026b - aws-sdk-core (3.190.0) sha256=a3455fb3fc1691dd5331282ff16cb0b2ef136a5b63ed68b77e9fda447ea7cfa6 - aws-sdk-kms (1.74.0) sha256=b2537b21d051f6112c3bd71d2b60783435d08b152c2873d4593af93f539059c7 - aws-sdk-s3 (1.141.0) sha256=cadb88497af6736e86a4a1fc8eb42333fb27ae85901686334252c50862bdd02e - aws-sdk-sqs (1.69.0) sha256=142a748b303af9a6e9e54be122ac429cfb59c528656ed477bacae9f3042659db - aws-sigv4 (1.8.0) sha256=84dd99768b91b93b63d1d8e53ee837cfd06ab402812772a7899a78f9f9117cbc - base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 - bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 - benchmark-ips (2.12.0) sha256=09dd4d5be05db42470e7e7b01be7310564073a054e35d9d9ec7840c523f3dbcb - bindata (2.4.15) sha256=e567e4278223e041caf4e623de870b2df8a93479d8f13e2b478bad45e0fbc413 - bitarray (1.2.0) sha256=7f9f31fadbd87bf51544cf13058e81cd6ec408ff40f127902cef3d6767b23f11 - bloomer (1.0.0) sha256=57a0d3a78628db9a92c6723f06c67697e420abcdb05aa757c6dfae607251d272 - bootsnap (1.17.0) sha256=6b0ea4dd68f0d424968dcd13953c3f04b13a19a8761c540d3af13507fcfa1347 - brakeman (6.1.0) sha256=0d4066936dd58f0fe757d0ff1ec0744479be9ff06c771be4b581bdf0cb8d7403 - browser (5.3.1) sha256=62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c - builder (3.2.4) sha256=99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10 - byebug (11.1.3) sha256=2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b - capybara (3.39.2) sha256=d6f0ca5f30897e64789428d4b047a0df105815a302069913578ac35d5ca99884 - cbor (0.5.9.6) sha256=434a147658dd1df24ec9e7b3297c1fd4f8a691c97d0e688b3049df8e728b2114 - cgi (0.4.0) sha256=20a62f3b05234e09e141d461ad4de057c4aea699d72a958b3a3bf00680a49d48 - chartkick (5.0.5) sha256=62f096048bc5c7cdab00a0f125042863eb7832ab70ec317002484b6e35d3d8f2 - choice (0.2.0) sha256=a19617f7dfd4921b38a85d0616446620de685a113ec6d1ecc85bdb67bf38c974 - chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe - clearance (2.6.1) sha256=20593dfbe647d063b950f27bf5deca6f2090ea04db6728a403dd973f50d7ffca - coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b - compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b - concurrent-ruby (1.2.2) sha256=3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f - cose (1.3.0) sha256=63247c66a5bc76e53926756574fe3724cc0a88707e358c90532ae2a320e98601 - crack (0.4.5) sha256=798416fb29b8c9f655d139d5559169b39c4a0a3b8f8f39b7f670eec1af9b21b3 - crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d - css_parser (1.16.0) sha256=f70fb492254418522ea77c01d57bf64452d6c7465001926c3620d0b50289b1a2 - dalli (3.2.6) sha256=879092a248bc0c8ff4b40ff233686d794422e8af4f3a5395c7bdc7432de0fd8c - dartsass-ruby (3.0.1) sha256=e6929ed3b4059236b10766dfb4218a04e0fe708ee2a8c85fb24fb6edad6e6f7c - dartsass-sprockets (3.0.0) sha256=9dd57f8b2471356cb25ed0a3069c6242433eaac80affcf89113f4693629982a0 - datadog-ci (0.5.0) sha256=34da9a0e56888568f4ab0b92249be874cf9121af54a78be5e6105e4df65b044d - date (3.3.4) sha256=971f2cb66b945bcbea4ddd9c7908c9400b31a71bc316833cb42fa584b59d3291 - ddtrace (1.18.0) sha256=92539e16d6251c77cc91ac75021ac27507ac5a8242e686614935d9884a8dc038 - dead_end (4.0.0) sha256=695c8438993bb4c5415b1618a1b6e0afcae849ef2812fb8cb3846723904307eb - debase-ruby_core_source (3.2.3) sha256=bec157b6c01a07fdcaa09c42f6c6015bc8941a85e18ec6b9cd122c263437c912 - derailed_benchmarks (2.1.2) sha256=eaadc6206ceeb5538ff8f5e04a0023d54ebdd95d04f33e8960fb95a5f189a14f - docile (1.4.0) sha256=5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3 - dogstatsd-ruby (5.6.1) sha256=615dd1328c57ab66fb48cbf83b38ce2060d8353707be0bc3deb0ae960a659982 - domain_name (0.5.20190701) sha256=000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851 - dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 - dotenv-rails (2.8.1) sha256=b05b81b7f4e51d1359e218d92279db1d84b12440f7118c9df19b5dfffb620f6c - dry-initializer (3.1.1) sha256=4d267dea367ccabe498b259c62b909b99d577d6db547d9510561999403546dec - email_validator (2.2.3) sha256=4ff52cb6e6f90f898c66de66a221e69b3104767eeea46e7f4c2deab73cbbe399 - erb (4.0.3) sha256=351a013ffee7e4e40b593df76dd4d0b2bd612e14dbff1d66273221d0cff6a49a - erubi (1.12.0) sha256=27bedb74dfb1e04ff60674975e182d8ca787f2224f2e8143268c7696f42e4723 - et-orbi (1.2.7) sha256=3b693d47f94a4060ccc07e60adda488759b1e8b9228a633ebbad842dfc245fb4 - execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb - factory_bot (6.4.2) sha256=f236ff24212b721ef3794cf6a33c6c1f562da8a778264b907d58ab090fae6466 - factory_bot_rails (6.4.2) sha256=0c384f626f9eb8afa50b50f4c2b3d70769e53b54cda4101e4da11f187be8e2e0 - faraday (2.7.12) sha256=ed38dcd396d2defcf8a536bbf7ef45e6385392ab815fe087df46777be3a781a7 - faraday-follow_redirects (0.3.0) sha256=d92d975635e2c7fe525dd494fcd4b9bb7f0a4a0ec0d5f4c15c729530fdb807f9 - faraday-net_http (3.0.2) sha256=6882929abed8094e1ee30344a3369e856fe34530044630d1f652bf70ebd87e8d - faraday_middleware-aws-sigv4 (1.0.1) sha256=a001ea4f687ca1c60bad8f2a627196905ce3dbf285e461dc153240e92eaabe8f - ffi (1.16.3) sha256=6d3242ff10c87271b0675c58d68d3f10148fabc2ad6da52a18123f06078871fb - ffi-compiler (1.0.1) sha256=019f389b078a2fec9de7f4f65771095f80a447e34436b4588bcb629e2a564c30 - fugit (1.9.0) sha256=e92ae18828a094afdd753e42ded0b83ad3449d79adddc5d1ab88e28dbfedd221 - get_process_mem (0.2.7) sha256=4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba - globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9 - good_job (3.21.5) sha256=a1ca5decb3036306b4b968fee3a9a7321a23d25dc31372ed501193e06e3512d3 - google-protobuf (3.25.1) sha256=e740e099193f8dc4db638326e23868d6c799dbd5ae2fd7565e78d1530cc6d1a3 - gravtastic (3.2.6) sha256=ef98abcecf7c402b61cff1ae7c50a2c6d97dd22bac21ea9b421ce05bc03d734f - groupdate (6.4.0) sha256=65940645bf2a48f9b2d10ab7a1d19bdc78f3c89559d8fce39cea3448a15aec54 - hashdiff (1.0.1) sha256=2cd4d04f5080314ecc8403c4e2e00dbaa282dff395e2d031bc16c8d501bdd6db - hashie (5.0.0) sha256=9d6c4e51f2a36d4616cbc8a322d619a162d8f42815a792596039fc95595603da - heapy (0.2.0) sha256=74141e845d61ffc7c1e8bf8b127c8cf94544ec7a1181aec613288682543585ea - high_voltage (3.1.2) sha256=7786716114cc21b3754e537818a6d9b2c91aa1126aa77272977d50598e556c2f - honeybadger (5.4.0) sha256=b2ce46319887aafe053bad167fa1722244d48eaa1e8282d5af5b0ae0c8c9dd35 - http (5.1.1) sha256=fcaec14a4f82de6d2f9cb978c07326814c6c2b42b8974f6ec166ff19c645ebaf - http-cookie (1.0.5) sha256=73756d46c7dbdc7023deecdb8a171348ea95a1b99810b31cfe8b4fb4e9a6318f - http-form_data (2.3.0) sha256=cc4eeb1361d9876821e31d7b1cf0b68f1cf874b201d27903480479d86448a5f3 - http_accept_language (2.1.1) sha256=0043f0d55a148cf45b604dbdd197cb36437133e990016c68c892d49dbea31634 - httparty (0.21.0) sha256=00ef7bf9a71f30a3bff88edeb5b16a34bea883ab67c246b3f0db2d6794fe1214 - i18n (1.14.1) sha256=9d03698903547c060928e70a9bc8b6b87fda674453cda918fc7ab80235ae4a61 - inline_svg (1.9.0) sha256=f44c5e3d2e401fd619ad3047b7c8cee384517d855edb1d1fb1a248d3cae535d6 - jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 - job-iteration (1.4.1) sha256=7243c40e4decc3d49529867e9c504afaea332976c967ffdebed9ff863c6424af - jquery-rails (4.6.0) sha256=3c4e6bf47274340b44d836b8aa1b5472c6d451e2739af5ec094421f39025a7e2 - json (2.6.3) sha256=86aaea16adf346a2b22743d88f8dcceeb1038843989ab93cda44b5176c845459 - json-jwt (1.16.3) sha256=51c971248913ad82428ab8f7c6dca8b8320455fbe941437ed197444e1b6065d9 - jwt (2.7.0) sha256=b7e0b8669170abfe90cae8b6534d3d05d60fa3f785f0f005e44a41abcb1fd227 - kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e - kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 - kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 - kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff - launchdarkly-server-sdk (8.0.0) sha256=c5b0a0df6a87535037486390489bd18815690c285a8378f810a3c7519e894e55 - launchy (2.5.2) sha256=8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b - ld-eventsource (2.2.2) sha256=5ea087a6f06bbd8e325d2c1aaead50f37f13d025b952985739e9380a78a96beb - letter_opener (1.8.1) sha256=2d2841ec4767786996498f3f71e591458d1d0ba14d7df5162a5b044a6e24adf8 - letter_opener_web (2.0.0) sha256=33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f - libdatadog (5.0.0.1.0) sha256=c61b02584c02447089ad8e71b7aa8e5a660fd675b727635c1a2eaec59b1840e8 - libddwaf (1.14.0.0.0) sha256=b91ea9675f7d79d1cd10dd6513e3706760ac442cb8902164fbcef79b7082a8fd - listen (3.8.0) sha256=9679040ac6e7845ad9f19cf59ecde60861c78e2fae57a5c20fe35e94959b2f8f - llhttp-ffi (0.4.0) sha256=e5f7327db3cf8007e648342ef76347d6e0ae545a8402e519cca9c886eb37b001 - loofah (2.22.0) sha256=10d76e070c86b12fec74b6a9515fd1940f4459198b991342d0a7897d86c372fe - mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad - maintenance_tasks (2.3.3) sha256=c27ccee383140dbe73921ea66488b5546e35781edc424b7772abf848981811eb - marcel (1.0.2) sha256=a013b677ef46cbcb49fd5c59b3d35803d2ee04dd75d8bfdc43533fc5a31f7e4e - matrix (0.4.2) sha256=71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0 - memory_profiler (1.0.1) sha256=38cdb42f22d9100df2eba0365c199724b58b05c38e765cd764a07392916901b1 - meta-tags (2.19.0) sha256=7e7f78d430dfbf5efaff97d02609766e6a0296ea82db3bba776af9f8ca41e2ef - method_source (1.0.0) sha256=d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede - mini_histogram (0.3.1) sha256=6a114b504e4618b0e076cc672996036870f7cc6f16b8e5c25c0c637726d2dd94 - mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef - mini_portile2 (2.8.5) sha256=7a37db8ae758086c3c3ac3a59c036704d331e965d5e106635e4a42d6e66089ce - minitest (5.20.0) sha256=a3faf26a757ced073aaae0bd10481340f53e221a4f50d8a6033591555374752e - minitest-gcstats (1.3.0) sha256=c1bef6374d53771a6f7776f7a9c9fac5d1231fc83dfff5acea16fed66b886afd - minitest-profile (0.0.2) sha256=caf5306217b59ea2854d75ff6b312e2c139f2948f2b6a39486677b8317b7048e - minitest-reporters (1.6.1) sha256=f8fe74e46ab40dada29402f55ca236368d0af65afc410db4219189b7a1c0fc38 - mocha (2.1.0) sha256=f98757589e417b492383592ca6a3d7a98ed367a2289c797d13c120b522a25453 - msgpack (1.7.2) sha256=59ab62fd8a4d0dfbde45009f87eb6f158ab2628a7c48886b0256f175166baaa8 - multi_json (1.15.0) sha256=1fd04138b6e4a90017e8d1b804c039031399866ff3fbabb7822aea367c78615d - multi_xml (0.6.0) sha256=d24393cf958adb226db884b976b007914a89c53ad88718e25679d7008823ad52 - net-imap (0.4.6) sha256=31975db12d9f7979338ad29934ded585a1fdc881b096dc72b35596a563281a3e - net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 - net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 - net-smtp (0.4.0) sha256=2d7eb8de289ba8dce3f0d436ee40b9366bea28354c5ba183c8ab2ec05139a3e7 - nio4r (2.6.1) sha256=284aae4adc431498a8f2a8e6027da72bca5f2ea8134d770ffc6f8e45bf6b29f9 - nokogiri (1.15.5) sha256=22448ca35dbcbdcec60dbe25ccf452b685a5436c28f21b2fec2e20917aba9100 - oauth2 (2.0.9) sha256=b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb - octokit (8.0.0) sha256=c362be3ee7cd2f01f5af98c06b182587fbe4ae57c2385cdf94a1b04154d8d085 - omniauth (2.1.1) sha256=f2e43fc889fd915838bd1c812569e7e8211b7a1a53d1e0dbb9f694a163d7b297 - omniauth-github (2.0.1) sha256=8ff8e70ac6d6db9d52485eef52cfa894938c941496e66b52b5e2773ade3ccad4 - omniauth-oauth2 (1.8.0) sha256=b2f8e9559cc7e2d4efba57607691d6d2b634b879fc5b5b6ccfefa3da85089e78 - omniauth-rails_csrf_protection (1.0.1) sha256=fc546aeb7d43b7b9d7737051c380156e61c8f080b898cd4934d523eaa7e59acf - openid_connect (2.2.0) sha256=45b4c7621be512c8bd3abde88587c2639d03f6371f6b3f3d9b5a98c377e029c8 - opensearch-ruby (3.1.0) sha256=fc6147c682be51356140a8163e46713018b5220bde3f80ba0b36a0fd1feffdb6 - openssl (3.1.0) sha256=e3a01279e918a7a5cf741db69b124864878b1a9783b1f2d34854bc1d444ac430 - openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80 - optimist (3.1.0) sha256=81886f53ee8919f330aa30076d320d88eef9bc85aae2275376b4afb007c69260 - pagy (6.2.0) sha256=6dd5a71077faaefee1d79c757e8ed974c6eba4303a2d743f46d06abd6e8f876f - parallel (1.22.1) sha256=ebdf1f0c51f182df38522f70ba770214940bef998cdb6e00f36492b29699761f - parser (3.2.1.1) sha256=2c3fe2cbc54c90b74f2de92e61bc533090bc6eb84290db03a9dd1eee902b5ba8 - pg (1.5.4) sha256=04f7b247151c639a0b955d8e5a9a41541343f4640aa3c2bdf749a872c339d25d - phlex (1.9.0) sha256=75b06a334833542594ef65d0532c6c964c78648f459d855cb54cd8c0ddcdb549 - phlex-rails (1.1.1) sha256=b0e82d0ba541eca55ca39051de8be2817a7ed400f8a630e21b261239c8f812d0 - pry (0.14.1) sha256=99b6df0665875dd5a39d85e0150aa5a12e2bb4fef401b6c4f64d32ee502f8454 - pry-byebug (3.10.1) sha256=c8f975c32255bfdb29e151f5532130be64ff3d0042dc858d0907e849125581f8 - psych (5.1.1.1) sha256=44b0d1823629ac815f1f470af642dc7261489d67feb622a3f5573aa9f5cc5f72 - public_suffix (5.0.4) sha256=35cd648e0d21d06b8dce9331d19619538d1d898ba6d56a6f2258409d2526d1ae - puma (6.4.0) sha256=d5dda11362744df9f4694708a62e3cfddf72eba7498c16016ebbb30f106712f9 - pundit (2.3.1) sha256=5c62a2e7c65278828d51fb921a3e608472a262a39a046d53d0e78588a556b181 - pwned (2.3.0) sha256=63f5a9576f109203684e9dd053f815649fd5bc0a0348b7190568272641b22353 - raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 - racc (1.7.3) sha256=b785ab8a30ec43bce073c51dbbe791fd27000f68d1c996c95da98bf685316905 - rack (2.2.8) sha256=7b83a1f1304a8f5554c67bc83632d29ecd2ed1daeb88d276b7898533fde22d97 - rack-attack (6.7.0) sha256=3ca47e8f66cd33b2c96af53ea4754525cd928ed3fa8da10ee6dad0277791d77c - rack-oauth2 (2.2.0) sha256=a03f6790350b29b0ace98f66eb65ec15adc4193c6799e9d4de76445864f70737 - rack-protection (3.0.5) sha256=3a428f9de18ee2a4080e2fab308f20f9e98d74dcbe06ed407a8035b46ba822a8 - rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb - rack-utf8_sanitizer (1.9.1) sha256=6414b70172f5678e23044abf1d00f6a32e62a335507c9548bc5caf9e3bff6da0 - rails (7.0.8) sha256=8e43af921acf766fb429126f020ec90c3b25809631f8fbdff95c3553828d5867 - rails-controller-testing (1.0.5) sha256=741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94 - rails-dom-testing (2.2.0) sha256=e515712e48df1f687a1d7c380fd7b07b8558faa26464474da64183a7426fa93b - rails-erd (1.7.2) sha256=0b17d0fba25d319d8da8af7a3e5e2149d02d6187cc7351e8be43423f07c48bcd - rails-html-sanitizer (1.6.0) sha256=86e9f19d2e6748890dcc2633c8945ca45baa08a1df9d8c215ce17b3b0afaa4de - rails-i18n (7.0.8) sha256=ee9ff92bc4734085aaf234157a7c1c795a29bf3f7edeede1e14e3d4247dd12cd - rails_semantic_logger (4.14.0) sha256=738ca601d544108765bb0c9ea45d5ef7967777fecc5bba83bc9c2d86ac4a127f - railties (7.0.8) sha256=12325c3933efd33f8ead640197dec3b8c27c8d45607dd68b7b925896bf09cc69 - rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a - rake (13.1.0) sha256=be6a3e1aa7f66e6c65fa57555234eb75ce4cf4ada077658449207205474199c6 - rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe - rb-inotify (0.10.1) sha256=050062d4f31d307cca52c3f6a7f4b946df8de25fc4bd373e1a5142e41034a7ca - rbtrace (0.5.0) sha256=85e7743d591b77a9b134b286e2d90c47e99995ee72f7503e0ba957d396c66167 - rdoc (6.6.1) sha256=d9d46f8f9bef79cfe45d9bea2b6b7f4d0a1c02bc9582135175faf6d5afcc4afd - regexp_parser (2.8.1) sha256=83f63e2bfae3db38f988c66f114485140ff1791321fd827480bc75aa42cacb8c - rexml (3.2.6) sha256=e0669a2d4e9f109951cb1fde723d8acd285425d81594a2ea929304af50282816 - roadie (5.2.0) sha256=f4dc8e41ccc331ebe4833cfa8e7495446db8bfd725c1a9b480147cb53c4945e3 - roadie-rails (3.1.0) sha256=5a45e1a7eb2f7cac29325ef8be64684c9d67cec608b235a69e38a7b79c64de21 - rotp (6.3.0) sha256=75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854 - rqrcode (2.2.0) sha256=23eea88bb44c7ee6d6cab9354d08c287f7ebcdc6112e1fe7bcc2d010d1ffefc1 - rqrcode_core (1.2.0) sha256=cf4989dc82d24e2877984738c4ee569308625fed2a810960f1b02d68d0308d1a - rubocop (1.48.0) sha256=2a90d242c2155c6d72cfaaf86d68bbbe58a6816cc8b192ac8c6702466c40c231 - rubocop-ast (1.27.0) sha256=0a88b64598ce7a7422579a11f419e4424fb2b10537b34b1a1b107f88f95c2a9a - rubocop-capybara (2.17.1) sha256=d648a6efd9607da494e379cdeb8d4569f88eb0e6c0a35ba964af2f0c6815e5df - rubocop-minitest (0.29.0) sha256=e815a04e68bffe10701c7537d388cbfef85b44d445d2651b7db9db8b70448ab6 - rubocop-performance (1.16.0) sha256=cc764f84bf37c6ef269973e686c3b33f258ffd612130e40856b25103de06efd8 - rubocop-rails (2.18.0) sha256=a646566f436b18f66e0b89a23df87ec4eb5318a74c9e6044271aca126b03fd8d - ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 - ruby-magic (0.6.0) sha256=7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101 - ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 - ruby-statistics (3.0.2) sha256=fb53e7a9f9681dac144c02539d3535fb2e8fae626f78b907219b0586ff53ec20 - ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef - rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f - safety_net_attestation (0.4.0) sha256=96be2d74e7ed26453a51894913449bea0e072f44490021545ac2d1c38b0718ce - sass-embedded (1.66.1) sha256=5e7660b520109584f441d60bfa9d349884dab6be70aa4392a389eb82d7fc0e0d - sawyer (0.9.2) sha256=fa3a72d62a4525517b18857ddb78926aab3424de0129be6772a8e2ba240e7aca - searchkick (5.3.1) sha256=dc1181543f6a68354e380651f235fa7f3df6a09e4cd67fc284dc293fa9860f57 - selenium-webdriver (4.16.0) sha256=237013649ea52435fe386cf4069b56d3f64c127b05af5f4d5c059bd71ee4c3e3 - semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 - semantic_logger (4.15.0) sha256=ec4f56122b5d2e2117d148b86c69fb62c1194a2b01a271be04ba8678a85f81ff - shoryuken (6.1.1) sha256=394affa8aca9160c72e44a0a2f89b3bab635bf217d4cb1c26f1fc5371e4a1277 - shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34 - shoulda-matchers (5.3.0) sha256=f6ba863b8752bb5956aaa73b046d5df5ecfbe9a7acb61f31bf853613e0932f86 - simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5 - simplecov-cobertura (2.1.0) sha256=2c6532e34df2e38a379d72cef9a05c3b16c64ce90566beebc6887801c4ad3f02 - simplecov-html (0.12.3) sha256=4b1aad33259ffba8b29c6876c12db70e5750cb9df829486e4c6e5da4fa0aa07b - simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 - snaky_hash (2.0.1) sha256=1ac87ec157fcfe7a460e821e0cd48ae1e6f5e3e082ab520f03f31a9259dbdc31 - sprockets (4.0.2) sha256=68d44758ae3da4f172c80abeff323100b4c4bb2f0ff6e1a3cb6e6c69e8e26f46 - sprockets-rails (3.4.2) sha256=36d6327757ccf7460a00d1d52b2d5ef0019a4670503046a129fa1fb1300931ad - statsd-instrument (3.6.1) sha256=fdaf73665c9a4d99aeddcda2e70fc266935919225dc0bf01257234f59f8f55df - stringio (3.1.0) sha256=c1f6263ae03a15025e51194ab19b06b15e06adcaaedb7f5f6c06ab60f5d67718 - strong_migrations (1.6.4) sha256=0e67822fc47e778835339d10955d687d71db7f36a89e76d399542661e20e8761 - swd (2.0.2) sha256=ea6201e0b4477973388ceba7c028a417d6bf1096e542bb1109b8b349ccdf216b - tailwindcss-rails (2.0.33) sha256=27541d2091a3cdb67653bb0e89c6d881c6ca55f92434c41109c172d633f01ea6 - terser (1.1.20) sha256=4921b2f9daf4d830eb4af90d692b5d73e899352f9eec77dc345da5717e479e2e - thor (1.3.0) sha256=1adc7f9e5b3655a68c71393fee8bd0ad088d14ee8e83a0b73726f23cbb3ca7c3 - tilt (2.2.0) sha256=e76f850e611128a87992bb13ba74807624a9b8ec748e2c2ea7139580f67ab22e - timeout (0.4.1) sha256=6f1f4edd4bca28cffa59501733a94215407c6960bd2107331f0280d4abdebb9a - toxiproxy (2.0.2) sha256=2e3b53604fb921d40da3db8f78a52b3133fcae33e93d440725335b15974e440a - tpm-key_attestation (0.12.0) sha256=e133d80cf24fef0e7a7dfad00fd6aeff01fc79875fbfc66cd8537bbd622b1e6d - turbo-rails (1.5.0) sha256=b426cc762fb0940277729b3f1751a9f0bd269f5613c1d62ac73e5f0be7c7a83e - turbo_power (0.5.0) sha256=869726c3a4308b038d6244f7a80eb1331bdd3171fa320dddcff71899fcae24fb - tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b - unf (0.1.4) sha256=4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e - unf_ext (0.0.8.2) sha256=90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa - unicode-display_width (2.4.2) sha256=6a10205d1a19ca790c4e53064ba93f09d9eb234bf6bd135d9deb6001c21428be - unpwn (1.0.0) sha256=6239d17d46a882b3719b24fb79c78a34caff89d57ab0f5e546be5b5c882bc7d3 - validate_email (0.1.6) sha256=9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4 - validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 - validates_formatting_of (0.9.0) sha256=139590a4b87596dbfb04d93e897bd2e6d30fb849d04fab0343e71ed2ca856e7e - version_gem (1.1.1) sha256=3c2da6ded29045ddcc0387e152dc634e1f0c490b7128dce0697ccc1cf0915b6c - view_component (3.8.0) sha256=3ec17fe3b56e0d679064b27dde796d7d4254d7bf8110594de85471b86ac0c5f6 - webauthn (3.0.0) sha256=3f77d422c2a8a4b31e56cf42f83414bd066e0506e9896936e1730262dc4a20e6 - webfinger (2.1.2) sha256=d2549726f327b2a3dba4496b5430a18ef638cd51b856aafb4d39691d45a4617c - webmock (3.19.1) sha256=eae7eee33989478188451f1fda4224d7fbe097c5c14e96b40b57347ef2d5d16d - websocket (1.2.10) sha256=2cc1a4a79b6e63637b326b4273e46adcddf7871caa5dc5711f2ca4061a629fa8 - websocket-driver (0.7.6) sha256=f69400be7bc197879726ad8e6f5869a61823147372fd8928836a53c2c741d0db - websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d - xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e - zeitwerk (2.6.12) sha256=561e12975d0332fd3b62cc859aff3bab432e5f320689c8a10cd4674b5c0439be - BUNDLED WITH 2.5.1 From 240b365230704fcdb128743c46b5e268362c2a9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:31:55 -0800 Subject: [PATCH 074/112] Bump rbtrace from 0.5.0 to 0.5.1 (#4300) Bumps [rbtrace](https://github.com/tmm1/rbtrace) from 0.5.0 to 0.5.1. - [Changelog](https://github.com/tmm1/rbtrace/blob/master/CHANGELOG) - [Commits](https://github.com/tmm1/rbtrace/compare/v0.5.0...v0.5.1) --- updated-dependencies: - dependency-name: rbtrace dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e4d4cdf8e11..e98412a06dc 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,7 @@ gem "pg", "~> 1.4" gem "puma", "~> 6.4" gem "rack", "~> 2.2" gem "rack-utf8_sanitizer", "~> 1.8" -gem "rbtrace", "~> 0.5.0" +gem "rbtrace", "~> 0.5.1" gem "rdoc", "~> 6.6" gem "roadie-rails", "~> 3.0" gem "ruby-magic", "~> 0.6" diff --git a/Gemfile.lock b/Gemfile.lock index c0457782425..29bae60e30d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -516,7 +516,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rbtrace (0.5.0) + rbtrace (0.5.1) ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) @@ -755,7 +755,7 @@ DEPENDENCIES rails-erd (~> 1.7) rails-i18n (~> 7.0) rails_semantic_logger (~> 4.14) - rbtrace (~> 0.5.0) + rbtrace (~> 0.5.1) rdoc (~> 6.6) roadie-rails (~> 3.0) rotp (~> 6.2) From bf6aa45dc9badc1e3642e5cdbf6619289eda7469 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 20 Dec 2023 00:32:46 +0800 Subject: [PATCH 075/112] Use ruby/setup-ruby to switch rubygems version (#4298) --- .github/actions/setup-rubygems.org/action.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/actions/setup-rubygems.org/action.yml b/.github/actions/setup-rubygems.org/action.yml index dbd26c1b503..4c3e6974f68 100644 --- a/.github/actions/setup-rubygems.org/action.yml +++ b/.github/actions/setup-rubygems.org/action.yml @@ -18,20 +18,14 @@ runs: shell: bash run: | timeout 300 bash -c "until curl --silent --output /dev/null http://localhost:9200/_cat/health?h=st; do printf '.'; sleep 5; done; printf '\n'" - - uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 # v1.157.0 + - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 with: ruby-version: ${{ inputs.ruby-version }} bundler-cache: true - - name: set rubygems version + rubygems: ${{ inputs.rubygems-version }} + - name: Print bundle environment shell: bash - run: | - if [ "${{ inputs.rubygems-version }}" != "latest" ]; then - gem update --system ${{ inputs.rubygems-version }}; - else - gem update --system - fi - gem --version - bundle --version + run: bundle env - name: Prepare environment shell: bash run: | From 59fbea9583f294ba7c86c3df2d65d6c87937f7ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 01:43:30 +0000 Subject: [PATCH 076/112] Bump actions/upload-artifact from 3.1.3 to 4.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.3 to 4.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/a8a3f3ad30e3422c9c7b888a15615d19a852ae32...c7d193f32edcb7bfad88892161225aeda64e9392) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4e8bbee38b5..cd9e276255e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,7 +50,7 @@ jobs: env: ENVIRONMENT: "${{ matrix.environment }}" REVISION: "${{ github.sha }}" - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: "${{ matrix.environment }}.rendered.yaml" path: "config/deploy/${{ matrix.environment }}.rendered.yaml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b996e5ab12e..023dfac8cba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,7 +56,7 @@ jobs: - name: Save capybara screenshots if: ${{ failure() && steps.test-all.outcome == 'failure' }} - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: capybara-screenshots path: tmp/capybara From 1234fc80aa31cbf7539e13fc5b18b79c4e9fbd1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:25:28 -0800 Subject: [PATCH 077/112] Bump omniauth from 2.1.1 to 2.1.2 (#4301) --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 29bae60e30d..2d3cbea98a0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -389,7 +389,7 @@ GEM octokit (8.0.0) faraday (>= 1, < 3) sawyer (~> 0.9) - omniauth (2.1.1) + omniauth (2.1.2) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection @@ -461,8 +461,8 @@ GEM faraday-follow_redirects json-jwt (>= 1.11.0) rack (>= 2.1.0) - rack-protection (3.0.5) - rack + rack-protection (3.1.0) + rack (~> 2.2, >= 2.2.4) rack-test (2.1.0) rack (>= 1.3) rack-utf8_sanitizer (1.9.1) From 0d850fd99f2c7e2de601413598fc05602a66fd5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:25:59 -0800 Subject: [PATCH 078/112] Bump faraday from 2.7.12 to 2.8.0 (#4302) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e98412a06dc..0e5c6d6e0a1 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ gem "dalli", "~> 3.2" gem "ddtrace", "~> 1.18", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" gem "google-protobuf", "~> 3.25" -gem "faraday", "~> 2.7" +gem "faraday", "~> 2.8" gem "good_job", "~> 3.21" gem "gravtastic", "~> 3.2" gem "high_voltage", "~> 3.1" diff --git a/Gemfile.lock b/Gemfile.lock index 2d3cbea98a0..eafb8164e8b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -220,7 +220,7 @@ GEM factory_bot_rails (6.4.2) factory_bot (~> 6.4) railties (>= 5.0.0) - faraday (2.7.12) + faraday (2.8.0) base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) @@ -711,7 +711,7 @@ DEPENDENCIES dogstatsd-ruby (~> 5.5) dotenv-rails (~> 2.8) factory_bot_rails (~> 6.4) - faraday (~> 2.7) + faraday (~> 2.8) faraday_middleware-aws-sigv4 (~> 1.0) good_job (~> 3.21) google-protobuf (~> 3.25) From 74393eccaa1cafcf4eb7366eb94deba015cf11cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:26:19 -0800 Subject: [PATCH 079/112] Bump tailwindcss-rails from 2.0.33 to 2.1.0 (#4303) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 0e5c6d6e0a1..1500884c1f1 100644 --- a/Gemfile +++ b/Gemfile @@ -66,7 +66,7 @@ gem "amazing_print", "~> 1.4" gem "rails_semantic_logger", "~> 4.14" group :assets, :development do - gem "tailwindcss-rails", "~> 2.0" + gem "tailwindcss-rails", "~> 2.1" end group :assets do diff --git a/Gemfile.lock b/Gemfile.lock index eafb8164e8b..ea33220cd3f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -619,7 +619,7 @@ GEM attr_required (>= 0.0.5) faraday (~> 2.0) faraday-follow_redirects - tailwindcss-rails (2.0.33) + tailwindcss-rails (2.1.0) railties (>= 6.0.0) terser (1.1.20) execjs (>= 0.3.0, < 3) @@ -776,7 +776,7 @@ DEPENDENCIES sprockets-rails (~> 3.4) statsd-instrument (~> 3.5) strong_migrations (~> 1.6) - tailwindcss-rails (~> 2.0) + tailwindcss-rails (~> 2.1) terser (~> 1.1) toxiproxy (~> 2.0) unpwn (~> 1.0) From ff15586cd1f7bac43a13679242c720118a6e1b44 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 20 Dec 2023 17:38:19 -0800 Subject: [PATCH 080/112] Remove ApiKey#user_id entirely (#4258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josef Šimánek --- app/models/api_key.rb | 2 -- db/migrate/20231130000000_remove_user_id_from_api_keys.rb | 5 +++++ db/schema.rb | 3 --- 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20231130000000_remove_user_id_from_api_keys.rb diff --git a/app/models/api_key.rb b/app/models/api_key.rb index bcf4e219d1c..7d93eeeab80 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -2,8 +2,6 @@ class ApiKey < ApplicationRecord API_SCOPES = %i[index_rubygems push_rubygem yank_rubygem add_owner remove_owner access_webhooks show_dashboard].freeze APPLICABLE_GEM_API_SCOPES = %i[push_rubygem yank_rubygem add_owner remove_owner].freeze - self.ignored_columns += %w[user_id] - belongs_to :owner, polymorphic: true has_one :api_key_rubygem_scope, dependent: :destroy diff --git a/db/migrate/20231130000000_remove_user_id_from_api_keys.rb b/db/migrate/20231130000000_remove_user_id_from_api_keys.rb new file mode 100644 index 00000000000..37f3d30626a --- /dev/null +++ b/db/migrate/20231130000000_remove_user_id_from_api_keys.rb @@ -0,0 +1,5 @@ +class RemoveUserIdFromApiKeys < ActiveRecord::Migration[7.0] + def change + safety_assured { remove_column :api_keys, :user_id, :integer } + end +end diff --git a/db/schema.rb b/db/schema.rb index cf39d481311..39105ce57f3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -37,7 +37,6 @@ end create_table "api_keys", force: :cascade do |t| - t.bigint "user_id" t.string "name", null: false t.string "hashed_key", null: false t.boolean "index_rubygems", default: false, null: false @@ -58,7 +57,6 @@ t.bigint "owner_id" t.index ["hashed_key"], name: "index_api_keys_on_hashed_key", unique: true t.index ["owner_type", "owner_id"], name: "index_api_keys_on_owner" - t.index ["user_id"], name: "index_api_keys_on_user_id" t.check_constraint "owner_id IS NOT NULL", name: "api_keys_owner_id_null" t.check_constraint "owner_type IS NOT NULL", name: "api_keys_owner_type_null" end @@ -525,7 +523,6 @@ t.index ["user_id"], name: "index_webauthn_verifications_on_user_id", unique: true end - add_foreign_key "api_keys", "users" add_foreign_key "oidc_api_key_roles", "oidc_providers" add_foreign_key "oidc_api_key_roles", "users" add_foreign_key "oidc_id_tokens", "api_keys" From ec9c33d4e892c3a414e3f21167c8b170a0e16454 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 20 Dec 2023 18:07:33 -0800 Subject: [PATCH 081/112] Add pp to gemfile (#4304) So we ensure it is loaded for faraday logger Adding to gemfile since it is a stdlib gem --- Gemfile | 1 + Gemfile.lock | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Gemfile b/Gemfile index 1500884c1f1..bee06395f0a 100644 --- a/Gemfile +++ b/Gemfile @@ -64,6 +64,7 @@ gem "groupdate", "~> 6.2" # Logging gem "amazing_print", "~> 1.4" gem "rails_semantic_logger", "~> 4.14" +gem "pp", "0.5.0" group :assets, :development do gem "tailwindcss-rails", "~> 2.1" diff --git a/Gemfile.lock b/Gemfile.lock index ea33220cd3f..1d02bfbf66d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -435,6 +435,9 @@ GEM phlex (~> 1.9) railties (>= 6.1, < 8) zeitwerk (~> 2.6) + pp (0.5.0) + prettyprint + prettyprint (0.2.0) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) @@ -743,6 +746,7 @@ DEPENDENCIES opensearch-ruby (~> 3.1) pg (~> 1.4) phlex-rails (~> 1.1) + pp (= 0.5.0) pry-byebug (~> 3.10) puma (~> 6.4) pundit (~> 2.3) From e77453107ebe248be43da80657178c5265704bbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:01:32 -0800 Subject: [PATCH 082/112] Bump faraday from 2.8.0 to 2.8.1 (#4306) --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1d02bfbf66d..6e3b3da8cd5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -220,7 +220,7 @@ GEM factory_bot_rails (6.4.2) factory_bot (~> 6.4) railties (>= 5.0.0) - faraday (2.8.0) + faraday (2.8.1) base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) From 6171de431211c1cca0f5b5dd12719da37bb5b2d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 08:17:31 -0800 Subject: [PATCH 083/112] Bump maintenance_tasks from 2.3.3 to 2.4.0 (#4305) --- Gemfile | 2 +- Gemfile.lock | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index bee06395f0a..75d2d1ca536 100644 --- a/Gemfile +++ b/Gemfile @@ -50,7 +50,7 @@ gem "unpwn", "~> 1.0" gem "webauthn", "~> 3.0" gem "browser", "~> 5.3", ">= 5.3.1" gem "bcrypt", "~> 3.1" -gem "maintenance_tasks", "~> 2.1" +gem "maintenance_tasks", "~> 2.4" gem "strong_migrations", "~> 1.6" gem "phlex-rails", "~> 1.1" diff --git a/Gemfile.lock b/Gemfile.lock index 6e3b3da8cd5..1381d4ebf35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -337,12 +337,13 @@ GEM net-imap net-pop net-smtp - maintenance_tasks (2.3.3) + maintenance_tasks (2.4.0) actionpack (>= 6.0) activejob (>= 6.0) activerecord (>= 6.0) job-iteration (>= 1.3.6) railties (>= 6.0) + zeitwerk (>= 2.6.2) marcel (1.0.2) matrix (0.4.2) memory_profiler (1.0.1) @@ -731,7 +732,7 @@ DEPENDENCIES letter_opener_web (~> 2.0) listen (~> 3.8) mail (~> 2.8) - maintenance_tasks (~> 2.1) + maintenance_tasks (~> 2.4) memory_profiler (~> 1.0) minitest (~> 5.18) minitest-gcstats (~> 1.3) From 0f2dad23da09293a06f54a6181b81aa8b3fda0f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 16:42:42 -0800 Subject: [PATCH 084/112] Bump honeybadger from 5.4.0 to 5.4.1 (#4307) --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1381d4ebf35..9bf9f228c9d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -257,7 +257,7 @@ GEM heapy (0.2.0) thor high_voltage (3.1.2) - honeybadger (5.4.0) + honeybadger (5.4.1) http (5.1.1) addressable (~> 2.8) http-cookie (~> 1.0) From 100d199d949bd6a33cd57652c9ee410c9fb3a5c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 14:13:37 +0000 Subject: [PATCH 085/112] Bump brakeman from 6.1.0 to 6.1.1 Bumps [brakeman](https://github.com/presidentbeef/brakeman) from 6.1.0 to 6.1.1. - [Release notes](https://github.com/presidentbeef/brakeman/releases) - [Changelog](https://github.com/presidentbeef/brakeman/blob/main/CHANGES.md) - [Commits](https://github.com/presidentbeef/brakeman/compare/v6.1.0...v6.1.1) --- updated-dependencies: - dependency-name: brakeman dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9bf9f228c9d..5a3d8c4b00a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,7 +129,8 @@ GEM msgpack bootsnap (1.17.0) msgpack (~> 1.2) - brakeman (6.1.0) + brakeman (6.1.1) + racc browser (5.3.1) builder (3.2.4) byebug (11.1.3) From 3b396bc18d40a2fd898004be019c3dd7f106b458 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 14:14:04 +0000 Subject: [PATCH 086/112] Bump aws-sdk-s3 from 1.141.0 to 1.142.0 Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.141.0 to 1.142.0. - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-s3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 75d2d1ca536..6cd077b61c0 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" -gem "aws-sdk-s3", "~> 1.141" +gem "aws-sdk-s3", "~> 1.142" gem "aws-sdk-sqs", "~> 1.69" gem "bootsnap", "~> 1.16" gem "clearance", "~> 2.6" diff --git a/Gemfile.lock b/Gemfile.lock index 9bf9f228c9d..630eba2e094 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,16 +101,16 @@ GEM zeitwerk (>= 2.6.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.861.0) - aws-sdk-core (3.190.0) + aws-partitions (1.873.0) + aws-sdk-core (3.190.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.74.0) + aws-sdk-kms (1.75.0) aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.141.0) + aws-sdk-s3 (1.142.0) aws-sdk-core (~> 3, >= 3.189.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) @@ -698,7 +698,7 @@ DEPENDENCIES amazing_print (~> 1.4) autoprefixer-rails (~> 10.4) avo (~> 2.46) - aws-sdk-s3 (~> 1.141) + aws-sdk-s3 (~> 1.142) aws-sdk-sqs (~> 1.69) bcrypt (~> 3.1) bootsnap (~> 1.16) From 5c44c3793033e90047e8e691e10f9a2362a3a61a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 14:14:35 +0000 Subject: [PATCH 087/112] Bump shoulda-matchers from 5.3.0 to 6.0.0 Bumps [shoulda-matchers](https://github.com/thoughtbot/shoulda-matchers) from 5.3.0 to 6.0.0. - [Release notes](https://github.com/thoughtbot/shoulda-matchers/releases) - [Changelog](https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md) - [Commits](https://github.com/thoughtbot/shoulda-matchers/compare/v5.3.0...v6.0.0) --- updated-dependencies: - dependency-name: shoulda-matchers dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 75d2d1ca536..a281c70be3f 100644 --- a/Gemfile +++ b/Gemfile @@ -107,7 +107,7 @@ group :test do gem "rails-controller-testing", "~> 1.0" gem "mocha", "~> 2.0", require: false gem "shoulda-context", "~> 2.0" - gem "shoulda-matchers", "~> 5.3" + gem "shoulda-matchers", "~> 6.0" gem "selenium-webdriver", "~> 4.16" gem "webmock", "~> 3.18" gem "simplecov", "~> 0.22", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 9bf9f228c9d..ebf37af39b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -593,7 +593,7 @@ GEM concurrent-ruby thor shoulda-context (2.0.0) - shoulda-matchers (5.3.0) + shoulda-matchers (6.0.0) activesupport (>= 5.2.0) simplecov (0.22.0) docile (~> 1.1) @@ -775,7 +775,7 @@ DEPENDENCIES selenium-webdriver (~> 4.16) shoryuken (~> 6.1) shoulda-context (~> 2.0) - shoulda-matchers (~> 5.3) + shoulda-matchers (~> 6.0) simplecov (~> 0.22) simplecov-cobertura (~> 2.1) sprockets-rails (~> 3.4) From 5c3e960bfada1502237b9bd4e14fff06fd4db3f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Dec 2023 14:51:38 +0000 Subject: [PATCH 088/112] Bump ruby/setup-ruby from 1.162.0 to 1.163.0 Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.162.0 to 1.163.0. - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Commits](https://github.com/ruby/setup-ruby/compare/af848b40be8bb463a751551a1180d74782ba8a72...b256bd96bb4867e7d23e92e087d9bb697270b725) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cd9e276255e..716e12a2939 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 + - uses: ruby/setup-ruby@b256bd96bb4867e7d23e92e087d9bb697270b725 # v1.163.0 with: bundler-cache: true - name: Rubocop @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 + - uses: ruby/setup-ruby@b256bd96bb4867e7d23e92e087d9bb697270b725 # v1.163.0 with: bundler-cache: true - name: Brakeman @@ -41,7 +41,7 @@ jobs: - name: login to Github Packages run: echo "${{ github.token }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 + - uses: ruby/setup-ruby@b256bd96bb4867e7d23e92e087d9bb697270b725 # v1.163.0 with: bundler-cache: true - name: krane render From 8962424e267e9932d056e044426baac5431b2cdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:44:03 -0800 Subject: [PATCH 089/112] Bump ruby/setup-ruby from 1.163.0 to 1.165.1 (#4319) --- .github/workflows/lint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 716e12a2939..d9d832b5a25 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@b256bd96bb4867e7d23e92e087d9bb697270b725 # v1.163.0 + - uses: ruby/setup-ruby@360dc864d5da99d54fcb8e9148c14a84b90d3e88 # v1.165.1 with: bundler-cache: true - name: Rubocop @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@b256bd96bb4867e7d23e92e087d9bb697270b725 # v1.163.0 + - uses: ruby/setup-ruby@360dc864d5da99d54fcb8e9148c14a84b90d3e88 # v1.165.1 with: bundler-cache: true - name: Brakeman @@ -41,7 +41,7 @@ jobs: - name: login to Github Packages run: echo "${{ github.token }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: ruby/setup-ruby@b256bd96bb4867e7d23e92e087d9bb697270b725 # v1.163.0 + - uses: ruby/setup-ruby@360dc864d5da99d54fcb8e9148c14a84b90d3e88 # v1.165.1 with: bundler-cache: true - name: krane render From 19b82e85aacbd6c3268c564d4e203736040de177 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:44:54 -0800 Subject: [PATCH 090/112] Bump webauthn from 3.0.0 to 3.1.0 (#4316) --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 8a439b16d26..e0e094f9837 100644 --- a/Gemfile +++ b/Gemfile @@ -47,7 +47,7 @@ gem "rack-attack", "~> 6.6" gem "rqrcode", "~> 2.1" gem "rotp", "~> 6.2" gem "unpwn", "~> 1.0" -gem "webauthn", "~> 3.0" +gem "webauthn", "~> 3.1" gem "browser", "~> 5.3", ">= 5.3.1" gem "bcrypt", "~> 3.1" gem "maintenance_tasks", "~> 2.4" diff --git a/Gemfile.lock b/Gemfile.lock index 051f5137b7d..b6773eadc0a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -290,7 +290,7 @@ GEM bindata faraday (~> 2.0) faraday-follow_redirects - jwt (2.7.0) + jwt (2.7.1) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -420,7 +420,7 @@ GEM opensearch-ruby (3.1.0) faraday (>= 1.0, < 3) multi_json (>= 1.0) - openssl (3.1.0) + openssl (3.2.0) openssl-signature_algorithm (1.3.0) openssl (> 2.0) optimist (3.1.0) @@ -664,7 +664,7 @@ GEM activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) - webauthn (3.0.0) + webauthn (3.1.0) android_key_attestation (~> 0.3.0) awrence (~> 1.1) bindata (~> 2.4) @@ -788,7 +788,7 @@ DEPENDENCIES unpwn (~> 1.0) validates_formatting_of (~> 0.9) view_component (~> 3.8) - webauthn (~> 3.0) + webauthn (~> 3.1) webmock (~> 3.18) xml-simple (~> 1.1) From b198e476dd3ca997fafc02101cc2db75f567f9c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:50:45 +0000 Subject: [PATCH 091/112] Bump openid_connect from 2.2.0 to 2.2.1 (#4315) --- Gemfile.lock | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b6773eadc0a..653eab8c47d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,7 +81,7 @@ GEM ffi (~> 1.14) ffi-compiler (~> 1.0) ast (2.4.2) - attr_required (1.0.1) + attr_required (1.0.2) autoprefixer-rails (10.4.16.0) execjs (~> 2) avo (2.46.0) @@ -284,7 +284,7 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.6.3) - json-jwt (1.16.3) + json-jwt (1.16.4) activesupport (>= 4.2) aes_key_wrap bindata @@ -368,7 +368,7 @@ GEM msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) - net-imap (0.4.6) + net-imap (0.4.9) date net-protocol net-pop (0.1.2) @@ -404,13 +404,12 @@ GEM omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - openid_connect (2.2.0) + openid_connect (2.2.1) activemodel attr_required (>= 1.0.0) faraday (~> 2.0) faraday-follow_redirects json-jwt (>= 1.16) - net-smtp rack-oauth2 (~> 2.2) swd (~> 2.0) tzinfo @@ -459,7 +458,7 @@ GEM rack (2.2.8) rack-attack (6.7.0) rack (>= 1.0, < 4) - rack-oauth2 (2.2.0) + rack-oauth2 (2.2.1) activesupport attr_required faraday (~> 2.0) @@ -619,7 +618,7 @@ GEM stringio (3.1.0) strong_migrations (1.6.4) activerecord (>= 5.2) - swd (2.0.2) + swd (2.0.3) activesupport (>= 3) attr_required (>= 0.0.5) faraday (~> 2.0) @@ -673,7 +672,7 @@ GEM openssl (>= 2.2) safety_net_attestation (~> 0.4.0) tpm-key_attestation (~> 0.12.0) - webfinger (2.1.2) + webfinger (2.1.3) activesupport faraday (~> 2.0) faraday-follow_redirects From 47cef0dccafb7dd296f48399911b2af1668ca7a4 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Fri, 29 Dec 2023 12:07:07 -0700 Subject: [PATCH 092/112] Test against ruby 3.3.0 (#4297) --- .github/actions/setup-rubygems.org/action.yml | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/test.yml | 9 +- Gemfile | 7 + Gemfile.lock | 283 +++++++++++++++++- app/avo/concerns/auditable.rb | 4 +- app/controllers/concerns/avo_auditable.rb | 4 +- lib/gem_package_enumerator.rb | 10 +- .../api/v1/rubygems_controller_test.rb | 6 +- 9 files changed, 308 insertions(+), 19 deletions(-) diff --git a/.github/actions/setup-rubygems.org/action.yml b/.github/actions/setup-rubygems.org/action.yml index 4c3e6974f68..a18c2e27bfd 100644 --- a/.github/actions/setup-rubygems.org/action.yml +++ b/.github/actions/setup-rubygems.org/action.yml @@ -18,7 +18,7 @@ runs: shell: bash run: | timeout 300 bash -c "until curl --silent --output /dev/null http://localhost:9200/_cat/health?h=st; do printf '.'; sleep 5; done; printf '\n'" - - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 + - uses: ruby/setup-ruby@360dc864d5da99d54fcb8e9148c14a84b90d3e88 # v1.165.1 with: ruby-version: ${{ inputs.ruby-version }} bundler-cache: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2001fa58559..7bf776ae700 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,7 +14,7 @@ jobs: name: Docker build (and optional push) runs-on: ubuntu-22.04 env: - RUBYGEMS_VERSION: 3.5.1 + RUBYGEMS_VERSION: 3.5.3 RUBY_VERSION: 3.2.2 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 023dfac8cba..15aaa6eb202 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,16 +26,19 @@ jobs: matrix: rubygems: - name: locked - version: "3.5.1" + version: "3.5.3" - name: latest version: latest - ruby_version: ["3.2.2"] + ruby_version: ["3.2.2", "3.3.0"] tests: - name: general command: test - name: system command: test:system - name: Rails tests ${{ matrix.tests.name }} (RubyGems ${{ matrix.rubygems.name }}) + exclude: + - ruby_version: "3.3.0" + rubygems: { name: latest, version: latest } + name: Rails tests ${{ matrix.tests.name }} (RubyGems ${{ matrix.rubygems.name }}, Ruby ${{ matrix.ruby_version }}) runs-on: ubuntu-22.04 env: RUBYGEMS_VERSION: ${{ matrix.rubygems.version }} diff --git a/Gemfile b/Gemfile index e0e094f9837..90cdf3cc7c1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,12 @@ source "https://rubygems.org" +# Former default gems +gem "bigdecimal", "~> 3.1" # activesupport-7.0.8 +gem "mutex_m", "~> 0.2.0" # activesupport-7.0.8 +gem "net-smtp", "~> 0.4.0" # mail-2.8.1 +gem "csv", "~> 3.2" # zeitwerk-2.6.12 +gem "observer", "~> 0.1.2" # launchdarkly-server-sdk-8.0.0 + gem "rails", "~> 7.0.0" gem "rails-i18n", "~> 7.0" diff --git a/Gemfile.lock b/Gemfile.lock index 653eab8c47d..05ec6da0640 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,6 +122,7 @@ GEM base64 (0.2.0) bcrypt (3.1.20) benchmark-ips (2.12.0) + bigdecimal (3.1.5) bindata (2.4.15) bitarray (1.2.0) bloomer (1.0.0) @@ -167,6 +168,7 @@ GEM crass (1.0.6) css_parser (1.16.0) addressable + csv (3.2.8) dalli (3.2.6) dartsass-ruby (3.0.1) sass-embedded (~> 1.54) @@ -368,6 +370,7 @@ GEM msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) + mutex_m (0.2.0) net-imap (0.4.9) date net-protocol @@ -388,6 +391,7 @@ GEM rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) + observer (0.1.2) octokit (8.0.0) faraday (>= 1, < 3) sawyer (~> 0.9) @@ -425,8 +429,9 @@ GEM optimist (3.1.0) pagy (6.2.0) parallel (1.22.1) - parser (3.2.1.1) + parser (3.2.2.4) ast (~> 2.4.1) + racc pg (1.5.4) phlex (1.9.0) concurrent-ruby (~> 1.2) @@ -701,6 +706,7 @@ DEPENDENCIES aws-sdk-s3 (~> 1.142) aws-sdk-sqs (~> 1.69) bcrypt (~> 3.1) + bigdecimal (~> 3.1) bootsnap (~> 1.16) brakeman (~> 6.1) browser (~> 5.3, >= 5.3.1) @@ -708,6 +714,7 @@ DEPENDENCIES chartkick (~> 5.0) clearance (~> 2.6) compact_index (~> 0.15.0) + csv (~> 3.2) dalli (~> 3.2) dartsass-sprockets (~> 3.0) ddtrace (~> 1.18) @@ -739,6 +746,9 @@ DEPENDENCIES minitest-profile (~> 0.0.2) minitest-reporters (~> 1.6) mocha (~> 2.0) + mutex_m (~> 0.2.0) + net-smtp (~> 0.4.0) + observer (~> 0.1.2) octokit (~> 8.0) omniauth (~> 2.1) omniauth-github (~> 2.0) @@ -791,5 +801,274 @@ DEPENDENCIES webmock (~> 3.18) xml-simple (~> 1.1) +CHECKSUMS + actioncable (7.0.8) sha256=1f504ddb4ab6a34f7c52e9df924441a403e9f358bace330c36dcca6358ecfb84 + actionmailbox (7.0.8) sha256=9420037b801e44aa4e36cf113f4bd6eb25c17eb1b84d9c8865e8abf8846c14e5 + actionmailer (7.0.8) sha256=22574f270ed80bcd158f16b99068fad7772173e21c4332504238dae58fdccf70 + actionpack (7.0.8) sha256=2b998c6f6540ec07ad2e16b39f9acae22c8c4fda6b377417c2cfddf8c04d61d0 + actiontext (7.0.8) sha256=f7966296cec0a48e8644b59de2bfc8b7847d43a7809dfe040015a32aecc88744 + actionview (7.0.8) sha256=a22d692b9a6422f36882425301a4043fbe078a66e94a909a60a6a216246fd776 + active_link_to (1.0.5) sha256=4830847b3d14589df1e9fc62038ceec015257fce975ec1c2a77836c461b139ba + activejob (7.0.8) sha256=cb63d6a9f9af3379b7927bcb09a453d63db66ba9ec681018a8b21c5a0f8bc1b2 + activemodel (7.0.8) sha256=95beb8a2f6d1e0c7b4e3c0f17771b3a3024a25ad8c6e9d2d357e3cf1d5479c00 + activerecord (7.0.8) sha256=f236255235ab8c15f7a7bea3b77a35377801827e24d6e536dc776080f4dd8a13 + activestorage (7.0.8) sha256=8c2cae8de321ec899c7e7c4655331714fdd57f0966215286330f5c4d95a9db34 + activesupport (7.0.8) sha256=458316bb5098211ba9436d3c64d883177f09c49d1e29aa00f970d160275f13a1 + addressable (2.8.5) sha256=63f0fbcde42edf116d6da98a9437f19dd1692152f1efa3fcc4741e443c772117 + aes_key_wrap (1.1.0) sha256=b935f4756b37375895db45669e79dfcdc0f7901e12d4e08974d5540c8e0776a5 + aggregate_assertions (0.2.0) sha256=9bc51a48323a8e7b82f47cc38d48132817247345e5a8713686c9d65b25daca9e + amazing_print (1.5.0) sha256=f9f411b37257333a0f0cc16ce6520b2217a6f0b5a9f35656e1d403cd5e0c3362 + android_key_attestation (0.3.0) sha256=467eb01a99d2bb48ef9cf24cc13712669d7056cba5a52d009554ff037560570b + ansi (1.5.0) sha256=5408253274e33d9d27d4a98c46d2998266fd51cba58a7eb9d08f50e57ed23592 + argon2 (2.1.1) sha256=0fd6b50051102026e8b776b55fe5096065d84c214f837c94cede890370d3983c + ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12 + attr_required (1.0.2) sha256=f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99 + autoprefixer-rails (10.4.16.0) sha256=40c4b14d6f26f66026cd0d4631baf18d6c56aab425b36059c8abbda17f19a706 + avo (2.46.0) sha256=bec3d981a5e6f3e324da1aaf5379b8f6efc26f3eff5aa38c043fdb2aa1e002eb + awrence (1.2.1) sha256=dd1d214c12a91f449d1ef81d7ee3babc2816944e450752e7522c65521872483e + aws-eventstream (1.3.0) sha256=f1434cc03ab2248756eb02cfa45e900e59a061d7fbdc4a9fd82a5dd23d796d3f + aws-partitions (1.873.0) sha256=462818ceb80c45472fad99b77785f7b4533d3af76128de59cd2ffbccb7e621c1 + aws-sdk-core (3.190.1) sha256=b02aa7981f955c6021405c89b66e99061b99e2edc4f5b48c0f3dc742dd53daaa + aws-sdk-kms (1.75.0) sha256=21dbeab2328fa2ae6dbfcc7f11a4b6afb7fcaea02fa83458cb855a989fc3fde3 + aws-sdk-s3 (1.142.0) sha256=79cd888eca66fd2ef3ae8b74d76173a2eccbeff6a1bba62a60b7c7dadc8dd7e9 + aws-sdk-sqs (1.69.0) sha256=142a748b303af9a6e9e54be122ac429cfb59c528656ed477bacae9f3042659db + aws-sigv4 (1.8.0) sha256=84dd99768b91b93b63d1d8e53ee837cfd06ab402812772a7899a78f9f9117cbc + base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 + bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 + benchmark-ips (2.12.0) sha256=09dd4d5be05db42470e7e7b01be7310564073a054e35d9d9ec7840c523f3dbcb + bigdecimal (3.1.5) sha256=534faee5ae3b4a0a6369fe56cd944e907bf862a9209544a9e55f550592c22fac + bindata (2.4.15) sha256=e567e4278223e041caf4e623de870b2df8a93479d8f13e2b478bad45e0fbc413 + bitarray (1.2.0) sha256=7f9f31fadbd87bf51544cf13058e81cd6ec408ff40f127902cef3d6767b23f11 + bloomer (1.0.0) sha256=57a0d3a78628db9a92c6723f06c67697e420abcdb05aa757c6dfae607251d272 + bootsnap (1.17.0) sha256=6b0ea4dd68f0d424968dcd13953c3f04b13a19a8761c540d3af13507fcfa1347 + brakeman (6.1.1) sha256=7ee39855c3f0c3bf7169ea58e60e4a9968468e5ab3343f3ddf87b7db8bd613aa + browser (5.3.1) sha256=62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c + builder (3.2.4) sha256=99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10 + byebug (11.1.3) sha256=2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b + capybara (3.39.2) sha256=d6f0ca5f30897e64789428d4b047a0df105815a302069913578ac35d5ca99884 + cbor (0.5.9.6) sha256=434a147658dd1df24ec9e7b3297c1fd4f8a691c97d0e688b3049df8e728b2114 + cgi (0.4.0) sha256=20a62f3b05234e09e141d461ad4de057c4aea699d72a958b3a3bf00680a49d48 + chartkick (5.0.5) sha256=62f096048bc5c7cdab00a0f125042863eb7832ab70ec317002484b6e35d3d8f2 + choice (0.2.0) sha256=a19617f7dfd4921b38a85d0616446620de685a113ec6d1ecc85bdb67bf38c974 + chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe + clearance (2.6.1) sha256=20593dfbe647d063b950f27bf5deca6f2090ea04db6728a403dd973f50d7ffca + coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b + compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b + concurrent-ruby (1.2.2) sha256=3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f + cose (1.3.0) sha256=63247c66a5bc76e53926756574fe3724cc0a88707e358c90532ae2a320e98601 + crack (0.4.5) sha256=798416fb29b8c9f655d139d5559169b39c4a0a3b8f8f39b7f670eec1af9b21b3 + crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d + css_parser (1.16.0) sha256=f70fb492254418522ea77c01d57bf64452d6c7465001926c3620d0b50289b1a2 + csv (3.2.8) sha256=2f5e11e8897040b97baf2abfe8fa265b314efeb8a9b7f756db9ebcf79e7db9fe + dalli (3.2.6) sha256=879092a248bc0c8ff4b40ff233686d794422e8af4f3a5395c7bdc7432de0fd8c + dartsass-ruby (3.0.1) sha256=e6929ed3b4059236b10766dfb4218a04e0fe708ee2a8c85fb24fb6edad6e6f7c + dartsass-sprockets (3.0.0) sha256=9dd57f8b2471356cb25ed0a3069c6242433eaac80affcf89113f4693629982a0 + datadog-ci (0.5.0) sha256=34da9a0e56888568f4ab0b92249be874cf9121af54a78be5e6105e4df65b044d + date (3.3.4) sha256=971f2cb66b945bcbea4ddd9c7908c9400b31a71bc316833cb42fa584b59d3291 + ddtrace (1.18.0) sha256=92539e16d6251c77cc91ac75021ac27507ac5a8242e686614935d9884a8dc038 + dead_end (4.0.0) sha256=695c8438993bb4c5415b1618a1b6e0afcae849ef2812fb8cb3846723904307eb + debase-ruby_core_source (3.2.3) sha256=bec157b6c01a07fdcaa09c42f6c6015bc8941a85e18ec6b9cd122c263437c912 + derailed_benchmarks (2.1.2) sha256=eaadc6206ceeb5538ff8f5e04a0023d54ebdd95d04f33e8960fb95a5f189a14f + docile (1.4.0) sha256=5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3 + dogstatsd-ruby (5.6.1) sha256=615dd1328c57ab66fb48cbf83b38ce2060d8353707be0bc3deb0ae960a659982 + domain_name (0.5.20190701) sha256=000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851 + dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 + dotenv-rails (2.8.1) sha256=b05b81b7f4e51d1359e218d92279db1d84b12440f7118c9df19b5dfffb620f6c + dry-initializer (3.1.1) sha256=4d267dea367ccabe498b259c62b909b99d577d6db547d9510561999403546dec + email_validator (2.2.3) sha256=4ff52cb6e6f90f898c66de66a221e69b3104767eeea46e7f4c2deab73cbbe399 + erb (4.0.3) sha256=351a013ffee7e4e40b593df76dd4d0b2bd612e14dbff1d66273221d0cff6a49a + erubi (1.12.0) sha256=27bedb74dfb1e04ff60674975e182d8ca787f2224f2e8143268c7696f42e4723 + et-orbi (1.2.7) sha256=3b693d47f94a4060ccc07e60adda488759b1e8b9228a633ebbad842dfc245fb4 + execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb + factory_bot (6.4.2) sha256=f236ff24212b721ef3794cf6a33c6c1f562da8a778264b907d58ab090fae6466 + factory_bot_rails (6.4.2) sha256=0c384f626f9eb8afa50b50f4c2b3d70769e53b54cda4101e4da11f187be8e2e0 + faraday (2.8.1) sha256=a823dc6e1f5deaf6f91c8c1c02f37393f2339b39d811d9de33cef59d7d2ee4a6 + faraday-follow_redirects (0.3.0) sha256=d92d975635e2c7fe525dd494fcd4b9bb7f0a4a0ec0d5f4c15c729530fdb807f9 + faraday-net_http (3.0.2) sha256=6882929abed8094e1ee30344a3369e856fe34530044630d1f652bf70ebd87e8d + faraday_middleware-aws-sigv4 (1.0.1) sha256=a001ea4f687ca1c60bad8f2a627196905ce3dbf285e461dc153240e92eaabe8f + ffi (1.16.3) sha256=6d3242ff10c87271b0675c58d68d3f10148fabc2ad6da52a18123f06078871fb + ffi-compiler (1.0.1) sha256=019f389b078a2fec9de7f4f65771095f80a447e34436b4588bcb629e2a564c30 + fugit (1.9.0) sha256=e92ae18828a094afdd753e42ded0b83ad3449d79adddc5d1ab88e28dbfedd221 + get_process_mem (0.2.7) sha256=4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba + globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9 + good_job (3.21.5) sha256=a1ca5decb3036306b4b968fee3a9a7321a23d25dc31372ed501193e06e3512d3 + google-protobuf (3.25.1) sha256=e740e099193f8dc4db638326e23868d6c799dbd5ae2fd7565e78d1530cc6d1a3 + gravtastic (3.2.6) sha256=ef98abcecf7c402b61cff1ae7c50a2c6d97dd22bac21ea9b421ce05bc03d734f + groupdate (6.4.0) sha256=65940645bf2a48f9b2d10ab7a1d19bdc78f3c89559d8fce39cea3448a15aec54 + hashdiff (1.0.1) sha256=2cd4d04f5080314ecc8403c4e2e00dbaa282dff395e2d031bc16c8d501bdd6db + hashie (5.0.0) sha256=9d6c4e51f2a36d4616cbc8a322d619a162d8f42815a792596039fc95595603da + heapy (0.2.0) sha256=74141e845d61ffc7c1e8bf8b127c8cf94544ec7a1181aec613288682543585ea + high_voltage (3.1.2) sha256=7786716114cc21b3754e537818a6d9b2c91aa1126aa77272977d50598e556c2f + honeybadger (5.4.1) sha256=aea65109e148db2b0a0916e9600ecb7e0f11c9ee753c1c71764cb0ade13e0b1a + http (5.1.1) sha256=fcaec14a4f82de6d2f9cb978c07326814c6c2b42b8974f6ec166ff19c645ebaf + http-cookie (1.0.5) sha256=73756d46c7dbdc7023deecdb8a171348ea95a1b99810b31cfe8b4fb4e9a6318f + http-form_data (2.3.0) sha256=cc4eeb1361d9876821e31d7b1cf0b68f1cf874b201d27903480479d86448a5f3 + http_accept_language (2.1.1) sha256=0043f0d55a148cf45b604dbdd197cb36437133e990016c68c892d49dbea31634 + httparty (0.21.0) sha256=00ef7bf9a71f30a3bff88edeb5b16a34bea883ab67c246b3f0db2d6794fe1214 + i18n (1.14.1) sha256=9d03698903547c060928e70a9bc8b6b87fda674453cda918fc7ab80235ae4a61 + inline_svg (1.9.0) sha256=f44c5e3d2e401fd619ad3047b7c8cee384517d855edb1d1fb1a248d3cae535d6 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + job-iteration (1.4.1) sha256=7243c40e4decc3d49529867e9c504afaea332976c967ffdebed9ff863c6424af + jquery-rails (4.6.0) sha256=3c4e6bf47274340b44d836b8aa1b5472c6d451e2739af5ec094421f39025a7e2 + json (2.6.3) sha256=86aaea16adf346a2b22743d88f8dcceeb1038843989ab93cda44b5176c845459 + json-jwt (1.16.4) sha256=cfbcb4d354cc2f41977f8402f93d7907edfdc975b8cf0fee3c427e83895204ad + jwt (2.7.1) sha256=07357cd2f180739b2f8184eda969e252d850ac996ed0a23f616e8ff0a90ae19b + kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e + kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 + kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 + kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff + launchdarkly-server-sdk (8.0.0) sha256=c5b0a0df6a87535037486390489bd18815690c285a8378f810a3c7519e894e55 + launchy (2.5.2) sha256=8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b + ld-eventsource (2.2.2) sha256=5ea087a6f06bbd8e325d2c1aaead50f37f13d025b952985739e9380a78a96beb + letter_opener (1.8.1) sha256=2d2841ec4767786996498f3f71e591458d1d0ba14d7df5162a5b044a6e24adf8 + letter_opener_web (2.0.0) sha256=33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f + libdatadog (5.0.0.1.0) sha256=c61b02584c02447089ad8e71b7aa8e5a660fd675b727635c1a2eaec59b1840e8 + libddwaf (1.14.0.0.0) sha256=b91ea9675f7d79d1cd10dd6513e3706760ac442cb8902164fbcef79b7082a8fd + listen (3.8.0) sha256=9679040ac6e7845ad9f19cf59ecde60861c78e2fae57a5c20fe35e94959b2f8f + llhttp-ffi (0.4.0) sha256=e5f7327db3cf8007e648342ef76347d6e0ae545a8402e519cca9c886eb37b001 + loofah (2.22.0) sha256=10d76e070c86b12fec74b6a9515fd1940f4459198b991342d0a7897d86c372fe + mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad + maintenance_tasks (2.4.0) sha256=807899c1aa55e6a9eeed57cf5b9c595a9724670356d7e872181bcf8887f7aca7 + marcel (1.0.2) sha256=a013b677ef46cbcb49fd5c59b3d35803d2ee04dd75d8bfdc43533fc5a31f7e4e + matrix (0.4.2) sha256=71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0 + memory_profiler (1.0.1) sha256=38cdb42f22d9100df2eba0365c199724b58b05c38e765cd764a07392916901b1 + meta-tags (2.19.0) sha256=7e7f78d430dfbf5efaff97d02609766e6a0296ea82db3bba776af9f8ca41e2ef + method_source (1.0.0) sha256=d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede + mini_histogram (0.3.1) sha256=6a114b504e4618b0e076cc672996036870f7cc6f16b8e5c25c0c637726d2dd94 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + mini_portile2 (2.8.5) sha256=7a37db8ae758086c3c3ac3a59c036704d331e965d5e106635e4a42d6e66089ce + minitest (5.20.0) sha256=a3faf26a757ced073aaae0bd10481340f53e221a4f50d8a6033591555374752e + minitest-gcstats (1.3.0) sha256=c1bef6374d53771a6f7776f7a9c9fac5d1231fc83dfff5acea16fed66b886afd + minitest-profile (0.0.2) sha256=caf5306217b59ea2854d75ff6b312e2c139f2948f2b6a39486677b8317b7048e + minitest-reporters (1.6.1) sha256=f8fe74e46ab40dada29402f55ca236368d0af65afc410db4219189b7a1c0fc38 + mocha (2.1.0) sha256=f98757589e417b492383592ca6a3d7a98ed367a2289c797d13c120b522a25453 + msgpack (1.7.2) sha256=59ab62fd8a4d0dfbde45009f87eb6f158ab2628a7c48886b0256f175166baaa8 + multi_json (1.15.0) sha256=1fd04138b6e4a90017e8d1b804c039031399866ff3fbabb7822aea367c78615d + multi_xml (0.6.0) sha256=d24393cf958adb226db884b976b007914a89c53ad88718e25679d7008823ad52 + mutex_m (0.2.0) sha256=b6ef0c6c842ede846f2ec0ade9e266b1a9dac0bc151682b04835e8ebd54840d5 + net-imap (0.4.9) sha256=97cdc934002e27921c9e2a0478390bb1b23e01dbca8f2e3619d1591b7573f1c7 + net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 + net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 + net-smtp (0.4.0) sha256=2d7eb8de289ba8dce3f0d436ee40b9366bea28354c5ba183c8ab2ec05139a3e7 + nio4r (2.6.1) sha256=284aae4adc431498a8f2a8e6027da72bca5f2ea8134d770ffc6f8e45bf6b29f9 + nokogiri (1.15.5) sha256=22448ca35dbcbdcec60dbe25ccf452b685a5436c28f21b2fec2e20917aba9100 + oauth2 (2.0.9) sha256=b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb + observer (0.1.2) sha256=d8a3107131ba661138d748e7be3dbafc0d82e732fffba9fccb3d7829880950ac + octokit (8.0.0) sha256=c362be3ee7cd2f01f5af98c06b182587fbe4ae57c2385cdf94a1b04154d8d085 + omniauth (2.1.2) sha256=def03277298b8f8a5d3ff16cdb2eb5edb9bffed60ee7dda24cc0c89b3ae6a0ce + omniauth-github (2.0.1) sha256=8ff8e70ac6d6db9d52485eef52cfa894938c941496e66b52b5e2773ade3ccad4 + omniauth-oauth2 (1.8.0) sha256=b2f8e9559cc7e2d4efba57607691d6d2b634b879fc5b5b6ccfefa3da85089e78 + omniauth-rails_csrf_protection (1.0.1) sha256=fc546aeb7d43b7b9d7737051c380156e61c8f080b898cd4934d523eaa7e59acf + openid_connect (2.2.1) sha256=31264e85b5a36e33dff5e31560cc176175c26f30c260d7e95af93ff0065f3101 + opensearch-ruby (3.1.0) sha256=fc6147c682be51356140a8163e46713018b5220bde3f80ba0b36a0fd1feffdb6 + openssl (3.2.0) sha256=3c4bb8760977b4becd2819c6c2569bcf5c6f48b32b9f7a4ce1fd37f996378d14 + openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80 + optimist (3.1.0) sha256=81886f53ee8919f330aa30076d320d88eef9bc85aae2275376b4afb007c69260 + pagy (6.2.0) sha256=6dd5a71077faaefee1d79c757e8ed974c6eba4303a2d743f46d06abd6e8f876f + parallel (1.22.1) sha256=ebdf1f0c51f182df38522f70ba770214940bef998cdb6e00f36492b29699761f + parser (3.2.2.4) sha256=edbe6751f85599c8152173ccadbd708f444b7214de2a1d4969441a68e06ac964 + pg (1.5.4) sha256=04f7b247151c639a0b955d8e5a9a41541343f4640aa3c2bdf749a872c339d25d + phlex (1.9.0) sha256=75b06a334833542594ef65d0532c6c964c78648f459d855cb54cd8c0ddcdb549 + phlex-rails (1.1.1) sha256=b0e82d0ba541eca55ca39051de8be2817a7ed400f8a630e21b261239c8f812d0 + pp (0.5.0) sha256=f8f40bc2ba9e1ab351b9458151da3a89f46034f7f599a8e0a06abb9b9f4411dd + prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 + pry (0.14.1) sha256=99b6df0665875dd5a39d85e0150aa5a12e2bb4fef401b6c4f64d32ee502f8454 + pry-byebug (3.10.1) sha256=c8f975c32255bfdb29e151f5532130be64ff3d0042dc858d0907e849125581f8 + psych (5.1.1.1) sha256=44b0d1823629ac815f1f470af642dc7261489d67feb622a3f5573aa9f5cc5f72 + public_suffix (5.0.4) sha256=35cd648e0d21d06b8dce9331d19619538d1d898ba6d56a6f2258409d2526d1ae + puma (6.4.0) sha256=d5dda11362744df9f4694708a62e3cfddf72eba7498c16016ebbb30f106712f9 + pundit (2.3.1) sha256=5c62a2e7c65278828d51fb921a3e608472a262a39a046d53d0e78588a556b181 + pwned (2.3.0) sha256=63f5a9576f109203684e9dd053f815649fd5bc0a0348b7190568272641b22353 + raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 + racc (1.7.3) sha256=b785ab8a30ec43bce073c51dbbe791fd27000f68d1c996c95da98bf685316905 + rack (2.2.8) sha256=7b83a1f1304a8f5554c67bc83632d29ecd2ed1daeb88d276b7898533fde22d97 + rack-attack (6.7.0) sha256=3ca47e8f66cd33b2c96af53ea4754525cd928ed3fa8da10ee6dad0277791d77c + rack-oauth2 (2.2.1) sha256=c73aa87c508043e2258f02b4fb110cacba9b37d2ccf884e22487d014a120d1a5 + rack-protection (3.1.0) sha256=f9bc997fa87ab5fe3eb5d9d00e2a6222df3f9b8e6e9d610909ea3fc6203a5f77 + rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb + rack-utf8_sanitizer (1.9.1) sha256=6414b70172f5678e23044abf1d00f6a32e62a335507c9548bc5caf9e3bff6da0 + rails (7.0.8) sha256=8e43af921acf766fb429126f020ec90c3b25809631f8fbdff95c3553828d5867 + rails-controller-testing (1.0.5) sha256=741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94 + rails-dom-testing (2.2.0) sha256=e515712e48df1f687a1d7c380fd7b07b8558faa26464474da64183a7426fa93b + rails-erd (1.7.2) sha256=0b17d0fba25d319d8da8af7a3e5e2149d02d6187cc7351e8be43423f07c48bcd + rails-html-sanitizer (1.6.0) sha256=86e9f19d2e6748890dcc2633c8945ca45baa08a1df9d8c215ce17b3b0afaa4de + rails-i18n (7.0.8) sha256=ee9ff92bc4734085aaf234157a7c1c795a29bf3f7edeede1e14e3d4247dd12cd + rails_semantic_logger (4.14.0) sha256=738ca601d544108765bb0c9ea45d5ef7967777fecc5bba83bc9c2d86ac4a127f + railties (7.0.8) sha256=12325c3933efd33f8ead640197dec3b8c27c8d45607dd68b7b925896bf09cc69 + rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a + rake (13.1.0) sha256=be6a3e1aa7f66e6c65fa57555234eb75ce4cf4ada077658449207205474199c6 + rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe + rb-inotify (0.10.1) sha256=050062d4f31d307cca52c3f6a7f4b946df8de25fc4bd373e1a5142e41034a7ca + rbtrace (0.5.1) sha256=e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc + rdoc (6.6.2) sha256=f763dbec81079236bcccded19d69680471bd55da8f731ea6f583d019dacd9693 + regexp_parser (2.8.3) sha256=953277d2268bfb2f03275f36222ba9b36342f744a886cb7c8eefa8b985842ff7 + rexml (3.2.6) sha256=e0669a2d4e9f109951cb1fde723d8acd285425d81594a2ea929304af50282816 + roadie (5.2.0) sha256=f4dc8e41ccc331ebe4833cfa8e7495446db8bfd725c1a9b480147cb53c4945e3 + roadie-rails (3.1.0) sha256=5a45e1a7eb2f7cac29325ef8be64684c9d67cec608b235a69e38a7b79c64de21 + rotp (6.3.0) sha256=75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854 + rqrcode (2.2.0) sha256=23eea88bb44c7ee6d6cab9354d08c287f7ebcdc6112e1fe7bcc2d010d1ffefc1 + rqrcode_core (1.2.0) sha256=cf4989dc82d24e2877984738c4ee569308625fed2a810960f1b02d68d0308d1a + rubocop (1.48.0) sha256=2a90d242c2155c6d72cfaaf86d68bbbe58a6816cc8b192ac8c6702466c40c231 + rubocop-ast (1.27.0) sha256=0a88b64598ce7a7422579a11f419e4424fb2b10537b34b1a1b107f88f95c2a9a + rubocop-capybara (2.17.1) sha256=d648a6efd9607da494e379cdeb8d4569f88eb0e6c0a35ba964af2f0c6815e5df + rubocop-minitest (0.29.0) sha256=e815a04e68bffe10701c7537d388cbfef85b44d445d2651b7db9db8b70448ab6 + rubocop-performance (1.16.0) sha256=cc764f84bf37c6ef269973e686c3b33f258ffd612130e40856b25103de06efd8 + rubocop-rails (2.18.0) sha256=a646566f436b18f66e0b89a23df87ec4eb5318a74c9e6044271aca126b03fd8d + ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 + ruby-magic (0.6.0) sha256=7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101 + ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 + ruby-statistics (3.0.2) sha256=fb53e7a9f9681dac144c02539d3535fb2e8fae626f78b907219b0586ff53ec20 + ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef + rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f + safety_net_attestation (0.4.0) sha256=96be2d74e7ed26453a51894913449bea0e072f44490021545ac2d1c38b0718ce + sass-embedded (1.66.1) sha256=5e7660b520109584f441d60bfa9d349884dab6be70aa4392a389eb82d7fc0e0d + sawyer (0.9.2) sha256=fa3a72d62a4525517b18857ddb78926aab3424de0129be6772a8e2ba240e7aca + searchkick (5.3.1) sha256=dc1181543f6a68354e380651f235fa7f3df6a09e4cd67fc284dc293fa9860f57 + selenium-webdriver (4.16.0) sha256=237013649ea52435fe386cf4069b56d3f64c127b05af5f4d5c059bd71ee4c3e3 + semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 + semantic_logger (4.15.0) sha256=ec4f56122b5d2e2117d148b86c69fb62c1194a2b01a271be04ba8678a85f81ff + shoryuken (6.1.1) sha256=394affa8aca9160c72e44a0a2f89b3bab635bf217d4cb1c26f1fc5371e4a1277 + shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34 + shoulda-matchers (6.0.0) sha256=472e8ccc10b5e73612e80ea96843c9091ca0696f478780b2854b968a5b65bc38 + simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5 + simplecov-cobertura (2.1.0) sha256=2c6532e34df2e38a379d72cef9a05c3b16c64ce90566beebc6887801c4ad3f02 + simplecov-html (0.12.3) sha256=4b1aad33259ffba8b29c6876c12db70e5750cb9df829486e4c6e5da4fa0aa07b + simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 + snaky_hash (2.0.1) sha256=1ac87ec157fcfe7a460e821e0cd48ae1e6f5e3e082ab520f03f31a9259dbdc31 + sprockets (4.0.2) sha256=68d44758ae3da4f172c80abeff323100b4c4bb2f0ff6e1a3cb6e6c69e8e26f46 + sprockets-rails (3.4.2) sha256=36d6327757ccf7460a00d1d52b2d5ef0019a4670503046a129fa1fb1300931ad + statsd-instrument (3.6.1) sha256=fdaf73665c9a4d99aeddcda2e70fc266935919225dc0bf01257234f59f8f55df + stringio (3.1.0) sha256=c1f6263ae03a15025e51194ab19b06b15e06adcaaedb7f5f6c06ab60f5d67718 + strong_migrations (1.6.4) sha256=0e67822fc47e778835339d10955d687d71db7f36a89e76d399542661e20e8761 + swd (2.0.3) sha256=4cdbe2a4246c19f093fce22e967ec3ebdd4657d37673672e621bf0c7eb770655 + tailwindcss-rails (2.1.0) sha256=9badde83d85d3437073640be26d6880e6e627eed60e7f8cfefa81e28c177d62b + terser (1.1.20) sha256=4921b2f9daf4d830eb4af90d692b5d73e899352f9eec77dc345da5717e479e2e + thor (1.3.0) sha256=1adc7f9e5b3655a68c71393fee8bd0ad088d14ee8e83a0b73726f23cbb3ca7c3 + tilt (2.2.0) sha256=e76f850e611128a87992bb13ba74807624a9b8ec748e2c2ea7139580f67ab22e + timeout (0.4.1) sha256=6f1f4edd4bca28cffa59501733a94215407c6960bd2107331f0280d4abdebb9a + toxiproxy (2.0.2) sha256=2e3b53604fb921d40da3db8f78a52b3133fcae33e93d440725335b15974e440a + tpm-key_attestation (0.12.0) sha256=e133d80cf24fef0e7a7dfad00fd6aeff01fc79875fbfc66cd8537bbd622b1e6d + turbo-rails (1.5.0) sha256=b426cc762fb0940277729b3f1751a9f0bd269f5613c1d62ac73e5f0be7c7a83e + turbo_power (0.5.0) sha256=869726c3a4308b038d6244f7a80eb1331bdd3171fa320dddcff71899fcae24fb + tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b + unf (0.1.4) sha256=4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e + unf_ext (0.0.8.2) sha256=90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa + unicode-display_width (2.4.2) sha256=6a10205d1a19ca790c4e53064ba93f09d9eb234bf6bd135d9deb6001c21428be + unpwn (1.0.0) sha256=6239d17d46a882b3719b24fb79c78a34caff89d57ab0f5e546be5b5c882bc7d3 + validate_email (0.1.6) sha256=9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4 + validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 + validates_formatting_of (0.9.0) sha256=139590a4b87596dbfb04d93e897bd2e6d30fb849d04fab0343e71ed2ca856e7e + version_gem (1.1.1) sha256=3c2da6ded29045ddcc0387e152dc634e1f0c490b7128dce0697ccc1cf0915b6c + view_component (3.8.0) sha256=3ec17fe3b56e0d679064b27dde796d7d4254d7bf8110594de85471b86ac0c5f6 + webauthn (3.1.0) sha256=e545fcf17d8a6b821161a37c1c4bc8c3d2ead0ff6ff3b098f57f417e731790b7 + webfinger (2.1.3) sha256=567a52bde77fb38ca6b67e55db755f988766ec4651c1d24916a65aa70540695c + webmock (3.19.1) sha256=eae7eee33989478188451f1fda4224d7fbe097c5c14e96b40b57347ef2d5d16d + websocket (1.2.10) sha256=2cc1a4a79b6e63637b326b4273e46adcddf7871caa5dc5711f2ca4061a629fa8 + websocket-driver (0.7.6) sha256=f69400be7bc197879726ad8e6f5869a61823147372fd8928836a53c2c741d0db + websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 + xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d + xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e + zeitwerk (2.6.12) sha256=561e12975d0332fd3b62cc859aff3bab432e5f320689c8a10cd4674b5c0439be + BUNDLED WITH - 2.5.1 + 2.5.3 diff --git a/app/avo/concerns/auditable.rb b/app/avo/concerns/auditable.rb index f6654400e57..79cd0667a75 100644 --- a/app/avo/concerns/auditable.rb +++ b/app/avo/concerns/auditable.rb @@ -10,7 +10,7 @@ def merge_changes!(changes, changes_to_save) end end - def in_audited_transaction(auditable:, admin_github_user:, action:, fields:, arguments:, models:, &) # rubocop:disable Metrics + def in_audited_transaction(auditable:, admin_github_user:, action:, fields:, arguments:, models:, &blk) # rubocop:disable Metrics Naming/BlockForwarding logger.debug { "Auditing changes to #{auditable}: #{fields}" } User.transaction do @@ -25,7 +25,7 @@ def in_audited_transaction(auditable:, admin_github_user:, action:, fields:, arg merge_changes!((changed_records[record] ||= {}), record.attributes.transform_values { [nil, _1] }) if record.new_record? merge_changes!((changed_records[record] ||= {}), record.changes_to_save) end - end, "sql.active_record", &) + end, "sql.active_record", &blk) case auditable when :return diff --git a/app/controllers/concerns/avo_auditable.rb b/app/controllers/concerns/avo_auditable.rb index 3a4ce7313eb..457847b7c47 100644 --- a/app/controllers/concerns/avo_auditable.rb +++ b/app/controllers/concerns/avo_auditable.rb @@ -5,7 +5,7 @@ module AvoAuditable include Auditable end - def perform_action_and_record_errors(&) + def perform_action_and_record_errors(&blk) # rubocop:disable Naming/BlockForwarding super do action = params.fetch(:action) fields = action == "destroy" ? {} : cast_nullable(model_params) @@ -21,7 +21,7 @@ def perform_action_and_record_errors(&) fields: fields.reverse_merge(comment: action_name), arguments: {}, models: [@model], - & + &blk # rubocop:disable Naming/BlockForwarding ) value end diff --git a/lib/gem_package_enumerator.rb b/lib/gem_package_enumerator.rb index 57bcc7a70ad..3ebe6219b76 100644 --- a/lib/gem_package_enumerator.rb +++ b/lib/gem_package_enumerator.rb @@ -10,9 +10,9 @@ def initialize(package) raise ArgumentError, "package must be a Gem::Package" end - def each(&) - return enum_for(__method__).lazy unless block_given? - open_data_tar { |data_tar| data_tar.each(&) } + def each(&blk) + return enum_for(__method__).lazy unless blk + open_data_tar { |data_tar| data_tar.each(&blk) } end def map(&) @@ -25,11 +25,11 @@ def filter_map(&) private - def open_data_tar(&) + def open_data_tar(&blk) # rubocop:disable Naming/BlockForwarding @package.verify @package.gem.with_read_io do |io| Gem::Package::TarReader.new(io).seek("data.tar.gz") do |gem_entry| - @package.open_tar_gz(gem_entry, &) + @package.open_tar_gz(gem_entry, &blk) # rubocop:disable Naming/BlockForwarding end end end diff --git a/test/functional/api/v1/rubygems_controller_test.rb b/test/functional/api/v1/rubygems_controller_test.rb index f26ed302993..3cc56ddf0f0 100644 --- a/test/functional/api/v1/rubygems_controller_test.rb +++ b/test/functional/api/v1/rubygems_controller_test.rb @@ -23,7 +23,7 @@ def self.should_respond_to_show end end - def self.should_respond_to(format, &) + def self.should_respond_to(format, &blk) # rubocop:disable Naming/BlockForwarding context "with #{format.to_s.upcase} for a hosted gem" do setup do @rubygem = create(:rubygem) @@ -31,7 +31,7 @@ def self.should_respond_to(format, &) get :show, params: { id: @rubygem.slug }, format: format end - should_respond_to_show(&) + should_respond_to_show(&blk) # rubocop:disable Naming/BlockForwarding end context "with #{format.to_s.upcase} for a hosted gem with a period in its name" do @@ -41,7 +41,7 @@ def self.should_respond_to(format, &) get :show, params: { id: @rubygem.slug }, format: format end - should_respond_to_show(&) + should_respond_to_show(&blk) # rubocop:disable Naming/BlockForwarding end end From 8ce6c46d5aa64635de06e481f8655ae898c505f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Sun, 12 Nov 2023 01:52:08 +0100 Subject: [PATCH 093/112] Remove Clearance from UsersController. - it is not used, since it is overridden --- app/controllers/application_controller.rb | 5 ++++- app/controllers/sessions_controller.rb | 2 +- app/controllers/users_controller.rb | 22 +++++++++++++++++----- app/models/user.rb | 12 ------------ test/functional/users_controller_test.rb | 15 +++++++++++++-- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 32d8420d12f..d0be78968fe 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,5 @@ class ApplicationController < ActionController::Base include Clearance::Authentication - include Clearance::Authorization include ApplicationMultifactorMethods include TraceTagger @@ -69,6 +68,10 @@ def redirect_to_signin redirect_to sign_in_path, alert: t("please_sign_in") end + def redirect_to_root + redirect_to root_path + end + def find_rubygem @rubygem = Rubygem.find_by_name(params[:rubygem_id] || params[:id]) return if @rubygem diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 03262e3fdc5..10b354c8449 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -111,7 +111,7 @@ def do_login if status.success? StatsD.increment "login.success" set_login_flash - redirect_back_or(url_after_create) + redirect_to(url_after_create) else login_failure(status.failure_message) end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ade0c2215fa..d7ae97308f6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,14 +1,16 @@ -class UsersController < Clearance::UsersController +class UsersController < ApplicationController + before_action :redirect_to_root, if: :signed_in? + def new - @user = user_from_params + @user = User.new end def create - @user = user_from_params + @user = User.new(user_params) if @user.save Mailer.email_confirmation(@user).deliver_later flash[:notice] = t(".email_sent") - redirect_back_or url_after_create + redirect_back_or_to root_path else render template: "users/new" end @@ -17,6 +19,16 @@ def create private def user_params - params.permit(user: Array(User::PERMITTED_ATTRS)).fetch(:user, {}) + params.require(:user).permit( + :bio, + :email, + :handle, + :public_email, + :location, + :password, + :website, + :twitter_username, + :full_name + ) end end diff --git a/app/models/user.rb b/app/models/user.rb index b0f8dc05c15..a57c60a8300 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,18 +4,6 @@ class User < ApplicationRecord include Gravtastic is_gravtastic default: "retro" - PERMITTED_ATTRS = %i[ - bio - email - handle - public_email - location - password - website - twitter_username - full_name - ].freeze - before_save :_generate_confirmation_token_no_reset_unconfirmed_email, if: :will_save_change_to_unconfirmed_email? before_create :_generate_confirmation_token_no_reset_unconfirmed_email before_destroy :yank_gems diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb index a0115bb99d6..c038a29023a 100644 --- a/test/functional/users_controller_test.rb +++ b/test/functional/users_controller_test.rb @@ -14,6 +14,17 @@ class UsersControllerTest < ActionController::TestCase page.assert_text "Sign up" page.assert_selector "input[type=password][autocomplete=new-password]" end + + context "when logged in" do + setup do + @user = create(:user) + sign_in_as(@user) + + get :new + end + + should redirect_to("root") { root_path } + end end context "on POST to create" do @@ -26,9 +37,9 @@ class UsersControllerTest < ActionController::TestCase end context "when missing a parameter" do - should "raises parameter missing" do + should "reports validation error" do assert_no_changes -> { User.count } do - post :create + post :create, params: { user: { password: PasswordHelpers::SECURE_TEST_PASSWORD } } end assert_response :ok assert page.has_content?("Email address is not a valid email") From 0eab9f94b363aad9f0068d66dd6c1c0ccd7cce3a Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sat, 30 Dec 2023 20:15:54 -0700 Subject: [PATCH 094/112] Avoid exceptions when authenticating users with an invalid encoding (#4241) Fixes https://app.honeybadger.io/projects/40972/faults/102413552 --- app/models/user.rb | 6 +++++- test/functional/api/v1/api_keys_controller_test.rb | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index a57c60a8300..f5803b6d7a3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,9 +63,13 @@ class User < ApplicationRecord validate :toxic_email_domain, on: :create def self.authenticate(who, password) + # Avoid exceptions when string is invalid in the given encoding, _or_ cannot be converted + # to UTF-8. + who = who.encode(Encoding::UTF_8) + user = find_by(email: who.downcase) || find_by(handle: who) user if user&.authenticated?(password) - rescue BCrypt::Errors::InvalidHash + rescue BCrypt::Errors::InvalidHash, Encoding::UndefinedConversionError nil end diff --git a/test/functional/api/v1/api_keys_controller_test.rb b/test/functional/api/v1/api_keys_controller_test.rb index 8a03c0268c9..e032d219515 100644 --- a/test/functional/api/v1/api_keys_controller_test.rb +++ b/test/functional/api/v1/api_keys_controller_test.rb @@ -207,6 +207,15 @@ def self.should_expect_otp_for_update should_deny_access end + context "with credentials with invalid encoding" do + setup do + @user = create(:user) + authorize_with("\x12\xff\x12:creds".force_encoding(Encoding::UTF_8)) + get :show + end + should_deny_access + end + context "with correct credentials" do setup do @user = create(:user) From 2c5f889c2ca231cad3aa3bd0306f2c0fdd422b5c Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sun, 31 Dec 2023 23:06:17 -0700 Subject: [PATCH 095/112] Make pushing idempotent, and after-write job enqueuing atomic with making the version indexed (#4255) To fix the issue where a version can be marked as indexed, but the after-write jobs never get enqueued, which can happen when pushing gets killed at the wrong point (due to hitting the 15s request timeout) --- app/avo/actions/version_after_write.rb | 20 +++++++++ app/avo/resources/version_resource.rb | 1 + app/jobs/after_version_write_job.rb | 31 +++++++++++++ app/models/pusher.rb | 62 ++++++++++++-------------- app/models/rubygem.rb | 4 -- app/models/version.rb | 3 +- app/views/mailer/gem_pushed.html.erb | 4 ++ lib/certificate_chain_serializer.rb | 4 +- test/integration/push_test.rb | 42 ++++++++++++++++- test/models/pusher_test.rb | 7 ++- test/system/avo/versions_test.rb | 36 ++++++++++++++- test/unit/mailer_test.rb | 4 ++ 12 files changed, 172 insertions(+), 46 deletions(-) create mode 100644 app/avo/actions/version_after_write.rb create mode 100644 app/jobs/after_version_write_job.rb diff --git a/app/avo/actions/version_after_write.rb b/app/avo/actions/version_after_write.rb new file mode 100644 index 00000000000..94490970847 --- /dev/null +++ b/app/avo/actions/version_after_write.rb @@ -0,0 +1,20 @@ +class VersionAfterWrite < BaseAction + self.name = "Run version post-write job" + self.visible = lambda { + current_user.team_member?("rubygems-org") && + view == :show && + resource.model.deletion.blank? + } + + self.message = lambda { + "Are you sure you would like to run the after-write job for #{record.full_name}? The version is #{'not ' unless record.indexed?} indexed." + } + + self.confirm_button_label = "Run Job" + + class ActionHandler < ActionHandler + def handle_model(version) + AfterVersionWriteJob.new(version: version).perform(version: version) + end + end +end diff --git a/app/avo/resources/version_resource.rb b/app/avo/resources/version_resource.rb index 4334c6f1c89..62d853fb69c 100644 --- a/app/avo/resources/version_resource.rb +++ b/app/avo/resources/version_resource.rb @@ -6,6 +6,7 @@ class VersionResource < Avo::BaseResource } action RestoreVersion + action VersionAfterWrite class IndexedFilter < ScopeBooleanFilter; end filter IndexedFilter, arguments: { default: { indexed: true, yanked: true } } diff --git a/app/jobs/after_version_write_job.rb b/app/jobs/after_version_write_job.rb new file mode 100644 index 00000000000..ca898da7423 --- /dev/null +++ b/app/jobs/after_version_write_job.rb @@ -0,0 +1,31 @@ +class AfterVersionWriteJob < ApplicationJob + queue_as :default + + def perform(version:) + version.transaction do + rubygem = version.rubygem + version.rubygem.push_notifiable_owners.each do |notified_user| + Mailer.gem_pushed(owner, version.id, notified_user.id).deliver_later + end + Indexer.perform_later + UploadVersionsFileJob.perform_later + UploadInfoFileJob.perform_later(rubygem_name: rubygem.name) + UploadNamesFileJob.perform_later + ReindexRubygemJob.perform_later(rubygem:) + StoreVersionContentsJob.perform_later(version:) if ld_variation(key: "gemcutter.pusher.store_version_contents", default: false) + version.update!(indexed: true) + end + end + + def ld_variation(key:, default:) + return default unless owner + + Rails.configuration.launch_darkly_client.variation( + key, owner.ld_context, default + ) + end + + def owner + arguments.dig(0, :version).pusher_api_key&.owner + end +end diff --git a/app/models/pusher.rb b/app/models/pusher.rb index 6d7922a6076..347251863fc 100644 --- a/app/models/pusher.rb +++ b/app/models/pusher.rb @@ -86,16 +86,36 @@ def pull_spec MSG end - def find # rubocop:disable Metrics/AbcSize + def find # rubocop:disable Metrics/AbcSize, Metrics/MethodLength name = spec.name.to_s set_tag "gemcutter.rubygem.name", name @rubygem = Rubygem.name_is(name).first || Rubygem.new(name: name) + sha256 = Digest::SHA2.base64digest(body.string) + spec_sha256 = Digest::SHA2.base64digest(spec_contents) + + version = @rubygem.versions + .create_with(indexed: false, cert_chain: spec.cert_chain) + .find_or_initialize_by( + number: spec.version.to_s, + platform: spec.original_platform.to_s, + gem_platform: spec.platform.to_s, + size: size, + sha256: sha256, + spec_sha256: spec_sha256, + pusher: api_key.user, + pusher_api_key: api_key + ) + unless @rubygem.new_record? - if (version = @rubygem.find_version_from_spec spec) - republish_notification(version) - return false + # Return success for idempotent pushes + return notify("Gem was already pushed: #{version.to_title}", 200) if version.indexed? + + # If the gem is yanked, we can't repush it + # Additionally, we don't allow overwriting existing versions + if (existing = @rubygem.versions.find_by(number: version.number, platform: version.platform)) + return republish_notification(existing) end if @rubygem.name != name && @rubygem.indexed_versions? @@ -106,19 +126,7 @@ def find # rubocop:disable Metrics/AbcSize # Update the name to reflect a valid case change @rubygem.name = name - - sha256 = Digest::SHA2.base64digest(body.string) - spec_sha256 = Digest::SHA2.base64digest(spec_contents) - - @version = @rubygem.versions.new number: spec.version.to_s, - platform: spec.original_platform.to_s, - gem_platform: spec.platform.to_s, - size: size, - sha256: sha256, - spec_sha256: spec_sha256, - pusher: api_key.user, - pusher_api_key: api_key, - cert_chain: spec.cert_chain + @version = version set_tags "gemcutter.rubygem.version" => @version.number, "gemcutter.rubygem.platform" => @version.platform log_pushing @@ -137,27 +145,12 @@ def inspect private def after_write - @version_id = version.id - version.rubygem.push_notifiable_owners.each do |notified_user| - Mailer.gem_pushed(owner, @version_id, notified_user.id).deliver_later - end - Indexer.perform_later - UploadVersionsFileJob.perform_later - UploadInfoFileJob.perform_later(rubygem_name: rubygem.name) - UploadNamesFileJob.perform_later - ReindexRubygemJob.perform_later(rubygem:) GemCachePurger.call(rubygem.name) - StoreVersionContentsJob.perform_later(version:) if ld_variation(key: "gemcutter.pusher.store_version_contents", default: false) RackAttackReset.gem_push_backoff(@remote_ip, owner.to_gid) if @remote_ip.present? + AfterVersionWriteJob.new(version:).perform(version:) StatsD.increment "push.success" end - def ld_variation(key:, default:) - Rails.configuration.launch_darkly_client.variation( - key, owner.ld_context, default - ) - end - def notify(message, code) logger.info { { message:, code:, owner: owner, api_key: api_key&.id, rubygem: rubygem&.name, version: version&.full_name } } @@ -204,6 +197,9 @@ def republish_notification(version) notify("Repushing of gem versions is not allowed.\n" \ "Please bump the version number and push a new different release.\n" \ "See also `gem yank` if you want to unpublish the bad release.", 409) + elsif version.deletion.nil? + notify("It appears that #{version.full_name} did not finish pushing.\n" \ + "Please contact support@rubygems.org for assistance if you pushed this gem more than a minute ago.", 409) else different_owner = "pushed by a previous owner of this gem " unless owner.owns_gem?(version.rubygem) notify("A yanked version #{different_owner}already exists (#{version.full_name}).\n" \ diff --git a/app/models/rubygem.rb b/app/models/rubygem.rb index 69cb7ae936c..8b0eb2a1299 100644 --- a/app/models/rubygem.rb +++ b/app/models/rubygem.rb @@ -323,10 +323,6 @@ def disown oidc_rubygem_trusted_publishers.clear end - def find_version_from_spec(spec) - versions.find_by_number_and_platform(spec.version.to_s, spec.original_platform.to_s) - end - def find_or_initialize_version_from_spec(spec) version = versions.find_or_initialize_by(number: spec.version.to_s, platform: spec.original_platform.to_s, diff --git a/app/models/version.rb b/app/models/version.rb index 89a483f21bf..d8e9a02bff5 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -265,8 +265,7 @@ def update_attributes_from_gem_specification!(spec) requirements: spec.requirements, built_at: spec.date, required_rubygems_version: spec.required_rubygems_version.to_s, - required_ruby_version: spec.required_ruby_version.to_s, - indexed: true + required_ruby_version: spec.required_ruby_version.to_s ) end diff --git a/app/views/mailer/gem_pushed.html.erb b/app/views/mailer/gem_pushed.html.erb index f922d42bd8f..0f55c683479 100644 --- a/app/views/mailer/gem_pushed.html.erb +++ b/app/views/mailer/gem_pushed.html.erb @@ -17,6 +17,7 @@
    <% if @pushed_by_user.is_a?(User) %> Pushed by user: + <% if @pushed_by_user %> <%= link_to @pushed_by_user.handle, profile_url(@pushed_by_user.display_id), target: "_blank" %> <%= mail_to(@pushed_by_user.email) if @pushed_by_user.public_email? %> @@ -27,6 +28,9 @@ <% end %>
    + <% else %> + Unknown + <% end %> Pushed at: <%= @version.created_at.to_formatted_s(:rfc822) %>


    diff --git a/lib/certificate_chain_serializer.rb b/lib/certificate_chain_serializer.rb index 853b164cb97..954d7622d07 100644 --- a/lib/certificate_chain_serializer.rb +++ b/lib/certificate_chain_serializer.rb @@ -3,7 +3,7 @@ class CertificateChainSerializer def self.load(chain) return [] unless chain - chain.scan(PATTERN).map do |cert| + chain.scan(PATTERN).map! do |cert| OpenSSL::X509::Certificate.new(cert) end end @@ -13,6 +13,6 @@ def self.dump(chain) normalised = chain.map do |cert| cert.respond_to?(:to_pem) ? cert : OpenSSL::X509::Certificate.new(cert) end - normalised.map(&:to_pem).join + normalised.map!(&:to_pem).join end end diff --git a/test/integration/push_test.rb b/test/integration/push_test.rb index af733526118..51e22fa2cde 100644 --- a/test/integration/push_test.rb +++ b/test/integration/push_test.rb @@ -252,7 +252,8 @@ class PushTest < ActionDispatch::IntegrationTest test "republish a yanked version" do rubygem = create(:rubygem, name: "sandworm", owners: [@user]) - create(:version, number: "1.0.0", indexed: false, rubygem: rubygem) + version = create(:version, number: "1.0.0", rubygem: rubygem) + create(:deletion, version:) build_gem "sandworm", "1.0.0" @@ -264,7 +265,8 @@ class PushTest < ActionDispatch::IntegrationTest test "republish a yanked version by a different owner" do rubygem = create(:rubygem, name: "sandworm") - create(:version, number: "1.0.0", indexed: false, rubygem: rubygem) + version = create(:version, number: "1.0.0", rubygem: rubygem) + create(:deletion, version:) build_gem "sandworm", "1.0.0" @@ -274,6 +276,42 @@ class PushTest < ActionDispatch::IntegrationTest assert_match(/A yanked version pushed by a previous owner of this gem already exists \(sandworm-1.0.0\)/, response.body) end + test "republish an indexed version" do + build_gem "sandworm", "1.0.0" + + push_gem "sandworm-1.0.0.gem" + + assert_response :success + + assert_enqueued_jobs 0 do + push_gem "sandworm-1.0.0.gem" + end + + assert_response :success + assert_equal("Gem was already pushed: sandworm (1.0.0)", response.body) + end + + test "republish a version where the gem is un-indexed but not yanked" do + build_gem "sandworm", "1.0.0" + + Pusher.any_instance.stubs(:after_write) + + push_gem "sandworm-1.0.0.gem" + + Pusher.any_instance.unstub(:after_write) + + assert_enqueued_jobs 0 do + push_gem "sandworm-1.0.0.gem" + end + + assert_response :conflict + assert_equal( + "It appears that sandworm-1.0.0 did not finish pushing.\n" \ + "Please contact support@rubygems.org for assistance if you pushed this gem more than a minute ago.", + response.body + ) + end + test "publishing a gem with ceritifcate but not signatures" do build_gem "sandworm", "2.0.0" do |gemspec| gemspec.cert_chain = [File.read(File.expand_path("../certs/chain.pem", __dir__))] diff --git a/test/models/pusher_test.rb b/test/models/pusher_test.rb index bfa0daeddd1..585f466a036 100644 --- a/test/models/pusher_test.rb +++ b/test/models/pusher_test.rb @@ -435,8 +435,11 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: spec = mock spec.expects(:name).returns @rubygem.name.upcase spec.expects(:version).returns Gem::Version.new("1.3.3.7") + spec.expects(:platform).returns "ruby" spec.expects(:original_platform).returns "ruby" + spec.expects(:cert_chain).returns nil @cutter.stubs(:spec).returns spec + @cutter.stubs(:spec_contents).returns "spec" refute @cutter.find @@ -592,7 +595,7 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: setup do @rubygem = create(:rubygem, name: "gemsgemsgems") @cutter.stubs(:rubygem).returns @rubygem - create(:version, rubygem: @rubygem, number: "0.1.1", summary: "old summary") + create(:version, rubygem: @rubygem, number: "0.1.1", summary: "old summary", pusher_api_key: @cutter.api_key) @spec = mock @cutter.stubs(:version).returns @rubygem.versions[0] @cutter.stubs(:spec).returns(@spec) @@ -695,7 +698,7 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: @rubygem = create(:rubygem) @cutter.stubs(:rubygem).returns @rubygem create(:version, rubygem: @rubygem, summary: "old summary") - @version = create(:version, rubygem: @rubygem, summary: "new summary") + @version = create(:version, rubygem: @rubygem, summary: "new summary", pusher_api_key: @cutter.api_key) @cutter.stubs(:version).returns @version @rubygem.stubs(:update_attributes_from_gem_specification!) @cutter.stubs(:version).returns @version diff --git a/test/system/avo/versions_test.rb b/test/system/avo/versions_test.rb index c72c89a0cd8..871ca77e06b 100644 --- a/test/system/avo/versions_test.rb +++ b/test/system/avo/versions_test.rb @@ -27,7 +27,6 @@ def sign_in_as(user) end test "restore a rubygem version" do - Minitest::Test.make_my_diffs_pretty! admin_user = create(:admin_github_user, :is_admin) sign_in_as admin_user @@ -125,4 +124,39 @@ def sign_in_as(user) audit.audited_changes ) end + + test "run afer version write job" do + admin_user = create(:admin_github_user, :is_admin) + sign_in_as admin_user + + rubygem = create(:rubygem, owners: [create(:user)]) + version = create(:version, rubygem: rubygem) + + visit avo.resources_version_path(version) + + click_button "Actions" + + click_on "Run version post-write job" + + fill_in "Comment", with: "A nice long comment" + + click_button "Run Job" + + page.assert_text "Action ran successfully!" + page.assert_text version.to_global_id.uri.to_s + + perform_enqueued_jobs + + assert_equal 1, ActionMailer::Base.deliveries.size + + rubygem.reload + version.reload + + audit = version.audits.sole + + assert_equal( + ["gid://gemcutter/Version/#{version.id}"], + audit.audited_changes["models"] + ) + end end diff --git a/test/unit/mailer_test.rb b/test/unit/mailer_test.rb index d93189db30c..a84ce8c12e7 100644 --- a/test/unit/mailer_test.rb +++ b/test/unit/mailer_test.rb @@ -4,6 +4,10 @@ class MailerTest < ActionMailer::TestCase MIN_DOWNLOADS_FOR_MFA_RECOMMENDATION_POLICY = 165_000_000 MIN_DOWNLOADS_FOR_MFA_REQUIRED_POLICY = 180_000_000 + setup do + TOPLEVEL_BINDING.receiver.stubs(:mx_exists?).returns(true) + end + context "sending mail for mfa recommendation announcement" do setup do @user = create(:user) From 9073050cdc2a8e744a591a716f3c67904d2e6b9f Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sun, 31 Dec 2023 23:10:14 -0700 Subject: [PATCH 096/112] Update to ruby 3.3 completely (#4320) --- .devcontainer/Dockerfile | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/test.yml | 5 +---- .rubocop.yml | 2 +- .ruby-version | 2 +- CONTRIBUTING.md | 4 ++-- Dockerfile | 8 ++------ 7 files changed, 9 insertions(+), 16 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 301e7791822..c59aeef03e5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG RUBY_VERSION=3.2 +ARG RUBY_VERSION=3.3 FROM ruby:${RUBY_VERSION}-alpine diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7bf776ae700..77f9f076c21 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-22.04 env: RUBYGEMS_VERSION: 3.5.3 - RUBY_VERSION: 3.2.2 + RUBY_VERSION: 3.3.0 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Docker Buildx diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15aaa6eb202..4b537168a5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,15 +29,12 @@ jobs: version: "3.5.3" - name: latest version: latest - ruby_version: ["3.2.2", "3.3.0"] + ruby_version: ["3.3.0"] tests: - name: general command: test - name: system command: test:system - exclude: - - ruby_version: "3.3.0" - rubygems: { name: latest, version: latest } name: Rails tests ${{ matrix.tests.name }} (RubyGems ${{ matrix.rubygems.name }}, Ruby ${{ matrix.ruby_version }}) runs-on: ubuntu-22.04 env: diff --git a/.rubocop.yml b/.rubocop.yml index c963f80f35d..1f1942788ed 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -13,7 +13,7 @@ AllCops: - !ruby/regexp /(vendor|bundle|bin|db|tmp|server)\/.*/ DisplayCopNames: true DisplayStyleGuide: true - TargetRubyVersion: 3.2 + TargetRubyVersion: 3.3 NewCops: enable Rails: diff --git a/.ruby-version b/.ruby-version index be94e6f53db..15a27998172 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.2 +3.3.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef54aa86624..cc927182591 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,10 +114,10 @@ Follow the instructions below on how to install Bundler and setup the database. ### Installing ruby, gem dependencies, and setting up the database -* Use Ruby 3.2.x +* Use Ruby 3.3.x * See: [Ruby install instructions](https://www.ruby-lang.org/en/downloads/). * `.ruby-version` is present and can be used. -* Use Rubygems 3.3.x +* Use Rubygems 3.5.x * Install bundler: `gem install bundler` * Install dependencies and setup the database: diff --git a/Dockerfile b/Dockerfile index 79d07e43c5d..832b8a07710 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax = docker/dockerfile:1.4 # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile -ARG RUBY_VERSION=3.2.2 +ARG RUBY_VERSION=3.3.0 ARG ALPINE_VERSION=3.18 FROM ruby:$RUBY_VERSION-alpine${ALPINE_VERSION} as base @@ -25,11 +25,7 @@ ENV BUNDLE_APP_CONFIG=".bundle_app_config" # Update rubygems ARG RUBYGEMS_VERSION -RUN gem update --system ${RUBYGEMS_VERSION} --no-document && \ - # rubygems-update is completely unused after the `gem update --system` process - gem uninstall rubygems-update -x && \ - # Remove rubygems cache files, they are unused - rm -r /usr/local/bundle/cache/ /root/.local/share/gem/ +RUN gem update --system ${RUBYGEMS_VERSION} --no-document # Throw-away build stage to reduce size of final image FROM base as build From 2a74f6791ebd94d878d4e5e5d04b5ae3b9535f3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 19:25:15 -0700 Subject: [PATCH 097/112] Bump dartsass-sprockets from 3.0.0 to 3.1.0 (#4321) --- Gemfile | 2 +- Gemfile.lock | 291 ++------------------------------------------------- 2 files changed, 12 insertions(+), 281 deletions(-) diff --git a/Gemfile b/Gemfile index 90cdf3cc7c1..5ff91a26fb2 100644 --- a/Gemfile +++ b/Gemfile @@ -78,7 +78,7 @@ group :assets, :development do end group :assets do - gem "dartsass-sprockets", "~> 3.0" + gem "dartsass-sprockets", "~> 3.1" gem "terser", "~> 1.1" gem "autoprefixer-rails", "~> 10.4" end diff --git a/Gemfile.lock b/Gemfile.lock index 05ec6da0640..7331918341a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -170,11 +170,9 @@ GEM addressable csv (3.2.8) dalli (3.2.6) - dartsass-ruby (3.0.1) - sass-embedded (~> 1.54) - dartsass-sprockets (3.0.0) - dartsass-ruby (~> 3.0) + dartsass-sprockets (3.1.0) railties (>= 4.0.0) + sassc-embedded (~> 1.69) sprockets (> 3.0) sprockets-rails tilt @@ -381,7 +379,7 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.6.1) - nokogiri (1.15.5) + nokogiri (1.16.0) mini_portile2 (~> 2.8.2) racc (~> 1.4) oauth2 (2.0.9) @@ -577,9 +575,11 @@ GEM rubyzip (2.3.2) safety_net_attestation (0.4.0) jwt (~> 2.0) - sass-embedded (1.66.1) - google-protobuf (~> 3.23) + sass-embedded (1.69.6) + google-protobuf (~> 3.25) rake (>= 13.0.0) + sassc-embedded (1.69.1) + sass-embedded (~> 1.69) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) @@ -612,9 +612,9 @@ GEM snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) - sprockets (4.0.2) + sprockets (4.2.1) concurrent-ruby (~> 1.0) - rack (> 1, < 3) + rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) @@ -633,7 +633,7 @@ GEM terser (1.1.20) execjs (>= 0.3.0, < 3) thor (1.3.0) - tilt (2.2.0) + tilt (2.3.0) timeout (0.4.1) toxiproxy (2.0.2) tpm-key_attestation (0.12.0) @@ -716,7 +716,7 @@ DEPENDENCIES compact_index (~> 0.15.0) csv (~> 3.2) dalli (~> 3.2) - dartsass-sprockets (~> 3.0) + dartsass-sprockets (~> 3.1) ddtrace (~> 1.18) derailed_benchmarks (~> 2.1) dogstatsd-ruby (~> 5.5) @@ -801,274 +801,5 @@ DEPENDENCIES webmock (~> 3.18) xml-simple (~> 1.1) -CHECKSUMS - actioncable (7.0.8) sha256=1f504ddb4ab6a34f7c52e9df924441a403e9f358bace330c36dcca6358ecfb84 - actionmailbox (7.0.8) sha256=9420037b801e44aa4e36cf113f4bd6eb25c17eb1b84d9c8865e8abf8846c14e5 - actionmailer (7.0.8) sha256=22574f270ed80bcd158f16b99068fad7772173e21c4332504238dae58fdccf70 - actionpack (7.0.8) sha256=2b998c6f6540ec07ad2e16b39f9acae22c8c4fda6b377417c2cfddf8c04d61d0 - actiontext (7.0.8) sha256=f7966296cec0a48e8644b59de2bfc8b7847d43a7809dfe040015a32aecc88744 - actionview (7.0.8) sha256=a22d692b9a6422f36882425301a4043fbe078a66e94a909a60a6a216246fd776 - active_link_to (1.0.5) sha256=4830847b3d14589df1e9fc62038ceec015257fce975ec1c2a77836c461b139ba - activejob (7.0.8) sha256=cb63d6a9f9af3379b7927bcb09a453d63db66ba9ec681018a8b21c5a0f8bc1b2 - activemodel (7.0.8) sha256=95beb8a2f6d1e0c7b4e3c0f17771b3a3024a25ad8c6e9d2d357e3cf1d5479c00 - activerecord (7.0.8) sha256=f236255235ab8c15f7a7bea3b77a35377801827e24d6e536dc776080f4dd8a13 - activestorage (7.0.8) sha256=8c2cae8de321ec899c7e7c4655331714fdd57f0966215286330f5c4d95a9db34 - activesupport (7.0.8) sha256=458316bb5098211ba9436d3c64d883177f09c49d1e29aa00f970d160275f13a1 - addressable (2.8.5) sha256=63f0fbcde42edf116d6da98a9437f19dd1692152f1efa3fcc4741e443c772117 - aes_key_wrap (1.1.0) sha256=b935f4756b37375895db45669e79dfcdc0f7901e12d4e08974d5540c8e0776a5 - aggregate_assertions (0.2.0) sha256=9bc51a48323a8e7b82f47cc38d48132817247345e5a8713686c9d65b25daca9e - amazing_print (1.5.0) sha256=f9f411b37257333a0f0cc16ce6520b2217a6f0b5a9f35656e1d403cd5e0c3362 - android_key_attestation (0.3.0) sha256=467eb01a99d2bb48ef9cf24cc13712669d7056cba5a52d009554ff037560570b - ansi (1.5.0) sha256=5408253274e33d9d27d4a98c46d2998266fd51cba58a7eb9d08f50e57ed23592 - argon2 (2.1.1) sha256=0fd6b50051102026e8b776b55fe5096065d84c214f837c94cede890370d3983c - ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12 - attr_required (1.0.2) sha256=f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99 - autoprefixer-rails (10.4.16.0) sha256=40c4b14d6f26f66026cd0d4631baf18d6c56aab425b36059c8abbda17f19a706 - avo (2.46.0) sha256=bec3d981a5e6f3e324da1aaf5379b8f6efc26f3eff5aa38c043fdb2aa1e002eb - awrence (1.2.1) sha256=dd1d214c12a91f449d1ef81d7ee3babc2816944e450752e7522c65521872483e - aws-eventstream (1.3.0) sha256=f1434cc03ab2248756eb02cfa45e900e59a061d7fbdc4a9fd82a5dd23d796d3f - aws-partitions (1.873.0) sha256=462818ceb80c45472fad99b77785f7b4533d3af76128de59cd2ffbccb7e621c1 - aws-sdk-core (3.190.1) sha256=b02aa7981f955c6021405c89b66e99061b99e2edc4f5b48c0f3dc742dd53daaa - aws-sdk-kms (1.75.0) sha256=21dbeab2328fa2ae6dbfcc7f11a4b6afb7fcaea02fa83458cb855a989fc3fde3 - aws-sdk-s3 (1.142.0) sha256=79cd888eca66fd2ef3ae8b74d76173a2eccbeff6a1bba62a60b7c7dadc8dd7e9 - aws-sdk-sqs (1.69.0) sha256=142a748b303af9a6e9e54be122ac429cfb59c528656ed477bacae9f3042659db - aws-sigv4 (1.8.0) sha256=84dd99768b91b93b63d1d8e53ee837cfd06ab402812772a7899a78f9f9117cbc - base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 - bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 - benchmark-ips (2.12.0) sha256=09dd4d5be05db42470e7e7b01be7310564073a054e35d9d9ec7840c523f3dbcb - bigdecimal (3.1.5) sha256=534faee5ae3b4a0a6369fe56cd944e907bf862a9209544a9e55f550592c22fac - bindata (2.4.15) sha256=e567e4278223e041caf4e623de870b2df8a93479d8f13e2b478bad45e0fbc413 - bitarray (1.2.0) sha256=7f9f31fadbd87bf51544cf13058e81cd6ec408ff40f127902cef3d6767b23f11 - bloomer (1.0.0) sha256=57a0d3a78628db9a92c6723f06c67697e420abcdb05aa757c6dfae607251d272 - bootsnap (1.17.0) sha256=6b0ea4dd68f0d424968dcd13953c3f04b13a19a8761c540d3af13507fcfa1347 - brakeman (6.1.1) sha256=7ee39855c3f0c3bf7169ea58e60e4a9968468e5ab3343f3ddf87b7db8bd613aa - browser (5.3.1) sha256=62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c - builder (3.2.4) sha256=99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10 - byebug (11.1.3) sha256=2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b - capybara (3.39.2) sha256=d6f0ca5f30897e64789428d4b047a0df105815a302069913578ac35d5ca99884 - cbor (0.5.9.6) sha256=434a147658dd1df24ec9e7b3297c1fd4f8a691c97d0e688b3049df8e728b2114 - cgi (0.4.0) sha256=20a62f3b05234e09e141d461ad4de057c4aea699d72a958b3a3bf00680a49d48 - chartkick (5.0.5) sha256=62f096048bc5c7cdab00a0f125042863eb7832ab70ec317002484b6e35d3d8f2 - choice (0.2.0) sha256=a19617f7dfd4921b38a85d0616446620de685a113ec6d1ecc85bdb67bf38c974 - chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe - clearance (2.6.1) sha256=20593dfbe647d063b950f27bf5deca6f2090ea04db6728a403dd973f50d7ffca - coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b - compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b - concurrent-ruby (1.2.2) sha256=3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f - cose (1.3.0) sha256=63247c66a5bc76e53926756574fe3724cc0a88707e358c90532ae2a320e98601 - crack (0.4.5) sha256=798416fb29b8c9f655d139d5559169b39c4a0a3b8f8f39b7f670eec1af9b21b3 - crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d - css_parser (1.16.0) sha256=f70fb492254418522ea77c01d57bf64452d6c7465001926c3620d0b50289b1a2 - csv (3.2.8) sha256=2f5e11e8897040b97baf2abfe8fa265b314efeb8a9b7f756db9ebcf79e7db9fe - dalli (3.2.6) sha256=879092a248bc0c8ff4b40ff233686d794422e8af4f3a5395c7bdc7432de0fd8c - dartsass-ruby (3.0.1) sha256=e6929ed3b4059236b10766dfb4218a04e0fe708ee2a8c85fb24fb6edad6e6f7c - dartsass-sprockets (3.0.0) sha256=9dd57f8b2471356cb25ed0a3069c6242433eaac80affcf89113f4693629982a0 - datadog-ci (0.5.0) sha256=34da9a0e56888568f4ab0b92249be874cf9121af54a78be5e6105e4df65b044d - date (3.3.4) sha256=971f2cb66b945bcbea4ddd9c7908c9400b31a71bc316833cb42fa584b59d3291 - ddtrace (1.18.0) sha256=92539e16d6251c77cc91ac75021ac27507ac5a8242e686614935d9884a8dc038 - dead_end (4.0.0) sha256=695c8438993bb4c5415b1618a1b6e0afcae849ef2812fb8cb3846723904307eb - debase-ruby_core_source (3.2.3) sha256=bec157b6c01a07fdcaa09c42f6c6015bc8941a85e18ec6b9cd122c263437c912 - derailed_benchmarks (2.1.2) sha256=eaadc6206ceeb5538ff8f5e04a0023d54ebdd95d04f33e8960fb95a5f189a14f - docile (1.4.0) sha256=5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3 - dogstatsd-ruby (5.6.1) sha256=615dd1328c57ab66fb48cbf83b38ce2060d8353707be0bc3deb0ae960a659982 - domain_name (0.5.20190701) sha256=000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851 - dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 - dotenv-rails (2.8.1) sha256=b05b81b7f4e51d1359e218d92279db1d84b12440f7118c9df19b5dfffb620f6c - dry-initializer (3.1.1) sha256=4d267dea367ccabe498b259c62b909b99d577d6db547d9510561999403546dec - email_validator (2.2.3) sha256=4ff52cb6e6f90f898c66de66a221e69b3104767eeea46e7f4c2deab73cbbe399 - erb (4.0.3) sha256=351a013ffee7e4e40b593df76dd4d0b2bd612e14dbff1d66273221d0cff6a49a - erubi (1.12.0) sha256=27bedb74dfb1e04ff60674975e182d8ca787f2224f2e8143268c7696f42e4723 - et-orbi (1.2.7) sha256=3b693d47f94a4060ccc07e60adda488759b1e8b9228a633ebbad842dfc245fb4 - execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb - factory_bot (6.4.2) sha256=f236ff24212b721ef3794cf6a33c6c1f562da8a778264b907d58ab090fae6466 - factory_bot_rails (6.4.2) sha256=0c384f626f9eb8afa50b50f4c2b3d70769e53b54cda4101e4da11f187be8e2e0 - faraday (2.8.1) sha256=a823dc6e1f5deaf6f91c8c1c02f37393f2339b39d811d9de33cef59d7d2ee4a6 - faraday-follow_redirects (0.3.0) sha256=d92d975635e2c7fe525dd494fcd4b9bb7f0a4a0ec0d5f4c15c729530fdb807f9 - faraday-net_http (3.0.2) sha256=6882929abed8094e1ee30344a3369e856fe34530044630d1f652bf70ebd87e8d - faraday_middleware-aws-sigv4 (1.0.1) sha256=a001ea4f687ca1c60bad8f2a627196905ce3dbf285e461dc153240e92eaabe8f - ffi (1.16.3) sha256=6d3242ff10c87271b0675c58d68d3f10148fabc2ad6da52a18123f06078871fb - ffi-compiler (1.0.1) sha256=019f389b078a2fec9de7f4f65771095f80a447e34436b4588bcb629e2a564c30 - fugit (1.9.0) sha256=e92ae18828a094afdd753e42ded0b83ad3449d79adddc5d1ab88e28dbfedd221 - get_process_mem (0.2.7) sha256=4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba - globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9 - good_job (3.21.5) sha256=a1ca5decb3036306b4b968fee3a9a7321a23d25dc31372ed501193e06e3512d3 - google-protobuf (3.25.1) sha256=e740e099193f8dc4db638326e23868d6c799dbd5ae2fd7565e78d1530cc6d1a3 - gravtastic (3.2.6) sha256=ef98abcecf7c402b61cff1ae7c50a2c6d97dd22bac21ea9b421ce05bc03d734f - groupdate (6.4.0) sha256=65940645bf2a48f9b2d10ab7a1d19bdc78f3c89559d8fce39cea3448a15aec54 - hashdiff (1.0.1) sha256=2cd4d04f5080314ecc8403c4e2e00dbaa282dff395e2d031bc16c8d501bdd6db - hashie (5.0.0) sha256=9d6c4e51f2a36d4616cbc8a322d619a162d8f42815a792596039fc95595603da - heapy (0.2.0) sha256=74141e845d61ffc7c1e8bf8b127c8cf94544ec7a1181aec613288682543585ea - high_voltage (3.1.2) sha256=7786716114cc21b3754e537818a6d9b2c91aa1126aa77272977d50598e556c2f - honeybadger (5.4.1) sha256=aea65109e148db2b0a0916e9600ecb7e0f11c9ee753c1c71764cb0ade13e0b1a - http (5.1.1) sha256=fcaec14a4f82de6d2f9cb978c07326814c6c2b42b8974f6ec166ff19c645ebaf - http-cookie (1.0.5) sha256=73756d46c7dbdc7023deecdb8a171348ea95a1b99810b31cfe8b4fb4e9a6318f - http-form_data (2.3.0) sha256=cc4eeb1361d9876821e31d7b1cf0b68f1cf874b201d27903480479d86448a5f3 - http_accept_language (2.1.1) sha256=0043f0d55a148cf45b604dbdd197cb36437133e990016c68c892d49dbea31634 - httparty (0.21.0) sha256=00ef7bf9a71f30a3bff88edeb5b16a34bea883ab67c246b3f0db2d6794fe1214 - i18n (1.14.1) sha256=9d03698903547c060928e70a9bc8b6b87fda674453cda918fc7ab80235ae4a61 - inline_svg (1.9.0) sha256=f44c5e3d2e401fd619ad3047b7c8cee384517d855edb1d1fb1a248d3cae535d6 - jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 - job-iteration (1.4.1) sha256=7243c40e4decc3d49529867e9c504afaea332976c967ffdebed9ff863c6424af - jquery-rails (4.6.0) sha256=3c4e6bf47274340b44d836b8aa1b5472c6d451e2739af5ec094421f39025a7e2 - json (2.6.3) sha256=86aaea16adf346a2b22743d88f8dcceeb1038843989ab93cda44b5176c845459 - json-jwt (1.16.4) sha256=cfbcb4d354cc2f41977f8402f93d7907edfdc975b8cf0fee3c427e83895204ad - jwt (2.7.1) sha256=07357cd2f180739b2f8184eda969e252d850ac996ed0a23f616e8ff0a90ae19b - kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e - kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 - kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 - kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff - launchdarkly-server-sdk (8.0.0) sha256=c5b0a0df6a87535037486390489bd18815690c285a8378f810a3c7519e894e55 - launchy (2.5.2) sha256=8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b - ld-eventsource (2.2.2) sha256=5ea087a6f06bbd8e325d2c1aaead50f37f13d025b952985739e9380a78a96beb - letter_opener (1.8.1) sha256=2d2841ec4767786996498f3f71e591458d1d0ba14d7df5162a5b044a6e24adf8 - letter_opener_web (2.0.0) sha256=33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f - libdatadog (5.0.0.1.0) sha256=c61b02584c02447089ad8e71b7aa8e5a660fd675b727635c1a2eaec59b1840e8 - libddwaf (1.14.0.0.0) sha256=b91ea9675f7d79d1cd10dd6513e3706760ac442cb8902164fbcef79b7082a8fd - listen (3.8.0) sha256=9679040ac6e7845ad9f19cf59ecde60861c78e2fae57a5c20fe35e94959b2f8f - llhttp-ffi (0.4.0) sha256=e5f7327db3cf8007e648342ef76347d6e0ae545a8402e519cca9c886eb37b001 - loofah (2.22.0) sha256=10d76e070c86b12fec74b6a9515fd1940f4459198b991342d0a7897d86c372fe - mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad - maintenance_tasks (2.4.0) sha256=807899c1aa55e6a9eeed57cf5b9c595a9724670356d7e872181bcf8887f7aca7 - marcel (1.0.2) sha256=a013b677ef46cbcb49fd5c59b3d35803d2ee04dd75d8bfdc43533fc5a31f7e4e - matrix (0.4.2) sha256=71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0 - memory_profiler (1.0.1) sha256=38cdb42f22d9100df2eba0365c199724b58b05c38e765cd764a07392916901b1 - meta-tags (2.19.0) sha256=7e7f78d430dfbf5efaff97d02609766e6a0296ea82db3bba776af9f8ca41e2ef - method_source (1.0.0) sha256=d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede - mini_histogram (0.3.1) sha256=6a114b504e4618b0e076cc672996036870f7cc6f16b8e5c25c0c637726d2dd94 - mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef - mini_portile2 (2.8.5) sha256=7a37db8ae758086c3c3ac3a59c036704d331e965d5e106635e4a42d6e66089ce - minitest (5.20.0) sha256=a3faf26a757ced073aaae0bd10481340f53e221a4f50d8a6033591555374752e - minitest-gcstats (1.3.0) sha256=c1bef6374d53771a6f7776f7a9c9fac5d1231fc83dfff5acea16fed66b886afd - minitest-profile (0.0.2) sha256=caf5306217b59ea2854d75ff6b312e2c139f2948f2b6a39486677b8317b7048e - minitest-reporters (1.6.1) sha256=f8fe74e46ab40dada29402f55ca236368d0af65afc410db4219189b7a1c0fc38 - mocha (2.1.0) sha256=f98757589e417b492383592ca6a3d7a98ed367a2289c797d13c120b522a25453 - msgpack (1.7.2) sha256=59ab62fd8a4d0dfbde45009f87eb6f158ab2628a7c48886b0256f175166baaa8 - multi_json (1.15.0) sha256=1fd04138b6e4a90017e8d1b804c039031399866ff3fbabb7822aea367c78615d - multi_xml (0.6.0) sha256=d24393cf958adb226db884b976b007914a89c53ad88718e25679d7008823ad52 - mutex_m (0.2.0) sha256=b6ef0c6c842ede846f2ec0ade9e266b1a9dac0bc151682b04835e8ebd54840d5 - net-imap (0.4.9) sha256=97cdc934002e27921c9e2a0478390bb1b23e01dbca8f2e3619d1591b7573f1c7 - net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 - net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 - net-smtp (0.4.0) sha256=2d7eb8de289ba8dce3f0d436ee40b9366bea28354c5ba183c8ab2ec05139a3e7 - nio4r (2.6.1) sha256=284aae4adc431498a8f2a8e6027da72bca5f2ea8134d770ffc6f8e45bf6b29f9 - nokogiri (1.15.5) sha256=22448ca35dbcbdcec60dbe25ccf452b685a5436c28f21b2fec2e20917aba9100 - oauth2 (2.0.9) sha256=b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb - observer (0.1.2) sha256=d8a3107131ba661138d748e7be3dbafc0d82e732fffba9fccb3d7829880950ac - octokit (8.0.0) sha256=c362be3ee7cd2f01f5af98c06b182587fbe4ae57c2385cdf94a1b04154d8d085 - omniauth (2.1.2) sha256=def03277298b8f8a5d3ff16cdb2eb5edb9bffed60ee7dda24cc0c89b3ae6a0ce - omniauth-github (2.0.1) sha256=8ff8e70ac6d6db9d52485eef52cfa894938c941496e66b52b5e2773ade3ccad4 - omniauth-oauth2 (1.8.0) sha256=b2f8e9559cc7e2d4efba57607691d6d2b634b879fc5b5b6ccfefa3da85089e78 - omniauth-rails_csrf_protection (1.0.1) sha256=fc546aeb7d43b7b9d7737051c380156e61c8f080b898cd4934d523eaa7e59acf - openid_connect (2.2.1) sha256=31264e85b5a36e33dff5e31560cc176175c26f30c260d7e95af93ff0065f3101 - opensearch-ruby (3.1.0) sha256=fc6147c682be51356140a8163e46713018b5220bde3f80ba0b36a0fd1feffdb6 - openssl (3.2.0) sha256=3c4bb8760977b4becd2819c6c2569bcf5c6f48b32b9f7a4ce1fd37f996378d14 - openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80 - optimist (3.1.0) sha256=81886f53ee8919f330aa30076d320d88eef9bc85aae2275376b4afb007c69260 - pagy (6.2.0) sha256=6dd5a71077faaefee1d79c757e8ed974c6eba4303a2d743f46d06abd6e8f876f - parallel (1.22.1) sha256=ebdf1f0c51f182df38522f70ba770214940bef998cdb6e00f36492b29699761f - parser (3.2.2.4) sha256=edbe6751f85599c8152173ccadbd708f444b7214de2a1d4969441a68e06ac964 - pg (1.5.4) sha256=04f7b247151c639a0b955d8e5a9a41541343f4640aa3c2bdf749a872c339d25d - phlex (1.9.0) sha256=75b06a334833542594ef65d0532c6c964c78648f459d855cb54cd8c0ddcdb549 - phlex-rails (1.1.1) sha256=b0e82d0ba541eca55ca39051de8be2817a7ed400f8a630e21b261239c8f812d0 - pp (0.5.0) sha256=f8f40bc2ba9e1ab351b9458151da3a89f46034f7f599a8e0a06abb9b9f4411dd - prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 - pry (0.14.1) sha256=99b6df0665875dd5a39d85e0150aa5a12e2bb4fef401b6c4f64d32ee502f8454 - pry-byebug (3.10.1) sha256=c8f975c32255bfdb29e151f5532130be64ff3d0042dc858d0907e849125581f8 - psych (5.1.1.1) sha256=44b0d1823629ac815f1f470af642dc7261489d67feb622a3f5573aa9f5cc5f72 - public_suffix (5.0.4) sha256=35cd648e0d21d06b8dce9331d19619538d1d898ba6d56a6f2258409d2526d1ae - puma (6.4.0) sha256=d5dda11362744df9f4694708a62e3cfddf72eba7498c16016ebbb30f106712f9 - pundit (2.3.1) sha256=5c62a2e7c65278828d51fb921a3e608472a262a39a046d53d0e78588a556b181 - pwned (2.3.0) sha256=63f5a9576f109203684e9dd053f815649fd5bc0a0348b7190568272641b22353 - raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 - racc (1.7.3) sha256=b785ab8a30ec43bce073c51dbbe791fd27000f68d1c996c95da98bf685316905 - rack (2.2.8) sha256=7b83a1f1304a8f5554c67bc83632d29ecd2ed1daeb88d276b7898533fde22d97 - rack-attack (6.7.0) sha256=3ca47e8f66cd33b2c96af53ea4754525cd928ed3fa8da10ee6dad0277791d77c - rack-oauth2 (2.2.1) sha256=c73aa87c508043e2258f02b4fb110cacba9b37d2ccf884e22487d014a120d1a5 - rack-protection (3.1.0) sha256=f9bc997fa87ab5fe3eb5d9d00e2a6222df3f9b8e6e9d610909ea3fc6203a5f77 - rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb - rack-utf8_sanitizer (1.9.1) sha256=6414b70172f5678e23044abf1d00f6a32e62a335507c9548bc5caf9e3bff6da0 - rails (7.0.8) sha256=8e43af921acf766fb429126f020ec90c3b25809631f8fbdff95c3553828d5867 - rails-controller-testing (1.0.5) sha256=741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94 - rails-dom-testing (2.2.0) sha256=e515712e48df1f687a1d7c380fd7b07b8558faa26464474da64183a7426fa93b - rails-erd (1.7.2) sha256=0b17d0fba25d319d8da8af7a3e5e2149d02d6187cc7351e8be43423f07c48bcd - rails-html-sanitizer (1.6.0) sha256=86e9f19d2e6748890dcc2633c8945ca45baa08a1df9d8c215ce17b3b0afaa4de - rails-i18n (7.0.8) sha256=ee9ff92bc4734085aaf234157a7c1c795a29bf3f7edeede1e14e3d4247dd12cd - rails_semantic_logger (4.14.0) sha256=738ca601d544108765bb0c9ea45d5ef7967777fecc5bba83bc9c2d86ac4a127f - railties (7.0.8) sha256=12325c3933efd33f8ead640197dec3b8c27c8d45607dd68b7b925896bf09cc69 - rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a - rake (13.1.0) sha256=be6a3e1aa7f66e6c65fa57555234eb75ce4cf4ada077658449207205474199c6 - rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe - rb-inotify (0.10.1) sha256=050062d4f31d307cca52c3f6a7f4b946df8de25fc4bd373e1a5142e41034a7ca - rbtrace (0.5.1) sha256=e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc - rdoc (6.6.2) sha256=f763dbec81079236bcccded19d69680471bd55da8f731ea6f583d019dacd9693 - regexp_parser (2.8.3) sha256=953277d2268bfb2f03275f36222ba9b36342f744a886cb7c8eefa8b985842ff7 - rexml (3.2.6) sha256=e0669a2d4e9f109951cb1fde723d8acd285425d81594a2ea929304af50282816 - roadie (5.2.0) sha256=f4dc8e41ccc331ebe4833cfa8e7495446db8bfd725c1a9b480147cb53c4945e3 - roadie-rails (3.1.0) sha256=5a45e1a7eb2f7cac29325ef8be64684c9d67cec608b235a69e38a7b79c64de21 - rotp (6.3.0) sha256=75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854 - rqrcode (2.2.0) sha256=23eea88bb44c7ee6d6cab9354d08c287f7ebcdc6112e1fe7bcc2d010d1ffefc1 - rqrcode_core (1.2.0) sha256=cf4989dc82d24e2877984738c4ee569308625fed2a810960f1b02d68d0308d1a - rubocop (1.48.0) sha256=2a90d242c2155c6d72cfaaf86d68bbbe58a6816cc8b192ac8c6702466c40c231 - rubocop-ast (1.27.0) sha256=0a88b64598ce7a7422579a11f419e4424fb2b10537b34b1a1b107f88f95c2a9a - rubocop-capybara (2.17.1) sha256=d648a6efd9607da494e379cdeb8d4569f88eb0e6c0a35ba964af2f0c6815e5df - rubocop-minitest (0.29.0) sha256=e815a04e68bffe10701c7537d388cbfef85b44d445d2651b7db9db8b70448ab6 - rubocop-performance (1.16.0) sha256=cc764f84bf37c6ef269973e686c3b33f258ffd612130e40856b25103de06efd8 - rubocop-rails (2.18.0) sha256=a646566f436b18f66e0b89a23df87ec4eb5318a74c9e6044271aca126b03fd8d - ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 - ruby-magic (0.6.0) sha256=7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101 - ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 - ruby-statistics (3.0.2) sha256=fb53e7a9f9681dac144c02539d3535fb2e8fae626f78b907219b0586ff53ec20 - ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef - rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f - safety_net_attestation (0.4.0) sha256=96be2d74e7ed26453a51894913449bea0e072f44490021545ac2d1c38b0718ce - sass-embedded (1.66.1) sha256=5e7660b520109584f441d60bfa9d349884dab6be70aa4392a389eb82d7fc0e0d - sawyer (0.9.2) sha256=fa3a72d62a4525517b18857ddb78926aab3424de0129be6772a8e2ba240e7aca - searchkick (5.3.1) sha256=dc1181543f6a68354e380651f235fa7f3df6a09e4cd67fc284dc293fa9860f57 - selenium-webdriver (4.16.0) sha256=237013649ea52435fe386cf4069b56d3f64c127b05af5f4d5c059bd71ee4c3e3 - semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 - semantic_logger (4.15.0) sha256=ec4f56122b5d2e2117d148b86c69fb62c1194a2b01a271be04ba8678a85f81ff - shoryuken (6.1.1) sha256=394affa8aca9160c72e44a0a2f89b3bab635bf217d4cb1c26f1fc5371e4a1277 - shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34 - shoulda-matchers (6.0.0) sha256=472e8ccc10b5e73612e80ea96843c9091ca0696f478780b2854b968a5b65bc38 - simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5 - simplecov-cobertura (2.1.0) sha256=2c6532e34df2e38a379d72cef9a05c3b16c64ce90566beebc6887801c4ad3f02 - simplecov-html (0.12.3) sha256=4b1aad33259ffba8b29c6876c12db70e5750cb9df829486e4c6e5da4fa0aa07b - simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 - snaky_hash (2.0.1) sha256=1ac87ec157fcfe7a460e821e0cd48ae1e6f5e3e082ab520f03f31a9259dbdc31 - sprockets (4.0.2) sha256=68d44758ae3da4f172c80abeff323100b4c4bb2f0ff6e1a3cb6e6c69e8e26f46 - sprockets-rails (3.4.2) sha256=36d6327757ccf7460a00d1d52b2d5ef0019a4670503046a129fa1fb1300931ad - statsd-instrument (3.6.1) sha256=fdaf73665c9a4d99aeddcda2e70fc266935919225dc0bf01257234f59f8f55df - stringio (3.1.0) sha256=c1f6263ae03a15025e51194ab19b06b15e06adcaaedb7f5f6c06ab60f5d67718 - strong_migrations (1.6.4) sha256=0e67822fc47e778835339d10955d687d71db7f36a89e76d399542661e20e8761 - swd (2.0.3) sha256=4cdbe2a4246c19f093fce22e967ec3ebdd4657d37673672e621bf0c7eb770655 - tailwindcss-rails (2.1.0) sha256=9badde83d85d3437073640be26d6880e6e627eed60e7f8cfefa81e28c177d62b - terser (1.1.20) sha256=4921b2f9daf4d830eb4af90d692b5d73e899352f9eec77dc345da5717e479e2e - thor (1.3.0) sha256=1adc7f9e5b3655a68c71393fee8bd0ad088d14ee8e83a0b73726f23cbb3ca7c3 - tilt (2.2.0) sha256=e76f850e611128a87992bb13ba74807624a9b8ec748e2c2ea7139580f67ab22e - timeout (0.4.1) sha256=6f1f4edd4bca28cffa59501733a94215407c6960bd2107331f0280d4abdebb9a - toxiproxy (2.0.2) sha256=2e3b53604fb921d40da3db8f78a52b3133fcae33e93d440725335b15974e440a - tpm-key_attestation (0.12.0) sha256=e133d80cf24fef0e7a7dfad00fd6aeff01fc79875fbfc66cd8537bbd622b1e6d - turbo-rails (1.5.0) sha256=b426cc762fb0940277729b3f1751a9f0bd269f5613c1d62ac73e5f0be7c7a83e - turbo_power (0.5.0) sha256=869726c3a4308b038d6244f7a80eb1331bdd3171fa320dddcff71899fcae24fb - tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b - unf (0.1.4) sha256=4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e - unf_ext (0.0.8.2) sha256=90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa - unicode-display_width (2.4.2) sha256=6a10205d1a19ca790c4e53064ba93f09d9eb234bf6bd135d9deb6001c21428be - unpwn (1.0.0) sha256=6239d17d46a882b3719b24fb79c78a34caff89d57ab0f5e546be5b5c882bc7d3 - validate_email (0.1.6) sha256=9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4 - validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 - validates_formatting_of (0.9.0) sha256=139590a4b87596dbfb04d93e897bd2e6d30fb849d04fab0343e71ed2ca856e7e - version_gem (1.1.1) sha256=3c2da6ded29045ddcc0387e152dc634e1f0c490b7128dce0697ccc1cf0915b6c - view_component (3.8.0) sha256=3ec17fe3b56e0d679064b27dde796d7d4254d7bf8110594de85471b86ac0c5f6 - webauthn (3.1.0) sha256=e545fcf17d8a6b821161a37c1c4bc8c3d2ead0ff6ff3b098f57f417e731790b7 - webfinger (2.1.3) sha256=567a52bde77fb38ca6b67e55db755f988766ec4651c1d24916a65aa70540695c - webmock (3.19.1) sha256=eae7eee33989478188451f1fda4224d7fbe097c5c14e96b40b57347ef2d5d16d - websocket (1.2.10) sha256=2cc1a4a79b6e63637b326b4273e46adcddf7871caa5dc5711f2ca4061a629fa8 - websocket-driver (0.7.6) sha256=f69400be7bc197879726ad8e6f5869a61823147372fd8928836a53c2c741d0db - websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d - xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e - zeitwerk (2.6.12) sha256=561e12975d0332fd3b62cc859aff3bab432e5f320689c8a10cd4674b5c0439be - BUNDLED WITH 2.5.3 From fe178aaa109d239a37c3a77eb02da6f4f225c976 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 02:26:54 +0000 Subject: [PATCH 098/112] Bump factory_bot_rails from 6.4.2 to 6.4.3 Bumps [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails) from 6.4.2 to 6.4.3. - [Release notes](https://github.com/thoughtbot/factory_bot_rails/releases) - [Changelog](https://github.com/thoughtbot/factory_bot_rails/blob/main/NEWS.md) - [Commits](https://github.com/thoughtbot/factory_bot_rails/compare/v6.4.2...v6.4.3) --- updated-dependencies: - dependency-name: factory_bot_rails dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7331918341a..7d51d0dd97f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -216,9 +216,9 @@ GEM et-orbi (1.2.7) tzinfo execjs (2.9.1) - factory_bot (6.4.2) + factory_bot (6.4.5) activesupport (>= 5.0.0) - factory_bot_rails (6.4.2) + factory_bot_rails (6.4.3) factory_bot (~> 6.4) railties (>= 5.0.0) faraday (2.8.1) From 0d8ac01b4883c171e3bfcb0d8e648e51766ba0b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 16:12:54 -0700 Subject: [PATCH 099/112] Bump puma from 6.4.0 to 6.4.1 (#4323) --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7331918341a..42d034dc300 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -378,7 +378,7 @@ GEM timeout net-smtp (0.4.0) net-protocol - nio4r (2.6.1) + nio4r (2.7.0) nokogiri (1.16.0) mini_portile2 (~> 2.8.2) racc (~> 1.4) @@ -451,7 +451,7 @@ GEM psych (5.1.1.1) stringio public_suffix (5.0.4) - puma (6.4.0) + puma (6.4.1) nio4r (~> 2.0) pundit (2.3.1) activesupport (>= 3.0.0) From 584e369d937c2977283b2a0bb121ac8bdfd63c97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 22:05:02 +0000 Subject: [PATCH 100/112] Bump view_component from 3.8.0 to 3.9.0 Bumps [view_component](https://github.com/viewcomponent/view_component) from 3.8.0 to 3.9.0. - [Release notes](https://github.com/viewcomponent/view_component/releases) - [Changelog](https://github.com/ViewComponent/view_component/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/viewcomponent/view_component/compare/v3.8.0...v3.9.0) --- updated-dependencies: - dependency-name: view_component dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 5ff91a26fb2..b33a94497e8 100644 --- a/Gemfile +++ b/Gemfile @@ -63,7 +63,7 @@ gem "phlex-rails", "~> 1.1" # Admin dashboard gem "avo", "~> 2.46" -gem "view_component", "~> 3.8" +gem "view_component", "~> 3.9" gem "pundit", "~> 2.3" gem "chartkick", "~> 5.0" gem "groupdate", "~> 6.2" diff --git a/Gemfile.lock b/Gemfile.lock index abec9c99675..a937151ef29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -664,7 +664,7 @@ GEM validates_formatting_of (0.9.0) activemodel version_gem (1.1.1) - view_component (3.8.0) + view_component (3.9.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) @@ -796,7 +796,7 @@ DEPENDENCIES toxiproxy (~> 2.0) unpwn (~> 1.0) validates_formatting_of (~> 0.9) - view_component (~> 3.8) + view_component (~> 3.9) webauthn (~> 3.1) webmock (~> 3.18) xml-simple (~> 1.1) From 40869b8c5b2c14df0d1891850c9aba0718ed1de9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:16:15 +0000 Subject: [PATCH 101/112] Bump launchdarkly-server-sdk from 8.0.0 to 8.1.0 Bumps [launchdarkly-server-sdk](https://github.com/launchdarkly/ruby-server-sdk) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/launchdarkly/ruby-server-sdk/releases) - [Changelog](https://github.com/launchdarkly/ruby-server-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/launchdarkly/ruby-server-sdk/compare/8.0.0...8.1.0) --- updated-dependencies: - dependency-name: launchdarkly-server-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index b33a94497e8..06aa1ea6e06 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,7 @@ gem "honeybadger", "~> 5.4" gem "http_accept_language", "~> 2.1" gem "jquery-rails", "~> 4.5" gem "kaminari", "~> 1.2" -gem "launchdarkly-server-sdk", "~> 8.0" +gem "launchdarkly-server-sdk", "~> 8.1" gem "mail", "~> 2.8" gem "octokit", "~> 8.0" gem "omniauth-github", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index a937151ef29..c427249c18f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,7 +69,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) aggregate_assertions (0.2.0) @@ -201,8 +201,7 @@ GEM thor (>= 0.19, < 2) docile (1.4.0) dogstatsd-ruby (5.6.1) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20231109) dotenv (2.8.1) dotenv-rails (2.8.1) dotenv (= 2.8.1) @@ -283,7 +282,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.3) + json (2.7.1) json-jwt (1.16.4) activesupport (>= 4.2) aes_key_wrap @@ -303,7 +302,7 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - launchdarkly-server-sdk (8.0.0) + launchdarkly-server-sdk (8.1.0) concurrent-ruby (~> 1.1) http (>= 4.4.0, < 6.0.0) json (~> 2.3) @@ -648,9 +647,6 @@ GEM turbo-rails (~> 1.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) unicode-display_width (2.4.2) unpwn (1.0.0) bloomer (~> 1.0) @@ -733,7 +729,7 @@ DEPENDENCIES http_accept_language (~> 2.1) jquery-rails (~> 4.5) kaminari (~> 1.2) - launchdarkly-server-sdk (~> 8.0) + launchdarkly-server-sdk (~> 8.1) launchy (~> 2.5) letter_opener (~> 1.8) letter_opener_web (~> 2.0) From 2b9c8335265323c5cf61eec00e5d457f930fcb44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:05:23 -0700 Subject: [PATCH 102/112] Bump openid_connect from 2.2.1 to 2.3.0 (#4326) --- Gemfile | 2 +- Gemfile.lock | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 06aa1ea6e06..96c90821604 100644 --- a/Gemfile +++ b/Gemfile @@ -32,7 +32,7 @@ gem "octokit", "~> 8.0" gem "omniauth-github", "~> 2.0" gem "omniauth", "~> 2.1" gem "omniauth-rails_csrf_protection", "~> 1.0" -gem "openid_connect", "~> 2.2" +gem "openid_connect", "~> 2.3" gem "pg", "~> 1.4" gem "puma", "~> 6.4" gem "rack", "~> 2.2" diff --git a/Gemfile.lock b/Gemfile.lock index c427249c18f..3928fbbbcd0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -283,9 +283,10 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.7.1) - json-jwt (1.16.4) + json-jwt (1.16.5) activesupport (>= 4.2) aes_key_wrap + base64 bindata faraday (~> 2.0) faraday-follow_redirects @@ -405,16 +406,17 @@ GEM omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - openid_connect (2.2.1) + openid_connect (2.3.0) activemodel attr_required (>= 1.0.0) + email_validator faraday (~> 2.0) faraday-follow_redirects json-jwt (>= 1.16) + mail rack-oauth2 (~> 2.2) swd (~> 2.0) tzinfo - validate_email validate_url webfinger (~> 2.0) opensearch-ruby (3.1.0) @@ -651,9 +653,6 @@ GEM unpwn (1.0.0) bloomer (~> 1.0) pwned (~> 2.0) - validate_email (0.1.6) - activemodel (>= 3.0) - mail (>= 2.2.5) validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix @@ -749,7 +748,7 @@ DEPENDENCIES omniauth (~> 2.1) omniauth-github (~> 2.0) omniauth-rails_csrf_protection (~> 1.0) - openid_connect (~> 2.2) + openid_connect (~> 2.3) opensearch-ruby (~> 3.1) pg (~> 1.4) phlex-rails (~> 1.1) From ea45dc0e8d033f577ffdfb45333024d2379dabac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:16:50 +0000 Subject: [PATCH 103/112] Bump good_job from 3.21.5 to 3.22.0 Bumps [good_job](https://github.com/bensheldon/good_job) from 3.21.5 to 3.22.0. - [Release notes](https://github.com/bensheldon/good_job/releases) - [Changelog](https://github.com/bensheldon/good_job/blob/main/CHANGELOG.md) - [Commits](https://github.com/bensheldon/good_job/compare/v3.21.5...v3.22.0) --- updated-dependencies: - dependency-name: good_job dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 96c90821604..721f0417f4e 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ gem "ddtrace", "~> 1.18", require: "ddtrace/auto_instrument" gem "dogstatsd-ruby", "~> 5.5" gem "google-protobuf", "~> 3.25" gem "faraday", "~> 2.8" -gem "good_job", "~> 3.21" +gem "good_job", "~> 3.22" gem "gravtastic", "~> 3.2" gem "high_voltage", "~> 3.1" gem "honeybadger", "~> 5.4" diff --git a/Gemfile.lock b/Gemfile.lock index 3928fbbbcd0..3ae17c3efd4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,7 +241,7 @@ GEM ffi (~> 1.0) globalid (1.2.1) activesupport (>= 6.1) - good_job (3.21.5) + good_job (3.22.0) activejob (>= 6.0.0) activerecord (>= 6.0.0) concurrent-ruby (>= 1.0.2) @@ -719,7 +719,7 @@ DEPENDENCIES factory_bot_rails (~> 6.4) faraday (~> 2.8) faraday_middleware-aws-sigv4 (~> 1.0) - good_job (~> 3.21) + good_job (~> 3.22) google-protobuf (~> 3.25) gravtastic (~> 3.2) groupdate (~> 6.2) From 75b24c2634ea0ead76d433266c98e4d219a7633c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jan 2024 19:40:09 -0700 Subject: [PATCH 104/112] Bump tailwindcss-rails from 2.1.0 to 2.2.0 (#4329) Bumps [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/rails/tailwindcss-rails/releases) - [Changelog](https://github.com/rails/tailwindcss-rails/blob/main/CHANGELOG.md) - [Commits](https://github.com/rails/tailwindcss-rails/compare/v2.1.0...v2.2.0) --- updated-dependencies: - dependency-name: tailwindcss-rails dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 721f0417f4e..a1a0b2586e5 100644 --- a/Gemfile +++ b/Gemfile @@ -74,7 +74,7 @@ gem "rails_semantic_logger", "~> 4.14" gem "pp", "0.5.0" group :assets, :development do - gem "tailwindcss-rails", "~> 2.1" + gem "tailwindcss-rails", "~> 2.2" end group :assets do diff --git a/Gemfile.lock b/Gemfile.lock index 3ae17c3efd4..9458e70863e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -629,7 +629,7 @@ GEM attr_required (>= 0.0.5) faraday (~> 2.0) faraday-follow_redirects - tailwindcss-rails (2.1.0) + tailwindcss-rails (2.2.0) railties (>= 6.0.0) terser (1.1.20) execjs (>= 0.3.0, < 3) @@ -786,7 +786,7 @@ DEPENDENCIES sprockets-rails (~> 3.4) statsd-instrument (~> 3.5) strong_migrations (~> 1.6) - tailwindcss-rails (~> 2.1) + tailwindcss-rails (~> 2.2) terser (~> 1.1) toxiproxy (~> 2.0) unpwn (~> 1.0) From 6893b94724c298c31529a464326963194b5d6a09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jan 2024 19:40:48 -0700 Subject: [PATCH 105/112] Bump net-smtp from 0.4.0 to 0.4.0.1 (#4328) Bumps [net-smtp](https://github.com/ruby/net-smtp) from 0.4.0 to 0.4.0.1. - [Release notes](https://github.com/ruby/net-smtp/releases) - [Changelog](https://github.com/ruby/net-smtp/blob/master/NEWS.md) - [Commits](https://github.com/ruby/net-smtp/compare/v0.4.0...v0.4.0.1) --- updated-dependencies: - dependency-name: net-smtp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9458e70863e..ea9da5a1ce3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -376,7 +376,7 @@ GEM net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol nio4r (2.7.0) nokogiri (1.16.0) From 0b3272ac17b45748ee0d1867c49867c7deb26565 Mon Sep 17 00:00:00 2001 From: Martin Emde Date: Fri, 22 Dec 2023 11:53:04 -0800 Subject: [PATCH 106/112] Protect forgotten password changes from MFA bypass. It was previously possible to submit the password update form directly with only the confirmation token, bypassing MFA protections. --- app/controllers/adoptions_controller.rb | 2 +- app/controllers/application_controller.rb | 4 - .../concerns/session_verifiable.rb | 13 +- app/controllers/passwords_controller.rb | 34 ++- app/controllers/sessions_controller.rb | 4 +- app/views/passwords/edit.html.erb | 4 +- config/locales/de.yml | 1 + config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + config/locales/ja.yml | 1 + config/locales/nl.yml | 1 + config/locales/pt-BR.yml | 1 + config/locales/zh-CN.yml | 1 + config/locales/zh-TW.yml | 1 + test/functional/passwords_controller_test.rb | 254 +++++++++++++----- test/integration/password_reset_test.rb | 34 ++- 17 files changed, 269 insertions(+), 89 deletions(-) diff --git a/app/controllers/adoptions_controller.rb b/app/controllers/adoptions_controller.rb index 1cc0b4858b6..5c3cdb58a5e 100644 --- a/app/controllers/adoptions_controller.rb +++ b/app/controllers/adoptions_controller.rb @@ -3,7 +3,7 @@ class AdoptionsController < ApplicationController before_action :find_rubygem before_action :verify_ownership_requestable - before_action :redirect_to_verify, if: -> { current_user_is_owner? && !password_session_active? } + before_action :redirect_to_verify, if: -> { current_user_is_owner? && !verified_session_active? } def index @ownership_call = @rubygem.ownership_call diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d0be78968fe..92aed8dd6c6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -153,10 +153,6 @@ def set_cache_headers response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" end - def password_session_active? - session[:verification] && session[:verification] > Time.current && session.fetch(:verified_user, "") == current_user.id - end - def set_error_context_user return unless current_user diff --git a/app/controllers/concerns/session_verifiable.rb b/app/controllers/concerns/session_verifiable.rb index 2f7a6d68b8d..864e9c95747 100644 --- a/app/controllers/concerns/session_verifiable.rb +++ b/app/controllers/concerns/session_verifiable.rb @@ -6,7 +6,7 @@ def verify_session_before(**opts) before_action :redirect_to_signin, **opts, unless: :signed_in? before_action :redirect_to_new_mfa, **opts, if: :mfa_required_not_yet_enabled? before_action :redirect_to_settings_strong_mfa_required, **opts, if: :mfa_required_weak_level_enabled? - before_action :redirect_to_verify, **opts, unless: :password_session_active? + before_action :redirect_to_verify, **opts, unless: :verified_session_active? end end @@ -25,5 +25,16 @@ def redirect_to_verify session[:redirect_uri] = verify_session_redirect_path redirect_to verify_session_path end + + def session_verified + session[:verified_user] = current_user.id + session[:verification] = Gemcutter::PASSWORD_VERIFICATION_EXPIRY.from_now + end + + def verified_session_active? + session[:verification] && + session[:verification] > Time.current && + session.fetch(:verified_user, "") == current_user.id + end end end diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 13304eaaf80..250b2578913 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -1,10 +1,17 @@ class PasswordsController < Clearance::PasswordsController include MfaExpiryMethods include WebauthnVerifiable + include SessionVerifiable before_action :validate_confirmation_token, only: %i[edit otp_edit webauthn_edit] after_action :delete_mfa_expiry_session, only: %i[otp_edit webauthn_edit] + # By default, clearance expects the token to be submitted with the password update. + # We already invalidated the token when the user became verified by token(+mfa). + skip_before_action :ensure_existing_user, only: %i[update] + # Instead of the token, we now require the user to have been verified recently. + verify_session_before only: %i[update] + def edit if @user.mfa_enabled? @otp_verification_url = otp_edit_user_password_url(@user, token: @user.confirmation_token) @@ -14,17 +21,16 @@ def edit render template: "multifactor_auths/prompt" else + # When user doesn't have mfa, a valid token is a full "magic link" sign in. + verified_sign_in render template: "passwords/edit" end end def update - @user = find_user_for_update - - if @user.update_password password_from_password_reset_params - @user.reset_api_key! if reset_params[:reset_api_key] == "true" - @user.api_keys.expire_all! if reset_params[:reset_api_keys] == "true" - sign_in @user + if current_user.update_password password_from_password_reset_params + current_user.reset_api_key! if reset_params[:reset_api_key] == "true" + current_user.api_keys.expire_all! if reset_params[:reset_api_keys] == "true" redirect_to url_after_update session[:password_reset_token] = nil else @@ -35,6 +41,8 @@ def update def otp_edit if otp_edit_conditions_met? + # When the user identified by the email token submits adequate totp, they are logged in + verified_sign_in render template: "passwords/edit" elsif !session_active? login_failure(t("multifactor_auths.session_expired")) @@ -51,11 +59,20 @@ def webauthn_edit return login_failure(@webauthn_error) unless webauthn_credential_verified? + # When the user identified by the email token submits verified webauthn, they are logged in + verified_sign_in render template: "passwords/edit" end private + def verified_sign_in + sign_in @user + session_verified + @user.update!(confirmation_token: nil) + StatsD.increment "login.success" + end + def url_after_update dashboard_path end @@ -81,4 +98,9 @@ def login_failure(message) flash.now.alert = message render template: "multifactor_auths/prompt", status: :unauthorized end + + def redirect_to_verify + session[:redirect_uri] = verify_session_redirect_path + redirect_to verify_session_path, alert: t("verification_expired") + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 10b354c8449..d696234a435 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,6 +1,7 @@ class SessionsController < Clearance::SessionsController include MfaExpiryMethods include WebauthnVerifiable + include SessionVerifiable before_action :redirect_to_signin, unless: :signed_in?, only: %i[verify webauthn_authenticate authenticate] before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled?, only: %i[verify webauthn_authenticate authenticate] @@ -93,8 +94,7 @@ def webauthn_authenticate private def mark_verified - session[:verified_user] = current_user.id - session[:verification] = Gemcutter::PASSWORD_VERIFICATION_EXPIRY.from_now + session_verified redirect_to session.delete(:redirect_uri) || root_path end diff --git a/app/views/passwords/edit.html.erb b/app/views/passwords/edit.html.erb index fa6d7fc38a4..dea3648b554 100644 --- a/app/views/passwords/edit.html.erb +++ b/app/views/passwords/edit.html.erb @@ -1,9 +1,9 @@ <% @title = t('.title') %> <%= form_for(:password_reset, - :url => user_password_path(@user, :token => @user.confirmation_token), + :url => user_password_path(current_user), :html => { :method => :put }) do |form| %> - <%= error_messages_for @user %> + <%= error_messages_for current_user %>
    <%= form.label :password, "Password", :class => 'form__label' %> <%= form.password_field :password, autocomplete: 'new-password', class: 'form__input' %> diff --git a/config/locales/de.yml b/config/locales/de.yml index 9e6df4cee0f..caae7be98e8 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -3,6 +3,7 @@ de: credentials_required: edit: Bearbeiten failure_when_forbidden: + verification_expired: feed_latest: RubyGems.org | Neueste Gems feed_subscribed: RubyGems.org | Abonnierte Gems footer_about_html: diff --git a/config/locales/en.yml b/config/locales/en.yml index 9e16b317646..6edb8565416 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3,6 +3,7 @@ en: credentials_required: Credentials required edit: Edit failure_when_forbidden: Please double check the URL or try submitting it again. + verification_expired: The verification has expired. Please verify again. feed_latest: RubyGems.org | Latest Gems feed_subscribed: RubyGems.org | Subscribed Gems footer_about_html: diff --git a/config/locales/es.yml b/config/locales/es.yml index f4e3a95a71a..147eb0f5f19 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -3,6 +3,7 @@ es: credentials_required: Credenciales requeridas edit: Editar failure_when_forbidden: Por favor verifica la URL o inténtalo nuevamente. + verification_expired: feed_latest: RubyGems.org | Gemas más recientes feed_subscribed: RubyGems.org | Suscripciones a gemas footer_about_html: RubyGems.org es el servicio de alojamiento de Gemas de la comunidad diff --git a/config/locales/fr.yml b/config/locales/fr.yml index eed71b4557c..812d4d95e40 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -3,6 +3,7 @@ fr: credentials_required: edit: Modification failure_when_forbidden: Veuillez vérifier l'URL ou réessayer. + verification_expired: feed_latest: RubyGems.org | Derniers Gems feed_subscribed: RubyGems.org | Gems abonnés footer_about_html: RubyGems.org est le service d’hébergement de la communauté diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 61cac4f0ac6..3a421f6dcb1 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -3,6 +3,7 @@ ja: credentials_required: 認証情報が必要です edit: 編集 failure_when_forbidden: URLを見返すか、再度送信してみてください。 + verification_expired: feed_latest: RubyGems.org | 最新のgemの一覧 feed_subscribed: RubyGems.org | 購読したgemの一覧 footer_about_html: RubyGems.orgはRubyコミュニティのgemのホスティングサービスです。すぐにgemを公開して发布您的 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 0e023a49a58..526a8cb8d5b 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -3,6 +3,7 @@ zh-TW: credentials_required: edit: 編輯 failure_when_forbidden: 請確認 URL 或再次提交 + verification_expired: feed_latest: RubyGems.org | 最新 Gems feed_subscribed: RubyGems.org | 訂閱 Gems footer_about_html: RubyGems.org 是 Ruby 社群的 Gem 套件管理服務,讓你能立即地發佈及安裝你的 Gem 套件,並且利用 diff --git a/test/functional/passwords_controller_test.rb b/test/functional/passwords_controller_test.rb index 9328fe8391c..e63640901b6 100644 --- a/test/functional/passwords_controller_test.rb +++ b/test/functional/passwords_controller_test.rb @@ -35,6 +35,14 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :success + should "sign in the user" do + assert_predicate @controller.request.env[:clearance], :signed_in? + end + + should "invalidate the confirmation_token" do + assert_nil @user.reload.confirmation_token + end + should "display edit form" do page.assert_text("Reset password") page.assert_selector("input[type=password][autocomplete=new-password]") @@ -49,6 +57,10 @@ class PasswordsControllerTest < ActionController::TestCase should redirect_to("the home page") { root_path } + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "warn about invalid url" do assert_equal "Please double check the URL or try submitting it again.", flash[:alert] end @@ -62,6 +74,10 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :success + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "display otp form" do assert page.has_content?("Multi-factor authentication") assert page.has_content?("OTP code") @@ -80,6 +96,10 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :success + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "display webauthn prompt" do assert page.has_button?("Authenticate with security device") end @@ -97,6 +117,10 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :success + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "display webauthn prompt" do assert page.has_button?("Authenticate with security device") end @@ -115,6 +139,10 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :success + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "display webauthn prompt" do assert page.has_button?("Authenticate with security device") end @@ -142,9 +170,18 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :success + should "sign in the user" do + assert_predicate @controller.request.env[:clearance], :signed_in? + end + + should "invalidate the confirmation_token" do + assert_nil @user.reload.confirmation_token + end + should "display edit form" do page.assert_text("Reset password") end + should "clear mfa_expires_at" do assert_nil @controller.session[:mfa_expires_at] end @@ -158,6 +195,10 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :unauthorized + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "alert about otp being incorrect" do assert_equal "Your OTP code is incorrect.", flash[:alert] end @@ -222,6 +263,14 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :success + should "sign in the user" do + assert_predicate @controller.request.env[:clearance], :signed_in? + end + + should "invalidate the confirmation_token" do + assert_nil @user.reload.confirmation_token + end + should "display edit form" do page.assert_text("Reset password") end @@ -238,6 +287,10 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :unauthorized + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "set flash notice" do assert_equal "Credentials required", flash[:alert] end @@ -266,9 +319,14 @@ class PasswordsControllerTest < ActionController::TestCase should respond_with :unauthorized + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end + should "set flash notice" do assert_equal "WebAuthn::ChallengeVerificationError", flash[:alert] end + should "still have the webauthn form url" do assert_not_nil page.find(".js-webauthn-session--form")[:action] end @@ -322,16 +380,16 @@ class PasswordsControllerTest < ActionController::TestCase @old_encrypted_password = @user.encrypted_password end - context "with reset_api_key and invalid password" do + context "when not signed in" do setup do put :update, params: { user_id: @user.id, token: @user.confirmation_token, - password_reset: { reset_api_key: "true", password: "pass" } + password_reset: { reset_api_key: "true", reset_api_keys: "true", password: PasswordHelpers::SECURE_TEST_PASSWORD } } end - should respond_with :success + should redirect_to("the sign in page") { sign_in_path } should "not change api_key" do assert_equal(@user.reload.api_key, @api_key) @@ -339,89 +397,145 @@ class PasswordsControllerTest < ActionController::TestCase should "not change password" do assert_equal(@user.reload.encrypted_password, @old_encrypted_password) end + should "not sign in the user" do + refute_predicate @controller.request.env[:clearance], :signed_in? + end end - context "without reset_api_key and valid password" do + context "when signed in" do setup do - put :update, params: { - user_id: @user.id, - token: @user.confirmation_token, - password_reset: { password: PasswordHelpers::SECURE_TEST_PASSWORD } - } + sign_in_as @user + session[:verification] = 10.minutes.from_now + session[:verified_user] = @user.id end - should respond_with :found - - should "not change api_key" do - assert_equal(@user.reload.api_key, @api_key) + teardown do + session[:verification] = nil + session[:verified_user] = nil end - should "change password" do - refute_equal(@user.reload.encrypted_password, @old_encrypted_password) - end - end - context "with reset_api_key false and valid password" do - setup do - put :update, params: { - user_id: @user.id, - token: @user.confirmation_token, - password_reset: { reset_api_key: "false", password: PasswordHelpers::SECURE_TEST_PASSWORD } - } - end + context "with invalid password" do + setup do + put :update, params: { + user_id: @user.id, + token: @user.confirmation_token, + password_reset: { reset_api_key: "true", password: "pass" } + } + end - should respond_with :found + should respond_with :success - should "not change api_key" do - assert_equal(@user.reload.api_key, @api_key) - end - should "change password" do - refute_equal(@user.reload.encrypted_password, @old_encrypted_password) + should "not change api_key" do + assert_equal(@user.reload.api_key, @api_key) + end + should "not change password" do + assert_equal(@user.reload.encrypted_password, @old_encrypted_password) + end end - end - context "with reset_api_key and valid password" do - setup do - put :update, params: { - user_id: @user.id, - token: @user.confirmation_token, - password_reset: { reset_api_key: "true", password: PasswordHelpers::SECURE_TEST_PASSWORD } - } - end + context "with a valid password" do + context "when verification has expired" do + setup do + travel 16.minutes do + put :update, params: { + user_id: @user.id, + token: @user.confirmation_token, + password_reset: { password: PasswordHelpers::SECURE_TEST_PASSWORD } + } + end + end - should respond_with :found + should set_flash[:alert] + should redirect_to("the verification page") { verify_session_path } - should "change api_key" do - refute_equal(@user.reload.api_key, @api_key) - end - should "change password" do - refute_equal(@user.reload.encrypted_password, @old_encrypted_password) - end - should "not delete new api key" do - refute_predicate @new_api_key.reload, :destroyed? - refute_empty @user.reload.api_keys - end - end + should "not sign the user out" do + assert_predicate @controller.request.env[:clearance], :signed_in? + end + end - context "with reset_api_key and reset_api_keys and valid password" do - setup do - put :update, params: { - user_id: @user.id, - token: @user.confirmation_token, - password_reset: { reset_api_key: "true", reset_api_keys: "true", password: PasswordHelpers::SECURE_TEST_PASSWORD } - } - end + context "without reset_api_key" do + setup do + put :update, params: { + user_id: @user.id, + token: @user.confirmation_token, + password_reset: { password: PasswordHelpers::SECURE_TEST_PASSWORD } + } + end - should respond_with :found + should respond_with :found - should "change api_key" do - refute_equal(@user.reload.api_key, @api_key) - end - should "change password" do - refute_equal(@user.reload.encrypted_password, @old_encrypted_password) - end - should "expire new api key" do - assert_empty @user.reload.api_keys.unexpired - refute_empty @user.reload.api_keys.expired + should "not change api_key" do + assert_equal(@user.reload.api_key, @api_key) + end + should "change password" do + refute_equal(@user.reload.encrypted_password, @old_encrypted_password) + end + end + + context "with reset_api_key false" do + setup do + put :update, params: { + user_id: @user.id, + token: @user.confirmation_token, + password_reset: { reset_api_key: "false", password: PasswordHelpers::SECURE_TEST_PASSWORD } + } + end + + should respond_with :found + + should "not change api_key" do + assert_equal(@user.reload.api_key, @api_key) + end + should "change password" do + refute_equal(@user.reload.encrypted_password, @old_encrypted_password) + end + end + + context "with reset_api_key" do + setup do + put :update, params: { + user_id: @user.id, + token: @user.confirmation_token, + password_reset: { reset_api_key: "true", password: PasswordHelpers::SECURE_TEST_PASSWORD } + } + end + + should respond_with :found + + should "change api_key" do + refute_equal(@user.reload.api_key, @api_key) + end + should "change password" do + refute_equal(@user.reload.encrypted_password, @old_encrypted_password) + end + should "not delete new api key" do + refute_predicate @new_api_key.reload, :destroyed? + refute_empty @user.reload.api_keys + end + end + + context "with reset_api_key and reset_api_keys" do + setup do + put :update, params: { + user_id: @user.id, + token: @user.confirmation_token, + password_reset: { reset_api_key: "true", reset_api_keys: "true", password: PasswordHelpers::SECURE_TEST_PASSWORD } + } + end + + should respond_with :found + + should "change api_key" do + refute_equal(@user.reload.api_key, @api_key) + end + should "change password" do + refute_equal(@user.reload.encrypted_password, @old_encrypted_password) + end + should "expire new api key" do + assert_empty @user.reload.api_keys.unexpired + refute_empty @user.reload.api_keys.expired + end + end end end end diff --git a/test/integration/password_reset_test.rb b/test/integration/password_reset_test.rb index 8ed199be1ee..d56ab2a930e 100644 --- a/test/integration/password_reset_test.rb +++ b/test/integration/password_reset_test.rb @@ -55,13 +55,36 @@ def forgot_password_with(email) visit password_reset_link + assert page.has_content?("Sign out") + fill_in "Password", with: "" click_button "Save this password" assert page.has_content? "Password can't be blank." - assert page.has_content? "Sign in" + assert page.has_content? "Reset password" + + # try again + fill_in "Password", with: PasswordHelpers::SECURE_TEST_PASSWORD + click_button "Save this password" + + assert @user.reload.authenticated? PasswordHelpers::SECURE_TEST_PASSWORD + end + + test "resetting a password but waiting too long after token auth" do + forgot_password_with @user.email + + visit password_reset_link + + fill_in "Password", with: PasswordHelpers::SECURE_TEST_PASSWORD + + travel 16.minutes do + click_button "Save this password" + + assert page.has_content? "verification has expired. Please verify again." + end end + test "resetting a password when signed in" do visit sign_in_path @@ -78,6 +101,8 @@ def forgot_password_with(email) visit password_reset_link + assert page.has_content?("Sign out") + fill_in "Password", with: PasswordHelpers::SECURE_TEST_PASSWORD click_button "Save this password" @@ -90,13 +115,15 @@ def forgot_password_with(email) visit password_reset_link + refute page.has_content?("Sign out") + fill_in "otp", with: ROTP::TOTP.new(@user.totp_seed).now click_button "Authenticate" + assert page.has_content?("Sign out") + fill_in "Password", with: PasswordHelpers::SECURE_TEST_PASSWORD click_button "Save this password" - - assert page.has_content?("Sign out") end test "resetting a password when mfa is enabled but mfa session is expired" do @@ -141,6 +168,7 @@ def forgot_password_with(email) visit password_reset_link + refute page.has_content? "Sign out" assert page.has_content? "Multi-factor authentication" assert page.has_content? "Security Device" assert page.has_content? "Recovery code" From c204f62b560b263239d8cb196a331b3ea96b4463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Mon, 8 Jan 2024 03:45:52 +0100 Subject: [PATCH 107/112] Remove empty line breaking rubocop. --- test/integration/password_reset_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/password_reset_test.rb b/test/integration/password_reset_test.rb index d56ab2a930e..dda77df7ae3 100644 --- a/test/integration/password_reset_test.rb +++ b/test/integration/password_reset_test.rb @@ -84,7 +84,6 @@ def forgot_password_with(email) end end - test "resetting a password when signed in" do visit sign_in_path From 056a10c1634ae77a2dd418bf7303b06ccd1b9267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Mon, 8 Jan 2024 13:48:59 +0100 Subject: [PATCH 108/112] Revert "Make pushing idempotent, and after-write job enqueuing atomic with making the version indexed (#4255)" This reverts commit 2c5f889c2ca231cad3aa3bd0306f2c0fdd422b5c. --- app/avo/actions/version_after_write.rb | 20 --------- app/avo/resources/version_resource.rb | 1 - app/jobs/after_version_write_job.rb | 31 ------------- app/models/pusher.rb | 62 ++++++++++++++------------ app/models/rubygem.rb | 4 ++ app/models/version.rb | 3 +- app/views/mailer/gem_pushed.html.erb | 4 -- lib/certificate_chain_serializer.rb | 4 +- test/integration/push_test.rb | 42 +---------------- test/models/pusher_test.rb | 7 +-- test/system/avo/versions_test.rb | 36 +-------------- test/unit/mailer_test.rb | 4 -- 12 files changed, 46 insertions(+), 172 deletions(-) delete mode 100644 app/avo/actions/version_after_write.rb delete mode 100644 app/jobs/after_version_write_job.rb diff --git a/app/avo/actions/version_after_write.rb b/app/avo/actions/version_after_write.rb deleted file mode 100644 index 94490970847..00000000000 --- a/app/avo/actions/version_after_write.rb +++ /dev/null @@ -1,20 +0,0 @@ -class VersionAfterWrite < BaseAction - self.name = "Run version post-write job" - self.visible = lambda { - current_user.team_member?("rubygems-org") && - view == :show && - resource.model.deletion.blank? - } - - self.message = lambda { - "Are you sure you would like to run the after-write job for #{record.full_name}? The version is #{'not ' unless record.indexed?} indexed." - } - - self.confirm_button_label = "Run Job" - - class ActionHandler < ActionHandler - def handle_model(version) - AfterVersionWriteJob.new(version: version).perform(version: version) - end - end -end diff --git a/app/avo/resources/version_resource.rb b/app/avo/resources/version_resource.rb index 62d853fb69c..4334c6f1c89 100644 --- a/app/avo/resources/version_resource.rb +++ b/app/avo/resources/version_resource.rb @@ -6,7 +6,6 @@ class VersionResource < Avo::BaseResource } action RestoreVersion - action VersionAfterWrite class IndexedFilter < ScopeBooleanFilter; end filter IndexedFilter, arguments: { default: { indexed: true, yanked: true } } diff --git a/app/jobs/after_version_write_job.rb b/app/jobs/after_version_write_job.rb deleted file mode 100644 index ca898da7423..00000000000 --- a/app/jobs/after_version_write_job.rb +++ /dev/null @@ -1,31 +0,0 @@ -class AfterVersionWriteJob < ApplicationJob - queue_as :default - - def perform(version:) - version.transaction do - rubygem = version.rubygem - version.rubygem.push_notifiable_owners.each do |notified_user| - Mailer.gem_pushed(owner, version.id, notified_user.id).deliver_later - end - Indexer.perform_later - UploadVersionsFileJob.perform_later - UploadInfoFileJob.perform_later(rubygem_name: rubygem.name) - UploadNamesFileJob.perform_later - ReindexRubygemJob.perform_later(rubygem:) - StoreVersionContentsJob.perform_later(version:) if ld_variation(key: "gemcutter.pusher.store_version_contents", default: false) - version.update!(indexed: true) - end - end - - def ld_variation(key:, default:) - return default unless owner - - Rails.configuration.launch_darkly_client.variation( - key, owner.ld_context, default - ) - end - - def owner - arguments.dig(0, :version).pusher_api_key&.owner - end -end diff --git a/app/models/pusher.rb b/app/models/pusher.rb index 347251863fc..6d7922a6076 100644 --- a/app/models/pusher.rb +++ b/app/models/pusher.rb @@ -86,36 +86,16 @@ def pull_spec MSG end - def find # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def find # rubocop:disable Metrics/AbcSize name = spec.name.to_s set_tag "gemcutter.rubygem.name", name @rubygem = Rubygem.name_is(name).first || Rubygem.new(name: name) - sha256 = Digest::SHA2.base64digest(body.string) - spec_sha256 = Digest::SHA2.base64digest(spec_contents) - - version = @rubygem.versions - .create_with(indexed: false, cert_chain: spec.cert_chain) - .find_or_initialize_by( - number: spec.version.to_s, - platform: spec.original_platform.to_s, - gem_platform: spec.platform.to_s, - size: size, - sha256: sha256, - spec_sha256: spec_sha256, - pusher: api_key.user, - pusher_api_key: api_key - ) - unless @rubygem.new_record? - # Return success for idempotent pushes - return notify("Gem was already pushed: #{version.to_title}", 200) if version.indexed? - - # If the gem is yanked, we can't repush it - # Additionally, we don't allow overwriting existing versions - if (existing = @rubygem.versions.find_by(number: version.number, platform: version.platform)) - return republish_notification(existing) + if (version = @rubygem.find_version_from_spec spec) + republish_notification(version) + return false end if @rubygem.name != name && @rubygem.indexed_versions? @@ -126,7 +106,19 @@ def find # rubocop:disable Metrics/AbcSize, Metrics/MethodLength # Update the name to reflect a valid case change @rubygem.name = name - @version = version + + sha256 = Digest::SHA2.base64digest(body.string) + spec_sha256 = Digest::SHA2.base64digest(spec_contents) + + @version = @rubygem.versions.new number: spec.version.to_s, + platform: spec.original_platform.to_s, + gem_platform: spec.platform.to_s, + size: size, + sha256: sha256, + spec_sha256: spec_sha256, + pusher: api_key.user, + pusher_api_key: api_key, + cert_chain: spec.cert_chain set_tags "gemcutter.rubygem.version" => @version.number, "gemcutter.rubygem.platform" => @version.platform log_pushing @@ -145,12 +137,27 @@ def inspect private def after_write + @version_id = version.id + version.rubygem.push_notifiable_owners.each do |notified_user| + Mailer.gem_pushed(owner, @version_id, notified_user.id).deliver_later + end + Indexer.perform_later + UploadVersionsFileJob.perform_later + UploadInfoFileJob.perform_later(rubygem_name: rubygem.name) + UploadNamesFileJob.perform_later + ReindexRubygemJob.perform_later(rubygem:) GemCachePurger.call(rubygem.name) + StoreVersionContentsJob.perform_later(version:) if ld_variation(key: "gemcutter.pusher.store_version_contents", default: false) RackAttackReset.gem_push_backoff(@remote_ip, owner.to_gid) if @remote_ip.present? - AfterVersionWriteJob.new(version:).perform(version:) StatsD.increment "push.success" end + def ld_variation(key:, default:) + Rails.configuration.launch_darkly_client.variation( + key, owner.ld_context, default + ) + end + def notify(message, code) logger.info { { message:, code:, owner: owner, api_key: api_key&.id, rubygem: rubygem&.name, version: version&.full_name } } @@ -197,9 +204,6 @@ def republish_notification(version) notify("Repushing of gem versions is not allowed.\n" \ "Please bump the version number and push a new different release.\n" \ "See also `gem yank` if you want to unpublish the bad release.", 409) - elsif version.deletion.nil? - notify("It appears that #{version.full_name} did not finish pushing.\n" \ - "Please contact support@rubygems.org for assistance if you pushed this gem more than a minute ago.", 409) else different_owner = "pushed by a previous owner of this gem " unless owner.owns_gem?(version.rubygem) notify("A yanked version #{different_owner}already exists (#{version.full_name}).\n" \ diff --git a/app/models/rubygem.rb b/app/models/rubygem.rb index 8b0eb2a1299..69cb7ae936c 100644 --- a/app/models/rubygem.rb +++ b/app/models/rubygem.rb @@ -323,6 +323,10 @@ def disown oidc_rubygem_trusted_publishers.clear end + def find_version_from_spec(spec) + versions.find_by_number_and_platform(spec.version.to_s, spec.original_platform.to_s) + end + def find_or_initialize_version_from_spec(spec) version = versions.find_or_initialize_by(number: spec.version.to_s, platform: spec.original_platform.to_s, diff --git a/app/models/version.rb b/app/models/version.rb index d8e9a02bff5..89a483f21bf 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -265,7 +265,8 @@ def update_attributes_from_gem_specification!(spec) requirements: spec.requirements, built_at: spec.date, required_rubygems_version: spec.required_rubygems_version.to_s, - required_ruby_version: spec.required_ruby_version.to_s + required_ruby_version: spec.required_ruby_version.to_s, + indexed: true ) end diff --git a/app/views/mailer/gem_pushed.html.erb b/app/views/mailer/gem_pushed.html.erb index 0f55c683479..f922d42bd8f 100644 --- a/app/views/mailer/gem_pushed.html.erb +++ b/app/views/mailer/gem_pushed.html.erb @@ -17,7 +17,6 @@
    <% if @pushed_by_user.is_a?(User) %> Pushed by user: - <% if @pushed_by_user %> <%= link_to @pushed_by_user.handle, profile_url(@pushed_by_user.display_id), target: "_blank" %> <%= mail_to(@pushed_by_user.email) if @pushed_by_user.public_email? %> @@ -28,9 +27,6 @@ <% end %>
    - <% else %> - Unknown - <% end %> Pushed at: <%= @version.created_at.to_formatted_s(:rfc822) %>


    diff --git a/lib/certificate_chain_serializer.rb b/lib/certificate_chain_serializer.rb index 954d7622d07..853b164cb97 100644 --- a/lib/certificate_chain_serializer.rb +++ b/lib/certificate_chain_serializer.rb @@ -3,7 +3,7 @@ class CertificateChainSerializer def self.load(chain) return [] unless chain - chain.scan(PATTERN).map! do |cert| + chain.scan(PATTERN).map do |cert| OpenSSL::X509::Certificate.new(cert) end end @@ -13,6 +13,6 @@ def self.dump(chain) normalised = chain.map do |cert| cert.respond_to?(:to_pem) ? cert : OpenSSL::X509::Certificate.new(cert) end - normalised.map!(&:to_pem).join + normalised.map(&:to_pem).join end end diff --git a/test/integration/push_test.rb b/test/integration/push_test.rb index 51e22fa2cde..af733526118 100644 --- a/test/integration/push_test.rb +++ b/test/integration/push_test.rb @@ -252,8 +252,7 @@ class PushTest < ActionDispatch::IntegrationTest test "republish a yanked version" do rubygem = create(:rubygem, name: "sandworm", owners: [@user]) - version = create(:version, number: "1.0.0", rubygem: rubygem) - create(:deletion, version:) + create(:version, number: "1.0.0", indexed: false, rubygem: rubygem) build_gem "sandworm", "1.0.0" @@ -265,8 +264,7 @@ class PushTest < ActionDispatch::IntegrationTest test "republish a yanked version by a different owner" do rubygem = create(:rubygem, name: "sandworm") - version = create(:version, number: "1.0.0", rubygem: rubygem) - create(:deletion, version:) + create(:version, number: "1.0.0", indexed: false, rubygem: rubygem) build_gem "sandworm", "1.0.0" @@ -276,42 +274,6 @@ class PushTest < ActionDispatch::IntegrationTest assert_match(/A yanked version pushed by a previous owner of this gem already exists \(sandworm-1.0.0\)/, response.body) end - test "republish an indexed version" do - build_gem "sandworm", "1.0.0" - - push_gem "sandworm-1.0.0.gem" - - assert_response :success - - assert_enqueued_jobs 0 do - push_gem "sandworm-1.0.0.gem" - end - - assert_response :success - assert_equal("Gem was already pushed: sandworm (1.0.0)", response.body) - end - - test "republish a version where the gem is un-indexed but not yanked" do - build_gem "sandworm", "1.0.0" - - Pusher.any_instance.stubs(:after_write) - - push_gem "sandworm-1.0.0.gem" - - Pusher.any_instance.unstub(:after_write) - - assert_enqueued_jobs 0 do - push_gem "sandworm-1.0.0.gem" - end - - assert_response :conflict - assert_equal( - "It appears that sandworm-1.0.0 did not finish pushing.\n" \ - "Please contact support@rubygems.org for assistance if you pushed this gem more than a minute ago.", - response.body - ) - end - test "publishing a gem with ceritifcate but not signatures" do build_gem "sandworm", "2.0.0" do |gemspec| gemspec.cert_chain = [File.read(File.expand_path("../certs/chain.pem", __dir__))] diff --git a/test/models/pusher_test.rb b/test/models/pusher_test.rb index 585f466a036..bfa0daeddd1 100644 --- a/test/models/pusher_test.rb +++ b/test/models/pusher_test.rb @@ -435,11 +435,8 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: spec = mock spec.expects(:name).returns @rubygem.name.upcase spec.expects(:version).returns Gem::Version.new("1.3.3.7") - spec.expects(:platform).returns "ruby" spec.expects(:original_platform).returns "ruby" - spec.expects(:cert_chain).returns nil @cutter.stubs(:spec).returns spec - @cutter.stubs(:spec_contents).returns "spec" refute @cutter.find @@ -595,7 +592,7 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: setup do @rubygem = create(:rubygem, name: "gemsgemsgems") @cutter.stubs(:rubygem).returns @rubygem - create(:version, rubygem: @rubygem, number: "0.1.1", summary: "old summary", pusher_api_key: @cutter.api_key) + create(:version, rubygem: @rubygem, number: "0.1.1", summary: "old summary") @spec = mock @cutter.stubs(:version).returns @rubygem.versions[0] @cutter.stubs(:spec).returns(@spec) @@ -698,7 +695,7 @@ def two_cert_chain(signing_key:, root_not_before: Time.current, cert_not_before: @rubygem = create(:rubygem) @cutter.stubs(:rubygem).returns @rubygem create(:version, rubygem: @rubygem, summary: "old summary") - @version = create(:version, rubygem: @rubygem, summary: "new summary", pusher_api_key: @cutter.api_key) + @version = create(:version, rubygem: @rubygem, summary: "new summary") @cutter.stubs(:version).returns @version @rubygem.stubs(:update_attributes_from_gem_specification!) @cutter.stubs(:version).returns @version diff --git a/test/system/avo/versions_test.rb b/test/system/avo/versions_test.rb index 871ca77e06b..c72c89a0cd8 100644 --- a/test/system/avo/versions_test.rb +++ b/test/system/avo/versions_test.rb @@ -27,6 +27,7 @@ def sign_in_as(user) end test "restore a rubygem version" do + Minitest::Test.make_my_diffs_pretty! admin_user = create(:admin_github_user, :is_admin) sign_in_as admin_user @@ -124,39 +125,4 @@ def sign_in_as(user) audit.audited_changes ) end - - test "run afer version write job" do - admin_user = create(:admin_github_user, :is_admin) - sign_in_as admin_user - - rubygem = create(:rubygem, owners: [create(:user)]) - version = create(:version, rubygem: rubygem) - - visit avo.resources_version_path(version) - - click_button "Actions" - - click_on "Run version post-write job" - - fill_in "Comment", with: "A nice long comment" - - click_button "Run Job" - - page.assert_text "Action ran successfully!" - page.assert_text version.to_global_id.uri.to_s - - perform_enqueued_jobs - - assert_equal 1, ActionMailer::Base.deliveries.size - - rubygem.reload - version.reload - - audit = version.audits.sole - - assert_equal( - ["gid://gemcutter/Version/#{version.id}"], - audit.audited_changes["models"] - ) - end end diff --git a/test/unit/mailer_test.rb b/test/unit/mailer_test.rb index a84ce8c12e7..d93189db30c 100644 --- a/test/unit/mailer_test.rb +++ b/test/unit/mailer_test.rb @@ -4,10 +4,6 @@ class MailerTest < ActionMailer::TestCase MIN_DOWNLOADS_FOR_MFA_RECOMMENDATION_POLICY = 165_000_000 MIN_DOWNLOADS_FOR_MFA_REQUIRED_POLICY = 180_000_000 - setup do - TOPLEVEL_BINDING.receiver.stubs(:mx_exists?).returns(true) - end - context "sending mail for mfa recommendation announcement" do setup do @user = create(:user) From b2a7894fb186bb6ff06eca77fbb9a7ff3c470897 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:36:26 +0000 Subject: [PATCH 109/112] Bump puma from 6.4.1 to 6.4.2 Bumps [puma](https://github.com/puma/puma) from 6.4.1 to 6.4.2. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v6.4.1...v6.4.2) --- updated-dependencies: - dependency-name: puma dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ea9da5a1ce3..0c6b9ede3cb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -452,7 +452,7 @@ GEM psych (5.1.1.1) stringio public_suffix (5.0.4) - puma (6.4.1) + puma (6.4.2) nio4r (~> 2.0) pundit (2.3.1) activesupport (>= 3.0.0) From 54cfdc006162588df16ce190d8b8c468437fcb7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:37:01 +0000 Subject: [PATCH 110/112] Bump tailwindcss-rails from 2.2.0 to 2.2.1 Bumps [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) from 2.2.0 to 2.2.1. - [Release notes](https://github.com/rails/tailwindcss-rails/releases) - [Changelog](https://github.com/rails/tailwindcss-rails/blob/main/CHANGELOG.md) - [Commits](https://github.com/rails/tailwindcss-rails/compare/v2.2.0...v2.2.1) --- updated-dependencies: - dependency-name: tailwindcss-rails dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ea9da5a1ce3..40955c3dcd5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -629,7 +629,7 @@ GEM attr_required (>= 0.0.5) faraday (~> 2.0) faraday-follow_redirects - tailwindcss-rails (2.2.0) + tailwindcss-rails (2.2.1) railties (>= 6.0.0) terser (1.1.20) execjs (>= 0.3.0, < 3) From 8119f37eec274b14e40458d5fc8a2e8429d8010b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:37:30 +0000 Subject: [PATCH 111/112] Bump strong_migrations from 1.6.4 to 1.7.0 Bumps [strong_migrations](https://github.com/ankane/strong_migrations) from 1.6.4 to 1.7.0. - [Changelog](https://github.com/ankane/strong_migrations/blob/master/CHANGELOG.md) - [Commits](https://github.com/ankane/strong_migrations/compare/v1.6.4...v1.7.0) --- updated-dependencies: - dependency-name: strong_migrations dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index a1a0b2586e5..93282f4a537 100644 --- a/Gemfile +++ b/Gemfile @@ -58,7 +58,7 @@ gem "webauthn", "~> 3.1" gem "browser", "~> 5.3", ">= 5.3.1" gem "bcrypt", "~> 3.1" gem "maintenance_tasks", "~> 2.4" -gem "strong_migrations", "~> 1.6" +gem "strong_migrations", "~> 1.7" gem "phlex-rails", "~> 1.1" # Admin dashboard diff --git a/Gemfile.lock b/Gemfile.lock index ea9da5a1ce3..fda56d22d45 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -622,7 +622,7 @@ GEM sprockets (>= 3.0.0) statsd-instrument (3.6.1) stringio (3.1.0) - strong_migrations (1.6.4) + strong_migrations (1.7.0) activerecord (>= 5.2) swd (2.0.3) activesupport (>= 3) @@ -785,7 +785,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1) sprockets-rails (~> 3.4) statsd-instrument (~> 3.5) - strong_migrations (~> 1.6) + strong_migrations (~> 1.7) tailwindcss-rails (~> 2.2) terser (~> 1.1) toxiproxy (~> 2.0) From 71e699df0190e17b3cab79df7393da6b15101884 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sun, 7 Jan 2024 22:50:19 -0700 Subject: [PATCH 112/112] Update to rubygems 3.5.4 / bundler 2.5.4 --- .github/workflows/docker.yml | 2 +- .github/workflows/test.yml | 2 +- Gemfile.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 77f9f076c21..b0df8b8be59 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,7 +14,7 @@ jobs: name: Docker build (and optional push) runs-on: ubuntu-22.04 env: - RUBYGEMS_VERSION: 3.5.3 + RUBYGEMS_VERSION: 3.5.4 RUBY_VERSION: 3.3.0 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b537168a5a..33c822e314a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: matrix: rubygems: - name: locked - version: "3.5.3" + version: "3.5.4" - name: latest version: latest ruby_version: ["3.3.0"] diff --git a/Gemfile.lock b/Gemfile.lock index 1b6658fc265..56361ed37eb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -797,4 +797,4 @@ DEPENDENCIES xml-simple (~> 1.1) BUNDLED WITH - 2.5.3 + 2.5.4