[rubygems/rubygems] Add WebauthnListener response classes

https://github.com/rubygems/rubygems/commit/0e9a26acb1
This commit is contained in:
Jenny Shen 2023-02-15 10:48:35 -05:00 committed by Hiroshi SHIBATA
parent ea95ec5443
commit 332c4b6726
7 changed files with 254 additions and 0 deletions

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
##
# The WebauthnListener Response class is used by the WebauthnListener to print
# the specified response to the Gem host using the provided socket. It also closes
# the socket after printing the response.
#
# Types of response classes:
# - ResponseOk
# - ResponseNoContent
# - ResponseBadRequest
# - ResponseNotFound
# - ResponseMethodNotAllowed
#
# Example:
# socket = TCPSocket.new(host, port)
# Gem::WebauthnListener::ResponseOk.send(socket, host)
#
class Gem::WebauthnListener
class Response
attr_reader :host
def initialize(host)
@host = host
end
def self.send(socket, host)
socket.print new(host).payload
socket.close
end
def payload
status_line_and_connection + access_control_headers + content
end
private
def status_line_and_connection
<<~RESPONSE
HTTP/1.1 #{status}
Connection: close
RESPONSE
end
def access_control_headers
return "" unless add_access_control_headers?
<<~RESPONSE
Access-Control-Allow-Origin: #{host}
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization, x-csrf-token
RESPONSE
end
def content
return "" unless body
<<~RESPONSE
Content-Type: text/plain
Content-Length: #{body.bytesize}
#{body}
RESPONSE
end
def status
raise NotImplementedError
end
def add_access_control_headers?
false
end
def body; end
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
require_relative "../response"
class Gem::WebauthnListener::ResponseBadRequest < Gem::WebauthnListener::Response
private
def status
"400 Bad Request"
end
def body
"missing code parameter"
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
require_relative "../response"
class Gem::WebauthnListener::ResponseMethodNotAllowed < Gem::WebauthnListener::Response
private
def status
"405 Method Not Allowed"
end
def content
<<~RESPONSE
Allow: GET, OPTIONS
RESPONSE
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
require_relative "../response"
class Gem::WebauthnListener::ResponseNoContent < Gem::WebauthnListener::Response
private
def status
"204 No Content"
end
def add_access_control_headers?
true
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
require_relative "../response"
class Gem::WebauthnListener::ResponseNotFound < Gem::WebauthnListener::Response
private
def status
"404 Not Found"
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative "../response"
class Gem::WebauthnListener::ResponseOk < Gem::WebauthnListener::Response
private
def status
"200 OK"
end
def add_access_control_headers?
true
end
def body
"success"
end
end

View File

@ -0,0 +1,107 @@
# frozen_string_literal: true
require_relative "helper"
require "rubygems/webauthn_listener/response/response_ok"
require "rubygems/webauthn_listener/response/response_no_content"
require "rubygems/webauthn_listener/response/response_bad_request"
require "rubygems/webauthn_listener/response/response_not_found"
require "rubygems/webauthn_listener/response/response_method_not_allowed"
class WebauthnListenerResponseTest < Gem::TestCase
class MockResponse < Gem::WebauthnListener::Response
def payload
"hello world"
end
end
def setup
super
@host = "rubygems.example"
end
def test_ok_response_payload
payload = Gem::WebauthnListener::ResponseOk.new(@host).payload
expected_payload = <<~RESPONSE
HTTP/1.1 200 OK
Connection: close
Access-Control-Allow-Origin: rubygems.example
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization, x-csrf-token
Content-Type: text/plain
Content-Length: 7
success
RESPONSE
assert_equal expected_payload, payload
end
def test_no_payload_response_payload
payload = Gem::WebauthnListener::ResponseNoContent.new(@host).payload
expected_payload = <<~RESPONSE
HTTP/1.1 204 No Content
Connection: close
Access-Control-Allow-Origin: rubygems.example
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization, x-csrf-token
RESPONSE
assert_equal expected_payload, payload
end
def test_method_not_allowed_response_payload
payload = Gem::WebauthnListener::ResponseMethodNotAllowed.new(@host).payload
expected_payload = <<~RESPONSE
HTTP/1.1 405 Method Not Allowed
Connection: close
Allow: GET, OPTIONS
RESPONSE
assert_equal expected_payload, payload
end
def test_method_not_found_response_payload
payload = Gem::WebauthnListener::ResponseNotFound.new(@host).payload
expected_payload = <<~RESPONSE
HTTP/1.1 404 Not Found
Connection: close
RESPONSE
assert_equal expected_payload, payload
end
def test_bad_request_response_payload
payload = Gem::WebauthnListener::ResponseBadRequest.new(@host).payload
expected_payload = <<~RESPONSE
HTTP/1.1 400 Bad Request
Connection: close
Content-Type: text/plain
Content-Length: 22
missing code parameter
RESPONSE
assert_equal expected_payload, payload
end
def test_send_response
server = TCPServer.new "localhost", 5678
thread = Thread.new do
receive_socket = server.accept
Thread.current[:payload] = receive_socket.read
receive_socket.close
end
send_socket = TCPSocket.new "localhost", 5678
MockResponse.send(send_socket, @host)
thread.join
assert_equal "hello world", thread[:payload]
assert_predicate send_socket, :closed?
end
end