1require "test/unit"
2require "net/http"
3require "tempfile"
4require "webrick"
5require "webrick/httpauth/basicauth"
6require_relative "utils"
7
8class TestWEBrickHTTPAuth < Test::Unit::TestCase
9  def test_basic_auth
10    TestWEBrick.start_httpserver{|server, addr, port, log|
11      realm = "WEBrick's realm"
12      path = "/basic_auth"
13
14      server.mount_proc(path){|req, res|
15        WEBrick::HTTPAuth.basic_auth(req, res, realm){|user, pass|
16          user == "webrick" && pass == "supersecretpassword"
17        }
18        res.body = "hoge"
19      }
20      http = Net::HTTP.new(addr, port)
21      g = Net::HTTP::Get.new(path)
22      g.basic_auth("webrick", "supersecretpassword")
23      http.request(g){|res| assert_equal("hoge", res.body, log.call)}
24      g.basic_auth("webrick", "not super")
25      http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
26    }
27  end
28
29  def test_basic_auth2
30    TestWEBrick.start_httpserver{|server, addr, port, log|
31      realm = "WEBrick's realm"
32      path = "/basic_auth2"
33
34      tmpfile = Tempfile.new("test_webrick_auth")
35      tmpfile.close
36      tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
37      tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
38      tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
39      tmp_pass.flush
40
41      htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
42      users = []
43      htpasswd.each{|user, pass| users << user }
44      assert_equal(2, users.size, log.call)
45      assert(users.member?("webrick"), log.call)
46      assert(users.member?("foo"), log.call)
47
48      server.mount_proc(path){|req, res|
49        auth = WEBrick::HTTPAuth::BasicAuth.new(
50          :Realm => realm, :UserDB => htpasswd,
51          :Logger => server.logger
52        )
53        auth.authenticate(req, res)
54        res.body = "hoge"
55      }
56      http = Net::HTTP.new(addr, port)
57      g = Net::HTTP::Get.new(path)
58      g.basic_auth("webrick", "supersecretpassword")
59      http.request(g){|res| assert_equal("hoge", res.body, log.call)}
60      g.basic_auth("webrick", "not super")
61      http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
62      tmpfile.close(true)
63    }
64  end
65
66  def test_basic_auth3
67    tmpfile = Tempfile.new("test_webrick_auth")
68    tmpfile.puts("webrick:{SHA}GJYFRpBbdchp595jlh3Bhfmgp8k=")
69    tmpfile.flush
70    assert_raise(NotImplementedError){
71      WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
72    }
73    tmpfile.close(true)
74
75    tmpfile = Tempfile.new("test_webrick_auth")
76    tmpfile.puts("webrick:$apr1$IOVMD/..$rmnOSPXr0.wwrLPZHBQZy0")
77    tmpfile.flush
78    assert_raise(NotImplementedError){
79      WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
80    }
81    tmpfile.close(true)
82  end
83
84  DIGESTRES_ = /
85    ([a-zA-z\-]+)
86      [\s\t]*(?:\r\n[\s\t]*)*
87      =
88      [\s\t]*(?:\r\n[\s\t]*)*
89      (?:
90       "((?:[^"]+|\\[\x00-\x7F])*)" |
91       ([!\#$%&'*+\-.0-9A-Z^_`a-z|~]+)
92      )/x
93
94  def test_digest_auth
95    TestWEBrick.start_httpserver{|server, addr, port, log|
96      realm = "WEBrick's realm"
97      path = "/digest_auth"
98
99      tmpfile = Tempfile.new("test_webrick_auth")
100      tmpfile.close
101      tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
102      tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
103      tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
104      tmp_pass.flush
105
106      htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
107      users = []
108      htdigest.each{|user, pass| users << user }
109      assert_equal(2, users.size, log.call)
110      assert(users.member?("webrick"), log.call)
111      assert(users.member?("foo"), log.call)
112
113      auth = WEBrick::HTTPAuth::DigestAuth.new(
114        :Realm => realm, :UserDB => htdigest,
115        :Algorithm => 'MD5',
116        :Logger => server.logger
117      )
118      server.mount_proc(path){|req, res|
119        auth.authenticate(req, res)
120        res.body = "hoge"
121      }
122
123      Net::HTTP.start(addr, port) do |http|
124        g = Net::HTTP::Get.new(path)
125        params = {}
126        http.request(g) do |res|
127          assert_equal('401', res.code, log.call)
128          res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token|
129            params[key.downcase] = token || quoted.delete('\\')
130          end
131           params['uri'] = "http://#{addr}:#{port}#{path}"
132        end
133
134        g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
135        http.request(g){|res| assert_equal("hoge", res.body, log.call)}
136
137        params['algorithm'].downcase! #4936
138        g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
139        http.request(g){|res| assert_equal("hoge", res.body, log.call)}
140
141        g['Authorization'] = credentials_for_request('webrick', "not super", params)
142        http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
143      end
144      tmpfile.close(true)
145    }
146  end
147
148  private
149  def credentials_for_request(user, password, params)
150    cnonce = "hoge"
151    nonce_count = 1
152    ha1 = "#{user}:#{params['realm']}:#{password}"
153    ha2 = "GET:#{params['uri']}"
154    request_digest =
155      "#{Digest::MD5.hexdigest(ha1)}:" \
156      "#{params['nonce']}:#{'%08x' % nonce_count}:#{cnonce}:#{params['qop']}:" \
157      "#{Digest::MD5.hexdigest(ha2)}"
158    "Digest username=\"#{user}\"" \
159      ", realm=\"#{params['realm']}\"" \
160      ", nonce=\"#{params['nonce']}\"" \
161      ", uri=\"#{params['uri']}\"" \
162      ", qop=#{params['qop']}" \
163      ", nc=#{'%08x' % nonce_count}" \
164      ", cnonce=\"#{cnonce}\"" \
165      ", response=\"#{Digest::MD5.hexdigest(request_digest)}\"" \
166      ", opaque=\"#{params['opaque']}\"" \
167      ", algorithm=#{params['algorithm']}"
168  end
169end
170