[rubygems/rubygems] Move WebauthnListener into the Gem::GemcutterUtilities namespace
https://github.com/rubygems/rubygems/commit/3080394f81
This commit is contained in:
parent
108cc38a76
commit
fce04f9a6c
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
require_relative "remote_fetcher"
|
require_relative "remote_fetcher"
|
||||||
require_relative "text"
|
require_relative "text"
|
||||||
require_relative "webauthn_listener"
|
require_relative "gemcutter_utilities/webauthn_listener"
|
||||||
require_relative "gemcutter_utilities/webauthn_poller"
|
require_relative "gemcutter_utilities/webauthn_poller"
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -260,7 +260,7 @@ module Gem::GemcutterUtilities
|
|||||||
url_with_port = "#{webauthn_url}?port=#{port}"
|
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."
|
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)
|
otp_thread = wait_for_otp_thread(*threads)
|
||||||
|
|
||||||
threads.each(&:join)
|
threads.each(&:join)
|
||||||
@ -289,20 +289,6 @@ module Gem::GemcutterUtilities
|
|||||||
threads.each(&:exit)
|
threads.each(&:exit)
|
||||||
end
|
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)
|
def webauthn_verification_url(credentials)
|
||||||
response = rubygems_api_request(:post, "api/v1/webauthn_verification") do |request|
|
response = rubygems_api_request(:post, "api/v1/webauthn_verification") do |request|
|
||||||
if credentials.empty?
|
if credentials.empty?
|
||||||
|
108
lib/rubygems/gemcutter_utilities/webauthn_listener.rb
Normal file
108
lib/rubygems/gemcutter_utilities/webauthn_listener.rb
Normal file
@ -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
|
163
lib/rubygems/gemcutter_utilities/webauthn_listener/response.rb
Normal file
163
lib/rubygems/gemcutter_utilities/webauthn_listener/response.rb
Normal file
@ -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
|
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
|
use_ui @stub_ui do
|
||||||
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
||||||
end
|
end
|
||||||
@ -417,7 +417,7 @@ EOF
|
|||||||
)
|
)
|
||||||
|
|
||||||
TCPServer.stub(:new, server) 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 @stub_ui do
|
use_ui @stub_ui do
|
||||||
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
||||||
end
|
end
|
||||||
|
@ -445,7 +445,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||||||
)
|
)
|
||||||
|
|
||||||
TCPServer.stub(:new, server) do
|
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
|
use_ui @ui do
|
||||||
@cmd.send_gem(@path)
|
@cmd.send_gem(@path)
|
||||||
end
|
end
|
||||||
@ -482,7 +482,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||||||
|
|
||||||
error = assert_raise Gem::MockGemUi::TermError do
|
error = assert_raise Gem::MockGemUi::TermError do
|
||||||
TCPServer.stub(:new, server) 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
|
use_ui @ui do
|
||||||
@cmd.send_gem(@path)
|
@cmd.send_gem(@path)
|
||||||
end
|
end
|
||||||
|
@ -141,7 +141,7 @@ class TestGemCommandsYankCommand < Gem::TestCase
|
|||||||
@cmd.options[:version] = req("= 1.0")
|
@cmd.options[:version] = req("= 1.0")
|
||||||
|
|
||||||
TCPServer.stub(:new, server) do
|
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
|
use_ui @ui do
|
||||||
@cmd.execute
|
@cmd.execute
|
||||||
end
|
end
|
||||||
@ -185,7 +185,7 @@ class TestGemCommandsYankCommand < Gem::TestCase
|
|||||||
|
|
||||||
error = assert_raise Gem::MockGemUi::TermError do
|
error = assert_raise Gem::MockGemUi::TermError do
|
||||||
TCPServer.stub(:new, server) 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
|
use_ui @ui do
|
||||||
@cmd.execute
|
@cmd.execute
|
||||||
end
|
end
|
||||||
|
@ -229,7 +229,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
|
|||||||
@fetcher.respond_with_require_otp
|
@fetcher.respond_with_require_otp
|
||||||
@fetcher.respond_with_webauthn_url(webauthn_verification_url)
|
@fetcher.respond_with_webauthn_url(webauthn_verification_url)
|
||||||
TCPServer.stub(:new, server) do
|
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
|
util_sign_in
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
@ -252,7 +252,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
|
|||||||
@fetcher.respond_with_webauthn_url(webauthn_verification_url)
|
@fetcher.respond_with_webauthn_url(webauthn_verification_url)
|
||||||
error = assert_raise Gem::MockGemUi::TermError do
|
error = assert_raise Gem::MockGemUi::TermError do
|
||||||
TCPServer.stub(:new, server) 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
|
util_sign_in
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
|
@ -106,7 +106,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
|
|
||||||
def wait_for_otp_code
|
def wait_for_otp_code
|
||||||
@thread = Thread.new do
|
@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
|
end
|
||||||
@thread.abort_on_exception = true
|
@thread.abort_on_exception = true
|
||||||
@thread.report_on_exception = false
|
@thread.report_on_exception = false
|
||||||
@ -115,7 +115,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
def wait_for_otp_code_expect_error_with_message(message)
|
def wait_for_otp_code_expect_error_with_message(message)
|
||||||
@thread = Thread.new do
|
@thread = Thread.new do
|
||||||
error = assert_raise Gem::WebauthnVerificationError 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
|
end
|
||||||
|
|
||||||
assert_equal message, error.message
|
assert_equal message, error.message
|
||||||
|
@ -10,7 +10,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_ok_response_to_s
|
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
|
expected_to_s = <<~RESPONSE
|
||||||
HTTP/1.1 200 OK\r
|
HTTP/1.1 200 OK\r
|
||||||
@ -28,7 +28,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_no_to_s_response_to_s
|
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
|
expected_to_s = <<~RESPONSE
|
||||||
HTTP/1.1 204 No Content\r
|
HTTP/1.1 204 No Content\r
|
||||||
@ -43,7 +43,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_method_not_allowed_response_to_s
|
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
|
expected_to_s = <<~RESPONSE
|
||||||
HTTP/1.1 405 Method Not Allowed\r
|
HTTP/1.1 405 Method Not Allowed\r
|
||||||
@ -59,7 +59,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_method_not_found_response_to_s
|
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
|
expected_to_s = <<~RESPONSE
|
||||||
HTTP/1.1 404 Not Found\r
|
HTTP/1.1 404 Not Found\r
|
||||||
@ -74,7 +74,7 @@ class WebauthnListenerResponseTest < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_bad_request_response_to_s
|
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
|
expected_to_s = <<~RESPONSE
|
||||||
HTTP/1.1 400 Bad Request\r
|
HTTP/1.1 400 Bad Request\r
|
||||||
|
Loading…
x
Reference in New Issue
Block a user