[rubygems/rubygems] Create MultifactorAuthFetcher to reduce duplication among tests

https://github.com/rubygems/rubygems/commit/dead211206
This commit is contained in:
Jenny Shen 2023-07-11 00:40:05 -04:00 committed by git
parent e96b3138a8
commit 3954a87d65
5 changed files with 164 additions and 283 deletions

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
##
# A MultifactorAuthFetcher is a FakeFetcher that adds paths to data for requests related to
# multi-factor authentication.
#
require_relative "utilities"
require "json"
class Gem::MultifactorAuthFetcher < Gem::FakeFetcher
attr_reader :host, :webauthn_url
# GET /api/v1/webauthn_verification defaults to user does not have any security devices
def initialize(host: nil)
super()
@host = host || Gem.host
@path_token = "odow34b93t6aPCdY"
@webauthn_url = "#{@host}/webauthn_verification/#{@path_token}"
@data["#{@host}/api/v1/webauthn_verification"] = Gem::HTTPResponseFactory.create(
body: "You don't have any security devices",
code: 422,
msg: "Unprocessable Entity"
)
end
# given a url, return a response that requires multifactor authentication
def respond_with_require_otp(url, success_body)
response_fail = "You have enabled multifactor authentication"
@data[url] = proc do
@call_count ||= 0
if (@call_count += 1).odd?
Gem::HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized")
else
Gem::HTTPResponseFactory.create(body: success_body, code: 200, msg: "OK")
end
end
end
# GET /api/v1/webauthn_verification returns a webauthn url
# GET /api/v1/webauthn_verification/:token/status.json (polling url) returns pending status
def respond_with_webauthn_url
@data["#{@host}/api/v1/webauthn_verification"] = Gem::HTTPResponseFactory.create(body: @webauthn_url, code: 200, msg: "OK")
@data["#{@host}/api/v1/webauthn_verification/#{@path_token}/status.json"] = Gem::HTTPResponseFactory.create(
body: { status: "pending", message: "Security device authentication is still pending." }.to_json,
code: 200,
msg: "OK"
)
end
# GET /api/v1/webauthn_verification/:token/status.json returns success status with OTP code
def respond_with_webauthn_polling(code)
@data["#{@host}/api/v1/webauthn_verification/#{@path_token}/status.json"] = Gem::HTTPResponseFactory.create(
body: { status: "success", code: code }.to_json,
code: 200,
msg: "OK"
)
end
# GET /api/v1/webauthn_verification/:token/status.json returns expired status
def respond_with_webauthn_polling_failure
@data["#{@host}/api/v1/webauthn_verification/#{@path_token}/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 webauthn_url_with_port(port)
"#{@webauthn_url}?port=#{port}"
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "helper"
require_relative "multifactor_auth_fetcher"
require "rubygems/commands/owner_command"
class TestGemCommandsOwnerCommand < Gem::TestCase
@ -11,7 +12,7 @@ class TestGemCommandsOwnerCommand < Gem::TestCase
ENV["RUBYGEMS_HOST"] = nil
@stub_ui = Gem::MockGemUi.new
@stub_fetcher = Gem::FakeFetcher.new
@stub_fetcher = Gem::MultifactorAuthFetcher.new
Gem::RemoteFetcher.fetcher = @stub_fetcher
Gem.configuration = nil
Gem.configuration.rubygems_api_key = "ed244fbf2b1a52e012da8616c512fa47f9aa5250"
@ -324,15 +325,8 @@ EOF
end
def test_otp_verified_success
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."
@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"] =
HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
@stub_fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems/freewill/owners", response_success)
@otp_ui = Gem::MockGemUi.new "111111\n"
use_ui @otp_ui do
@ -363,22 +357,12 @@ EOF
end
def test_with_webauthn_enabled_success
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\":\"pending\",\"message\":\"Security device authentication is still pending.\"}",
code: 200,
msg: "OK"
)
@stub_fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems/freewill/owners", response_success)
@stub_fetcher.respond_with_webauthn_url
TCPServer.stub(:new, server) do
Gem::GemcutterUtilities::WebauthnListener.stub(:listener_thread, Thread.new { Thread.current[:otp] = "Uvh6T57tkWuUnWYo" }) do
@ -390,31 +374,20 @@ EOF
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 have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(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
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)
error = Gem::WebauthnVerificationError.new("Something went wrong")
@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\":\"pending\",\"message\":\"Security device authentication is still pending.\"}",
code: 200,
msg: "OK"
)
@stub_fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems/freewill/owners", response_success)
@stub_fetcher.respond_with_webauthn_url
TCPServer.stub(:new, server) do
Gem::GemcutterUtilities::WebauthnListener.stub(:listener_thread, Thread.new { Thread.current[:error] = error }) do
@ -426,32 +399,21 @@ EOF
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 "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(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: Something went wrong", @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_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"
)
@stub_fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems/freewill/owners", response_success)
@stub_fetcher.respond_with_webauthn_url
@stub_fetcher.respond_with_webauthn_polling("Uvh6T57tkWuUnWYo")
TCPServer.stub(:new, server) do
use_ui @stub_ui do
@ -461,8 +423,7 @@ EOF
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 " \
assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(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
@ -471,22 +432,16 @@ EOF
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"
@stub_fetcher.respond_with_require_otp(
"#{Gem.host}/api/v1/gems/freewill/owners",
response_success
)
@stub_fetcher.respond_with_webauthn_url
@stub_fetcher.respond_with_webauthn_polling_failure
TCPServer.stub(:new, server) do
use_ui @stub_ui do
@ -496,10 +451,8 @@ EOF
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 " \
assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(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 " \

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "helper"
require_relative "multifactor_auth_fetcher"
require "rubygems/commands/push_command"
require "rubygems/config_file"
@ -26,7 +27,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
@host = "https://rubygems.example"
@api_key = Gem.configuration.rubygems_api_key
@fetcher = Gem::FakeFetcher.new
@fetcher = Gem::MultifactorAuthFetcher.new
Gem::RemoteFetcher.fetcher = @fetcher
@cmd = Gem::Commands::PushCommand.new
@ -386,15 +387,9 @@ class TestGemCommandsPushCommand < Gem::TestCase
end
def test_otp_verified_success
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)"
@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: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
@fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems", response_success)
@otp_ui = Gem::MockGemUi.new "111111\n"
use_ui @otp_ui do
@ -427,22 +422,12 @@ class TestGemCommandsPushCommand < Gem::TestCase
end
def test_with_webauthn_enabled_success
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\":\"pending\",\"message\":\"Security device authentication is still pending.\"}",
code: 200,
msg: "OK"
)
@fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems", response_success)
@fetcher.respond_with_webauthn_url
TCPServer.stub(:new, server) do
Gem::GemcutterUtilities::WebauthnListener.stub(:listener_thread, Thread.new { Thread.current[:otp] = "Uvh6T57tkWuUnWYo" }) do
@ -454,31 +439,22 @@ class TestGemCommandsPushCommand < Gem::TestCase
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 have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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
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)
error = Gem::WebauthnVerificationError.new("Something went wrong")
@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\":\"pending\",\"message\":\"Security device authentication is still pending.\"}",
code: 200,
msg: "OK"
)
@fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems", response_success)
@fetcher.respond_with_webauthn_url
error = assert_raise Gem::MockGemUi::TermError do
TCPServer.stub(:new, server) do
@ -494,30 +470,22 @@ class TestGemCommandsPushCommand < Gem::TestCase
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 "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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: Something went wrong", @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_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"
)
@fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems", response_success)
@fetcher.respond_with_webauthn_url
@fetcher.respond_with_webauthn_polling("Uvh6T57tkWuUnWYo")
TCPServer.stub(:new, server) do
use_ui @ui do
@ -527,32 +495,22 @@ class TestGemCommandsPushCommand < Gem::TestCase
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 have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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"
)
@fetcher.respond_with_require_otp("#{Gem.host}/api/v1/gems", response_success)
@fetcher.respond_with_webauthn_url
@fetcher.respond_with_webauthn_polling_failure
error = assert_raise Gem::MockGemUi::TermError do
TCPServer.stub(:new, server) do
@ -566,8 +524,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
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 " \
assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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 " \

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "helper"
require_relative "multifactor_auth_fetcher"
require "rubygems/commands/yank_command"
class TestGemCommandsYankCommand < Gem::TestCase
@ -12,7 +13,8 @@ class TestGemCommandsYankCommand < Gem::TestCase
@cmd = Gem::Commands::YankCommand.new
@cmd.options[:host] = "http://example"
@fetcher = Gem::RemoteFetcher.fetcher
@fetcher = Gem::MultifactorAuthFetcher.new(host: "http://example")
Gem::RemoteFetcher.fetcher = @fetcher
Gem.configuration.rubygems_api_key = "key"
Gem.configuration.api_keys[:KEY] = "other"
@ -73,9 +75,6 @@ class TestGemCommandsYankCommand < Gem::TestCase
HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized"),
HTTPResponseFactory.create(body: "Successfully yanked", code: 200, msg: "OK"),
]
webauthn_uri = "http://example/api/v1/webauthn_verification"
@fetcher.data[webauthn_uri] =
HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
@cmd.options[:args] = %w[a]
@cmd.options[:added_platform] = true
@ -97,9 +96,6 @@ class TestGemCommandsYankCommand < Gem::TestCase
response = "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"
@fetcher.data[yank_uri] = HTTPResponseFactory.create(body: response, code: 401, msg: "Unauthorized")
webauthn_uri = "http://example/api/v1/webauthn_verification"
@fetcher.data[webauthn_uri] =
HTTPResponseFactory.create(body: "You don't have any security devices", code: 422, msg: "Unprocessable Entity")
@cmd.options[:args] = %w[a]
@cmd.options[:added_platform] = true
@ -117,24 +113,11 @@ class TestGemCommandsYankCommand < Gem::TestCase
end
def test_with_webauthn_enabled_success
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\":\"pending\",\"message\":\"Security device authentication is still pending.\"}",
code: 200,
msg: "OK"
)
@fetcher.respond_with_require_otp("http://example/api/v1/gems/yank", "Successfully yanked")
@fetcher.respond_with_webauthn_url
@cmd.options[:args] = %w[a]
@cmd.options[:added_platform] = true
@ -150,34 +133,22 @@ class TestGemCommandsYankCommand < Gem::TestCase
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 have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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
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)
error = Gem::WebauthnVerificationError.new("Something went wrong")
@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\":\"pending\",\"message\":\"Security device authentication is still pending.\"}",
code: 200,
msg: "OK"
)
@fetcher.respond_with_require_otp("http://example/api/v1/gems/yank", "Successfully yanked")
@fetcher.respond_with_webauthn_url
@cmd.options[:args] = %w[a]
@cmd.options[:added_platform] = true
@ -196,35 +167,23 @@ class TestGemCommandsYankCommand < Gem::TestCase
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 "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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: Something went wrong", @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_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"
)
@fetcher.respond_with_require_otp("http://example/api/v1/gems/yank", "Successfully yanked")
@fetcher.respond_with_webauthn_url
@fetcher.respond_with_webauthn_polling("Uvh6T57tkWuUnWYo")
@cmd.options[:args] = %w[a]
@cmd.options[:added_platform] = true
@ -238,35 +197,22 @@ class TestGemCommandsYankCommand < Gem::TestCase
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 have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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"
)
@fetcher.respond_with_require_otp("http://example/api/v1/gems/yank", "Successfully yanked")
@fetcher.respond_with_webauthn_url
@fetcher.respond_with_webauthn_polling_failure
@cmd.options[:args] = %w[a]
@cmd.options[:added_platform] = true
@ -283,13 +229,11 @@ class TestGemCommandsYankCommand < Gem::TestCase
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 "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "helper"
require_relative "multifactor_auth_fetcher"
require "rubygems"
require "rubygems/command"
require "rubygems/gemcutter_utilities"
@ -222,12 +223,11 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
def test_sign_in_with_webauthn_enabled
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_url
TCPServer.stub(:new, server) do
Gem::GemcutterUtilities::WebauthnListener.stub(:listener_thread, Thread.new { Thread.current[:otp] = "Uvh6T57tkWuUnWYo" }) do
util_sign_in
@ -236,20 +236,20 @@ class TestGemGemcutterUtilities < Gem::TestCase
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 have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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_error
webauthn_verification_url = "rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY"
port = 5678
server = TCPServer.new(port)
error = Gem::WebauthnVerificationError.new("Something went wrong")
@fetcher.respond_with_require_otp
@fetcher.respond_with_webauthn_url(webauthn_verification_url)
@fetcher.respond_with_webauthn_url
error = assert_raise Gem::MockGemUi::TermError do
TCPServer.stub(:new, server) do
Gem::GemcutterUtilities::WebauthnListener.stub(:listener_thread, Thread.new { Thread.current[:error] = error }) do
@ -261,19 +261,19 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
assert_equal 1, error.exit_code
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 have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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: Something went wrong", @sign_in_ui.error
refute_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
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_url
@fetcher.respond_with_webauthn_polling("Uvh6T57tkWuUnWYo")
TCPServer.stub(:new, server) do
@ -282,20 +282,18 @@ class TestGemGemcutterUtilities < Gem::TestCase
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 have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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_url
@fetcher.respond_with_webauthn_polling_failure
assert_raise Gem::MockGemUi::TermError do
@ -306,10 +304,9 @@ class TestGemGemcutterUtilities < Gem::TestCase
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 "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(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
@ -349,64 +346,18 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
end
class SignInFetcher < Gem::FakeFetcher
attr_reader :host, :api_key
class SignInFetcher < Gem::MultifactorAuthFetcher
attr_reader :api_key
def initialize(host: nil)
super()
@host = host || Gem.host
super(host: host)
@api_key = "a5fdbb6ba150cbb83aad2bb2fede64cf040453903"
@data["#{@host}/api/v1/api_key"] = Gem::HTTPResponseFactory.create(body: @api_key, code: 200, msg: "OK")
@data["#{@host}/api/v1/profile/me.yaml"] = Gem::HTTPResponseFactory.create(body: "mfa: disabled\n", code: 200, msg: "OK")
@data["#{@host}/api/v1/webauthn_verification"] = Gem::HTTPResponseFactory.create(
body: "You don't have any security devices",
code: 422,
msg: "Unprocessable Entity"
)
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
response_fail = "You have enabled multifactor authentication"
@data["#{host}/api/v1/api_key"] = proc do
@call_count ||= 0
if (@call_count += 1).odd?
Gem::HTTPResponseFactory.create(body: response_fail, code: 401, msg: "Unauthorized")
else
Gem::HTTPResponseFactory.create(body: @api_key, code: 200, msg: "OK")
end
end
super("#{host}/api/v1/api_key", @api_key)
end
def respond_with_forbidden_api_key_response