summaryrefslogtreecommitdiff
path: root/test/rubygems/test_webauthn_listener.rb
blob: 81cfd2fc611ed3d8fc2d5aac693acea3237a9ec5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# frozen_string_literal: true

require_relative "helper"
require "rubygems/webauthn_listener"

class WebauthnListenerTest < Gem::TestCase
  def setup
    super
    @server = TCPServer.new 0
    @port = @server.addr[1].to_s
  end

  def teardown
    @thread.kill.join if @thread
    @server&.close
    super
  end

  def test_wait_for_otp_code_get_follows_options
    wait_for_otp_code
    assert Gem::MockBrowser.options(URI("http://localhost:#{@port}?code=xyz")).is_a? Net::HTTPNoContent
    assert Gem::MockBrowser.get(URI("http://localhost:#{@port}?code=xyz")).is_a? Net::HTTPOK
  end

  def test_wait_for_otp_code_options_request
    wait_for_otp_code
    response = Gem::MockBrowser.options URI("http://localhost:#{@port}?code=xyz")

    assert response.is_a? Net::HTTPNoContent
    assert_equal Gem.host, response["access-control-allow-origin"]
    assert_equal "POST", response["access-control-allow-methods"]
    assert_equal "Content-Type, Authorization, x-csrf-token", response["access-control-allow-headers"]
    assert_equal "close", response["Connection"]
  end

  def test_wait_for_otp_code_get_request
    wait_for_otp_code
    response = Gem::MockBrowser.get URI("http://localhost:#{@port}?code=xyz")

    assert response.is_a? Net::HTTPOK
    assert_equal "text/plain", response["Content-Type"]
    assert_equal "7", response["Content-Length"]
    assert_equal Gem.host, response["access-control-allow-origin"]
    assert_equal "POST", response["access-control-allow-methods"]
    assert_equal "Content-Type, Authorization, x-csrf-token", response["access-control-allow-headers"]
    assert_equal "close", response["Connection"]
    assert_equal "success", response.body

    @thread.join
    assert_equal "xyz", @thread[:otp]
  end

  def test_wait_for_otp_code_invalid_post_req_method
    wait_for_otp_code_expect_error_with_message("Security device verification failed: Invalid HTTP method POST received.")
    response = Gem::MockBrowser.post URI("http://localhost:#{@port}?code=xyz")

    assert response
    assert response.is_a? Net::HTTPMethodNotAllowed
    assert_equal "GET, OPTIONS", response["allow"]
    assert_equal "close", response["Connection"]

    @thread.join
    assert_nil @thread[:otp]
  end

  def test_wait_for_otp_code_incorrect_path
    wait_for_otp_code_expect_error_with_message("Security device verification failed: Page at /path not found.")
    response = Gem::MockBrowser.post URI("http://localhost:#{@port}/path?code=xyz")

    assert response.is_a? Net::HTTPNotFound
    assert_equal "close", response["Connection"]

    @thread.join
    assert_nil @thread[:otp]
  end

  def test_wait_for_otp_code_no_params_response
    wait_for_otp_code_expect_error_with_message("Security device verification failed: Did not receive OTP from https://rubygems.org.")
    response = Gem::MockBrowser.get URI("http://localhost:#{@port}")

    assert response.is_a? Net::HTTPBadRequest
    assert_equal "text/plain", response["Content-Type"]
    assert_equal "22", response["Content-Length"]
    assert_equal "close", response["Connection"]
    assert_equal "missing code parameter", response.body

    @thread.join
    assert_nil @thread[:otp]
  end

  def test_wait_for_otp_code_incorrect_params
    wait_for_otp_code_expect_error_with_message("Security device verification failed: Did not receive OTP from https://rubygems.org.")
    response = Gem::MockBrowser.get URI("http://localhost:#{@port}?param=xyz")

    assert response.is_a? Net::HTTPBadRequest
    assert_equal "text/plain", response["Content-Type"]
    assert_equal "22", response["Content-Length"]
    assert_equal "close", response["Connection"]
    assert_equal "missing code parameter", response.body

    @thread.join
    assert_nil @thread[:otp]
  end

  private

  def wait_for_otp_code
    @thread = Thread.new do
      Thread.current[:otp] = Gem::WebauthnListener.wait_for_otp_code(Gem.host, @server)
    end
    @thread.abort_on_exception = true
    @thread.report_on_exception = false
  end

  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)
      end

      assert_equal message, error.message
    end
    @thread.abort_on_exception = true
    @thread.report_on_exception = false
  end
end