зеркало из https://github.com/github/ruby.git
[rubygems/rubygems] Move WebauthnListener into the Gem::GemcutterUtilities namespace
https://github.com/rubygems/rubygems/commit/3080394f81
This commit is contained in:
Родитель
108cc38a76
Коммит
fce04f9a6c
|
@ -2,7 +2,7 @@
|
|||
|
||||
require_relative "remote_fetcher"
|
||||
require_relative "text"
|
||||
require_relative "webauthn_listener"
|
||||
require_relative "gemcutter_utilities/webauthn_listener"
|
||||
require_relative "gemcutter_utilities/webauthn_poller"
|
||||
|
||||
##
|
||||
|
@ -260,7 +260,7 @@ module Gem::GemcutterUtilities
|
|||
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), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)]
|
||||
threads = [WebauthnListener.listener_thread(host, server), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)]
|
||||
otp_thread = wait_for_otp_thread(*threads)
|
||||
|
||||
threads.each(&:join)
|
||||
|
@ -289,20 +289,6 @@ module Gem::GemcutterUtilities
|
|||
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 webauthn_verification_url(credentials)
|
||||
response = rubygems_api_request(:post, "api/v1/webauthn_verification") do |request|
|
||||
if credentials.empty?
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "webauthn_listener/response"
|
||||
|
||||
##
|
||||
# The WebauthnListener class retrieves an OTP after a user successfully WebAuthns with the Gem host.
|
||||
# An instance opens a socket using the TCPServer instance given and listens for a request from the Gem host.
|
||||
# The request should be a GET request to the root path and contains the OTP code in the form
|
||||
# of a query parameter `code`. The listener will return the code which will be used as the OTP for
|
||||
# API requests.
|
||||
#
|
||||
# Types of responses sent by the listener after receiving a request:
|
||||
# - 200 OK: OTP code was successfully retrieved
|
||||
# - 204 No Content: If the request was an OPTIONS request
|
||||
# - 400 Bad Request: If the request did not contain a query parameter `code`
|
||||
# - 404 Not Found: The request was not to the root path
|
||||
# - 405 Method Not Allowed: OTP code was not retrieved because the request was not a GET/OPTIONS request
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# server = TCPServer.new(0)
|
||||
# otp = Gem::WebauthnListener.wait_for_otp_code("https://rubygems.example", server)
|
||||
#
|
||||
|
||||
module Gem::GemcutterUtilities
|
||||
class WebauthnListener
|
||||
attr_reader :host
|
||||
|
||||
def initialize(host)
|
||||
@host = host
|
||||
end
|
||||
|
||||
def self.listener_thread(host, server)
|
||||
thread = Thread.new do
|
||||
Thread.current[:otp] = 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 self.wait_for_otp_code(host, server)
|
||||
new(host).fetch_otp_from_connection(server)
|
||||
end
|
||||
|
||||
def fetch_otp_from_connection(server)
|
||||
loop do
|
||||
socket = server.accept
|
||||
request_line = socket.gets
|
||||
|
||||
method, req_uri, _protocol = request_line.split(" ")
|
||||
req_uri = URI.parse(req_uri)
|
||||
|
||||
responder = SocketResponder.new(socket)
|
||||
|
||||
unless root_path?(req_uri)
|
||||
responder.send(NotFoundResponse.for(host))
|
||||
raise Gem::WebauthnVerificationError, "Page at #{req_uri.path} not found."
|
||||
end
|
||||
|
||||
case method.upcase
|
||||
when "OPTIONS"
|
||||
responder.send(NoContentResponse.for(host))
|
||||
next # will be GET
|
||||
when "GET"
|
||||
if otp = parse_otp_from_uri(req_uri)
|
||||
responder.send(OkResponse.for(host))
|
||||
return otp
|
||||
end
|
||||
responder.send(BadRequestResponse.for(host))
|
||||
raise Gem::WebauthnVerificationError, "Did not receive OTP from #{host}."
|
||||
else
|
||||
responder.send(MethodNotAllowedResponse.for(host))
|
||||
raise Gem::WebauthnVerificationError, "Invalid HTTP method #{method.upcase} received."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def root_path?(uri)
|
||||
uri.path == "/"
|
||||
end
|
||||
|
||||
def parse_otp_from_uri(uri)
|
||||
require "cgi"
|
||||
|
||||
return if uri.query.nil?
|
||||
CGI.parse(uri.query).dig("code", 0)
|
||||
end
|
||||
|
||||
class SocketResponder
|
||||
def initialize(socket)
|
||||
@socket = socket
|
||||
end
|
||||
|
||||
def send(response)
|
||||
@socket.print response.to_s
|
||||
@socket.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,163 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
##
|
||||
# The WebauthnListener Response class is used by the WebauthnListener to create
|
||||
# responses to be sent to the Gem host. It creates a Net::HTTPResponse instance
|
||||
# when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`.
|
||||
# Net::HTTPResponse instances cannot be directly sent over a socket.
|
||||
#
|
||||
# Types of response classes:
|
||||
# - OkResponse
|
||||
# - NoContentResponse
|
||||
# - BadRequestResponse
|
||||
# - NotFoundResponse
|
||||
# - MethodNotAllowedResponse
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# server = TCPServer.new(0)
|
||||
# socket = server.accept
|
||||
#
|
||||
# response = OkResponse.for("https://rubygems.example")
|
||||
# socket.print response.to_s
|
||||
# socket.close
|
||||
#
|
||||
|
||||
module Gem::GemcutterUtilities
|
||||
class WebauthnListener
|
||||
class Response
|
||||
attr_reader :http_response
|
||||
|
||||
def self.for(host)
|
||||
new(host)
|
||||
end
|
||||
|
||||
def initialize(host)
|
||||
@host = host
|
||||
|
||||
build_http_response
|
||||
end
|
||||
|
||||
def to_s
|
||||
status_line = "HTTP/#{@http_response.http_version} #{@http_response.code} #{@http_response.message}\r\n"
|
||||
headers = @http_response.to_hash.map {|header, value| "#{header}: #{value.join(", ")}\r\n" }.join + "\r\n"
|
||||
body = @http_response.body ? "#{@http_response.body}\n" : ""
|
||||
|
||||
status_line + headers + body
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Must be implemented in subclasses
|
||||
def code
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def body; end
|
||||
|
||||
def build_http_response
|
||||
response_class = Net::HTTPResponse::CODE_TO_OBJ[code.to_s]
|
||||
@http_response = response_class.new("1.1", code, reason_phrase)
|
||||
@http_response.instance_variable_set(:@read, true)
|
||||
|
||||
add_connection_header
|
||||
add_access_control_headers
|
||||
add_body
|
||||
end
|
||||
|
||||
def add_connection_header
|
||||
@http_response["connection"] = "close"
|
||||
end
|
||||
|
||||
def add_access_control_headers
|
||||
@http_response["access-control-allow-origin"] = @host
|
||||
@http_response["access-control-allow-methods"] = "POST"
|
||||
@http_response["access-control-allow-headers"] = %w[Content-Type Authorization x-csrf-token]
|
||||
end
|
||||
|
||||
def add_body
|
||||
return unless body
|
||||
@http_response["content-type"] = "text/plain"
|
||||
@http_response["content-length"] = body.bytesize
|
||||
@http_response.instance_variable_set(:@body, body)
|
||||
end
|
||||
end
|
||||
|
||||
class OkResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
200
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"OK"
|
||||
end
|
||||
|
||||
def body
|
||||
"success"
|
||||
end
|
||||
end
|
||||
|
||||
class NoContentResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
204
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"No Content"
|
||||
end
|
||||
end
|
||||
|
||||
class BadRequestResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
400
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"Bad Request"
|
||||
end
|
||||
|
||||
def body
|
||||
"missing code parameter"
|
||||
end
|
||||
end
|
||||
|
||||
class NotFoundResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
404
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"Not Found"
|
||||
end
|
||||
end
|
||||
|
||||
class MethodNotAllowedResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
405
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"Method Not Allowed"
|
||||
end
|
||||
|
||||
def add_access_control_headers
|
||||
super
|
||||
@http_response["allow"] = %w[GET OPTIONS]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,92 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "webauthn_listener/response"
|
||||
|
||||
##
|
||||
# The WebauthnListener class retrieves an OTP after a user successfully WebAuthns with the Gem host.
|
||||
# An instance opens a socket using the TCPServer instance given and listens for a request from the Gem host.
|
||||
# The request should be a GET request to the root path and contains the OTP code in the form
|
||||
# of a query parameter `code`. The listener will return the code which will be used as the OTP for
|
||||
# API requests.
|
||||
#
|
||||
# Types of responses sent by the listener after receiving a request:
|
||||
# - 200 OK: OTP code was successfully retrieved
|
||||
# - 204 No Content: If the request was an OPTIONS request
|
||||
# - 400 Bad Request: If the request did not contain a query parameter `code`
|
||||
# - 404 Not Found: The request was not to the root path
|
||||
# - 405 Method Not Allowed: OTP code was not retrieved because the request was not a GET/OPTIONS request
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# server = TCPServer.new(0)
|
||||
# otp = Gem::WebauthnListener.wait_for_otp_code("https://rubygems.example", server)
|
||||
#
|
||||
|
||||
class Gem::WebauthnListener
|
||||
attr_reader :host
|
||||
|
||||
def initialize(host)
|
||||
@host = host
|
||||
end
|
||||
|
||||
def self.wait_for_otp_code(host, server)
|
||||
new(host).fetch_otp_from_connection(server)
|
||||
end
|
||||
|
||||
def fetch_otp_from_connection(server)
|
||||
loop do
|
||||
socket = server.accept
|
||||
request_line = socket.gets
|
||||
|
||||
method, req_uri, _protocol = request_line.split(" ")
|
||||
req_uri = URI.parse(req_uri)
|
||||
|
||||
responder = SocketResponder.new(socket)
|
||||
|
||||
unless root_path?(req_uri)
|
||||
responder.send(NotFoundResponse.for(host))
|
||||
raise Gem::WebauthnVerificationError, "Page at #{req_uri.path} not found."
|
||||
end
|
||||
|
||||
case method.upcase
|
||||
when "OPTIONS"
|
||||
responder.send(NoContentResponse.for(host))
|
||||
next # will be GET
|
||||
when "GET"
|
||||
if otp = parse_otp_from_uri(req_uri)
|
||||
responder.send(OkResponse.for(host))
|
||||
return otp
|
||||
end
|
||||
responder.send(BadRequestResponse.for(host))
|
||||
raise Gem::WebauthnVerificationError, "Did not receive OTP from #{host}."
|
||||
else
|
||||
responder.send(MethodNotAllowedResponse.for(host))
|
||||
raise Gem::WebauthnVerificationError, "Invalid HTTP method #{method.upcase} received."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def root_path?(uri)
|
||||
uri.path == "/"
|
||||
end
|
||||
|
||||
def parse_otp_from_uri(uri)
|
||||
require "cgi"
|
||||
|
||||
return if uri.query.nil?
|
||||
CGI.parse(uri.query).dig("code", 0)
|
||||
end
|
||||
|
||||
class SocketResponder
|
||||
def initialize(socket)
|
||||
@socket = socket
|
||||
end
|
||||
|
||||
def send(response)
|
||||
@socket.print response.to_s
|
||||
@socket.close
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,161 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
##
|
||||
# The WebauthnListener Response class is used by the WebauthnListener to create
|
||||
# responses to be sent to the Gem host. It creates a Net::HTTPResponse instance
|
||||
# when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`.
|
||||
# Net::HTTPResponse instances cannot be directly sent over a socket.
|
||||
#
|
||||
# Types of response classes:
|
||||
# - OkResponse
|
||||
# - NoContentResponse
|
||||
# - BadRequestResponse
|
||||
# - NotFoundResponse
|
||||
# - MethodNotAllowedResponse
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# server = TCPServer.new(0)
|
||||
# socket = server.accept
|
||||
#
|
||||
# response = OkResponse.for("https://rubygems.example")
|
||||
# socket.print response.to_s
|
||||
# socket.close
|
||||
#
|
||||
|
||||
class Gem::WebauthnListener
|
||||
class Response
|
||||
attr_reader :http_response
|
||||
|
||||
def self.for(host)
|
||||
new(host)
|
||||
end
|
||||
|
||||
def initialize(host)
|
||||
@host = host
|
||||
|
||||
build_http_response
|
||||
end
|
||||
|
||||
def to_s
|
||||
status_line = "HTTP/#{@http_response.http_version} #{@http_response.code} #{@http_response.message}\r\n"
|
||||
headers = @http_response.to_hash.map {|header, value| "#{header}: #{value.join(", ")}\r\n" }.join + "\r\n"
|
||||
body = @http_response.body ? "#{@http_response.body}\n" : ""
|
||||
|
||||
status_line + headers + body
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Must be implemented in subclasses
|
||||
def code
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def body; end
|
||||
|
||||
def build_http_response
|
||||
response_class = Net::HTTPResponse::CODE_TO_OBJ[code.to_s]
|
||||
@http_response = response_class.new("1.1", code, reason_phrase)
|
||||
@http_response.instance_variable_set(:@read, true)
|
||||
|
||||
add_connection_header
|
||||
add_access_control_headers
|
||||
add_body
|
||||
end
|
||||
|
||||
def add_connection_header
|
||||
@http_response["connection"] = "close"
|
||||
end
|
||||
|
||||
def add_access_control_headers
|
||||
@http_response["access-control-allow-origin"] = @host
|
||||
@http_response["access-control-allow-methods"] = "POST"
|
||||
@http_response["access-control-allow-headers"] = %w[Content-Type Authorization x-csrf-token]
|
||||
end
|
||||
|
||||
def add_body
|
||||
return unless body
|
||||
@http_response["content-type"] = "text/plain"
|
||||
@http_response["content-length"] = body.bytesize
|
||||
@http_response.instance_variable_set(:@body, body)
|
||||
end
|
||||
end
|
||||
|
||||
class OkResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
200
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"OK"
|
||||
end
|
||||
|
||||
def body
|
||||
"success"
|
||||
end
|
||||
end
|
||||
|
||||
class NoContentResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
204
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"No Content"
|
||||
end
|
||||
end
|
||||
|
||||
class BadRequestResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
400
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"Bad Request"
|
||||
end
|
||||
|
||||
def body
|
||||
"missing code parameter"
|
||||
end
|
||||
end
|
||||
|
||||
class NotFoundResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
404
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"Not Found"
|
||||
end
|
||||
end
|
||||
|
||||
class MethodNotAllowedResponse < Response
|
||||
private
|
||||
|
||||
def code
|
||||
405
|
||||
end
|
||||
|
||||
def reason_phrase
|
||||
"Method Not Allowed"
|
||||
end
|
||||
|
||||
def add_access_control_headers
|
||||
super
|
||||
@http_response["allow"] = %w[GET OPTIONS]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -381,7 +381,7 @@ EOF
|
|||
)
|
||||
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
use_ui @stub_ui do
|
||||
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
||||
end
|
||||
|
@ -417,7 +417,7 @@ EOF
|
|||
)
|
||||
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
use_ui @stub_ui do
|
||||
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
||||
end
|
||||
|
|
|
@ -445,7 +445,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
)
|
||||
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
use_ui @ui do
|
||||
@cmd.send_gem(@path)
|
||||
end
|
||||
|
@ -482,7 +482,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
|
||||
error = assert_raise Gem::MockGemUi::TermError do
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
use_ui @ui do
|
||||
@cmd.send_gem(@path)
|
||||
end
|
||||
|
|
|
@ -141,7 +141,7 @@ class TestGemCommandsYankCommand < Gem::TestCase
|
|||
@cmd.options[:version] = req("= 1.0")
|
||||
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
use_ui @ui do
|
||||
@cmd.execute
|
||||
end
|
||||
|
@ -185,7 +185,7 @@ class TestGemCommandsYankCommand < Gem::TestCase
|
|||
|
||||
error = assert_raise Gem::MockGemUi::TermError do
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
use_ui @ui do
|
||||
@cmd.execute
|
||||
end
|
||||
|
|
|
@ -229,7 +229,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
|
|||
@fetcher.respond_with_require_otp
|
||||
@fetcher.respond_with_webauthn_url(webauthn_verification_url)
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, "Uvh6T57tkWuUnWYo") do
|
||||
util_sign_in
|
||||
end
|
||||
ensure
|
||||
|
@ -252,7 +252,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
|
|||
@fetcher.respond_with_webauthn_url(webauthn_verification_url)
|
||||
error = assert_raise Gem::MockGemUi::TermError do
|
||||
TCPServer.stub(:new, server) do
|
||||
Gem::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
Gem::GemcutterUtilities::WebauthnListener.stub(:wait_for_otp_code, raise_error) do
|
||||
util_sign_in
|
||||
end
|
||||
ensure
|
||||
|
|
|
@ -106,7 +106,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||
|
||||
def wait_for_otp_code
|
||||
@thread = Thread.new do
|
||||
Thread.current[:otp] = Gem::WebauthnListener.wait_for_otp_code(Gem.host, @server)
|
||||
Thread.current[:otp] = Gem::GemcutterUtilities::WebauthnListener.wait_for_otp_code(Gem.host, @server)
|
||||
end
|
||||
@thread.abort_on_exception = true
|
||||
@thread.report_on_exception = false
|
||||
|
@ -115,7 +115,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||
def wait_for_otp_code_expect_error_with_message(message)
|
||||
@thread = Thread.new do
|
||||
error = assert_raise Gem::WebauthnVerificationError do
|
||||
Thread.current[:otp] = Gem::WebauthnListener.wait_for_otp_code(Gem.host, @server)
|
||||
Thread.current[:otp] = Gem::GemcutterUtilities::WebauthnListener.wait_for_otp_code(Gem.host, @server)
|
||||
end
|
||||
|
||||
assert_equal message, error.message
|
||||
|
|
|
@ -10,7 +10,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_ok_response_to_s
|
||||
to_s = Gem::WebauthnListener::OkResponse.new(@host).to_s
|
||||
to_s = Gem::GemcutterUtilities::WebauthnListener::OkResponse.new(@host).to_s
|
||||
|
||||
expected_to_s = <<~RESPONSE
|
||||
HTTP/1.1 200 OK\r
|
||||
|
@ -28,7 +28,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_no_to_s_response_to_s
|
||||
to_s = Gem::WebauthnListener::NoContentResponse.new(@host).to_s
|
||||
to_s = Gem::GemcutterUtilities::WebauthnListener::NoContentResponse.new(@host).to_s
|
||||
|
||||
expected_to_s = <<~RESPONSE
|
||||
HTTP/1.1 204 No Content\r
|
||||
|
@ -43,7 +43,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_method_not_allowed_response_to_s
|
||||
to_s = Gem::WebauthnListener::MethodNotAllowedResponse.new(@host).to_s
|
||||
to_s = Gem::GemcutterUtilities::WebauthnListener::MethodNotAllowedResponse.new(@host).to_s
|
||||
|
||||
expected_to_s = <<~RESPONSE
|
||||
HTTP/1.1 405 Method Not Allowed\r
|
||||
|
@ -59,7 +59,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_method_not_found_response_to_s
|
||||
to_s = Gem::WebauthnListener::NotFoundResponse.new(@host).to_s
|
||||
to_s = Gem::GemcutterUtilities::WebauthnListener::NotFoundResponse.new(@host).to_s
|
||||
|
||||
expected_to_s = <<~RESPONSE
|
||||
HTTP/1.1 404 Not Found\r
|
||||
|
@ -74,7 +74,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_bad_request_response_to_s
|
||||
to_s = Gem::WebauthnListener::BadRequestResponse.new(@host).to_s
|
||||
to_s = Gem::GemcutterUtilities::WebauthnListener::BadRequestResponse.new(@host).to_s
|
||||
|
||||
expected_to_s = <<~RESPONSE
|
||||
HTTP/1.1 400 Bad Request\r
|
||||
|
|
Загрузка…
Ссылка в новой задаче