diff options
author | Jenny Shen <jenny.shen@shopify.com> | 2023-06-21 17:21:35 -0400 |
---|---|---|
committer | git <svn-admin@ruby-lang.org> | 2023-07-28 16:08:07 +0000 |
commit | 023d0f662b4487c2bd6636c4fcf1e223ef4c8b30 (patch) | |
tree | 87f76b4c37cd9b7ec662c8d60ac007086f644963 /test/rubygems | |
parent | 836e4eb3cd4c61823bf812957b555bb0ef79ade5 (diff) | |
download | ruby-023d0f662b4487c2bd6636c4fcf1e223ef4c8b30.tar.gz |
[rubygems/rubygems] Add Webauthn verification poller to fetch OTP
https://github.com/rubygems/rubygems/commit/39c5e86a67
Diffstat (limited to 'test/rubygems')
-rw-r--r-- | test/rubygems/test_gem_commands_owner_command.rb | 83 | ||||
-rw-r--r-- | test/rubygems/test_gem_commands_push_command.rb | 85 | ||||
-rw-r--r-- | test/rubygems/test_gem_commands_yank_command.rb | 102 | ||||
-rw-r--r-- | test/rubygems/test_gem_gemcutter_utilities.rb | 73 |
4 files changed, 343 insertions, 0 deletions
diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb index 091335ab4b..e89f160a96 100644 --- a/test/rubygems/test_gem_commands_owner_command.rb +++ b/test/rubygems/test_gem_commands_owner_command.rb @@ -374,6 +374,11 @@ EOF HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), ] + @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"pending\",\"message\":\"Security device authentication is still pending.\"}", + code: 200, + msg: "OK" + ) TCPServer.stub(:new, server) do Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do @@ -405,6 +410,11 @@ EOF HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), ] + @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"pending\",\"message\":\"Security device authentication is still pending.\"}", + code: 200, + msg: "OK" + ) TCPServer.stub(:new, server) do Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do @@ -425,6 +435,79 @@ EOF refute_match response_success, @stub_ui.output end + def test_with_webauthn_enabled_success_with_polling + webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY" + response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." + response_success = "Owner added successfully." + port = 5678 + server = TCPServer.new(port) + + @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [ + HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), + HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), + ] + @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"success\",\"code\":\"Uvh6T57tkWuUnWYo\"}", + code: 200, + msg: "OK" + ) + + TCPServer.stub(:new, server) do + use_ui @stub_ui do + @cmd.add_owners("freewill", ["user-new1@example.com"]) + end + ensure + server.close + end + + url_with_port = "#{webauthn_verification_url}?port=#{port}" + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @stub_ui.output + assert_match "You are verified with a security device. You may close the browser window.", @stub_ui.output + assert_equal "Uvh6T57tkWuUnWYo", @stub_fetcher.last_request["OTP"] + assert_match response_success, @stub_ui.output + end + + def test_with_webauthn_enabled_failure_with_polling + webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY" + response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." + response_success = "Owner added successfully." + port = 5678 + server = TCPServer.new(port) + + @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [ + HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), + HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), + ] + @stub_fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"expired\",\"message\":\"The token in the link you used has either expired or been used already.\"}", + code: 200, + msg: "OK" + ) + + TCPServer.stub(:new, server) do + use_ui @stub_ui do + @cmd.add_owners("freewill", ["user-new1@example.com"]) + end + ensure + server.close + end + + url_with_port = "#{webauthn_verification_url}?port=#{port}" + + assert_match @stub_fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @stub_ui.output + assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \ + "or been used already.", @stub_ui.error + refute_match "You are verified with a security device. You may close the browser window.", @stub_ui.output + refute_match response_success, @stub_ui.output + end + def test_remove_owners_unathorized_api_key response_forbidden = "The API key doesn't have access" response_success = "Owner removed successfully." diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index baaf8e85c2..33f2481feb 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -438,6 +438,11 @@ class TestGemCommandsPushCommand < Gem::TestCase HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), ] @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"pending\",\"message\":\"Security device authentication is still pending.\"}", + code: 200, + msg: "OK" + ) TCPServer.stub(:new, server) do Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do @@ -469,6 +474,11 @@ class TestGemCommandsPushCommand < Gem::TestCase HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), ] @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"pending\",\"message\":\"Security device authentication is still pending.\"}", + code: 200, + msg: "OK" + ) error = assert_raise Gem::MockGemUi::TermError do TCPServer.stub(:new, server) do @@ -491,6 +501,81 @@ class TestGemCommandsPushCommand < Gem::TestCase refute_match response_success, @ui.output end + def test_with_webauthn_enabled_success_with_polling + webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY" + response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." + response_success = "Successfully registered gem: freewill (1.0.0)" + port = 5678 + server = TCPServer.new(port) + + @fetcher.data["#{Gem.host}/api/v1/gems"] = [ + HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), + HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), + ] + @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"success\",\"code\":\"Uvh6T57tkWuUnWYo\"}", + code: 200, + msg: "OK" + ) + + TCPServer.stub(:new, server) do + use_ui @ui do + @cmd.send_gem(@path) + end + ensure + server.close + end + + url_with_port = "#{webauthn_verification_url}?port=#{port}" + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @ui.output + assert_match "You are verified with a security device. You may close the browser window.", @ui.output + assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"] + assert_match response_success, @ui.output + end + + def test_with_webauthn_enabled_failure_with_polling + webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY" + response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." + response_success = "Successfully registered gem: freewill (1.0.0)" + port = 5678 + server = TCPServer.new(port) + + @fetcher.data["#{Gem.host}/api/v1/gems"] = [ + HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), + HTTPResponseFactory.create(body: response_success, code: 200, msg: "OK"), + ] + @fetcher.data["#{Gem.host}/api/v1/webauthn_verification"] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @fetcher.data["#{Gem.host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"expired\",\"message\":\"The token in the link you used has either expired or been used already.\"}", + code: 200, + msg: "OK" + ) + + error = assert_raise Gem::MockGemUi::TermError do + TCPServer.stub(:new, server) do + use_ui @ui do + @cmd.send_gem(@path) + end + ensure + server.close + end + end + assert_equal 1, error.exit_code + + assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key + url_with_port = "#{webauthn_verification_url}?port=#{port}" + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @ui.output + assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \ + "or been used already.", @ui.error + refute_match "You are verified with a security device. You may close the browser window.", @ui.output + refute_match response_success, @ui.output + end + def test_sending_gem_unathorized_api_key_with_mfa_enabled response_mfa_enabled = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." response_forbidden = "The API key doesn't have access" diff --git a/test/rubygems/test_gem_commands_yank_command.rb b/test/rubygems/test_gem_commands_yank_command.rb index e5e234e0f8..6145e928ae 100644 --- a/test/rubygems/test_gem_commands_yank_command.rb +++ b/test/rubygems/test_gem_commands_yank_command.rb @@ -121,6 +121,7 @@ class TestGemCommandsYankCommand < Gem::TestCase response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." yank_uri = "http://example/api/v1/gems/yank" webauthn_uri = "http://example/api/v1/webauthn_verification" + status_uri = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json" port = 5678 server = TCPServer.new(port) @@ -129,6 +130,11 @@ class TestGemCommandsYankCommand < Gem::TestCase HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"), ] + @fetcher.data[status_uri] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"pending\",\"message\":\"Security device authentication is still pending.\"}", + code: 200, + msg: "OK" + ) @cmd.options[:args] = %w[a] @cmd.options[:added_platform] = true @@ -157,6 +163,7 @@ class TestGemCommandsYankCommand < Gem::TestCase response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." yank_uri = "http://example/api/v1/gems/yank" webauthn_uri = "http://example/api/v1/webauthn_verification" + status_uri = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json" port = 5678 server = TCPServer.new(port) raise_error = ->(*_args) { raise Gem::WebauthnVerificationError, "Something went wrong" } @@ -166,6 +173,11 @@ class TestGemCommandsYankCommand < Gem::TestCase HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"), ] + @fetcher.data[status_uri] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"pending\",\"message\":\"Security device authentication is still pending.\"}", + code: 200, + msg: "OK" + ) @cmd.options[:args] = %w[a] @cmd.options[:added_platform] = true @@ -194,6 +206,96 @@ class TestGemCommandsYankCommand < Gem::TestCase refute_match "Successfully yanked", @ui.output end + def test_with_webauthn_enabled_success_with_polling + webauthn_verification_url = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY" + response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." + yank_uri = "http://example/api/v1/gems/yank" + webauthn_uri = "http://example/api/v1/webauthn_verification" + status_uri = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json" + port = 5678 + server = TCPServer.new(port) + + @fetcher.data[webauthn_uri] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @fetcher.data[yank_uri] = [ + HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), + HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"), + ] + @fetcher.data[status_uri] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"success\",\"code\":\"Uvh6T57tkWuUnWYo\"}", + code: 200, + msg: "OK" + ) + + @cmd.options[:args] = %w[a] + @cmd.options[:added_platform] = true + @cmd.options[:version] = req("= 1.0") + + TCPServer.stub(:new, server) do + use_ui @ui do + @cmd.execute + end + ensure + server.close + end + + url_with_port = "#{webauthn_verification_url}?port=#{port}" + assert_match %r{Yanking gem from http://example}, @ui.output + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @ui.output + assert_match "You are verified with a security device. You may close the browser window.", @ui.output + assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"] + assert_match "Successfully yanked", @ui.output + end + + def test_with_webauthn_enabled_failure_with_polling + webauthn_verification_url = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY" + response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." + yank_uri = "http://example/api/v1/gems/yank" + webauthn_uri = "http://example/api/v1/webauthn_verification" + status_uri = "http://example/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json" + port = 5678 + server = TCPServer.new(port) + + @fetcher.data[webauthn_uri] = HTTPResponseFactory.create(body: webauthn_verification_url, code: 200, msg: "OK") + @fetcher.data[yank_uri] = [ + HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"), + HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"), + ] + @fetcher.data[status_uri] = Gem::HTTPResponseFactory.create( + body: "{\"status\":\"expired\",\"message\":\"The token in the link you used has either expired or been used already.\"}", + code: 200, + msg: "OK" + ) + + @cmd.options[:args] = %w[a] + @cmd.options[:added_platform] = true + @cmd.options[:version] = req("= 1.0") + + error = assert_raise Gem::MockGemUi::TermError do + TCPServer.stub(:new, server) do + use_ui @ui do + @cmd.execute + end + ensure + server.close + end + end + assert_equal 1, error.exit_code + + url_with_port = "#{webauthn_verification_url}?port=#{port}" + + assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key + assert_match %r{Yanking gem from http://example}, @ui.output + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @ui.output + assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \ + "or been used already.", @ui.error + refute_match "You are verified with a security device. You may close the browser window.", @ui.output + refute_match "Successfully yanked", @ui.output + end + def test_execute_key yank_uri = "http://example/api/v1/gems/yank" @fetcher.data[yank_uri] = HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK") diff --git a/test/rubygems/test_gem_gemcutter_utilities.rb b/test/rubygems/test_gem_gemcutter_utilities.rb index cc698c76ba..5bbc65b3da 100644 --- a/test/rubygems/test_gem_gemcutter_utilities.rb +++ b/test/rubygems/test_gem_gemcutter_utilities.rb @@ -268,6 +268,52 @@ class TestGemGemcutterUtilities < Gem::TestCase refute_match "Signed in with API key:", @sign_in_ui.output end + def test_sign_in_with_webauthn_enabled_with_polling + webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY" + port = 5678 + server = TCPServer.new(port) + @fetcher.respond_with_require_otp + @fetcher.respond_with_webauthn_url(webauthn_verification_url) + @fetcher.respond_with_webauthn_polling("Uvh6T57tkWuUnWYo") + + TCPServer.stub(:new, server) do + util_sign_in + ensure + server.close + end + + url_with_port = "#{webauthn_verification_url}?port=#{port}" + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @sign_in_ui.output + assert_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output + assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"] + end + + def test_sign_in_with_webauthn_enabled_with_polling_failure + webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY" + port = 5678 + server = TCPServer.new(port) + @fetcher.respond_with_require_otp + @fetcher.respond_with_webauthn_url(webauthn_verification_url) + @fetcher.respond_with_webauthn_polling_failure + + assert_raise Gem::MockGemUi::TermError do + TCPServer.stub(:new, server) do + util_sign_in + ensure + server.close + end + end + + url_with_port = "#{webauthn_verification_url}?port=#{port}" + assert_match "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate " \ + "via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \ + "command with the `--otp [your_code]` option.", @sign_in_ui.output + assert_match "ERROR: Security device verification failed: " \ + "The token in the link you used has either expired or been used already.", @sign_in_ui.error + end + def util_sign_in(args: [], extra_input: "") email = "you@example.com" password = "secret" @@ -320,7 +366,34 @@ class TestGemGemcutterUtilities < Gem::TestCase end def respond_with_webauthn_url(url) + require "json" @data["#{@host}/api/v1/webauthn_verification"] = Gem::HTTPResponseFactory.create(body: url, code: 200, msg: "OK") + @data["#{@host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: { status: "pending", message: "Security device authentication is still pending." }.to_json, + code: 200, + msg: "OK" + ) + end + + def respond_with_webauthn_polling(code) + require "json" + @data["#{@host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: { status: "success", code: code }.to_json, + code: 200, + msg: "OK" + ) + end + + def respond_with_webauthn_polling_failure + require "json" + @data["#{@host}/api/v1/webauthn_verification/odow34b93t6aPCdY/status.json"] = Gem::HTTPResponseFactory.create( + body: { + status: "expired", + message: "The token in the link you used has either expired or been used already.", + }.to_json, + code: 200, + msg: "OK" + ) end def respond_with_require_otp |