nov-ruby-openid 2.1.9

Sign up to get free protection for your applications and to get access to all the features.
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