[rubygems/rubygems] Add Webauthn verification poller to fetch OTP
https://github.com/rubygems/rubygems/commit/39c5e86a67
This commit is contained in:
parent
836e4eb3cd
commit
023d0f662b
@ -253,36 +253,82 @@ module Gem::GemcutterUtilities
|
||||
|
||||
def fetch_otp(credentials)
|
||||
options[:otp] = if webauthn_url = webauthn_verification_url(credentials)
|
||||
wait_for_otp(webauthn_url)
|
||||
server = TCPServer.new 0
|
||||
port = server.addr[1].to_s
|
||||
|
||||
url_with_port = "#{webauthn_url}?port=#{port}"
|
||||
say "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."
|
||||
|
||||
threads = [socket_thread(server), poll_thread(webauthn_url, credentials)]
|
||||
otp_thread = wait_for_otp_thread(*threads)
|
||||
|
||||
threads.each(&:join)
|
||||
|
||||
if error = otp_thread[:error]
|
||||
alert_error error.message
|
||||
terminate_interaction(1)
|
||||
end
|
||||
|
||||
say "You are verified with a security device. You may close the browser window."
|
||||
otp_thread[:otp]
|
||||
else
|
||||
say "You have enabled multi-factor authentication. Please enter OTP code."
|
||||
ask "Code: "
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_otp(webauthn_url)
|
||||
server = TCPServer.new 0
|
||||
port = server.addr[1].to_s
|
||||
def wait_for_otp_thread(*threads)
|
||||
loop do
|
||||
threads.each do |otp_thread|
|
||||
return otp_thread unless otp_thread.alive?
|
||||
end
|
||||
sleep 0.1
|
||||
end
|
||||
ensure
|
||||
threads.each(&:exit)
|
||||
end
|
||||
|
||||
def socket_thread(server)
|
||||
thread = Thread.new do
|
||||
Thread.current[:otp] = Gem::WebauthnListener.wait_for_otp_code(host, server)
|
||||
rescue Gem::WebauthnVerificationError => e
|
||||
Thread.current[:error] = e
|
||||
ensure
|
||||
server.close
|
||||
end
|
||||
thread.abort_on_exception = true
|
||||
thread.report_on_exception = false
|
||||
|
||||
thread
|
||||
end
|
||||
|
||||
def poll_thread(webauthn_url, credentials)
|
||||
thread = Thread.new do
|
||||
Timeout.timeout(300) do
|
||||
loop do
|
||||
response = webauthn_verification_poll_response(webauthn_url, credentials)
|
||||
raise Gem::WebauthnVerificationError, response.message unless response.is_a?(Net::HTTPSuccess)
|
||||
|
||||
require "json"
|
||||
parsed_response = JSON.parse(response.body)
|
||||
case parsed_response["status"]
|
||||
when "pending"
|
||||
sleep 5
|
||||
when "success"
|
||||
Thread.current[:otp] = parsed_response["code"]
|
||||
break
|
||||
else
|
||||
raise Gem::WebauthnVerificationError, parsed_response["message"]
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Gem::WebauthnVerificationError, Timeout::Error => e
|
||||
Thread.current[:error] = e
|
||||
end
|
||||
thread.abort_on_exception = true
|
||||
thread.report_on_exception = false
|
||||
|
||||
url_with_port = "#{webauthn_url}?port=#{port}"
|
||||
say "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."
|
||||
|
||||
thread.join
|
||||
if error = thread[:error]
|
||||
alert_error error.message
|
||||
terminate_interaction(1)
|
||||
end
|
||||
|
||||
say "You are verified with a security device. You may close the browser window."
|
||||
thread[:otp]
|
||||
thread
|
||||
end
|
||||
|
||||
def webauthn_verification_url(credentials)
|
||||
@ -296,6 +342,17 @@ module Gem::GemcutterUtilities
|
||||
response.is_a?(Net::HTTPSuccess) ? response.body : nil
|
||||
end
|
||||
|
||||
def webauthn_verification_poll_response(webauthn_url, credentials)
|
||||
webauthn_token = %r{(?<=\/)[^\/]+(?=$)}.match(webauthn_url)[0]
|
||||
rubygems_api_request(:get, "api/v1/webauthn_verification/#{webauthn_token}/status.json") do |request|
|
||||
if credentials.empty?
|
||||
request.add_field "Authorization", api_key
|
||||
else
|
||||
request.basic_auth credentials[:email], credentials[:password]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def pretty_host(host)
|
||||
if default_host?
|
||||
"RubyGems.org"
|
||||
|
@ -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."
|
||||
|
@ -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"
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user