nov-ruby-openid 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. data/CHANGELOG +215 -0
  2. data/CHANGES-2.1.0 +36 -0
  3. data/INSTALL +47 -0
  4. data/LICENSE +210 -0
  5. data/NOTICE +2 -0
  6. data/README +81 -0
  7. data/Rakefile +98 -0
  8. data/UPGRADE +127 -0
  9. data/VERSION +1 -0
  10. data/contrib/google/ruby-openid-apps-discovery-1.0.gem +0 -0
  11. data/contrib/google/ruby-openid-apps-discovery-1.01.gem +0 -0
  12. data/examples/README +32 -0
  13. data/examples/active_record_openid_store/README +58 -0
  14. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  15. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  16. data/examples/active_record_openid_store/init.rb +8 -0
  17. data/examples/active_record_openid_store/lib/association.rb +10 -0
  18. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  19. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  20. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  21. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  22. data/examples/discover +49 -0
  23. data/examples/rails_openid/README +153 -0
  24. data/examples/rails_openid/Rakefile +10 -0
  25. data/examples/rails_openid/app/controllers/application.rb +4 -0
  26. data/examples/rails_openid/app/controllers/consumer_controller.rb +122 -0
  27. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  28. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  29. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  30. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  31. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  32. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  33. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  34. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  35. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  36. data/examples/rails_openid/config/boot.rb +19 -0
  37. data/examples/rails_openid/config/database.yml +74 -0
  38. data/examples/rails_openid/config/environment.rb +54 -0
  39. data/examples/rails_openid/config/environments/development.rb +19 -0
  40. data/examples/rails_openid/config/environments/production.rb +19 -0
  41. data/examples/rails_openid/config/environments/test.rb +19 -0
  42. data/examples/rails_openid/config/routes.rb +24 -0
  43. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  44. data/examples/rails_openid/public/.htaccess +40 -0
  45. data/examples/rails_openid/public/404.html +8 -0
  46. data/examples/rails_openid/public/500.html +8 -0
  47. data/examples/rails_openid/public/dispatch.cgi +12 -0
  48. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  49. data/examples/rails_openid/public/dispatch.rb +12 -0
  50. data/examples/rails_openid/public/favicon.ico +0 -0
  51. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  52. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  53. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  54. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  55. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  56. data/examples/rails_openid/public/robots.txt +1 -0
  57. data/examples/rails_openid/script/about +3 -0
  58. data/examples/rails_openid/script/breakpointer +3 -0
  59. data/examples/rails_openid/script/console +3 -0
  60. data/examples/rails_openid/script/destroy +3 -0
  61. data/examples/rails_openid/script/generate +3 -0
  62. data/examples/rails_openid/script/performance/benchmarker +3 -0
  63. data/examples/rails_openid/script/performance/profiler +3 -0
  64. data/examples/rails_openid/script/plugin +3 -0
  65. data/examples/rails_openid/script/process/reaper +3 -0
  66. data/examples/rails_openid/script/process/spawner +3 -0
  67. data/examples/rails_openid/script/process/spinner +3 -0
  68. data/examples/rails_openid/script/runner +3 -0
  69. data/examples/rails_openid/script/server +3 -0
  70. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  71. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  72. data/examples/rails_openid/test/test_helper.rb +28 -0
  73. data/lib/hmac/hmac.rb +112 -0
  74. data/lib/hmac/sha1.rb +11 -0
  75. data/lib/hmac/sha2.rb +25 -0
  76. data/lib/openid.rb +20 -0
  77. data/lib/openid/association.rb +249 -0
  78. data/lib/openid/consumer.rb +395 -0
  79. data/lib/openid/consumer/associationmanager.rb +344 -0
  80. data/lib/openid/consumer/checkid_request.rb +186 -0
  81. data/lib/openid/consumer/discovery.rb +497 -0
  82. data/lib/openid/consumer/discovery_manager.rb +123 -0
  83. data/lib/openid/consumer/html_parse.rb +134 -0
  84. data/lib/openid/consumer/idres.rb +523 -0
  85. data/lib/openid/consumer/responses.rb +148 -0
  86. data/lib/openid/cryptutil.rb +115 -0
  87. data/lib/openid/dh.rb +89 -0
  88. data/lib/openid/extension.rb +39 -0
  89. data/lib/openid/extensions/ax.rb +539 -0
  90. data/lib/openid/extensions/oauth.rb +91 -0
  91. data/lib/openid/extensions/pape.rb +179 -0
  92. data/lib/openid/extensions/sreg.rb +277 -0
  93. data/lib/openid/extensions/ui.rb +53 -0
  94. data/lib/openid/extras.rb +11 -0
  95. data/lib/openid/fetchers.rb +258 -0
  96. data/lib/openid/kvform.rb +136 -0
  97. data/lib/openid/kvpost.rb +58 -0
  98. data/lib/openid/message.rb +553 -0
  99. data/lib/openid/protocolerror.rb +8 -0
  100. data/lib/openid/server.rb +1544 -0
  101. data/lib/openid/store/filesystem.rb +271 -0
  102. data/lib/openid/store/interface.rb +75 -0
  103. data/lib/openid/store/memcache.rb +107 -0
  104. data/lib/openid/store/memory.rb +84 -0
  105. data/lib/openid/store/nonce.rb +68 -0
  106. data/lib/openid/trustroot.rb +349 -0
  107. data/lib/openid/urinorm.rb +75 -0
  108. data/lib/openid/util.rb +110 -0
  109. data/lib/openid/yadis/accept.rb +148 -0
  110. data/lib/openid/yadis/constants.rb +21 -0
  111. data/lib/openid/yadis/discovery.rb +153 -0
  112. data/lib/openid/yadis/filters.rb +205 -0
  113. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  114. data/lib/openid/yadis/parsehtml.rb +45 -0
  115. data/lib/openid/yadis/services.rb +42 -0
  116. data/lib/openid/yadis/xrds.rb +155 -0
  117. data/lib/openid/yadis/xri.rb +90 -0
  118. data/lib/openid/yadis/xrires.rb +99 -0
  119. data/setup.rb +1551 -0
  120. data/test/data/accept.txt +124 -0
  121. data/test/data/dh.txt +29 -0
  122. data/test/data/example-xrds.xml +14 -0
  123. data/test/data/linkparse.txt +587 -0
  124. data/test/data/n2b64 +650 -0
  125. data/test/data/test1-discover.txt +137 -0
  126. data/test/data/test1-parsehtml.txt +152 -0
  127. data/test/data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/data/test_discover/openid.html +11 -0
  129. data/test/data/test_discover/openid2.html +11 -0
  130. data/test/data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/data/test_discover/openid_utf8.html +11 -0
  138. data/test/data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/data/test_discover/yadis_idp.xml +12 -0
  144. data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/data/test_xrds/README +12 -0
  148. data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/data/test_xrds/no-xrd.xml +7 -0
  152. data/test/data/test_xrds/not-xrds.xml +2 -0
  153. data/test/data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/data/test_xrds/ref.xrds +109 -0
  155. data/test/data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/data/test_xrds/spoof1.xrds +25 -0
  157. data/test/data/test_xrds/spoof2.xrds +25 -0
  158. data/test/data/test_xrds/spoof3.xrds +37 -0
  159. data/test/data/test_xrds/status222.xrds +9 -0
  160. data/test/data/test_xrds/subsegments.xrds +58 -0
  161. data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/data/trustroot.txt +153 -0
  163. data/test/data/urinorm.txt +79 -0
  164. data/test/discoverdata.rb +131 -0
  165. data/test/test_accept.rb +170 -0
  166. data/test/test_association.rb +266 -0
  167. data/test/test_associationmanager.rb +917 -0
  168. data/test/test_ax.rb +690 -0
  169. data/test/test_checkid_request.rb +294 -0
  170. data/test/test_consumer.rb +257 -0
  171. data/test/test_cryptutil.rb +119 -0
  172. data/test/test_dh.rb +86 -0
  173. data/test/test_discover.rb +852 -0
  174. data/test/test_discovery_manager.rb +262 -0
  175. data/test/test_extension.rb +46 -0
  176. data/test/test_extras.rb +35 -0
  177. data/test/test_fetchers.rb +565 -0
  178. data/test/test_filters.rb +270 -0
  179. data/test/test_idres.rb +963 -0
  180. data/test/test_kvform.rb +165 -0
  181. data/test/test_kvpost.rb +65 -0
  182. data/test/test_linkparse.rb +101 -0
  183. data/test/test_message.rb +1116 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +175 -0
  186. data/test/test_openid_yadis.rb +178 -0
  187. data/test/test_pape.rb +247 -0
  188. data/test/test_parsehtml.rb +80 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2457 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +298 -0
  193. data/test/test_trustroot.rb +113 -0
  194. data/test/test_ui.rb +93 -0
  195. data/test/test_urinorm.rb +35 -0
  196. data/test/test_util.rb +145 -0
  197. data/test/test_xrds.rb +169 -0
  198. data/test/test_xri.rb +48 -0
  199. data/test/test_xrires.rb +63 -0
  200. data/test/test_yadis_discovery.rb +220 -0
  201. data/test/testutil.rb +127 -0
  202. data/test/util.rb +53 -0
  203. metadata +336 -0
@@ -0,0 +1,80 @@
1
+ require 'test/unit'
2
+ require "openid/yadis/parsehtml"
3
+ require "testutil"
4
+
5
+ module OpenID
6
+ class ParseHTMLTestCase < Test::Unit::TestCase
7
+ include OpenID::TestDataMixin
8
+
9
+ def test_parsehtml
10
+ reserved_values = ['None', 'EOF']
11
+ chunks = read_data_file('test1-parsehtml.txt', false).split("\f\n")
12
+ test_num = 1
13
+
14
+ chunks.each{|c|
15
+ expected, html = c.split("\n", 2)
16
+ found = Yadis::html_yadis_location(html)
17
+
18
+ assert(!reserved_values.member?(found))
19
+
20
+ # this case is a little hard to detect and the distinction
21
+ # seems unimportant
22
+ expected = "None" if expected == "EOF"
23
+
24
+ found = "None" if found.nil?
25
+ assert_equal(expected, found, html.split("\n",2)[0])
26
+ }
27
+ end
28
+ end
29
+
30
+ # the HTML tokenizer test
31
+ class TC_TestHTMLTokenizer < Test::Unit::TestCase
32
+ def test_bad_link
33
+ toke = HTMLTokenizer.new("<p><a href=http://bad.com/link>foo</a></p>")
34
+ assert("http://bad.com/link" == toke.getTag("a").attr_hash['href'])
35
+ end
36
+
37
+ def test_namespace
38
+ toke = HTMLTokenizer.new("<f:table xmlns:f=\"http://www.com/foo\">")
39
+ assert("http://www.com/foo" == toke.getTag("f:table").attr_hash['xmlns:f'])
40
+ end
41
+
42
+ def test_comment
43
+ toke = HTMLTokenizer.new("<!-- comment on me -->")
44
+ t = toke.getNextToken
45
+ assert(HTMLComment == t.class)
46
+ assert("comment on me" == t.contents)
47
+ end
48
+
49
+ def test_full
50
+ page = "<HTML>
51
+ <HEAD>
52
+ <TITLE>This is the title</TITLE>
53
+ </HEAD>
54
+ <!-- Here comes the <a href=\"missing.link\">blah</a>
55
+ comment body
56
+ -->
57
+ <BODY>
58
+ <H1>This is the header</H1>
59
+ <P>
60
+ This is the paragraph, it contains
61
+ <a href=\"link.html\">links</a>,
62
+ <img src=\"blah.gif\" optional alt='images
63
+ are
64
+ really cool'>. Ok, here is some more text and
65
+ <A href=\"http://another.link.com/\" target=\"_blank\">another link</A>.
66
+ </P>
67
+ </body>
68
+ </HTML>
69
+ "
70
+ toke = HTMLTokenizer.new(page)
71
+
72
+ assert("<h1>" == toke.getTag("h1", "h2", "h3").to_s.downcase)
73
+ assert(HTMLTag.new("<a href=\"link.html\">") == toke.getTag("IMG", "A"))
74
+ assert("links" == toke.getTrimmedText)
75
+ assert(toke.getTag("IMG", "A").attr_hash['optional'])
76
+ assert("_blank" == toke.getTag("IMG", "A").attr_hash['target'])
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,63 @@
1
+ require "test/unit"
2
+ require "openid/consumer/discovery"
3
+ require "openid/consumer/responses"
4
+
5
+ module OpenID
6
+ class Consumer
7
+ module TestResponses
8
+ class TestSuccessResponse < Test::Unit::TestCase
9
+ def setup
10
+ @endpoint = OpenIDServiceEndpoint.new
11
+ @endpoint.claimed_id = 'identity_url'
12
+ end
13
+
14
+ def test_extension_response
15
+ q = {
16
+ 'ns.sreg' => 'urn:sreg',
17
+ 'ns.unittest' => 'urn:unittest',
18
+ 'unittest.one' => '1',
19
+ 'unittest.two' => '2',
20
+ 'sreg.nickname' => 'j3h',
21
+ 'return_to' => 'return_to',
22
+ }
23
+ signed_list = q.keys.map { |k| 'openid.' + k }
24
+ msg = Message.from_openid_args(q)
25
+ resp = SuccessResponse.new(@endpoint, msg, signed_list)
26
+ utargs = resp.extension_response('urn:unittest', false)
27
+ assert_equal(utargs, {'one' => '1', 'two' => '2'})
28
+ sregargs = resp.extension_response('urn:sreg', false)
29
+ assert_equal(sregargs, {'nickname' => 'j3h'})
30
+ end
31
+
32
+ def test_extension_response_signed
33
+ args = {
34
+ 'ns.sreg' => 'urn:sreg',
35
+ 'ns.unittest' => 'urn:unittest',
36
+ 'unittest.one' => '1',
37
+ 'unittest.two' => '2',
38
+ 'sreg.nickname' => 'j3h',
39
+ 'sreg.dob' => 'yesterday',
40
+ 'return_to' => 'return_to',
41
+ 'signed' => 'sreg.nickname,unittest.one,sreg.dob',
42
+ }
43
+
44
+ signed_list = ['openid.sreg.nickname',
45
+ 'openid.unittest.one',
46
+ 'openid.sreg.dob',]
47
+
48
+ msg = Message.from_openid_args(args)
49
+ resp = SuccessResponse.new(@endpoint, msg, signed_list)
50
+
51
+ # All args in this NS are signed, so expect all.
52
+ sregargs = resp.extension_response('urn:sreg', true)
53
+ assert_equal(sregargs, {'nickname' => 'j3h', 'dob' => 'yesterday'})
54
+
55
+ # Not all args in this NS are signed, so expect nil when
56
+ # asking for them.
57
+ utargs = resp.extension_response('urn:unittest', true)
58
+ assert_equal(nil, utargs)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,2457 @@
1
+ require 'openid/server'
2
+ require 'openid/cryptutil'
3
+ require 'openid/association'
4
+ require 'openid/util'
5
+ require 'openid/message'
6
+ require 'openid/store/memory'
7
+ require 'openid/dh'
8
+ require 'openid/consumer/associationmanager'
9
+ require 'util'
10
+ require "testutil"
11
+
12
+ require 'test/unit'
13
+ require 'uri'
14
+
15
+ # In general, if you edit or add tests here, try to move in the
16
+ # direction of testing smaller units. For testing the external
17
+ # interfaces, we'll be developing an implementation-agnostic testing
18
+ # suite.
19
+
20
+ # for more, see /etc/ssh/moduli
21
+
22
+ module OpenID
23
+
24
+ ALT_MODULUS = 0xCAADDDEC1667FC68B5FA15D53C4E1532DD24561A1A2D47A12C01ABEA1E00731F6921AAC40742311FDF9E634BB7131BEE1AF240261554389A910425E044E88C8359B010F5AD2B80E29CB1A5B027B19D9E01A6F63A6F45E5D7ED2FF6A2A0085050A7D0CF307C3DB51D2490355907B4427C23A98DF1EB8ABEF2BA209BB7AFFE86A7
25
+ ALT_GEN = 5
26
+
27
+ class CatchLogs
28
+ def catchlogs_setup
29
+ @old_logger = Util.logger
30
+ Util.logger = self.method('got_log_message')
31
+ @messages = []
32
+ end
33
+
34
+ def got_log_message(message)
35
+ @messages << message
36
+ end
37
+
38
+ def teardown
39
+ Util.logger = @old_logger
40
+ end
41
+ end
42
+
43
+ class TestProtocolError < Test::Unit::TestCase
44
+ def test_browserWithReturnTo
45
+ return_to = "http://rp.unittest/consumer"
46
+ # will be a ProtocolError raised by Decode or
47
+ # CheckIDRequest.answer
48
+ args = Message.from_post_args({
49
+ 'openid.mode' => 'monkeydance',
50
+ 'openid.identity' => 'http://wagu.unittest/',
51
+ 'openid.return_to' => return_to,
52
+ })
53
+ e = Server::ProtocolError.new(args, "plucky")
54
+ assert(e.has_return_to)
55
+ expected_args = {
56
+ 'openid.mode' => 'error',
57
+ 'openid.error' => 'plucky',
58
+ }
59
+
60
+ rt_base, result_args = e.encode_to_url.split('?', 2)
61
+ result_args = Util.parse_query(result_args)
62
+ assert_equal(result_args, expected_args)
63
+ end
64
+
65
+ def test_browserWithReturnTo_OpenID2_GET
66
+ return_to = "http://rp.unittest/consumer"
67
+ # will be a ProtocolError raised by Decode or
68
+ # CheckIDRequest.answer
69
+ args = Message.from_post_args({
70
+ 'openid.ns' => OPENID2_NS,
71
+ 'openid.mode' => 'monkeydance',
72
+ 'openid.identity' => 'http://wagu.unittest/',
73
+ 'openid.claimed_id' => 'http://wagu.unittest/',
74
+ 'openid.return_to' => return_to,
75
+ })
76
+ e = Server::ProtocolError.new(args, "plucky")
77
+ assert(e.has_return_to)
78
+ expected_args = {
79
+ 'openid.ns' => OPENID2_NS,
80
+ 'openid.mode' => 'error',
81
+ 'openid.error' => 'plucky',
82
+ }
83
+
84
+ rt_base, result_args = e.encode_to_url.split('?', 2)
85
+ result_args = Util.parse_query(result_args)
86
+ assert_equal(result_args, expected_args)
87
+ end
88
+
89
+ def test_browserWithReturnTo_OpenID2_POST
90
+ return_to = "http://rp.unittest/consumer" + ('x' * OPENID1_URL_LIMIT)
91
+ # will be a ProtocolError raised by Decode or
92
+ # CheckIDRequest.answer
93
+ args = Message.from_post_args({
94
+ 'openid.ns' => OPENID2_NS,
95
+ 'openid.mode' => 'monkeydance',
96
+ 'openid.identity' => 'http://wagu.unittest/',
97
+ 'openid.claimed_id' => 'http://wagu.unittest/',
98
+ 'openid.return_to' => return_to,
99
+ })
100
+ e = Server::ProtocolError.new(args, "plucky")
101
+ assert(e.has_return_to)
102
+ expected_args = {
103
+ 'openid.ns' => OPENID2_NS,
104
+ 'openid.mode' => 'error',
105
+ 'openid.error' => 'plucky',
106
+ }
107
+
108
+ assert(e.which_encoding == Server::ENCODE_HTML_FORM)
109
+ assert(e.to_form_markup == e.to_message.to_form_markup(
110
+ args.get_arg(OPENID_NS, 'return_to')))
111
+ end
112
+
113
+ def test_browserWithReturnTo_OpenID1_exceeds_limit
114
+ return_to = "http://rp.unittest/consumer" + ('x' * OPENID1_URL_LIMIT)
115
+ # will be a ProtocolError raised by Decode or
116
+ # CheckIDRequest.answer
117
+ args = Message.from_post_args({
118
+ 'openid.mode' => 'monkeydance',
119
+ 'openid.identity' => 'http://wagu.unittest/',
120
+ 'openid.return_to' => return_to,
121
+ })
122
+ e = Server::ProtocolError.new(args, "plucky")
123
+ assert(e.has_return_to)
124
+ expected_args = {
125
+ 'openid.mode' => 'error',
126
+ 'openid.error' => 'plucky',
127
+ }
128
+
129
+ assert(e.which_encoding == Server::ENCODE_URL)
130
+
131
+ rt_base, result_args = e.encode_to_url.split('?', 2)
132
+ result_args = Util.parse_query(result_args)
133
+ assert_equal(result_args, expected_args)
134
+ end
135
+
136
+ def test_noReturnTo
137
+ # will be a ProtocolError raised by Decode or
138
+ # CheckIDRequest.answer
139
+ args = Message.from_post_args({
140
+ 'openid.mode' => 'zebradance',
141
+ 'openid.identity' => 'http://wagu.unittest/',
142
+ })
143
+ e = Server::ProtocolError.new(args, "waffles")
144
+ assert(!e.has_return_to)
145
+ expected = "error:waffles\nmode:error\n"
146
+ assert_equal(e.encode_to_kvform, expected)
147
+ end
148
+
149
+ def test_no_message
150
+ e = Server::ProtocolError.new(nil, "no message")
151
+ assert(e.get_return_to.nil?)
152
+ assert_equal(e.which_encoding, nil)
153
+ end
154
+
155
+ def test_which_encoding_no_message
156
+ e = Server::ProtocolError.new(nil, "no message")
157
+ assert(e.which_encoding.nil?)
158
+ end
159
+ end
160
+
161
+ class TestDecode < Test::Unit::TestCase
162
+ def setup
163
+ @claimed_id = 'http://de.legating.de.coder.unittest/'
164
+ @id_url = "http://decoder.am.unittest/"
165
+ @rt_url = "http://rp.unittest/foobot/?qux=zam"
166
+ @tr_url = "http://rp.unittest/"
167
+ @assoc_handle = "{assoc}{handle}"
168
+ @op_endpoint = 'http://endpoint.unittest/encode'
169
+ @store = Store::Memory.new()
170
+ @server = Server::Server.new(@store, @op_endpoint)
171
+ @decode = Server::Decoder.new(@server).method('decode')
172
+ end
173
+
174
+ def test_none
175
+ args = {}
176
+ r = @decode.call(args)
177
+ assert_equal(r, nil)
178
+ end
179
+
180
+ def test_irrelevant
181
+ args = {
182
+ 'pony' => 'spotted',
183
+ 'sreg.mutant_power' => 'decaffinator',
184
+ }
185
+ assert_raise(Server::ProtocolError) {
186
+ @decode.call(args)
187
+ }
188
+ end
189
+
190
+ def test_bad
191
+ args = {
192
+ 'openid.mode' => 'twos-compliment',
193
+ 'openid.pants' => 'zippered',
194
+ }
195
+ assert_raise(Server::ProtocolError) {
196
+ @decode.call(args)
197
+ }
198
+ end
199
+
200
+ def test_dictOfLists
201
+ args = {
202
+ 'openid.mode' => ['checkid_setup'],
203
+ 'openid.identity' => @id_url,
204
+ 'openid.assoc_handle' => @assoc_handle,
205
+ 'openid.return_to' => @rt_url,
206
+ 'openid.trust_root' => @tr_url,
207
+ }
208
+ begin
209
+ result = @decode.call(args)
210
+ rescue ArgumentError => err
211
+ assert(!err.to_s.index('values').nil?, err)
212
+ else
213
+ flunk("Expected ArgumentError, but got result #{result}")
214
+ end
215
+ end
216
+
217
+ def test_checkidImmediate
218
+ args = {
219
+ 'openid.mode' => 'checkid_immediate',
220
+ 'openid.identity' => @id_url,
221
+ 'openid.assoc_handle' => @assoc_handle,
222
+ 'openid.return_to' => @rt_url,
223
+ 'openid.trust_root' => @tr_url,
224
+ # should be ignored
225
+ 'openid.some.extension' => 'junk',
226
+ }
227
+ r = @decode.call(args)
228
+ assert(r.is_a?(Server::CheckIDRequest))
229
+ assert_equal(r.mode, "checkid_immediate")
230
+ assert_equal(r.immediate, true)
231
+ assert_equal(r.identity, @id_url)
232
+ assert_equal(r.trust_root, @tr_url)
233
+ assert_equal(r.return_to, @rt_url)
234
+ assert_equal(r.assoc_handle, @assoc_handle)
235
+ end
236
+
237
+ def test_checkidImmediate_constructor
238
+ r = Server::CheckIDRequest.new(@id_url, @rt_url, nil,
239
+ @rt_url, true, @assoc_handle)
240
+ assert(r.mode == 'checkid_immediate')
241
+ assert(r.immediate)
242
+ end
243
+
244
+ def test_checkid_missing_return_to_and_trust_root
245
+ args = {
246
+ 'openid.ns' => OPENID2_NS,
247
+ 'openid.mode' => 'checkid_setup',
248
+ 'openid.identity' => @id_url,
249
+ 'openid.claimed_id' => @id_url,
250
+ 'openid.assoc_handle' => @assoc_handle,
251
+ }
252
+ assert_raise(Server::ProtocolError) {
253
+ m = Message.from_post_args(args)
254
+ Server::CheckIDRequest.from_message(m, @op_endpoint)
255
+ }
256
+ end
257
+
258
+ def test_checkid_id_select
259
+ args = {
260
+ 'openid.ns' => OPENID2_NS,
261
+ 'openid.mode' => 'checkid_setup',
262
+ 'openid.identity' => IDENTIFIER_SELECT,
263
+ 'openid.claimed_id' => IDENTIFIER_SELECT,
264
+ 'openid.assoc_handle' => @assoc_handle,
265
+ 'openid.return_to' => @rt_url,
266
+ 'openid.realm' => @tr_url,
267
+ }
268
+ m = Message.from_post_args(args)
269
+ req = Server::CheckIDRequest.from_message(m, @op_endpoint)
270
+ assert(req.id_select)
271
+ end
272
+
273
+ def test_checkid_not_id_select
274
+ args = {
275
+ 'openid.ns' => OPENID2_NS,
276
+ 'openid.mode' => 'checkid_setup',
277
+ 'openid.assoc_handle' => @assoc_handle,
278
+ 'openid.return_to' => @rt_url,
279
+ 'openid.realm' => @tr_url,
280
+ }
281
+
282
+ id_args = [
283
+ {'openid.claimed_id' => IDENTIFIER_SELECT,
284
+ 'openid.identity' => 'http://bogus.com/'},
285
+
286
+ {'openid.claimed_id' => 'http://bogus.com/',
287
+ 'openid.identity' => 'http://bogus.com/'},
288
+ ]
289
+
290
+ id_args.each { |id|
291
+ m = Message.from_post_args(args.merge(id))
292
+ req = Server::CheckIDRequest.from_message(m, @op_endpoint)
293
+ assert(!req.id_select)
294
+ }
295
+ end
296
+
297
+ def test_checkidSetup
298
+ args = {
299
+ 'openid.mode' => 'checkid_setup',
300
+ 'openid.identity' => @id_url,
301
+ 'openid.assoc_handle' => @assoc_handle,
302
+ 'openid.return_to' => @rt_url,
303
+ 'openid.trust_root' => @tr_url,
304
+ }
305
+ r = @decode.call(args)
306
+ assert(r.is_a?(Server::CheckIDRequest))
307
+ assert_equal(r.mode, "checkid_setup")
308
+ assert_equal(r.immediate, false)
309
+ assert_equal(r.identity, @id_url)
310
+ assert_equal(r.trust_root, @tr_url)
311
+ assert_equal(r.return_to, @rt_url)
312
+ end
313
+
314
+ def test_checkidSetupOpenID2
315
+ args = {
316
+ 'openid.ns' => OPENID2_NS,
317
+ 'openid.mode' => 'checkid_setup',
318
+ 'openid.identity' => @id_url,
319
+ 'openid.claimed_id' => @claimed_id,
320
+ 'openid.assoc_handle' => @assoc_handle,
321
+ 'openid.return_to' => @rt_url,
322
+ 'openid.realm' => @tr_url,
323
+ }
324
+ r = @decode.call(args)
325
+ assert(r.is_a?(Server::CheckIDRequest))
326
+ assert_equal(r.mode, "checkid_setup")
327
+ assert_equal(r.immediate, false)
328
+ assert_equal(r.identity, @id_url)
329
+ assert_equal(r.claimed_id, @claimed_id)
330
+ assert_equal(r.trust_root, @tr_url)
331
+ assert_equal(r.return_to, @rt_url)
332
+ end
333
+
334
+ def test_checkidSetupNoClaimedIDOpenID2
335
+ args = {
336
+ 'openid.ns' => OPENID2_NS,
337
+ 'openid.mode' => 'checkid_setup',
338
+ 'openid.identity' => @id_url,
339
+ 'openid.assoc_handle' => @assoc_handle,
340
+ 'openid.return_to' => @rt_url,
341
+ 'openid.realm' => @tr_url,
342
+ }
343
+ assert_raise(Server::ProtocolError) {
344
+ @decode.call(args)
345
+ }
346
+ end
347
+
348
+ def test_checkidSetupNoIdentityOpenID2
349
+ args = {
350
+ 'openid.ns' => OPENID2_NS,
351
+ 'openid.mode' => 'checkid_setup',
352
+ 'openid.assoc_handle' => @assoc_handle,
353
+ 'openid.return_to' => @rt_url,
354
+ 'openid.realm' => @tr_url,
355
+ }
356
+ r = @decode.call(args)
357
+ assert(r.is_a?(Server::CheckIDRequest))
358
+ assert_equal(r.mode, "checkid_setup")
359
+ assert_equal(r.immediate, false)
360
+ assert_equal(r.identity, nil)
361
+ assert_equal(r.trust_root, @tr_url)
362
+ assert_equal(r.return_to, @rt_url)
363
+ end
364
+
365
+ def test_checkidSetupNoReturnOpenID1
366
+ # Make sure an OpenID 1 request cannot be decoded if it lacks a
367
+ # return_to.
368
+ args = {
369
+ 'openid.mode' => 'checkid_setup',
370
+ 'openid.identity' => @id_url,
371
+ 'openid.assoc_handle' => @assoc_handle,
372
+ 'openid.trust_root' => @tr_url,
373
+ }
374
+ assert_raise(Server::ProtocolError) {
375
+ @decode.call(args)
376
+ }
377
+ end
378
+
379
+ def test_checkidSetupNoReturnOpenID2
380
+ # Make sure an OpenID 2 request with no return_to can be decoded,
381
+ # and make sure a response to such a request raises
382
+ # NoReturnToError.
383
+ args = {
384
+ 'openid.ns' => OPENID2_NS,
385
+ 'openid.mode' => 'checkid_setup',
386
+ 'openid.identity' => @id_url,
387
+ 'openid.claimed_id' => @id_url,
388
+ 'openid.assoc_handle' => @assoc_handle,
389
+ 'openid.realm' => @tr_url,
390
+ }
391
+
392
+ req = @decode.call(args)
393
+ assert(req.is_a?(Server::CheckIDRequest))
394
+
395
+ assert_raise(Server::NoReturnToError) {
396
+ req.answer(false)
397
+ }
398
+
399
+ assert_raise(Server::NoReturnToError) {
400
+ req.encode_to_url('bogus')
401
+ }
402
+
403
+ assert_raise(Server::NoReturnToError) {
404
+ req.cancel_url
405
+ }
406
+ end
407
+
408
+ def test_checkidSetupRealmRequiredOpenID2
409
+ # Make sure that an OpenID 2 request which lacks return_to cannot
410
+ # be decoded if it lacks a realm. Spec => This value
411
+ # (openid.realm) MUST be sent if openid.return_to is omitted.
412
+ args = {
413
+ 'openid.ns' => OPENID2_NS,
414
+ 'openid.mode' => 'checkid_setup',
415
+ 'openid.identity' => @id_url,
416
+ 'openid.assoc_handle' => @assoc_handle,
417
+ }
418
+ assert_raise(Server::ProtocolError) {
419
+ @decode.call(args)
420
+ }
421
+ end
422
+
423
+ def test_checkidSetupBadReturn
424
+ args = {
425
+ 'openid.mode' => 'checkid_setup',
426
+ 'openid.identity' => @id_url,
427
+ 'openid.assoc_handle' => @assoc_handle,
428
+ 'openid.return_to' => 'not a url',
429
+ }
430
+ begin
431
+ result = @decode.call(args)
432
+ rescue Server::ProtocolError => err
433
+ assert(err.openid_message)
434
+ else
435
+ flunk("Expected ProtocolError, instead returned with #{result}")
436
+ end
437
+ end
438
+
439
+ def test_checkidSetupUntrustedReturn
440
+ args = {
441
+ 'openid.mode' => 'checkid_setup',
442
+ 'openid.identity' => @id_url,
443
+ 'openid.assoc_handle' => @assoc_handle,
444
+ 'openid.return_to' => @rt_url,
445
+ 'openid.trust_root' => 'http://not-the-return-place.unittest/',
446
+ }
447
+ begin
448
+ result = @decode.call(args)
449
+ rescue Server::UntrustedReturnURL => err
450
+ assert(err.openid_message, err.to_s)
451
+ else
452
+ flunk("Expected UntrustedReturnURL, instead returned with #{result}")
453
+ end
454
+ end
455
+
456
+ def test_checkidSetupUntrustedReturn_Constructor
457
+ assert_raise(Server::UntrustedReturnURL) {
458
+ Server::CheckIDRequest.new(@id_url, @rt_url, nil,
459
+ 'http://not-the-return-place.unittest/',
460
+ false, @assoc_handle)
461
+ }
462
+ end
463
+
464
+ def test_checkidSetupMalformedReturnURL_Constructor
465
+ assert_raise(Server::MalformedReturnURL) {
466
+ Server::CheckIDRequest.new(@id_url, 'bogus://return.url', nil,
467
+ 'http://trustroot.com/',
468
+ false, @assoc_handle)
469
+ }
470
+ end
471
+
472
+ def test_checkAuth
473
+ args = {
474
+ 'openid.mode' => 'check_authentication',
475
+ 'openid.assoc_handle' => '{dumb}{handle}',
476
+ 'openid.sig' => 'sigblob',
477
+ 'openid.signed' => 'identity,return_to,response_nonce,mode',
478
+ 'openid.identity' => 'signedval1',
479
+ 'openid.return_to' => 'signedval2',
480
+ 'openid.response_nonce' => 'signedval3',
481
+ 'openid.baz' => 'unsigned',
482
+ }
483
+ r = @decode.call(args)
484
+ assert(r.is_a?(Server::CheckAuthRequest))
485
+ assert_equal(r.mode, 'check_authentication')
486
+ assert_equal(r.sig, 'sigblob')
487
+ end
488
+
489
+ def test_checkAuthMissingSignature
490
+ args = {
491
+ 'openid.mode' => 'check_authentication',
492
+ 'openid.assoc_handle' => '{dumb}{handle}',
493
+ 'openid.signed' => 'foo,bar,mode',
494
+ 'openid.foo' => 'signedval1',
495
+ 'openid.bar' => 'signedval2',
496
+ 'openid.baz' => 'unsigned',
497
+ }
498
+ assert_raise(Server::ProtocolError) {
499
+ @decode.call(args)
500
+ }
501
+ end
502
+
503
+ def test_checkAuthAndInvalidate
504
+ args = {
505
+ 'openid.mode' => 'check_authentication',
506
+ 'openid.assoc_handle' => '{dumb}{handle}',
507
+ 'openid.invalidate_handle' => '[[SMART_handle]]',
508
+ 'openid.sig' => 'sigblob',
509
+ 'openid.signed' => 'identity,return_to,response_nonce,mode',
510
+ 'openid.identity' => 'signedval1',
511
+ 'openid.return_to' => 'signedval2',
512
+ 'openid.response_nonce' => 'signedval3',
513
+ 'openid.baz' => 'unsigned',
514
+ }
515
+ r = @decode.call(args)
516
+ assert(r.is_a?(Server::CheckAuthRequest))
517
+ assert_equal(r.invalidate_handle, '[[SMART_handle]]')
518
+ end
519
+
520
+ def test_associateDH
521
+ args = {
522
+ 'openid.mode' => 'associate',
523
+ 'openid.session_type' => 'DH-SHA1',
524
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
525
+ }
526
+ r = @decode.call(args)
527
+ assert(r.is_a?(Server::AssociateRequest))
528
+ assert_equal(r.mode, "associate")
529
+ assert_equal(r.session.session_type, "DH-SHA1")
530
+ assert_equal(r.assoc_type, "HMAC-SHA1")
531
+ assert(r.session.consumer_pubkey)
532
+ end
533
+
534
+ def test_associateDHMissingKey
535
+ # Trying DH assoc w/o public key
536
+ args = {
537
+ 'openid.mode' => 'associate',
538
+ 'openid.session_type' => 'DH-SHA1',
539
+ }
540
+ # Using DH-SHA1 without supplying dh_consumer_public is an error.
541
+ assert_raise(Server::ProtocolError) {
542
+ @decode.call(args)
543
+ }
544
+ end
545
+
546
+ def test_associateDHpubKeyNotB64
547
+ args = {
548
+ 'openid.mode' => 'associate',
549
+ 'openid.session_type' => 'DH-SHA1',
550
+ 'openid.dh_consumer_public' => "donkeydonkeydonkey",
551
+ }
552
+ assert_raise(Server::ProtocolError) {
553
+ @decode.call(args)
554
+ }
555
+ end
556
+
557
+ def test_associateDHModGen
558
+ # test dh with non-default but valid values for dh_modulus and
559
+ # dh_gen
560
+ args = {
561
+ 'openid.mode' => 'associate',
562
+ 'openid.session_type' => 'DH-SHA1',
563
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
564
+ 'openid.dh_modulus' => CryptUtil.num_to_base64(ALT_MODULUS),
565
+ 'openid.dh_gen' => CryptUtil.num_to_base64(ALT_GEN) ,
566
+ }
567
+ r = @decode.call(args)
568
+ assert(r.is_a?(Server::AssociateRequest))
569
+ assert_equal(r.mode, "associate")
570
+ assert_equal(r.session.session_type, "DH-SHA1")
571
+ assert_equal(r.assoc_type, "HMAC-SHA1")
572
+ assert_equal(r.session.dh.modulus, ALT_MODULUS)
573
+ assert_equal(r.session.dh.generator, ALT_GEN)
574
+ assert(r.session.consumer_pubkey)
575
+ end
576
+
577
+ def test_associateDHCorruptModGen
578
+ # test dh with non-default but valid values for dh_modulus and
579
+ # dh_gen
580
+ args = {
581
+ 'openid.mode' => 'associate',
582
+ 'openid.session_type' => 'DH-SHA1',
583
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
584
+ 'openid.dh_modulus' => 'pizza',
585
+ 'openid.dh_gen' => 'gnocchi',
586
+ }
587
+ assert_raise(Server::ProtocolError) {
588
+ @decode.call(args)
589
+ }
590
+ end
591
+
592
+ def test_associateDHMissingGen
593
+ args = {
594
+ 'openid.mode' => 'associate',
595
+ 'openid.session_type' => 'DH-SHA1',
596
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
597
+ 'openid.dh_modulus' => 'pizza',
598
+ }
599
+ assert_raise(Server::ProtocolError) {
600
+ @decode.call(args)
601
+ }
602
+ end
603
+
604
+ def test_associateDHMissingMod
605
+ args = {
606
+ 'openid.mode' => 'associate',
607
+ 'openid.session_type' => 'DH-SHA1',
608
+ 'openid.dh_consumer_public' => "Rzup9265tw==",
609
+ 'openid.dh_gen' => 'pizza',
610
+ }
611
+ assert_raise(Server::ProtocolError) {
612
+ @decode.call(args)
613
+ }
614
+ end
615
+
616
+ # def test_associateDHInvalidModGen(self):
617
+ # # test dh with properly encoded values that are not a valid
618
+ # # modulus/generator combination.
619
+ # args = {
620
+ # 'openid.mode': 'associate',
621
+ # 'openid.session_type': 'DH-SHA1',
622
+ # 'openid.dh_consumer_public': "Rzup9265tw==",
623
+ # 'openid.dh_modulus': cryptutil.longToBase64(9),
624
+ # 'openid.dh_gen': cryptutil.longToBase64(27) ,
625
+ # }
626
+ # self.failUnlessRaises(server.ProtocolError, self.decode, args)
627
+ # test_associateDHInvalidModGen.todo = "low-priority feature"
628
+
629
+ def test_associateWeirdSession
630
+ args = {
631
+ 'openid.mode' => 'associate',
632
+ 'openid.session_type' => 'FLCL6',
633
+ 'openid.dh_consumer_public' => "YQ==\n",
634
+ }
635
+ assert_raise(Server::ProtocolError) {
636
+ @decode.call(args)
637
+ }
638
+ end
639
+
640
+ def test_associatePlain
641
+ args = {
642
+ 'openid.mode' => 'associate',
643
+ }
644
+ r = @decode.call(args)
645
+ assert(r.is_a?(Server::AssociateRequest))
646
+ assert_equal(r.mode, "associate")
647
+ assert_equal(r.session.session_type, "no-encryption")
648
+ assert_equal(r.assoc_type, "HMAC-SHA1")
649
+ end
650
+
651
+ def test_nomode
652
+ args = {
653
+ 'openid.session_type' => 'DH-SHA1',
654
+ 'openid.dh_consumer_public' => "my public keeey",
655
+ }
656
+ assert_raise(Server::ProtocolError) {
657
+ @decode.call(args)
658
+ }
659
+ end
660
+
661
+ def test_invalidns
662
+ args = {'openid.ns' => 'Vegetables',
663
+ 'openid.mode' => 'associate'}
664
+ begin
665
+ r = @decode.call(args)
666
+ rescue Server::ProtocolError => err
667
+ assert(err.openid_message)
668
+ assert(err.to_s.index('Vegetables'))
669
+ end
670
+ end
671
+ end
672
+
673
+ class BogusEncoder < Server::Encoder
674
+ def encode(response)
675
+ return "BOGUS"
676
+ end
677
+ end
678
+
679
+ class BogusDecoder < Server::Decoder
680
+ def decode(query)
681
+ return "BOGUS"
682
+ end
683
+ end
684
+
685
+ class TestEncode < Test::Unit::TestCase
686
+ def setup
687
+ @encoder = Server::Encoder.new
688
+ @encode = @encoder.method('encode')
689
+ @op_endpoint = 'http://endpoint.unittest/encode'
690
+ @store = Store::Memory.new
691
+ @server = Server::Server.new(@store, @op_endpoint)
692
+ end
693
+
694
+ def test_id_res_OpenID2_GET
695
+ # Check that when an OpenID 2 response does not exceed the OpenID
696
+ # 1 message size, a GET response (i.e., redirect) is issued.
697
+ request = Server::CheckIDRequest.new(
698
+ 'http://bombom.unittest/',
699
+ 'http://burr.unittest/999',
700
+ @server.op_endpoint,
701
+ 'http://burr.unittest/',
702
+ false,
703
+ nil)
704
+ request.message = Message.new(OPENID2_NS)
705
+ response = Server::OpenIDResponse.new(request)
706
+ response.fields = Message.from_openid_args({
707
+ 'ns' => OPENID2_NS,
708
+ 'mode' => 'id_res',
709
+ 'identity' => request.identity,
710
+ 'claimed_id' => request.identity,
711
+ 'return_to' => request.return_to,
712
+ })
713
+
714
+ assert(!response.render_as_form)
715
+ assert(response.which_encoding == Server::ENCODE_URL)
716
+ webresponse = @encode.call(response)
717
+ assert(webresponse.headers.member?('location'))
718
+ end
719
+
720
+ def test_id_res_OpenID2_POST
721
+ # Check that when an OpenID 2 response exceeds the OpenID 1
722
+ # message size, a POST response (i.e., an HTML form) is returned.
723
+ request = Server::CheckIDRequest.new(
724
+ 'http://bombom.unittest/',
725
+ 'http://burr.unittest/999',
726
+ @server.op_endpoint,
727
+ 'http://burr.unittest/',
728
+ false,
729
+ nil)
730
+ request.message = Message.new(OPENID2_NS)
731
+ response = Server::OpenIDResponse.new(request)
732
+ response.fields = Message.from_openid_args({
733
+ 'ns' => OPENID2_NS,
734
+ 'mode' => 'id_res',
735
+ 'identity' => request.identity,
736
+ 'claimed_id' => request.identity,
737
+ 'return_to' => 'x' * OPENID1_URL_LIMIT,
738
+ })
739
+
740
+ assert(response.render_as_form)
741
+ assert(response.encode_to_url.length > OPENID1_URL_LIMIT)
742
+ assert(response.which_encoding == Server::ENCODE_HTML_FORM)
743
+ webresponse = @encode.call(response)
744
+ assert_equal(webresponse.body, response.to_form_markup)
745
+ end
746
+
747
+ def test_to_form_markup
748
+ request = Server::CheckIDRequest.new(
749
+ 'http://bombom.unittest/',
750
+ 'http://burr.unittest/999',
751
+ @server.op_endpoint,
752
+ 'http://burr.unittest/',
753
+ false,
754
+ nil)
755
+ request.message = Message.new(OPENID2_NS)
756
+ response = Server::OpenIDResponse.new(request)
757
+ response.fields = Message.from_openid_args({
758
+ 'ns' => OPENID2_NS,
759
+ 'mode' => 'id_res',
760
+ 'identity' => request.identity,
761
+ 'claimed_id' => request.identity,
762
+ 'return_to' => 'x' * OPENID1_URL_LIMIT,
763
+ })
764
+ form_markup = response.to_form_markup({'foo'=>'bar'})
765
+ assert(/ foo="bar"/ =~ form_markup, form_markup)
766
+ end
767
+
768
+ def test_to_html
769
+ request = Server::CheckIDRequest.new(
770
+ 'http://bombom.unittest/',
771
+ 'http://burr.unittest/999',
772
+ @server.op_endpoint,
773
+ 'http://burr.unittest/',
774
+ false,
775
+ nil)
776
+ request.message = Message.new(OPENID2_NS)
777
+ response = Server::OpenIDResponse.new(request)
778
+ response.fields = Message.from_openid_args({
779
+ 'ns' => OPENID2_NS,
780
+ 'mode' => 'id_res',
781
+ 'identity' => request.identity,
782
+ 'claimed_id' => request.identity,
783
+ 'return_to' => 'x' * OPENID1_URL_LIMIT,
784
+ })
785
+ html = response.to_html
786
+ assert(html)
787
+ end
788
+
789
+ def test_id_res_OpenID1_exceeds_limit
790
+ # Check that when an OpenID 1 response exceeds the OpenID 1
791
+ # message size, a GET response is issued. Technically, this
792
+ # shouldn't be permitted by the library, but this test is in place
793
+ # to preserve the status quo for OpenID 1.
794
+ request = Server::CheckIDRequest.new(
795
+ 'http://bombom.unittest/',
796
+ 'http://burr.unittest/999',
797
+ @server.op_endpoint,
798
+ 'http://burr.unittest/',
799
+ false,
800
+ nil)
801
+ request.message = Message.new(OPENID1_NS)
802
+
803
+ response = Server::OpenIDResponse.new(request)
804
+ response.fields = Message.from_openid_args({
805
+ 'mode' => 'id_res',
806
+ 'identity' => request.identity,
807
+ 'return_to' => 'x' * OPENID1_URL_LIMIT,
808
+ })
809
+
810
+ assert(!response.render_as_form)
811
+ assert(response.encode_to_url.length > OPENID1_URL_LIMIT)
812
+ assert(response.which_encoding == Server::ENCODE_URL)
813
+ webresponse = @encode.call(response)
814
+ assert_equal(webresponse.headers['location'], response.encode_to_url)
815
+ end
816
+
817
+ def test_id_res
818
+ request = Server::CheckIDRequest.new(
819
+ 'http://bombom.unittest/',
820
+ 'http://burr.unittest/999',
821
+ @server.op_endpoint,
822
+ 'http://burr.unittest/',
823
+ false, nil)
824
+ request.message = Message.new(OPENID1_NS)
825
+ response = Server::OpenIDResponse.new(request)
826
+ response.fields = Message.from_openid_args({
827
+ 'mode' => 'id_res',
828
+ 'identity' => request.identity,
829
+ 'return_to' => request.return_to,
830
+ })
831
+ webresponse = @encode.call(response)
832
+ assert_equal(webresponse.code, Server::HTTP_REDIRECT)
833
+ assert(webresponse.headers.member?('location'))
834
+
835
+ location = webresponse.headers['location']
836
+ assert(location.starts_with?(request.return_to),
837
+ sprintf("%s does not start with %s",
838
+ location, request.return_to))
839
+ # argh.
840
+ q2 = Util.parse_query(URI::parse(location).query)
841
+ expected = response.fields.to_post_args
842
+ assert_equal(q2, expected)
843
+ end
844
+
845
+ def test_cancel
846
+ request = Server::CheckIDRequest.new(
847
+ 'http://bombom.unittest/',
848
+ 'http://burr.unittest/999',
849
+ @server.op_endpoint,
850
+ 'http://burr.unittest/',
851
+ false, nil)
852
+ request.message = Message.new(OPENID2_NS)
853
+ response = Server::OpenIDResponse.new(request)
854
+ response.fields = Message.from_openid_args({
855
+ 'mode' => 'cancel',
856
+ })
857
+ webresponse = @encode.call(response)
858
+ assert_equal(webresponse.code, Server::HTTP_REDIRECT)
859
+ assert(webresponse.headers.member?('location'))
860
+ end
861
+
862
+ def test_cancel_to_form
863
+ request = Server::CheckIDRequest.new(
864
+ 'http://bombom.unittest/',
865
+ 'http://burr.unittest/999',
866
+ @server.op_endpoint,
867
+ 'http://burr.unittest/',
868
+ false, nil)
869
+ request.message = Message.new(OPENID2_NS)
870
+ response = Server::OpenIDResponse.new(request)
871
+ response.fields = Message.from_openid_args({
872
+ 'mode' => 'cancel',
873
+ })
874
+ form = response.to_form_markup
875
+ assert(form.index(request.return_to))
876
+ end
877
+
878
+ def test_assocReply
879
+ msg = Message.new(OPENID2_NS)
880
+ msg.set_arg(OPENID2_NS, 'session_type', 'no-encryption')
881
+ request = Server::AssociateRequest.from_message(msg)
882
+ response = Server::OpenIDResponse.new(request)
883
+ response.fields = Message.from_post_args(
884
+ {'openid.assoc_handle' => "every-zig"})
885
+ webresponse = @encode.call(response)
886
+ body = "assoc_handle:every-zig\n"
887
+
888
+ assert_equal(webresponse.code, Server::HTTP_OK)
889
+ assert_equal(webresponse.headers, {})
890
+ assert_equal(webresponse.body, body)
891
+ end
892
+
893
+ def test_checkauthReply
894
+ request = Server::CheckAuthRequest.new('a_sock_monkey',
895
+ 'siggggg',
896
+ [])
897
+ request.message = Message.new(OPENID2_NS)
898
+ response = Server::OpenIDResponse.new(request)
899
+ response.fields = Message.from_openid_args({
900
+ 'is_valid' => 'true',
901
+ 'invalidate_handle' => 'xXxX:xXXx'
902
+ })
903
+ body = "invalidate_handle:xXxX:xXXx\nis_valid:true\n"
904
+
905
+ webresponse = @encode.call(response)
906
+ assert_equal(webresponse.code, Server::HTTP_OK)
907
+ assert_equal(webresponse.headers, {})
908
+ assert_equal(webresponse.body, body)
909
+ end
910
+
911
+ def test_unencodableError
912
+ args = Message.from_post_args({
913
+ 'openid.identity' => 'http://limu.unittest/',
914
+ })
915
+ e = Server::ProtocolError.new(args, "wet paint")
916
+ assert_raise(Server::EncodingError) {
917
+ @encode.call(e)
918
+ }
919
+ end
920
+
921
+ def test_encodableError
922
+ args = Message.from_post_args({
923
+ 'openid.mode' => 'associate',
924
+ 'openid.identity' => 'http://limu.unittest/',
925
+ })
926
+ body="error:snoot\nmode:error\n"
927
+ webresponse = @encode.call(Server::ProtocolError.new(args, "snoot"))
928
+ assert_equal(webresponse.code, Server::HTTP_ERROR)
929
+ assert_equal(webresponse.headers, {})
930
+ assert_equal(webresponse.body, body)
931
+ end
932
+ end
933
+
934
+ class TestSigningEncode < Test::Unit::TestCase
935
+ def setup
936
+ @_dumb_key = Server::Signatory._dumb_key
937
+ @_normal_key = Server::Signatory._normal_key
938
+ @store = Store::Memory.new()
939
+ @server = Server::Server.new(@store, "http://signing.unittest/enc")
940
+ @request = Server::CheckIDRequest.new(
941
+ 'http://bombom.unittest/',
942
+ 'http://burr.unittest/999',
943
+ @server.op_endpoint,
944
+ 'http://burr.unittest/',
945
+ false, nil)
946
+ @request.message = Message.new(OPENID2_NS)
947
+
948
+ @response = Server::OpenIDResponse.new(@request)
949
+ @response.fields = Message.from_openid_args({
950
+ 'mode' => 'id_res',
951
+ 'identity' => @request.identity,
952
+ 'return_to' => @request.return_to,
953
+ })
954
+ @signatory = Server::Signatory.new(@store)
955
+ @encoder = Server::SigningEncoder.new(@signatory)
956
+ @encode = @encoder.method('encode')
957
+ end
958
+
959
+ def test_idres
960
+ assoc_handle = '{bicycle}{shed}'
961
+ @store.store_association(
962
+ @_normal_key,
963
+ Association.from_expires_in(60, assoc_handle,
964
+ 'sekrit', 'HMAC-SHA1'))
965
+ @request.assoc_handle = assoc_handle
966
+ webresponse = @encode.call(@response)
967
+ assert_equal(webresponse.code, Server::HTTP_REDIRECT)
968
+ assert(webresponse.headers.member?('location'))
969
+
970
+ location = webresponse.headers['location']
971
+ query = Util.parse_query(URI::parse(location).query)
972
+ assert(query.member?('openid.sig'))
973
+ assert(query.member?('openid.assoc_handle'))
974
+ assert(query.member?('openid.signed'))
975
+ end
976
+
977
+ def test_idresDumb
978
+ webresponse = @encode.call(@response)
979
+ assert_equal(webresponse.code, Server::HTTP_REDIRECT)
980
+ assert(webresponse.headers.has_key?('location'))
981
+
982
+ location = webresponse.headers['location']
983
+ query = Util.parse_query(URI::parse(location).query)
984
+ assert(query.member?('openid.sig'))
985
+ assert(query.member?('openid.assoc_handle'))
986
+ assert(query.member?('openid.signed'))
987
+ end
988
+
989
+ def test_forgotStore
990
+ @encoder.signatory = nil
991
+ assert_raise(ArgumentError) {
992
+ @encode.call(@response)
993
+ }
994
+ end
995
+
996
+ def test_cancel
997
+ request = Server::CheckIDRequest.new(
998
+ 'http://bombom.unittest/',
999
+ 'http://burr.unittest/999',
1000
+ @server.op_endpoint,
1001
+ 'http://burr.unittest/',
1002
+ false, nil)
1003
+ request.message = Message.new(OPENID2_NS)
1004
+ response = Server::OpenIDResponse.new(request)
1005
+ response.fields.set_arg(OPENID_NS, 'mode', 'cancel')
1006
+ webresponse = @encode.call(response)
1007
+ assert_equal(webresponse.code, Server::HTTP_REDIRECT)
1008
+ assert(webresponse.headers.has_key?('location'))
1009
+ location = webresponse.headers['location']
1010
+ query = Util.parse_query(URI::parse(location).query)
1011
+ assert(!query.has_key?('openid.sig'), response.fields.to_post_args())
1012
+ end
1013
+
1014
+ def test_assocReply
1015
+ msg = Message.new(OPENID2_NS)
1016
+ msg.set_arg(OPENID2_NS, 'session_type', 'no-encryption')
1017
+ request = Server::AssociateRequest.from_message(msg)
1018
+ response = Server::OpenIDResponse.new(request)
1019
+ response.fields = Message.from_openid_args({'assoc_handle' => "every-zig"})
1020
+ webresponse = @encode.call(response)
1021
+ body = "assoc_handle:every-zig\n"
1022
+ assert_equal(webresponse.code, Server::HTTP_OK)
1023
+ assert_equal(webresponse.headers, {})
1024
+ assert_equal(webresponse.body, body)
1025
+ end
1026
+
1027
+ def test_alreadySigned
1028
+ @response.fields.set_arg(OPENID_NS, 'sig', 'priorSig==')
1029
+ assert_raise(Server::AlreadySigned) {
1030
+ @encode.call(@response)
1031
+ }
1032
+ end
1033
+ end
1034
+
1035
+ class TestCheckID < Test::Unit::TestCase
1036
+ def setup
1037
+ @op_endpoint = 'http://endpoint.unittest/'
1038
+ @store = Store::Memory.new()
1039
+ @server = Server::Server.new(@store, @op_endpoint)
1040
+ @request = Server::CheckIDRequest.new(
1041
+ 'http://bambam.unittest/',
1042
+ 'http://bar.unittest/999',
1043
+ @server.op_endpoint,
1044
+ 'http://bar.unittest/',
1045
+ false)
1046
+ @request.message = Message.new(OPENID2_NS)
1047
+ end
1048
+
1049
+ def test_trustRootInvalid
1050
+ @request.trust_root = "http://foo.unittest/17"
1051
+ @request.return_to = "http://foo.unittest/39"
1052
+ assert(!@request.trust_root_valid())
1053
+ end
1054
+
1055
+ def test_trustRootInvalid_modified
1056
+ @request.trust_root = "does://not.parse/"
1057
+ @request.message = :sentinel
1058
+ begin
1059
+ result = @request.trust_root_valid
1060
+ rescue Server::MalformedTrustRoot => why
1061
+ assert_equal(:sentinel, why.openid_message)
1062
+ else
1063
+ flunk("Expected MalformedTrustRoot, got #{result.inspect}")
1064
+ end
1065
+ end
1066
+
1067
+ def test_trustRootvalid_absent_trust_root
1068
+ @request.trust_root = nil
1069
+ assert(@request.trust_root_valid())
1070
+ end
1071
+
1072
+ def test_trustRootValid
1073
+ @request.trust_root = "http://foo.unittest/"
1074
+ @request.return_to = "http://foo.unittest/39"
1075
+ assert(@request.trust_root_valid())
1076
+ end
1077
+
1078
+ def test_trustRootValidNoReturnTo
1079
+ request = Server::CheckIDRequest.new(
1080
+ 'http://bambam.unittest/',
1081
+ nil,
1082
+ @server.op_endpoint,
1083
+ 'http://bar.unittest/',
1084
+ false)
1085
+
1086
+ assert(request.trust_root_valid())
1087
+ end
1088
+
1089
+ def test_returnToVerified_callsVerify
1090
+ # Make sure that verifyReturnTo is calling the trustroot
1091
+ # function verifyReturnTo
1092
+ # Ensure that exceptions are passed through
1093
+ sentinel = Exception.new()
1094
+
1095
+ __req = @request
1096
+ tc = self
1097
+
1098
+ vrfyExc = Proc.new { |trust_root, return_to|
1099
+ tc.assert_equal(__req.trust_root, trust_root)
1100
+ tc.assert_equal(__req.return_to, return_to)
1101
+ raise sentinel
1102
+ }
1103
+
1104
+ TrustRoot.extend(OverrideMethodMixin)
1105
+
1106
+ TrustRoot.with_method_overridden(:verify_return_to, vrfyExc) do
1107
+ begin
1108
+ @request.return_to_verified()
1109
+ flunk("Expected sentinel to be raised, got success")
1110
+ rescue Exception => e
1111
+ assert(e.equal?(sentinel), [e, sentinel].inspect)
1112
+ end
1113
+ end
1114
+
1115
+ # Ensure that True and False are passed through unchanged
1116
+ constVerify = Proc.new { |val|
1117
+ verify = Proc.new { |trust_root, return_to|
1118
+ tc.assert_equal(__req.trust_root, trust_root)
1119
+ tc.assert_equal(__req.request.return_to, return_to)
1120
+ return val
1121
+ }
1122
+
1123
+ return verify
1124
+ }
1125
+
1126
+ [true, false].each { |val|
1127
+ verifier = constVerify.call(val)
1128
+
1129
+ TrustRoot.with_method_overridden(:verify_return_to, verifier) do
1130
+ assert_equal(val, @request.return_to_verified())
1131
+ end
1132
+ }
1133
+ end
1134
+
1135
+ def _expectAnswer(answer, identity=nil, claimed_id=nil)
1136
+ expected_list = [
1137
+ ['mode', 'id_res'],
1138
+ ['return_to', @request.return_to],
1139
+ ['op_endpoint', @op_endpoint],
1140
+ ]
1141
+ if identity
1142
+ expected_list << ['identity', identity]
1143
+ if claimed_id
1144
+ expected_list << ['claimed_id', claimed_id]
1145
+ else
1146
+ expected_list << ['claimed_id', identity]
1147
+ end
1148
+ end
1149
+
1150
+ expected_list.each { |k, expected|
1151
+ actual = answer.fields.get_arg(OPENID_NS, k)
1152
+ assert_equal(expected, actual,
1153
+ sprintf("%s: expected %s, got %s",
1154
+ k, expected, actual))
1155
+ }
1156
+
1157
+ assert(answer.fields.has_key?(OPENID_NS, 'response_nonce'))
1158
+ assert(answer.fields.get_openid_namespace() == OPENID2_NS)
1159
+
1160
+ # One for nonce, one for ns
1161
+ assert_equal(answer.fields.to_post_args.length,
1162
+ expected_list.length + 2,
1163
+ answer.fields.to_post_args.inspect)
1164
+ end
1165
+
1166
+ def test_answerAllow
1167
+ # Check the fields specified by "Positive Assertions"
1168
+ #
1169
+ # including mode=id_res, identity, claimed_id, op_endpoint,
1170
+ # return_to
1171
+ answer = @request.answer(true)
1172
+ assert_equal(answer.request, @request)
1173
+ _expectAnswer(answer, @request.identity)
1174
+ end
1175
+
1176
+ def test_answerAllowDelegatedIdentity
1177
+ @request.claimed_id = 'http://delegating.unittest/'
1178
+ answer = @request.answer(true)
1179
+ _expectAnswer(answer, @request.identity,
1180
+ @request.claimed_id)
1181
+ end
1182
+
1183
+ def test_answerAllowWithoutIdentityReally
1184
+ @request.identity = nil
1185
+ answer = @request.answer(true)
1186
+ assert_equal(answer.request, @request)
1187
+ _expectAnswer(answer)
1188
+ end
1189
+
1190
+ def test_answerAllowAnonymousFail
1191
+ @request.identity = nil
1192
+ # XXX - Check on this, I think this behavior is legal in OpenID
1193
+ # 2.0?
1194
+ assert_raise(ArgumentError) {
1195
+ @request.answer(true, nil, "=V")
1196
+ }
1197
+ end
1198
+
1199
+ def test_answerAllowWithIdentity
1200
+ @request.identity = IDENTIFIER_SELECT
1201
+ selected_id = 'http://anon.unittest/9861'
1202
+ answer = @request.answer(true, nil, selected_id)
1203
+ _expectAnswer(answer, selected_id)
1204
+ end
1205
+
1206
+ def test_answerAllowWithNoIdentity
1207
+ @request.identity = IDENTIFIER_SELECT
1208
+ selected_id = 'http://anon.unittest/9861'
1209
+ assert_raise(ArgumentError) {
1210
+ answer = @request.answer(true, nil, nil)
1211
+ }
1212
+ end
1213
+
1214
+ def test_immediate_openid1_no_identity
1215
+ @request.message = Message.new(OPENID1_NS)
1216
+ @request.immediate = true
1217
+ @request.mode = 'checkid_immediate'
1218
+ resp = @request.answer(false)
1219
+ assert(resp.fields.get_arg(OPENID_NS, 'mode') == 'id_res')
1220
+ end
1221
+
1222
+ def test_checkid_setup_openid1_no_identity
1223
+ @request.message = Message.new(OPENID1_NS)
1224
+ @request.immediate = false
1225
+ @request.mode = 'checkid_setup'
1226
+ resp = @request.answer(false)
1227
+ assert(resp.fields.get_arg(OPENID_NS, 'mode') == 'cancel')
1228
+ end
1229
+
1230
+ def test_immediate_openid1_no_server_url
1231
+ @request.message = Message.new(OPENID1_NS)
1232
+ @request.immediate = true
1233
+ @request.mode = 'checkid_immediate'
1234
+ @request.op_endpoint = nil
1235
+
1236
+ assert_raise(ArgumentError) {
1237
+ resp = @request.answer(false)
1238
+ }
1239
+ end
1240
+
1241
+ def test_immediate_encode_to_url
1242
+ @request.message = Message.new(OPENID1_NS)
1243
+ @request.immediate = true
1244
+ @request.mode = 'checkid_immediate'
1245
+ @request.trust_root = "BOGUS"
1246
+ @request.assoc_handle = "ASSOC"
1247
+
1248
+ server_url = "http://server.com/server"
1249
+
1250
+ url = @request.encode_to_url(server_url)
1251
+ assert(url.starts_with?(server_url))
1252
+
1253
+ unused, query = url.split("?", 2)
1254
+ args = Util.parse_query(query)
1255
+
1256
+ m = Message.from_post_args(args)
1257
+ assert(m.get_arg(OPENID_NS, 'trust_root') == "BOGUS")
1258
+ assert(m.get_arg(OPENID_NS, 'assoc_handle') == "ASSOC")
1259
+ assert(m.get_arg(OPENID_NS, 'mode'), "checkid_immediate")
1260
+ assert(m.get_arg(OPENID_NS, 'identity') == @request.identity)
1261
+ assert(m.get_arg(OPENID_NS, 'claimed_id') == @request.claimed_id)
1262
+ assert(m.get_arg(OPENID_NS, 'return_to') == @request.return_to)
1263
+ end
1264
+
1265
+ def test_answerAllowWithDelegatedIdentityOpenID2
1266
+ # Answer an IDENTIFIER_SELECT case with a delegated identifier.
1267
+
1268
+ # claimed_id delegates to selected_id here.
1269
+ @request.identity = IDENTIFIER_SELECT
1270
+ selected_id = 'http://anon.unittest/9861'
1271
+ claimed_id = 'http://monkeyhat.unittest/'
1272
+ answer = @request.answer(true, nil, selected_id, claimed_id)
1273
+ _expectAnswer(answer, selected_id, claimed_id)
1274
+ end
1275
+
1276
+ def test_answerAllowWithDelegatedIdentityOpenID1
1277
+ # claimed_id parameter doesn't exist in OpenID 1.
1278
+ @request.message = Message.new(OPENID1_NS)
1279
+ # claimed_id delegates to selected_id here.
1280
+ @request.identity = IDENTIFIER_SELECT
1281
+ selected_id = 'http://anon.unittest/9861'
1282
+ claimed_id = 'http://monkeyhat.unittest/'
1283
+ assert_raise(Server::VersionError) {
1284
+ @request.answer(true, nil, selected_id, claimed_id)
1285
+ }
1286
+ end
1287
+
1288
+ def test_answerAllowWithAnotherIdentity
1289
+ # XXX - Check on this, I think this behavior is legal in OpenID
1290
+ # 2.0?
1291
+ assert_raise(ArgumentError){
1292
+ @request.answer(true, nil, "http://pebbles.unittest/")
1293
+ }
1294
+ end
1295
+
1296
+ def test_answerAllowNoIdentityOpenID1
1297
+ @request.message = Message.new(OPENID1_NS)
1298
+ @request.identity = nil
1299
+ assert_raise(ArgumentError) {
1300
+ @request.answer(true, nil, nil)
1301
+ }
1302
+ end
1303
+
1304
+ def test_answerAllowForgotEndpoint
1305
+ @request.op_endpoint = nil
1306
+ assert_raise(RuntimeError) {
1307
+ @request.answer(true)
1308
+ }
1309
+ end
1310
+
1311
+ def test_checkIDWithNoIdentityOpenID1
1312
+ msg = Message.new(OPENID1_NS)
1313
+ msg.set_arg(OPENID_NS, 'return_to', 'bogus')
1314
+ msg.set_arg(OPENID_NS, 'trust_root', 'bogus')
1315
+ msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
1316
+ msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
1317
+
1318
+ assert_raise(Server::ProtocolError) {
1319
+ Server::CheckIDRequest.from_message(msg, @server)
1320
+ }
1321
+ end
1322
+
1323
+ def test_fromMessageClaimedIDWithoutIdentityOpenID2
1324
+ msg = Message.new(OPENID2_NS)
1325
+ msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
1326
+ msg.set_arg(OPENID_NS, 'return_to', 'http://invalid:8000/rt')
1327
+ msg.set_arg(OPENID_NS, 'claimed_id', 'https://example.myopenid.com')
1328
+
1329
+ assert_raise(Server::ProtocolError) {
1330
+ Server::CheckIDRequest.from_message(msg, @server)
1331
+ }
1332
+ end
1333
+
1334
+ def test_fromMessageIdentityWithoutClaimedIDOpenID2
1335
+ msg = Message.new(OPENID2_NS)
1336
+ msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
1337
+ msg.set_arg(OPENID_NS, 'return_to', 'http://invalid:8000/rt')
1338
+ msg.set_arg(OPENID_NS, 'identity', 'https://example.myopenid.com')
1339
+
1340
+ assert_raise(Server::ProtocolError) {
1341
+ Server::CheckIDRequest.from_message(msg, @server)
1342
+ }
1343
+ end
1344
+
1345
+ def test_fromMessageWithEmptyTrustRoot
1346
+ return_to = 'http://some.url/foo?bar=baz'
1347
+ msg = Message.from_post_args({
1348
+ 'openid.assoc_handle' => '{blah}{blah}{OZivdQ==}',
1349
+ 'openid.claimed_id' => 'http://delegated.invalid/',
1350
+ 'openid.identity' => 'http://op-local.example.com/',
1351
+ 'openid.mode' => 'checkid_setup',
1352
+ 'openid.ns' => 'http://openid.net/signon/1.0',
1353
+ 'openid.return_to' => return_to,
1354
+ 'openid.trust_root' => ''
1355
+ });
1356
+ result = Server::CheckIDRequest.from_message(msg, @server)
1357
+ assert_equal(return_to, result.trust_root)
1358
+ end
1359
+
1360
+ def test_trustRootOpenID1
1361
+ # Ignore openid.realm in OpenID 1
1362
+ msg = Message.new(OPENID1_NS)
1363
+ msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
1364
+ msg.set_arg(OPENID_NS, 'trust_root', 'http://trustroot.com/')
1365
+ msg.set_arg(OPENID_NS, 'realm', 'http://fake_trust_root/')
1366
+ msg.set_arg(OPENID_NS, 'return_to', 'http://trustroot.com/foo')
1367
+ msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
1368
+ msg.set_arg(OPENID_NS, 'identity', 'george')
1369
+
1370
+ result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
1371
+
1372
+ assert(result.trust_root == 'http://trustroot.com/')
1373
+ end
1374
+
1375
+ def test_trustRootOpenID2
1376
+ # Ignore openid.trust_root in OpenID 2
1377
+ msg = Message.new(OPENID2_NS)
1378
+ msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
1379
+ msg.set_arg(OPENID_NS, 'realm', 'http://trustroot.com/')
1380
+ msg.set_arg(OPENID_NS, 'trust_root', 'http://fake_trust_root/')
1381
+ msg.set_arg(OPENID_NS, 'return_to', 'http://trustroot.com/foo')
1382
+ msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
1383
+ msg.set_arg(OPENID_NS, 'identity', 'george')
1384
+ msg.set_arg(OPENID_NS, 'claimed_id', 'george')
1385
+
1386
+ result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
1387
+
1388
+ assert(result.trust_root == 'http://trustroot.com/')
1389
+ end
1390
+
1391
+ def test_answerAllowNoTrustRoot
1392
+ @request.trust_root = nil
1393
+ answer = @request.answer(true)
1394
+ assert_equal(answer.request, @request)
1395
+ _expectAnswer(answer, @request.identity)
1396
+ end
1397
+
1398
+ def test_answerImmediateDenyOpenID2
1399
+ # Look for mode=setup_needed in checkid_immediate negative
1400
+ # response in OpenID 2 case.
1401
+ #
1402
+ # See specification Responding to Authentication Requests /
1403
+ # Negative Assertions / In Response to Immediate Requests.
1404
+ @request.mode = 'checkid_immediate'
1405
+ @request.immediate = true
1406
+
1407
+ server_url = "http://setup-url.unittest/"
1408
+ # crappiting setup_url, you dirty my interface with your presence!
1409
+ answer = @request.answer(false, server_url)
1410
+ assert_equal(answer.request, @request)
1411
+ assert_equal(answer.fields.to_post_args.length, 3, answer.fields)
1412
+ assert_equal(answer.fields.get_openid_namespace, OPENID2_NS)
1413
+ assert_equal(answer.fields.get_arg(OPENID_NS, 'mode'),
1414
+ 'setup_needed')
1415
+ # user_setup_url no longer required.
1416
+ end
1417
+
1418
+ def test_answerImmediateDenyOpenID1
1419
+ # Look for user_setup_url in checkid_immediate negative response
1420
+ # in OpenID 1 case.
1421
+ @request.message = Message.new(OPENID1_NS)
1422
+ @request.mode = 'checkid_immediate'
1423
+ @request.immediate = true
1424
+ @request.claimed_id = 'http://claimed-id.test/'
1425
+ server_url = "http://setup-url.unittest/"
1426
+ # crappiting setup_url, you dirty my interface with your presence!
1427
+ answer = @request.answer(false, server_url)
1428
+ assert_equal(answer.request, @request)
1429
+ assert_equal(2, answer.fields.to_post_args.length, answer.fields)
1430
+ assert_equal(OPENID1_NS, answer.fields.get_openid_namespace)
1431
+ assert_equal('id_res', answer.fields.get_arg(OPENID_NS, 'mode'))
1432
+
1433
+ usu = answer.fields.get_arg(OPENID_NS, 'user_setup_url', '')
1434
+ assert(usu.starts_with?(server_url))
1435
+ expected_substr = 'openid.claimed_id=http%3A%2F%2Fclaimed-id.test%2F'
1436
+ assert(!usu.index(expected_substr).nil?, usu)
1437
+ end
1438
+
1439
+ def test_answerSetupDeny
1440
+ answer = @request.answer(false)
1441
+ assert_equal(answer.fields.get_args(OPENID_NS), {
1442
+ 'mode' => 'cancel',
1443
+ })
1444
+ end
1445
+
1446
+ def test_encodeToURL
1447
+ server_url = 'http://openid-server.unittest/'
1448
+ result = @request.encode_to_url(server_url)
1449
+
1450
+ # How to check? How about a round-trip test.
1451
+ base, result_args = result.split('?', 2)
1452
+ result_args = Util.parse_query(result_args)
1453
+ message = Message.from_post_args(result_args)
1454
+ rebuilt_request = Server::CheckIDRequest.from_message(message,
1455
+ @server.op_endpoint)
1456
+
1457
+ @request.message = message
1458
+
1459
+ @request.instance_variables.each { |var|
1460
+ assert_equal(@request.instance_variable_get(var),
1461
+ rebuilt_request.instance_variable_get(var), var)
1462
+ }
1463
+ end
1464
+
1465
+ def test_getCancelURL
1466
+ url = @request.cancel_url
1467
+ rt, query_string = url.split('?', -1)
1468
+ assert_equal(@request.return_to, rt)
1469
+ query = Util.parse_query(query_string)
1470
+ assert_equal(query, {'openid.mode' => 'cancel',
1471
+ 'openid.ns' => OPENID2_NS})
1472
+ end
1473
+
1474
+ def test_getCancelURLimmed
1475
+ @request.mode = 'checkid_immediate'
1476
+ @request.immediate = true
1477
+ assert_raise(ArgumentError) {
1478
+ @request.cancel_url
1479
+ }
1480
+ end
1481
+
1482
+ def test_fromMessageWithoutTrustRoot
1483
+ msg = Message.new(OPENID2_NS)
1484
+ msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
1485
+ msg.set_arg(OPENID_NS, 'return_to', 'http://real.trust.root/foo')
1486
+ msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
1487
+ msg.set_arg(OPENID_NS, 'identity', 'george')
1488
+ msg.set_arg(OPENID_NS, 'claimed_id', 'george')
1489
+
1490
+ result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
1491
+
1492
+ assert_equal(result.trust_root, 'http://real.trust.root/foo')
1493
+ end
1494
+
1495
+ def test_fromMessageWithoutTrustRootOrReturnTo
1496
+ msg = Message.new(OPENID2_NS)
1497
+ msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
1498
+ msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
1499
+ msg.set_arg(OPENID_NS, 'identity', 'george')
1500
+ msg.set_arg(OPENID_NS, 'claimed_id', 'george')
1501
+
1502
+ assert_raises(Server::ProtocolError) {
1503
+ Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
1504
+ }
1505
+ end
1506
+ end
1507
+
1508
+ class TestCheckIDExtension < Test::Unit::TestCase
1509
+
1510
+ def setup
1511
+ @op_endpoint = 'http://endpoint.unittest/ext'
1512
+ @store = Store::Memory.new()
1513
+ @server = Server::Server.new(@store, @op_endpoint)
1514
+ @request = Server::CheckIDRequest.new(
1515
+ 'http://bambam.unittest/',
1516
+ 'http://bar.unittest/999',
1517
+ @server.op_endpoint,
1518
+ 'http://bar.unittest/',
1519
+ false)
1520
+ @request.message = Message.new(OPENID2_NS)
1521
+ @response = Server::OpenIDResponse.new(@request)
1522
+ @response.fields.set_arg(OPENID_NS, 'mode', 'id_res')
1523
+ @response.fields.set_arg(OPENID_NS, 'blue', 'star')
1524
+ end
1525
+
1526
+ def test_addField
1527
+ namespace = 'something:'
1528
+ @response.fields.set_arg(namespace, 'bright', 'potato')
1529
+ assert_equal(@response.fields.get_args(OPENID_NS),
1530
+ {'blue' => 'star',
1531
+ 'mode' => 'id_res',
1532
+ })
1533
+
1534
+ assert_equal(@response.fields.get_args(namespace),
1535
+ {'bright' => 'potato'})
1536
+ end
1537
+
1538
+ def test_addFields
1539
+ namespace = 'mi5:'
1540
+ args = {'tangy' => 'suspenders',
1541
+ 'bravo' => 'inclusion'}
1542
+ @response.fields.update_args(namespace, args)
1543
+ assert_equal(@response.fields.get_args(OPENID_NS),
1544
+ {'blue' => 'star',
1545
+ 'mode' => 'id_res',
1546
+ })
1547
+ assert_equal(@response.fields.get_args(namespace), args)
1548
+ end
1549
+ end
1550
+
1551
+ class MockSignatory
1552
+ attr_accessor :isValid, :assocs
1553
+
1554
+ def initialize(assoc)
1555
+ @isValid = true
1556
+ @assocs = [assoc]
1557
+ end
1558
+
1559
+ def verify(assoc_handle, message)
1560
+ Util.assert(message.has_key?(OPENID_NS, "sig"))
1561
+ if self.assocs.member?([true, assoc_handle])
1562
+ return @isValid
1563
+ else
1564
+ return false
1565
+ end
1566
+ end
1567
+
1568
+ def get_association(assoc_handle, dumb)
1569
+ if self.assocs.member?([dumb, assoc_handle])
1570
+ # This isn't a valid implementation for many uses of this
1571
+ # function, mind you.
1572
+ return true
1573
+ else
1574
+ return nil
1575
+ end
1576
+ end
1577
+
1578
+ def invalidate(assoc_handle, dumb)
1579
+ if self.assocs.member?([dumb, assoc_handle])
1580
+ @assocs.delete([dumb, assoc_handle])
1581
+ end
1582
+ end
1583
+ end
1584
+
1585
+ class TestCheckAuth < Test::Unit::TestCase
1586
+ def setup
1587
+ @assoc_handle = 'mooooooooo'
1588
+ @message = Message.from_post_args({
1589
+ 'openid.sig' => 'signarture',
1590
+ 'one' => 'alpha',
1591
+ 'two' => 'beta',
1592
+ })
1593
+ @request = Server::CheckAuthRequest.new(
1594
+ @assoc_handle, @message)
1595
+ @request.message = Message.new(OPENID2_NS)
1596
+
1597
+ @signatory = MockSignatory.new([true, @assoc_handle])
1598
+ end
1599
+
1600
+ def test_to_s
1601
+ @request.to_s
1602
+ end
1603
+
1604
+ def test_valid
1605
+ r = @request.answer(@signatory)
1606
+ assert_equal({'is_valid' => 'true'},
1607
+ r.fields.get_args(OPENID_NS))
1608
+ assert_equal(r.request, @request)
1609
+ end
1610
+
1611
+ def test_invalid
1612
+ @signatory.isValid = false
1613
+ r = @request.answer(@signatory)
1614
+ assert_equal({'is_valid' => 'false'},
1615
+ r.fields.get_args(OPENID_NS))
1616
+
1617
+ end
1618
+
1619
+ def test_replay
1620
+ # Don't validate the same response twice.
1621
+ #
1622
+ # From "Checking the Nonce"::
1623
+ #
1624
+ # When using "check_authentication", the OP MUST ensure that an
1625
+ # assertion has not yet been accepted with the same value for
1626
+ # "openid.response_nonce".
1627
+ #
1628
+ # In this implementation, the assoc_handle is only valid once.
1629
+ # And nonces are a signed component of the message, so they can't
1630
+ # be used with another handle without breaking the sig.
1631
+ r = @request.answer(@signatory)
1632
+ r = @request.answer(@signatory)
1633
+ assert_equal({'is_valid' => 'false'},
1634
+ r.fields.get_args(OPENID_NS))
1635
+ end
1636
+
1637
+ def test_invalidatehandle
1638
+ @request.invalidate_handle = "bogusHandle"
1639
+ r = @request.answer(@signatory)
1640
+ assert_equal(r.fields.get_args(OPENID_NS),
1641
+ {'is_valid' => 'true',
1642
+ 'invalidate_handle' => "bogusHandle"})
1643
+ assert_equal(r.request, @request)
1644
+ end
1645
+
1646
+ def test_invalidatehandleNo
1647
+ assoc_handle = 'goodhandle'
1648
+ @signatory.assocs << [false, 'goodhandle']
1649
+ @request.invalidate_handle = assoc_handle
1650
+ r = @request.answer(@signatory)
1651
+ assert_equal(r.fields.get_args(OPENID_NS), {'is_valid' => 'true'})
1652
+ end
1653
+ end
1654
+
1655
+ class TestAssociate < Test::Unit::TestCase
1656
+ # TODO: test DH with non-default values for modulus and gen.
1657
+ # (important to do because we actually had it broken for a while.)
1658
+
1659
+ def setup
1660
+ @request = Server::AssociateRequest.from_message(Message.from_post_args({}))
1661
+ @store = Store::Memory.new()
1662
+ @signatory = Server::Signatory.new(@store)
1663
+ end
1664
+
1665
+ def test_dhSHA1
1666
+ @assoc = @signatory.create_association(false, 'HMAC-SHA1')
1667
+ consumer_dh = DiffieHellman.from_defaults()
1668
+ cpub = consumer_dh.public
1669
+ server_dh = DiffieHellman.from_defaults()
1670
+ session = Server::DiffieHellmanSHA1ServerSession.new(server_dh, cpub)
1671
+ @request = Server::AssociateRequest.new(session, 'HMAC-SHA1')
1672
+ @request.message = Message.new(OPENID2_NS)
1673
+ response = @request.answer(@assoc)
1674
+ rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
1675
+ assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
1676
+ assert_equal(rfg.call("assoc_handle"), @assoc.handle)
1677
+ assert(!rfg.call("mac_key"))
1678
+ assert_equal(rfg.call("session_type"), "DH-SHA1")
1679
+ assert(rfg.call("enc_mac_key"))
1680
+ assert(rfg.call("dh_server_public"))
1681
+
1682
+ enc_key = Util.from_base64(rfg.call("enc_mac_key"))
1683
+ spub = CryptUtil.base64_to_num(rfg.call("dh_server_public"))
1684
+ secret = consumer_dh.xor_secret(CryptUtil.method('sha1'),
1685
+ spub, enc_key)
1686
+ assert_equal(secret, @assoc.secret)
1687
+ end
1688
+
1689
+ def test_dhSHA256
1690
+ @assoc = @signatory.create_association(false, 'HMAC-SHA256')
1691
+ consumer_dh = DiffieHellman.from_defaults()
1692
+ cpub = consumer_dh.public
1693
+ server_dh = DiffieHellman.from_defaults()
1694
+ session = Server::DiffieHellmanSHA256ServerSession.new(server_dh, cpub)
1695
+ @request = Server::AssociateRequest.new(session, 'HMAC-SHA256')
1696
+ @request.message = Message.new(OPENID2_NS)
1697
+ response = @request.answer(@assoc)
1698
+ rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
1699
+ assert_equal(rfg.call("assoc_type"), "HMAC-SHA256")
1700
+ assert_equal(rfg.call("assoc_handle"), @assoc.handle)
1701
+ assert(!rfg.call("mac_key"))
1702
+ assert_equal(rfg.call("session_type"), "DH-SHA256")
1703
+ assert(rfg.call("enc_mac_key"))
1704
+ assert(rfg.call("dh_server_public"))
1705
+
1706
+ enc_key = Util.from_base64(rfg.call("enc_mac_key"))
1707
+ spub = CryptUtil.base64_to_num(rfg.call("dh_server_public"))
1708
+ secret = consumer_dh.xor_secret(CryptUtil.method('sha256'),
1709
+ spub, enc_key)
1710
+ assert_equal(secret, @assoc.secret)
1711
+ end
1712
+
1713
+ def test_protoError256
1714
+ s256_session = Consumer::DiffieHellmanSHA256Session.new()
1715
+
1716
+ invalid_s256 = {'openid.assoc_type' => 'HMAC-SHA1',
1717
+ 'openid.session_type' => 'DH-SHA256',}
1718
+ invalid_s256.merge!(s256_session.get_request())
1719
+
1720
+ invalid_s256_2 = {'openid.assoc_type' => 'MONKEY-PIRATE',
1721
+ 'openid.session_type' => 'DH-SHA256',}
1722
+ invalid_s256_2.merge!(s256_session.get_request())
1723
+
1724
+ bad_request_argss = [
1725
+ invalid_s256,
1726
+ invalid_s256_2,
1727
+ ]
1728
+
1729
+ bad_request_argss.each { |request_args|
1730
+ message = Message.from_post_args(request_args)
1731
+ assert_raise(Server::ProtocolError) {
1732
+ Server::AssociateRequest.from_message(message)
1733
+ }
1734
+ }
1735
+ end
1736
+
1737
+ def test_protoError
1738
+ s1_session = Consumer::DiffieHellmanSHA1Session.new()
1739
+
1740
+ invalid_s1 = {'openid.assoc_type' => 'HMAC-SHA256',
1741
+ 'openid.session_type' => 'DH-SHA1',}
1742
+ invalid_s1.merge!(s1_session.get_request())
1743
+
1744
+ invalid_s1_2 = {'openid.assoc_type' => 'ROBOT-NINJA',
1745
+ 'openid.session_type' => 'DH-SHA1',}
1746
+ invalid_s1_2.merge!(s1_session.get_request())
1747
+
1748
+ bad_request_argss = [
1749
+ {'openid.assoc_type' => 'Wha?'},
1750
+ invalid_s1,
1751
+ invalid_s1_2,
1752
+ ]
1753
+
1754
+ bad_request_argss.each { |request_args|
1755
+ message = Message.from_post_args(request_args)
1756
+ assert_raise(Server::ProtocolError) {
1757
+ Server::AssociateRequest.from_message(message)
1758
+ }
1759
+ }
1760
+ end
1761
+
1762
+ def test_protoErrorFields
1763
+
1764
+ contact = 'user@example.invalid'
1765
+ reference = 'Trac ticket number MAX_INT'
1766
+ error = 'poltergeist'
1767
+
1768
+ openid1_args = {
1769
+ 'openid.identitiy' => 'invalid',
1770
+ 'openid.mode' => 'checkid_setup',
1771
+ }
1772
+
1773
+ openid2_args = openid1_args.dup
1774
+ openid2_args.merge!({'openid.ns' => OPENID2_NS})
1775
+
1776
+ # Check presence of optional fields in both protocol versions
1777
+
1778
+ openid1_msg = Message.from_post_args(openid1_args)
1779
+ p = Server::ProtocolError.new(openid1_msg, error,
1780
+ reference, contact)
1781
+ reply = p.to_message()
1782
+
1783
+ assert_equal(reply.get_arg(OPENID_NS, 'reference'), reference)
1784
+ assert_equal(reply.get_arg(OPENID_NS, 'contact'), contact)
1785
+
1786
+ openid2_msg = Message.from_post_args(openid2_args)
1787
+ p = Server::ProtocolError.new(openid2_msg, error,
1788
+ reference, contact)
1789
+ reply = p.to_message()
1790
+
1791
+ assert_equal(reply.get_arg(OPENID_NS, 'reference'), reference)
1792
+ assert_equal(reply.get_arg(OPENID_NS, 'contact'), contact)
1793
+ end
1794
+
1795
+ def failUnlessExpiresInMatches(msg, expected_expires_in)
1796
+ expires_in_str = msg.get_arg(OPENID_NS, 'expires_in', NO_DEFAULT)
1797
+ expires_in = expires_in_str.to_i
1798
+
1799
+ # Slop is necessary because the tests can sometimes get run
1800
+ # right on a second boundary
1801
+ slop = 1 # second
1802
+ difference = expected_expires_in - expires_in
1803
+
1804
+ error_message = sprintf('"expires_in" value not within %s of expected: ' +
1805
+ 'expected=%s, actual=%s', slop, expected_expires_in,
1806
+ expires_in)
1807
+ assert((0 <= difference and difference <= slop), error_message)
1808
+ end
1809
+
1810
+ def test_plaintext
1811
+ @assoc = @signatory.create_association(false, 'HMAC-SHA1')
1812
+ response = @request.answer(@assoc)
1813
+ rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
1814
+
1815
+ assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
1816
+ assert_equal(rfg.call("assoc_handle"), @assoc.handle)
1817
+
1818
+ failUnlessExpiresInMatches(response.fields,
1819
+ @signatory.secret_lifetime)
1820
+
1821
+ assert_equal(
1822
+ rfg.call("mac_key"), Util.to_base64(@assoc.secret))
1823
+ assert(!rfg.call("session_type"))
1824
+ assert(!rfg.call("enc_mac_key"))
1825
+ assert(!rfg.call("dh_server_public"))
1826
+ end
1827
+
1828
+ def test_plaintext_v2
1829
+ # The main difference between this and the v1 test is that
1830
+ # session_type is always returned in v2.
1831
+ args = {
1832
+ 'openid.ns' => OPENID2_NS,
1833
+ 'openid.mode' => 'associate',
1834
+ 'openid.assoc_type' => 'HMAC-SHA1',
1835
+ 'openid.session_type' => 'no-encryption',
1836
+ }
1837
+ @request = Server::AssociateRequest.from_message(
1838
+ Message.from_post_args(args))
1839
+
1840
+ assert(!@request.message.is_openid1())
1841
+
1842
+ @assoc = @signatory.create_association(false, 'HMAC-SHA1')
1843
+ response = @request.answer(@assoc)
1844
+ rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
1845
+
1846
+ assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
1847
+ assert_equal(rfg.call("assoc_handle"), @assoc.handle)
1848
+
1849
+ failUnlessExpiresInMatches(
1850
+ response.fields, @signatory.secret_lifetime)
1851
+
1852
+ assert_equal(
1853
+ rfg.call("mac_key"), Util.to_base64(@assoc.secret))
1854
+
1855
+ assert_equal(rfg.call("session_type"), "no-encryption")
1856
+ assert(!rfg.call("enc_mac_key"))
1857
+ assert(!rfg.call("dh_server_public"))
1858
+ end
1859
+
1860
+ def test_plaintext256
1861
+ @assoc = @signatory.create_association(false, 'HMAC-SHA256')
1862
+ response = @request.answer(@assoc)
1863
+ rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
1864
+
1865
+ assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
1866
+ assert_equal(rfg.call("assoc_handle"), @assoc.handle)
1867
+
1868
+ failUnlessExpiresInMatches(
1869
+ response.fields, @signatory.secret_lifetime)
1870
+
1871
+ assert_equal(
1872
+ rfg.call("mac_key"), Util.to_base64(@assoc.secret))
1873
+ assert(!rfg.call("session_type"))
1874
+ assert(!rfg.call("enc_mac_key"))
1875
+ assert(!rfg.call("dh_server_public"))
1876
+ end
1877
+
1878
+ def test_unsupportedPrefer
1879
+ allowed_assoc = 'COLD-PET-RAT'
1880
+ allowed_sess = 'FROG-BONES'
1881
+ message = 'This is a unit test'
1882
+
1883
+ # Set an OpenID 2 message so answerUnsupported doesn't raise
1884
+ # ProtocolError.
1885
+ @request.message = Message.new(OPENID2_NS)
1886
+
1887
+ response = @request.answer_unsupported(message,
1888
+ allowed_assoc,
1889
+ allowed_sess)
1890
+ rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
1891
+ assert_equal(rfg.call('error_code'), 'unsupported-type')
1892
+ assert_equal(rfg.call('assoc_type'), allowed_assoc)
1893
+ assert_equal(rfg.call('error'), message)
1894
+ assert_equal(rfg.call('session_type'), allowed_sess)
1895
+ end
1896
+
1897
+ def test_unsupported
1898
+ message = 'This is a unit test'
1899
+
1900
+ # Set an OpenID 2 message so answerUnsupported doesn't raise
1901
+ # ProtocolError.
1902
+ @request.message = Message.new(OPENID2_NS)
1903
+
1904
+ response = @request.answer_unsupported(message)
1905
+ rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
1906
+ assert_equal(rfg.call('error_code'), 'unsupported-type')
1907
+ assert_equal(rfg.call('assoc_type'), nil)
1908
+ assert_equal(rfg.call('error'), message)
1909
+ assert_equal(rfg.call('session_type'), nil)
1910
+ end
1911
+
1912
+ def test_openid1_unsupported_explode
1913
+ # answer_unsupported on an associate request should explode if
1914
+ # the request was an OpenID 1 request.
1915
+ m = Message.new(OPENID1_NS)
1916
+
1917
+ assert_raise(Server::ProtocolError) {
1918
+ @request.answer_unsupported(m)
1919
+ }
1920
+ end
1921
+ end
1922
+
1923
+ class Counter
1924
+ def initialize
1925
+ @count = 0
1926
+ end
1927
+
1928
+ def inc
1929
+ @count += 1
1930
+ end
1931
+ end
1932
+
1933
+ class UnhandledError < Exception
1934
+ end
1935
+
1936
+ class TestServer < Test::Unit::TestCase
1937
+ include TestUtil
1938
+
1939
+ def setup
1940
+ @store = Store::Memory.new()
1941
+ @server = Server::Server.new(@store, "http://server.unittest/endpt")
1942
+ # catchlogs_setup()
1943
+ end
1944
+
1945
+ def test_failed_dispatch
1946
+ request = Server::OpenIDRequest.new()
1947
+ request.mode = "monkeymode"
1948
+ request.message = Message.new(OPENID1_NS)
1949
+ assert_raise(RuntimeError) {
1950
+ webresult = @server.handle_request(request)
1951
+ }
1952
+ end
1953
+
1954
+ def test_decode_request
1955
+ @server.decoder = BogusDecoder.new(@server)
1956
+ assert(@server.decode_request({}) == "BOGUS")
1957
+ end
1958
+
1959
+ def test_encode_response
1960
+ @server.encoder = BogusEncoder.new
1961
+ assert(@server.encode_response(nil) == "BOGUS")
1962
+ end
1963
+
1964
+ def test_dispatch
1965
+ monkeycalled = Counter.new()
1966
+
1967
+ @server.extend(InstanceDefExtension)
1968
+ @server.instance_def(:openid_monkeymode) do |request|
1969
+ raise UnhandledError
1970
+ end
1971
+
1972
+ request = Server::OpenIDRequest.new()
1973
+ request.mode = "monkeymode"
1974
+ request.message = Message.new(OPENID1_NS)
1975
+ assert_raise(UnhandledError) {
1976
+ webresult = @server.handle_request(request)
1977
+ }
1978
+ end
1979
+
1980
+ def test_associate
1981
+ request = Server::AssociateRequest.from_message(Message.from_post_args({}))
1982
+ response = @server.openid_associate(request)
1983
+ assert(response.fields.has_key?(OPENID_NS, "assoc_handle"),
1984
+ sprintf("No assoc_handle here: %s", response.fields.inspect))
1985
+ end
1986
+
1987
+ def test_associate2
1988
+ # Associate when the server has no allowed association types
1989
+ #
1990
+ # Gives back an error with error_code and no fallback session or
1991
+ # assoc types.
1992
+ @server.negotiator.allowed_types = []
1993
+
1994
+ # Set an OpenID 2 message so answerUnsupported doesn't raise
1995
+ # ProtocolError.
1996
+ msg = Message.from_post_args({
1997
+ 'openid.ns' => OPENID2_NS,
1998
+ 'openid.session_type' => 'no-encryption',
1999
+ })
2000
+
2001
+ request = Server::AssociateRequest.from_message(msg)
2002
+
2003
+ response = @server.openid_associate(request)
2004
+ assert(response.fields.has_key?(OPENID_NS, "error"))
2005
+ assert(response.fields.has_key?(OPENID_NS, "error_code"))
2006
+ assert(!response.fields.has_key?(OPENID_NS, "assoc_handle"))
2007
+ assert(!response.fields.has_key?(OPENID_NS, "assoc_type"))
2008
+ assert(!response.fields.has_key?(OPENID_NS, "session_type"))
2009
+ end
2010
+
2011
+ def test_associate3
2012
+ # Request an assoc type that is not supported when there are
2013
+ # supported types.
2014
+ #
2015
+ # Should give back an error message with a fallback type.
2016
+ @server.negotiator.allowed_types = [['HMAC-SHA256', 'DH-SHA256']]
2017
+
2018
+ msg = Message.from_post_args({
2019
+ 'openid.ns' => OPENID2_NS,
2020
+ 'openid.session_type' => 'no-encryption',
2021
+ })
2022
+
2023
+ request = Server::AssociateRequest.from_message(msg)
2024
+ response = @server.openid_associate(request)
2025
+
2026
+ assert(response.fields.has_key?(OPENID_NS, "error"))
2027
+ assert(response.fields.has_key?(OPENID_NS, "error_code"))
2028
+ assert(!response.fields.has_key?(OPENID_NS, "assoc_handle"))
2029
+
2030
+ assert_equal(response.fields.get_arg(OPENID_NS, "assoc_type"),
2031
+ 'HMAC-SHA256')
2032
+ assert_equal(response.fields.get_arg(OPENID_NS, "session_type"),
2033
+ 'DH-SHA256')
2034
+ end
2035
+
2036
+ def test_associate4
2037
+ # DH-SHA256 association session
2038
+ @server.negotiator.allowed_types = [['HMAC-SHA256', 'DH-SHA256']]
2039
+
2040
+ query = {
2041
+ 'openid.dh_consumer_public' =>
2042
+ 'ALZgnx8N5Lgd7pCj8K86T/DDMFjJXSss1SKoLmxE72kJTzOtG6I2PaYrHX' +
2043
+ 'xku4jMQWSsGfLJxwCZ6280uYjUST/9NWmuAfcrBfmDHIBc3H8xh6RBnlXJ' +
2044
+ '1WxJY3jHd5k1/ZReyRZOxZTKdF/dnIqwF8ZXUwI6peV0TyS/K1fOfF/s',
2045
+
2046
+ 'openid.assoc_type' => 'HMAC-SHA256',
2047
+ 'openid.session_type' => 'DH-SHA256',
2048
+ }
2049
+
2050
+ message = Message.from_post_args(query)
2051
+ request = Server::AssociateRequest.from_message(message)
2052
+ response = @server.openid_associate(request)
2053
+ assert(response.fields.has_key?(OPENID_NS, "assoc_handle"))
2054
+ end
2055
+
2056
+ def test_no_encryption_openid1
2057
+ # Make sure no-encryption associate requests for OpenID 1 are
2058
+ # logged.
2059
+ assert_log_matches(/Continuing anyway./) {
2060
+ m = Message.from_openid_args({
2061
+ 'session_type' => 'no-encryption',
2062
+ })
2063
+
2064
+ req = Server::AssociateRequest.from_message(m)
2065
+ }
2066
+ end
2067
+
2068
+ def test_missingSessionTypeOpenID2
2069
+ # Make sure session_type is required in OpenID 2
2070
+ msg = Message.from_post_args({
2071
+ 'openid.ns' => OPENID2_NS,
2072
+ })
2073
+
2074
+ assert_raises(Server::ProtocolError) {
2075
+ Server::AssociateRequest.from_message(msg)
2076
+ }
2077
+ end
2078
+
2079
+ def test_checkAuth
2080
+ request = Server::CheckAuthRequest.new('arrrrrf', '0x3999', [])
2081
+ request.message = Message.new(OPENID2_NS)
2082
+ response = nil
2083
+ silence_logging {
2084
+ response = @server.openid_check_authentication(request)
2085
+ }
2086
+ assert(response.fields.has_key?(OPENID_NS, "is_valid"))
2087
+ end
2088
+ end
2089
+
2090
+ class TestingRequest < Server::OpenIDRequest
2091
+ attr_accessor :assoc_handle, :namespace
2092
+ end
2093
+
2094
+ class TestSignatory < Test::Unit::TestCase
2095
+ include TestUtil
2096
+
2097
+ def setup
2098
+ @store = Store::Memory.new()
2099
+ @signatory = Server::Signatory.new(@store)
2100
+ @_dumb_key = @signatory.class._dumb_key
2101
+ @_normal_key = @signatory.class._normal_key
2102
+ # CatchLogs.setUp(self)
2103
+ end
2104
+
2105
+ def test_get_association_nil
2106
+ assert_raises(ArgumentError) {
2107
+ @signatory.get_association(nil, false)
2108
+ }
2109
+ end
2110
+
2111
+ def test_sign
2112
+ request = TestingRequest.new()
2113
+ assoc_handle = '{assoc}{lookatme}'
2114
+ @store.store_association(
2115
+ @_normal_key,
2116
+ Association.from_expires_in(60, assoc_handle,
2117
+ 'sekrit', 'HMAC-SHA1'))
2118
+ request.assoc_handle = assoc_handle
2119
+ request.namespace = OPENID1_NS
2120
+ response = Server::OpenIDResponse.new(request)
2121
+ response.fields = Message.from_openid_args({
2122
+ 'foo' => 'amsigned',
2123
+ 'bar' => 'notsigned',
2124
+ 'azu' => 'alsosigned',
2125
+ })
2126
+ sresponse = @signatory.sign(response)
2127
+ assert_equal(
2128
+ sresponse.fields.get_arg(OPENID_NS, 'assoc_handle'),
2129
+ assoc_handle)
2130
+ assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),
2131
+ 'assoc_handle,azu,bar,foo,signed')
2132
+ assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
2133
+ # assert(!@messages, @messages)
2134
+ end
2135
+
2136
+ def test_signDumb
2137
+ request = TestingRequest.new()
2138
+ request.assoc_handle = nil
2139
+ request.namespace = OPENID2_NS
2140
+ response = Server::OpenIDResponse.new(request)
2141
+ response.fields = Message.from_openid_args({
2142
+ 'foo' => 'amsigned',
2143
+ 'bar' => 'notsigned',
2144
+ 'azu' => 'alsosigned',
2145
+ 'ns' => OPENID2_NS,
2146
+ })
2147
+ sresponse = @signatory.sign(response)
2148
+ assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')
2149
+ assert(assoc_handle)
2150
+ assoc = @signatory.get_association(assoc_handle, true)
2151
+ assert(assoc)
2152
+ assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),
2153
+ 'assoc_handle,azu,bar,foo,ns,signed')
2154
+ assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
2155
+ # assert(!@messages, @messages)
2156
+ end
2157
+
2158
+ def test_signExpired
2159
+ # Sign a response to a message with an expired handle (using
2160
+ # invalidate_handle).
2161
+ #
2162
+ # From "Verifying with an Association":
2163
+ #
2164
+ # If an authentication request included an association handle
2165
+ # for an association between the OP and the Relying party, and
2166
+ # the OP no longer wishes to use that handle (because it has
2167
+ # expired or the secret has been compromised, for instance),
2168
+ # the OP will send a response that must be verified directly
2169
+ # with the OP, as specified in Section 11.3.2. In that
2170
+ # instance, the OP will include the field
2171
+ # "openid.invalidate_handle" set to the association handle
2172
+ # that the Relying Party included with the original request.
2173
+ request = TestingRequest.new()
2174
+ request.namespace = OPENID2_NS
2175
+ assoc_handle = '{assoc}{lookatme}'
2176
+ @store.store_association(
2177
+ @_normal_key,
2178
+ Association.from_expires_in(-10, assoc_handle,
2179
+ 'sekrit', 'HMAC-SHA1'))
2180
+ assert(@store.get_association(@_normal_key, assoc_handle))
2181
+
2182
+ request.assoc_handle = assoc_handle
2183
+ response = Server::OpenIDResponse.new(request)
2184
+ response.fields = Message.from_openid_args({
2185
+ 'foo' => 'amsigned',
2186
+ 'bar' => 'notsigned',
2187
+ 'azu' => 'alsosigned',
2188
+ })
2189
+ sresponse = nil
2190
+ silence_logging {
2191
+ sresponse = @signatory.sign(response)
2192
+ }
2193
+
2194
+ new_assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')
2195
+ assert(new_assoc_handle)
2196
+ assert(new_assoc_handle != assoc_handle)
2197
+
2198
+ assert_equal(
2199
+ sresponse.fields.get_arg(OPENID_NS, 'invalidate_handle'),
2200
+ assoc_handle)
2201
+
2202
+ assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),
2203
+ 'assoc_handle,azu,bar,foo,invalidate_handle,signed')
2204
+ assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
2205
+
2206
+ # make sure the expired association is gone
2207
+ assert(!@store.get_association(@_normal_key, assoc_handle),
2208
+ "expired association is still retrievable.")
2209
+
2210
+ # make sure the new key is a dumb mode association
2211
+ assert(@store.get_association(@_dumb_key, new_assoc_handle))
2212
+ assert(!@store.get_association(@_normal_key, new_assoc_handle))
2213
+ # assert(@messages)
2214
+ end
2215
+
2216
+ def test_signInvalidHandle
2217
+ request = TestingRequest.new()
2218
+ request.namespace = OPENID2_NS
2219
+ assoc_handle = '{bogus-assoc}{notvalid}'
2220
+
2221
+ request.assoc_handle = assoc_handle
2222
+ response = Server::OpenIDResponse.new(request)
2223
+ response.fields = Message.from_openid_args({
2224
+ 'foo' => 'amsigned',
2225
+ 'bar' => 'notsigned',
2226
+ 'azu' => 'alsosigned',
2227
+ })
2228
+ sresponse = @signatory.sign(response)
2229
+
2230
+ new_assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')
2231
+ assert(new_assoc_handle)
2232
+ assert(new_assoc_handle != assoc_handle)
2233
+
2234
+ assert_equal(
2235
+ sresponse.fields.get_arg(OPENID_NS, 'invalidate_handle'),
2236
+ assoc_handle)
2237
+
2238
+ assert_equal(
2239
+ sresponse.fields.get_arg(OPENID_NS, 'signed'),
2240
+ 'assoc_handle,azu,bar,foo,invalidate_handle,signed')
2241
+ assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
2242
+
2243
+ # make sure the new key is a dumb mode association
2244
+ assert(@store.get_association(@_dumb_key, new_assoc_handle))
2245
+ assert(!@store.get_association(@_normal_key, new_assoc_handle))
2246
+ # @failIf(@messages, @messages)
2247
+ end
2248
+
2249
+ def test_verify
2250
+ assoc_handle = '{vroom}{zoom}'
2251
+ assoc = Association.from_expires_in(
2252
+ 60, assoc_handle, 'sekrit', 'HMAC-SHA1')
2253
+
2254
+ @store.store_association(@_dumb_key, assoc)
2255
+
2256
+ signed = Message.from_post_args({
2257
+ 'openid.foo' => 'bar',
2258
+ 'openid.apple' => 'orange',
2259
+ 'openid.assoc_handle' => assoc_handle,
2260
+ 'openid.signed' => 'apple,assoc_handle,foo,signed',
2261
+ 'openid.sig' => 'uXoT1qm62/BB09Xbj98TQ8mlBco=',
2262
+ })
2263
+
2264
+ verified = @signatory.verify(assoc_handle, signed)
2265
+ assert(verified)
2266
+ # assert(!@messages, @messages)
2267
+ end
2268
+
2269
+ def test_verifyBadSig
2270
+ assoc_handle = '{vroom}{zoom}'
2271
+ assoc = Association.from_expires_in(
2272
+ 60, assoc_handle, 'sekrit', 'HMAC-SHA1')
2273
+
2274
+ @store.store_association(@_dumb_key, assoc)
2275
+
2276
+ signed = Message.from_post_args({
2277
+ 'openid.foo' => 'bar',
2278
+ 'openid.apple' => 'orange',
2279
+ 'openid.assoc_handle' => assoc_handle,
2280
+ 'openid.signed' => 'apple,assoc_handle,foo,signed',
2281
+ 'openid.sig' => 'uXoT1qm62/BB09Xbj98TQ8mlBco=BOGUS'
2282
+ })
2283
+
2284
+ verified = @signatory.verify(assoc_handle, signed)
2285
+ # @failIf(@messages, @messages)
2286
+ assert(!verified)
2287
+ end
2288
+
2289
+ def test_verifyBadHandle
2290
+ assoc_handle = '{vroom}{zoom}'
2291
+ signed = Message.from_post_args({
2292
+ 'foo' => 'bar',
2293
+ 'apple' => 'orange',
2294
+ 'openid.sig' => "Ylu0KcIR7PvNegB/K41KpnRgJl0=",
2295
+ })
2296
+
2297
+ verified = nil
2298
+ silence_logging {
2299
+ verified = @signatory.verify(assoc_handle, signed)
2300
+ }
2301
+
2302
+ assert(!verified)
2303
+ #assert(@messages)
2304
+ end
2305
+
2306
+ def test_verifyAssocMismatch
2307
+ # Attempt to validate sign-all message with a signed-list assoc.
2308
+ assoc_handle = '{vroom}{zoom}'
2309
+ assoc = Association.from_expires_in(
2310
+ 60, assoc_handle, 'sekrit', 'HMAC-SHA1')
2311
+
2312
+ @store.store_association(@_dumb_key, assoc)
2313
+
2314
+ signed = Message.from_post_args({
2315
+ 'foo' => 'bar',
2316
+ 'apple' => 'orange',
2317
+ 'openid.sig' => "d71xlHtqnq98DonoSgoK/nD+QRM=",
2318
+ })
2319
+
2320
+ verified = nil
2321
+ silence_logging {
2322
+ verified = @signatory.verify(assoc_handle, signed)
2323
+ }
2324
+
2325
+ assert(!verified)
2326
+ #assert(@messages)
2327
+ end
2328
+
2329
+ def test_getAssoc
2330
+ assoc_handle = makeAssoc(true)
2331
+ assoc = @signatory.get_association(assoc_handle, true)
2332
+ assert(assoc)
2333
+ assert_equal(assoc.handle, assoc_handle)
2334
+ # @failIf(@messages, @messages)
2335
+ end
2336
+
2337
+ def test_getAssocExpired
2338
+ assoc_handle = makeAssoc(true, -10)
2339
+ assoc = nil
2340
+ silence_logging {
2341
+ assoc = @signatory.get_association(assoc_handle, true)
2342
+ }
2343
+ assert(!assoc, assoc)
2344
+ # assert(@messages)
2345
+ end
2346
+
2347
+ def test_getAssocInvalid
2348
+ ah = 'no-such-handle'
2349
+ silence_logging {
2350
+ assert_equal(
2351
+ @signatory.get_association(ah, false), nil)
2352
+ }
2353
+ # assert(!@messages, @messages)
2354
+ end
2355
+
2356
+ def test_getAssocDumbVsNormal
2357
+ # getAssociation(dumb=False) cannot get a dumb assoc
2358
+ assoc_handle = makeAssoc(true)
2359
+ silence_logging {
2360
+ assert_equal(
2361
+ @signatory.get_association(assoc_handle, false), nil)
2362
+ }
2363
+ # @failIf(@messages, @messages)
2364
+ end
2365
+
2366
+ def test_getAssocNormalVsDumb
2367
+ # getAssociation(dumb=True) cannot get a shared assoc
2368
+ #
2369
+ # From "Verifying Directly with the OpenID Provider"::
2370
+ #
2371
+ # An OP MUST NOT verify signatures for associations that have shared
2372
+ # MAC keys.
2373
+ assoc_handle = makeAssoc(false)
2374
+ silence_logging {
2375
+ assert_equal(
2376
+ @signatory.get_association(assoc_handle, true), nil)
2377
+ }
2378
+ # @failIf(@messages, @messages)
2379
+ end
2380
+
2381
+ def test_createAssociation
2382
+ assoc = @signatory.create_association(false)
2383
+ silence_logging {
2384
+ assert(@signatory.get_association(assoc.handle, false))
2385
+ }
2386
+ # @failIf(@messages, @messages)
2387
+ end
2388
+
2389
+ def makeAssoc(dumb, lifetime=60)
2390
+ assoc_handle = '{bling}'
2391
+ assoc = Association.from_expires_in(lifetime, assoc_handle,
2392
+ 'sekrit', 'HMAC-SHA1')
2393
+
2394
+ silence_logging {
2395
+ @store.store_association(((dumb and @_dumb_key) or @_normal_key), assoc)
2396
+ }
2397
+
2398
+ return assoc_handle
2399
+ end
2400
+
2401
+ def test_invalidate
2402
+ assoc_handle = '-squash-'
2403
+ assoc = Association.from_expires_in(60, assoc_handle,
2404
+ 'sekrit', 'HMAC-SHA1')
2405
+
2406
+ silence_logging {
2407
+ @store.store_association(@_dumb_key, assoc)
2408
+ assoc = @signatory.get_association(assoc_handle, true)
2409
+ assert(assoc)
2410
+ assoc = @signatory.get_association(assoc_handle, true)
2411
+ assert(assoc)
2412
+ @signatory.invalidate(assoc_handle, true)
2413
+ assoc = @signatory.get_association(assoc_handle, true)
2414
+ assert(!assoc)
2415
+ }
2416
+ # @failIf(@messages, @messages)
2417
+ end
2418
+ end
2419
+
2420
+ class RunthroughTestCase < Test::Unit::TestCase
2421
+ def setup
2422
+ @store = Store::Memory.new
2423
+ @server = Server::Server.new(@store, "http://example.com/openid/server")
2424
+ end
2425
+
2426
+ def test_openid1_assoc_checkid
2427
+ assoc_args = {'openid.mode' => 'associate',
2428
+ 'openid.assoc_type' => 'HMAC-SHA1'}
2429
+ areq = @server.decode_request(assoc_args)
2430
+ aresp = @server.handle_request(areq)
2431
+
2432
+ amess = aresp.fields
2433
+ assert(amess.is_openid1)
2434
+ ahandle = amess.get_arg(OPENID_NS, 'assoc_handle')
2435
+ assert(ahandle)
2436
+ assoc = @store.get_association('http://localhost/|normal', ahandle)
2437
+ assert(assoc.is_a?(Association))
2438
+
2439
+
2440
+ checkid_args = {'openid.mode' => 'checkid_setup',
2441
+ 'openid.return_to' => 'http://example.com/openid/consumer',
2442
+ 'openid.assoc_handle' => ahandle,
2443
+ 'openid.identity' => 'http://foo.com/'}
2444
+
2445
+ cireq = @server.decode_request(checkid_args)
2446
+ ciresp = cireq.answer(true)
2447
+
2448
+ signed_resp = @server.signatory.sign(ciresp)
2449
+
2450
+ assert_equal(assoc.get_message_signature(signed_resp.fields),
2451
+ signed_resp.fields.get_arg(OPENID_NS, 'sig'))
2452
+
2453
+ assert(assoc.check_message_signature(signed_resp.fields))
2454
+ end
2455
+
2456
+ end
2457
+ end