PageRenderTime 34ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/test/mri/webrick/test_httpauth.rb

https://github.com/shugo/jruby
Ruby | 348 lines | 327 code | 20 blank | 1 comment | 2 complexity | 6377eaabd0709383c90071440efd6814 MD5 | raw file
Possible License(s): GPL-2.0
  1. # frozen_string_literal: false
  2. require "test/unit"
  3. require "net/http"
  4. require "tempfile"
  5. require "webrick"
  6. require "webrick/httpauth/basicauth"
  7. require "stringio"
  8. require_relative "utils"
  9. class TestWEBrickHTTPAuth < Test::Unit::TestCase
  10. def teardown
  11. WEBrick::Utils::TimeoutHandler.terminate
  12. super
  13. end
  14. def test_basic_auth
  15. log_tester = lambda {|log, access_log|
  16. assert_equal(1, log.length)
  17. assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[0])
  18. }
  19. TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
  20. realm = "WEBrick's realm"
  21. path = "/basic_auth"
  22. server.mount_proc(path){|req, res|
  23. WEBrick::HTTPAuth.basic_auth(req, res, realm){|user, pass|
  24. user == "webrick" && pass == "supersecretpassword"
  25. }
  26. res.body = "hoge"
  27. }
  28. http = Net::HTTP.new(addr, port)
  29. g = Net::HTTP::Get.new(path)
  30. g.basic_auth("webrick", "supersecretpassword")
  31. http.request(g){|res| assert_equal("hoge", res.body, log.call)}
  32. g.basic_auth("webrick", "not super")
  33. http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
  34. }
  35. end
  36. def test_basic_auth2
  37. log_tester = lambda {|log, access_log|
  38. log.reject! {|line| /\A\s*\z/ =~ line }
  39. pats = [
  40. /ERROR Basic WEBrick's realm: webrick: password unmatch\./,
  41. /ERROR WEBrick::HTTPStatus::Unauthorized/
  42. ]
  43. pats.each {|pat|
  44. assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
  45. log.reject! {|line| pat =~ line }
  46. }
  47. assert_equal([], log)
  48. }
  49. TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
  50. realm = "WEBrick's realm"
  51. path = "/basic_auth2"
  52. Tempfile.create("test_webrick_auth") {|tmpfile|
  53. tmpfile.close
  54. tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
  55. tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
  56. tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
  57. tmp_pass.flush
  58. htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
  59. users = []
  60. htpasswd.each{|user, pass| users << user }
  61. assert_equal(2, users.size, log.call)
  62. assert(users.member?("webrick"), log.call)
  63. assert(users.member?("foo"), log.call)
  64. server.mount_proc(path){|req, res|
  65. auth = WEBrick::HTTPAuth::BasicAuth.new(
  66. :Realm => realm, :UserDB => htpasswd,
  67. :Logger => server.logger
  68. )
  69. auth.authenticate(req, res)
  70. res.body = "hoge"
  71. }
  72. http = Net::HTTP.new(addr, port)
  73. g = Net::HTTP::Get.new(path)
  74. g.basic_auth("webrick", "supersecretpassword")
  75. http.request(g){|res| assert_equal("hoge", res.body, log.call)}
  76. g.basic_auth("webrick", "not super")
  77. http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
  78. }
  79. }
  80. end
  81. def test_basic_auth3
  82. Tempfile.create("test_webrick_auth") {|tmpfile|
  83. tmpfile.puts("webrick:{SHA}GJYFRpBbdchp595jlh3Bhfmgp8k=")
  84. tmpfile.flush
  85. assert_raise(NotImplementedError){
  86. WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
  87. }
  88. }
  89. Tempfile.create("test_webrick_auth") {|tmpfile|
  90. tmpfile.puts("webrick:$apr1$IOVMD/..$rmnOSPXr0.wwrLPZHBQZy0")
  91. tmpfile.flush
  92. assert_raise(NotImplementedError){
  93. WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
  94. }
  95. }
  96. end
  97. def test_bad_username_with_control_characters
  98. log_tester = lambda {|log, access_log|
  99. assert_equal(2, log.length)
  100. assert_match(/ERROR Basic WEBrick's realm: foo\\ebar: the user is not allowed./, log[0])
  101. assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[1])
  102. }
  103. TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
  104. realm = "WEBrick's realm"
  105. path = "/basic_auth"
  106. Tempfile.create("test_webrick_auth") {|tmpfile|
  107. tmpfile.close
  108. tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
  109. tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
  110. tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
  111. tmp_pass.flush
  112. htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
  113. users = []
  114. htpasswd.each{|user, pass| users << user }
  115. server.mount_proc(path){|req, res|
  116. auth = WEBrick::HTTPAuth::BasicAuth.new(
  117. :Realm => realm, :UserDB => htpasswd,
  118. :Logger => server.logger
  119. )
  120. auth.authenticate(req, res)
  121. res.body = "hoge"
  122. }
  123. http = Net::HTTP.new(addr, port)
  124. g = Net::HTTP::Get.new(path)
  125. g.basic_auth("foo\ebar", "passwd")
  126. http.request(g){|res| assert_not_equal("hoge", res.body, log.call) }
  127. }
  128. }
  129. end
  130. DIGESTRES_ = /
  131. ([a-zA-Z\-]+)
  132. [ \t]*(?:\r\n[ \t]*)*
  133. =
  134. [ \t]*(?:\r\n[ \t]*)*
  135. (?:
  136. "((?:[^"]+|\\[\x00-\x7F])*)" |
  137. ([!\#$%&'*+\-.0-9A-Z^_`a-z|~]+)
  138. )/x
  139. def test_digest_auth
  140. log_tester = lambda {|log, access_log|
  141. log.reject! {|line| /\A\s*\z/ =~ line }
  142. pats = [
  143. /ERROR Digest WEBrick's realm: no credentials in the request\./,
  144. /ERROR WEBrick::HTTPStatus::Unauthorized/,
  145. /ERROR Digest WEBrick's realm: webrick: digest unmatch\./
  146. ]
  147. pats.each {|pat|
  148. assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
  149. log.reject! {|line| pat =~ line }
  150. }
  151. assert_equal([], log)
  152. }
  153. TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
  154. realm = "WEBrick's realm"
  155. path = "/digest_auth"
  156. Tempfile.create("test_webrick_auth") {|tmpfile|
  157. tmpfile.close
  158. tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
  159. tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
  160. tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
  161. tmp_pass.flush
  162. htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
  163. users = []
  164. htdigest.each{|user, pass| users << user }
  165. assert_equal(2, users.size, log.call)
  166. assert(users.member?("webrick"), log.call)
  167. assert(users.member?("foo"), log.call)
  168. auth = WEBrick::HTTPAuth::DigestAuth.new(
  169. :Realm => realm, :UserDB => htdigest,
  170. :Algorithm => 'MD5',
  171. :Logger => server.logger
  172. )
  173. server.mount_proc(path){|req, res|
  174. auth.authenticate(req, res)
  175. res.body = "hoge"
  176. }
  177. Net::HTTP.start(addr, port) do |http|
  178. g = Net::HTTP::Get.new(path)
  179. params = {}
  180. http.request(g) do |res|
  181. assert_equal('401', res.code, log.call)
  182. res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token|
  183. params[key.downcase] = token || quoted.delete('\\')
  184. end
  185. params['uri'] = "http://#{addr}:#{port}#{path}"
  186. end
  187. g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
  188. http.request(g){|res| assert_equal("hoge", res.body, log.call)}
  189. params['algorithm'].downcase! #4936
  190. g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
  191. http.request(g){|res| assert_equal("hoge", res.body, log.call)}
  192. g['Authorization'] = credentials_for_request('webrick', "not super", params)
  193. http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
  194. end
  195. }
  196. }
  197. end
  198. def test_digest_auth_int
  199. log_tester = lambda {|log, access_log|
  200. log.reject! {|line| /\A\s*\z/ =~ line }
  201. pats = [
  202. /ERROR Digest wb auth-int realm: no credentials in the request\./,
  203. /ERROR WEBrick::HTTPStatus::Unauthorized/,
  204. /ERROR Digest wb auth-int realm: foo: digest unmatch\./
  205. ]
  206. pats.each {|pat|
  207. assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
  208. log.reject! {|line| pat =~ line }
  209. }
  210. assert_equal([], log)
  211. }
  212. TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
  213. realm = "wb auth-int realm"
  214. path = "/digest_auth_int"
  215. Tempfile.create("test_webrick_auth_int") {|tmpfile|
  216. tmpfile.close
  217. tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
  218. tmp_pass.set_passwd(realm, "foo", "Hunter2")
  219. tmp_pass.flush
  220. htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
  221. users = []
  222. htdigest.each{|user, pass| users << user }
  223. assert_equal %w(foo), users
  224. auth = WEBrick::HTTPAuth::DigestAuth.new(
  225. :Realm => realm, :UserDB => htdigest,
  226. :Algorithm => 'MD5',
  227. :Logger => server.logger,
  228. :Qop => %w(auth-int),
  229. )
  230. server.mount_proc(path){|req, res|
  231. auth.authenticate(req, res)
  232. res.body = "bbb"
  233. }
  234. Net::HTTP.start(addr, port) do |http|
  235. post = Net::HTTP::Post.new(path)
  236. params = {}
  237. data = 'hello=world'
  238. body = StringIO.new(data)
  239. post.content_length = data.bytesize
  240. post['Content-Type'] = 'application/x-www-form-urlencoded'
  241. post.body_stream = body
  242. http.request(post) do |res|
  243. assert_equal('401', res.code, log.call)
  244. res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token|
  245. params[key.downcase] = token || quoted.delete('\\')
  246. end
  247. params['uri'] = "http://#{addr}:#{port}#{path}"
  248. end
  249. body.rewind
  250. cred = credentials_for_request('foo', 'Hunter3', params, body)
  251. post['Authorization'] = cred
  252. post.body_stream = body
  253. http.request(post){|res|
  254. assert_equal('401', res.code, log.call)
  255. assert_not_equal("bbb", res.body, log.call)
  256. }
  257. body.rewind
  258. cred = credentials_for_request('foo', 'Hunter2', params, body)
  259. post['Authorization'] = cred
  260. post.body_stream = body
  261. http.request(post){|res| assert_equal("bbb", res.body, log.call)}
  262. end
  263. }
  264. }
  265. end
  266. def test_digest_auth_invalid
  267. digest_auth = WEBrick::HTTPAuth::DigestAuth.new(Realm: 'realm', UserDB: '')
  268. def digest_auth.error(fmt, *)
  269. end
  270. def digest_auth.try_bad_request(len)
  271. request = {"Authorization" => %[Digest a="#{'\b'*len}]}
  272. authenticate request, nil
  273. end
  274. bad_request = WEBrick::HTTPStatus::BadRequest
  275. t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  276. assert_raise(bad_request) {digest_auth.try_bad_request(10)}
  277. limit = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0)
  278. [20, 50, 100, 200].each do |len|
  279. assert_raise(bad_request) do
  280. Timeout.timeout(len*limit) {digest_auth.try_bad_request(len)}
  281. end
  282. end
  283. end
  284. private
  285. def credentials_for_request(user, password, params, body = nil)
  286. cnonce = "hoge"
  287. nonce_count = 1
  288. ha1 = "#{user}:#{params['realm']}:#{password}"
  289. if body
  290. dig = Digest::MD5.new
  291. while buf = body.read(16384)
  292. dig.update(buf)
  293. end
  294. body.rewind
  295. ha2 = "POST:#{params['uri']}:#{dig.hexdigest}"
  296. else
  297. ha2 = "GET:#{params['uri']}"
  298. end
  299. request_digest =
  300. "#{Digest::MD5.hexdigest(ha1)}:" \
  301. "#{params['nonce']}:#{'%08x' % nonce_count}:#{cnonce}:#{params['qop']}:" \
  302. "#{Digest::MD5.hexdigest(ha2)}"
  303. "Digest username=\"#{user}\"" \
  304. ", realm=\"#{params['realm']}\"" \
  305. ", nonce=\"#{params['nonce']}\"" \
  306. ", uri=\"#{params['uri']}\"" \
  307. ", qop=#{params['qop']}" \
  308. ", nc=#{'%08x' % nonce_count}" \
  309. ", cnonce=\"#{cnonce}\"" \
  310. ", response=\"#{Digest::MD5.hexdigest(request_digest)}\"" \
  311. ", opaque=\"#{params['opaque']}\"" \
  312. ", algorithm=#{params['algorithm']}"
  313. end
  314. end