[rubygems/rubygems] Add WebauthnListener response classes
https://github.com/rubygems/rubygems/commit/0e9a26acb1
This commit is contained in:
parent
ea95ec5443
commit
332c4b6726
75
lib/rubygems/webauthn_listener/response.rb
Normal file
75
lib/rubygems/webauthn_listener/response.rb
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
18
lib/rubygems/webauthn_listener/response/response_ok.rb
Normal file
18
lib/rubygems/webauthn_listener/response/response_ok.rb
Normal 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
|
107
test/rubygems/test_webauthn_listener_response.rb
Normal file
107
test/rubygems/test_webauthn_listener_response.rb
Normal 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
|
Loading…
x
Reference in New Issue
Block a user