entp-ruby-openid 2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. data/CHANGELOG +215 -0
  2. data/INSTALL +47 -0
  3. data/LICENSE +210 -0
  4. data/NOTICE +2 -0
  5. data/README +85 -0
  6. data/UPGRADE +127 -0
  7. data/admin/runtests.rb +45 -0
  8. data/examples/README +32 -0
  9. data/examples/active_record_openid_store/README +58 -0
  10. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  11. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  12. data/examples/active_record_openid_store/init.rb +8 -0
  13. data/examples/active_record_openid_store/lib/association.rb +10 -0
  14. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  15. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  16. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  17. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  18. data/examples/discover +49 -0
  19. data/examples/rails_openid/README +153 -0
  20. data/examples/rails_openid/Rakefile +10 -0
  21. data/examples/rails_openid/app/controllers/application.rb +4 -0
  22. data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
  23. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  24. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  25. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  26. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  27. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  28. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  29. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  30. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  31. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  32. data/examples/rails_openid/config/boot.rb +19 -0
  33. data/examples/rails_openid/config/database.yml +74 -0
  34. data/examples/rails_openid/config/environment.rb +54 -0
  35. data/examples/rails_openid/config/environments/development.rb +19 -0
  36. data/examples/rails_openid/config/environments/production.rb +19 -0
  37. data/examples/rails_openid/config/environments/test.rb +19 -0
  38. data/examples/rails_openid/config/routes.rb +24 -0
  39. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  40. data/examples/rails_openid/public/404.html +8 -0
  41. data/examples/rails_openid/public/500.html +8 -0
  42. data/examples/rails_openid/public/dispatch.cgi +12 -0
  43. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  44. data/examples/rails_openid/public/dispatch.rb +12 -0
  45. data/examples/rails_openid/public/favicon.ico +0 -0
  46. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  47. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  48. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  49. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  50. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  51. data/examples/rails_openid/public/robots.txt +1 -0
  52. data/examples/rails_openid/script/about +3 -0
  53. data/examples/rails_openid/script/breakpointer +3 -0
  54. data/examples/rails_openid/script/console +3 -0
  55. data/examples/rails_openid/script/destroy +3 -0
  56. data/examples/rails_openid/script/generate +3 -0
  57. data/examples/rails_openid/script/performance/benchmarker +3 -0
  58. data/examples/rails_openid/script/performance/profiler +3 -0
  59. data/examples/rails_openid/script/plugin +3 -0
  60. data/examples/rails_openid/script/process/reaper +3 -0
  61. data/examples/rails_openid/script/process/spawner +3 -0
  62. data/examples/rails_openid/script/process/spinner +3 -0
  63. data/examples/rails_openid/script/runner +3 -0
  64. data/examples/rails_openid/script/server +3 -0
  65. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  66. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  67. data/examples/rails_openid/test/test_helper.rb +28 -0
  68. data/lib/hmac/hmac.rb +112 -0
  69. data/lib/hmac/sha1.rb +11 -0
  70. data/lib/hmac/sha2.rb +25 -0
  71. data/lib/openid.rb +22 -0
  72. data/lib/openid/association.rb +249 -0
  73. data/lib/openid/consumer.rb +395 -0
  74. data/lib/openid/consumer/associationmanager.rb +344 -0
  75. data/lib/openid/consumer/checkid_request.rb +186 -0
  76. data/lib/openid/consumer/discovery.rb +497 -0
  77. data/lib/openid/consumer/discovery_manager.rb +123 -0
  78. data/lib/openid/consumer/html_parse.rb +134 -0
  79. data/lib/openid/consumer/idres.rb +523 -0
  80. data/lib/openid/consumer/responses.rb +150 -0
  81. data/lib/openid/cryptutil.rb +115 -0
  82. data/lib/openid/dh.rb +89 -0
  83. data/lib/openid/extension.rb +39 -0
  84. data/lib/openid/extensions/ax.rb +539 -0
  85. data/lib/openid/extensions/oauth.rb +91 -0
  86. data/lib/openid/extensions/pape.rb +179 -0
  87. data/lib/openid/extensions/sreg.rb +277 -0
  88. data/lib/openid/extras.rb +11 -0
  89. data/lib/openid/fetchers.rb +258 -0
  90. data/lib/openid/kvform.rb +136 -0
  91. data/lib/openid/kvpost.rb +58 -0
  92. data/lib/openid/message.rb +553 -0
  93. data/lib/openid/protocolerror.rb +12 -0
  94. data/lib/openid/server.rb +1544 -0
  95. data/lib/openid/store.rb +10 -0
  96. data/lib/openid/store/filesystem.rb +272 -0
  97. data/lib/openid/store/interface.rb +75 -0
  98. data/lib/openid/store/memcache.rb +109 -0
  99. data/lib/openid/store/memory.rb +84 -0
  100. data/lib/openid/store/nonce.rb +68 -0
  101. data/lib/openid/trustroot.rb +349 -0
  102. data/lib/openid/urinorm.rb +75 -0
  103. data/lib/openid/util.rb +119 -0
  104. data/lib/openid/version.rb +3 -0
  105. data/lib/openid/yadis.rb +15 -0
  106. data/lib/openid/yadis/accept.rb +148 -0
  107. data/lib/openid/yadis/constants.rb +21 -0
  108. data/lib/openid/yadis/discovery.rb +153 -0
  109. data/lib/openid/yadis/filters.rb +205 -0
  110. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  111. data/lib/openid/yadis/parsehtml.rb +45 -0
  112. data/lib/openid/yadis/services.rb +42 -0
  113. data/lib/openid/yadis/xrds.rb +155 -0
  114. data/lib/openid/yadis/xri.rb +90 -0
  115. data/lib/openid/yadis/xrires.rb +91 -0
  116. data/test/data/test_discover/openid_utf8.html +11 -0
  117. data/test/support/test_data_mixin.rb +127 -0
  118. data/test/support/test_util.rb +53 -0
  119. data/test/support/yadis_data.rb +131 -0
  120. data/test/support/yadis_data/accept.txt +124 -0
  121. data/test/support/yadis_data/dh.txt +29 -0
  122. data/test/support/yadis_data/example-xrds.xml +14 -0
  123. data/test/support/yadis_data/linkparse.txt +587 -0
  124. data/test/support/yadis_data/n2b64 +650 -0
  125. data/test/support/yadis_data/test1-discover.txt +137 -0
  126. data/test/support/yadis_data/test1-parsehtml.txt +152 -0
  127. data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/support/yadis_data/test_discover/openid.html +11 -0
  129. data/test/support/yadis_data/test_discover/openid2.html +11 -0
  130. data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
  138. data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
  144. data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/support/yadis_data/test_xrds/README +12 -0
  148. data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
  152. data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
  153. data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
  155. data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
  157. data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
  158. data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
  159. data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
  160. data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
  161. data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/support/yadis_data/trustroot.txt +153 -0
  163. data/test/support/yadis_data/urinorm.txt +79 -0
  164. data/test/test_accept.rb +170 -0
  165. data/test/test_association.rb +268 -0
  166. data/test/test_associationmanager.rb +918 -0
  167. data/test/test_ax.rb +690 -0
  168. data/test/test_checkid_request.rb +293 -0
  169. data/test/test_consumer.rb +260 -0
  170. data/test/test_cryptutil.rb +119 -0
  171. data/test/test_dh.rb +85 -0
  172. data/test/test_discover.rb +848 -0
  173. data/test/test_discovery_manager.rb +259 -0
  174. data/test/test_extension.rb +46 -0
  175. data/test/test_extras.rb +35 -0
  176. data/test/test_fetchers.rb +554 -0
  177. data/test/test_filters.rb +269 -0
  178. data/test/test_helper.rb +4 -0
  179. data/test/test_idres.rb +961 -0
  180. data/test/test_kvform.rb +164 -0
  181. data/test/test_kvpost.rb +64 -0
  182. data/test/test_linkparse.rb +100 -0
  183. data/test/test_message.rb +1115 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +176 -0
  186. data/test/test_openid_yadis.rb +177 -0
  187. data/test/test_pape.rb +248 -0
  188. data/test/test_parsehtml.rb +79 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2455 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +292 -0
  193. data/test/test_trustroot.rb +111 -0
  194. data/test/test_urinorm.rb +34 -0
  195. data/test/test_util.rb +145 -0
  196. data/test/test_xrds.rb +167 -0
  197. data/test/test_xri.rb +48 -0
  198. data/test/test_xrires.rb +67 -0
  199. data/test/test_yadis_discovery.rb +218 -0
  200. metadata +268 -0
@@ -0,0 +1,268 @@
1
+ require 'test_helper'
2
+ require 'support/test_util'
3
+ require "openid/association"
4
+ require 'test_util'
5
+
6
+ module OpenID
7
+ class AssociationTestCase < Test::Unit::TestCase
8
+ def setup
9
+ # Use this funny way of getting a time so that it does not have
10
+ # fractional seconds, and so can be serialized exactly using our
11
+ # standard code.
12
+ issued = Time.at(Time.now.to_i)
13
+ lifetime = 600
14
+
15
+ @assoc = Association.new('handle', 'secret', issued,
16
+ lifetime, 'HMAC-SHA1')
17
+ end
18
+
19
+ def test_round_trip
20
+ assoc2 = Association.deserialize(@assoc.serialize())
21
+ [:handle, :secret, :lifetime, :assoc_type].each do |attr|
22
+ assert_equal(@assoc.send(attr), assoc2.send(attr))
23
+ end
24
+ end
25
+
26
+ def test_deserialize_failure
27
+ field_list = Util.kv_to_seq(@assoc.serialize)
28
+ kv = Util.seq_to_kv(field_list + [['monkeys', 'funny']])
29
+ assert_raises(ProtocolError) {
30
+ Association.deserialize(kv)
31
+ }
32
+
33
+ bad_version_list = field_list.dup
34
+ bad_version_list[0] = ['version', 'moon']
35
+ bad_version_kv = Util.seq_to_kv(bad_version_list)
36
+ assert_raises(ProtocolError) {
37
+ Association.deserialize(bad_version_kv)
38
+ }
39
+ end
40
+
41
+ def test_serialization_identity
42
+ assoc2 = Association.deserialize(@assoc.serialize)
43
+ assert_equal(@assoc, assoc2)
44
+ end
45
+
46
+ def test_expires_in
47
+ # Allow one second of slop
48
+ assert(@assoc.expires_in.between?(599,600))
49
+ assert(@assoc.expires_in(Time.now.to_i).between?(599,600))
50
+ assert_equal(0,@assoc.expires_in(Time.now.to_i + 10000),"negative expires_in")
51
+ end
52
+
53
+ def test_from_expires_in
54
+ start_time = Time.now
55
+ expires_in = @assoc.expires_in
56
+ assoc = Association.from_expires_in(expires_in,
57
+ @assoc.handle,
58
+ @assoc.secret,
59
+ @assoc.assoc_type)
60
+
61
+ # Allow one second of slop here for code execution time
62
+ assert_in_delta(1, assoc.expires_in, @assoc.expires_in)
63
+ [:handle, :secret, :assoc_type].each do |attr|
64
+ assert_equal(@assoc.send(attr), assoc.send(attr))
65
+ end
66
+
67
+ # Make sure the issued time is near the start
68
+ assert(assoc.issued >= start_time)
69
+ assert_in_delta(1, assoc.issued.to_f, start_time.to_f)
70
+ end
71
+
72
+ def test_sign_sha1
73
+ pairs = [['key1', 'value1'],
74
+ ['key2', 'value2']]
75
+
76
+ [['HMAC-SHA256', "\xfd\xaa\xfe;\xac\xfc*\x988\xad\x05d6-"\
77
+ "\xeaVy\xd5\xa5Z.<\xa9\xed\x18\x82\\$"\
78
+ "\x95x\x1c&"],
79
+ ['HMAC-SHA1', "\xe0\x1bv\x04\xf1G\xc0\xbb\x7f\x9a\x8b"\
80
+ "\xe9\xbc\xee}\\\xe5\xbb7*"],
81
+ ].each do |assoc_type, expected|
82
+ assoc = Association.from_expires_in(3600, "handle", 'very_secret',
83
+ assoc_type)
84
+ sig = assoc.sign(pairs)
85
+ assert_equal(sig, expected)
86
+
87
+ m = Message.new(OPENID2_NS)
88
+ pairs.each { |k, v|
89
+ m.set_arg(OPENID_NS, k, v)
90
+ }
91
+ m.set_arg(BARE_NS, "not_an_openid_arg", "bogus")
92
+
93
+ signed_m = assoc.sign_message(m)
94
+ assert(signed_m.has_key?(OPENID_NS, 'sig'))
95
+ assert_equal(signed_m.get_arg(OPENID_NS, 'signed'),
96
+ 'assoc_handle,key1,key2,ns,signed')
97
+ end
98
+ end
99
+
100
+ def test_sign_message_with_sig
101
+ assoc = Association.from_expires_in(3600, "handle", "very_secret",
102
+ "HMAC-SHA1")
103
+ m = Message.new(OPENID2_NS)
104
+ m.set_arg(OPENID_NS, 'sig', 'noise')
105
+ assert_raises(ArgumentError) {
106
+ assoc.sign_message(m)
107
+ }
108
+ end
109
+
110
+ def test_sign_message_with_signed
111
+ assoc = Association.from_expires_in(3600, "handle", "very_secret",
112
+ "HMAC-SHA1")
113
+ m = Message.new(OPENID2_NS)
114
+ m.set_arg(OPENID_NS, 'signed', 'fields')
115
+ assert_raises(ArgumentError) {
116
+ assoc.sign_message(m)
117
+ }
118
+ end
119
+
120
+ def test_sign_different_assoc_handle
121
+ assoc = Association.from_expires_in(3600, "handle", "very_secret",
122
+ "HMAC-SHA1")
123
+ m = Message.new(OPENID2_NS)
124
+ m.set_arg(OPENID_NS, 'assoc_handle', 'different')
125
+ assert_raises(ArgumentError) {
126
+ assoc.sign_message(m)
127
+ }
128
+ end
129
+
130
+ def test_sign_bad_assoc_type
131
+ @assoc.instance_eval { @assoc_type = 'Cookies' }
132
+ assert_raises(ProtocolError) {
133
+ @assoc.sign([])
134
+ }
135
+ end
136
+
137
+ def test_make_pairs
138
+ msg = Message.new(OPENID2_NS)
139
+ msg.update_args(OPENID2_NS, {
140
+ 'mode' => 'id_res',
141
+ 'identifier' => '=example',
142
+ 'signed' => 'identifier,mode',
143
+ 'sig' => 'cephalopod',
144
+ })
145
+ msg.update_args(BARE_NS, {'xey' => 'value'})
146
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
147
+ "HMAC-SHA1")
148
+ pairs = assoc.make_pairs(msg)
149
+ assert_equal([['identifier', '=example'],
150
+ ['mode', 'id_res']], pairs)
151
+ end
152
+
153
+ def test_check_message_signature_no_signed
154
+ m = Message.new(OPENID2_NS)
155
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
156
+ 'identifier' => '=example',
157
+ 'sig' => 'coyote',
158
+ })
159
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
160
+ "HMAC-SHA1")
161
+ assert_raises(ProtocolError) {
162
+ assoc.check_message_signature(m)
163
+ }
164
+ end
165
+
166
+ def test_check_message_signature_no_sig
167
+ m = Message.new(OPENID2_NS)
168
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
169
+ 'identifier' => '=example',
170
+ 'signed' => 'mode',
171
+ })
172
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
173
+ "HMAC-SHA1")
174
+ assert_raises(ProtocolError) {
175
+ assoc.check_message_signature(m)
176
+ }
177
+ end
178
+
179
+ def test_check_message_signature_bad_sig
180
+ m = Message.new(OPENID2_NS)
181
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
182
+ 'identifier' => '=example',
183
+ 'signed' => 'mode',
184
+ 'sig' => Util.to_base64('coyote'),
185
+ })
186
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
187
+ "HMAC-SHA1")
188
+ assert(!assoc.check_message_signature(m))
189
+ end
190
+
191
+ def test_check_message_signature_good_sig
192
+ m = Message.new(OPENID2_NS)
193
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
194
+ 'identifier' => '=example',
195
+ 'signed' => 'mode',
196
+ 'sig' => Util.to_base64('coyote'),
197
+ })
198
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
199
+ "HMAC-SHA1")
200
+ class << assoc
201
+ # Override sign, because it's already tested elsewhere
202
+ def sign(pairs)
203
+ "coyote"
204
+ end
205
+ end
206
+
207
+ assert(assoc.check_message_signature(m))
208
+ end
209
+ end
210
+
211
+ class AssociationNegotiatorTestCase < Test::Unit::TestCase
212
+ def assert_equal_under(item1, item2)
213
+ val1 = yield(item1)
214
+ val2 = yield(item2)
215
+ assert_equal(val1, val2)
216
+ end
217
+
218
+ def test_copy
219
+ neg = AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1']])
220
+ neg2 = neg.copy
221
+ assert_equal_under(neg, neg2) {|n| n.instance_eval{@allowed_types} }
222
+ assert(neg.object_id != neg2.object_id)
223
+ end
224
+
225
+ def test_add_allowed
226
+ neg = AssociationNegotiator.new([])
227
+ assert(!neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
228
+ assert(!neg.allowed?('HMAC-SHA1', 'no-encryption'))
229
+ assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
230
+ assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
231
+ neg.add_allowed_type('HMAC-SHA1')
232
+ assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
233
+ assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))
234
+ assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
235
+ assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
236
+ neg.add_allowed_type('HMAC-SHA256', 'DH-SHA256')
237
+ assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
238
+ assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))
239
+ assert(neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
240
+ assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
241
+ assert_equal(neg.get_allowed_type, ['HMAC-SHA1', 'DH-SHA1'])
242
+ end
243
+
244
+ def test_bad_assoc_type
245
+ assert_raises(ProtocolError) {
246
+ AssociationNegotiator.new([['OMG', 'Ponies']])
247
+ }
248
+ end
249
+
250
+ def test_bad_session_type
251
+ assert_raises(ProtocolError) {
252
+ AssociationNegotiator.new([['HMAC-SHA1', 'OMG-Ponies']])
253
+ }
254
+ end
255
+
256
+ def test_default_negotiator
257
+ assert_equal(DefaultNegotiator.get_allowed_type,
258
+ ['HMAC-SHA1', 'DH-SHA1'])
259
+ assert(DefaultNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))
260
+ end
261
+
262
+ def test_encrypted_negotiator
263
+ assert_equal(EncryptedNegotiator.get_allowed_type,
264
+ ['HMAC-SHA1', 'DH-SHA1'])
265
+ assert(!EncryptedNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,918 @@
1
+ require 'test_helper'
2
+ require 'support/test_util'
3
+ require "openid/consumer/associationmanager"
4
+ require "openid/association"
5
+ require "openid/dh"
6
+ require "openid/util"
7
+ require "openid/cryptutil"
8
+ require "openid/message"
9
+ require "openid/store/memory"
10
+ require "time"
11
+ require 'test_util'
12
+
13
+ module OpenID
14
+ class DHAssocSessionTest < Test::Unit::TestCase
15
+ def test_sha1_get_request
16
+ # Initialized without an explicit DH gets defaults
17
+ sess = Consumer::DiffieHellmanSHA1Session.new
18
+ assert_equal(['dh_consumer_public'], sess.get_request.keys)
19
+ assert_nothing_raised do
20
+ Util::from_base64(sess.get_request['dh_consumer_public'])
21
+ end
22
+ end
23
+
24
+ def test_sha1_get_request_custom_dh
25
+ dh = DiffieHellman.new(1299721, 2)
26
+ sess = Consumer::DiffieHellmanSHA1Session.new(dh)
27
+ req = sess.get_request
28
+ assert_equal(['dh_consumer_public', 'dh_modulus', 'dh_gen'].sort,
29
+ req.keys.sort)
30
+ assert_equal(dh.modulus, CryptUtil.base64_to_num(req['dh_modulus']))
31
+ assert_equal(dh.generator, CryptUtil.base64_to_num(req['dh_gen']))
32
+ assert_nothing_raised do
33
+ Util::from_base64(req['dh_consumer_public'])
34
+ end
35
+ end
36
+ end
37
+
38
+ module TestDiffieHellmanResponseParametersMixin
39
+ def setup
40
+ session_cls = self.class.session_cls
41
+
42
+ # Pre-compute DH with small prime so tests run quickly.
43
+ @server_dh = DiffieHellman.new(100389557, 2)
44
+ @consumer_dh = DiffieHellman.new(100389557, 2)
45
+
46
+ # base64(btwoc(g ^ xb mod p))
47
+ @dh_server_public = CryptUtil.num_to_base64(@server_dh.public)
48
+
49
+ @secret = CryptUtil.random_string(session_cls.secret_size)
50
+
51
+ enc_mac_key_unencoded =
52
+ @server_dh.xor_secret(session_cls.hashfunc,
53
+ @consumer_dh.public,
54
+ @secret)
55
+
56
+ @enc_mac_key = Util.to_base64(enc_mac_key_unencoded)
57
+
58
+ @consumer_session = session_cls.new(@consumer_dh)
59
+
60
+ @msg = Message.new(self.class.message_namespace)
61
+ end
62
+
63
+ def test_extract_secret
64
+ @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
65
+ @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
66
+
67
+ extracted = @consumer_session.extract_secret(@msg)
68
+ assert_equal(extracted, @secret)
69
+ end
70
+
71
+ def test_absent_serve_public
72
+ @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
73
+
74
+ assert_raises(Message::KeyNotFound) {
75
+ @consumer_session.extract_secret(@msg)
76
+ }
77
+ end
78
+
79
+ def test_absent_mac_key
80
+ @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
81
+
82
+ assert_raises(Message::KeyNotFound) {
83
+ @consumer_session.extract_secret(@msg)
84
+ }
85
+ end
86
+
87
+ def test_invalid_base64_public
88
+ @msg.set_arg(OPENID_NS, 'dh_server_public', 'n o t b a s e 6 4.')
89
+ @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
90
+
91
+ assert_raises(ArgumentError) {
92
+ @consumer_session.extract_secret(@msg)
93
+ }
94
+ end
95
+
96
+ def test_invalid_base64_mac_key
97
+ @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
98
+ @msg.set_arg(OPENID_NS, 'enc_mac_key', 'n o t base 64')
99
+
100
+ assert_raises(ArgumentError) {
101
+ @consumer_session.extract_secret(@msg)
102
+ }
103
+ end
104
+ end
105
+
106
+ class TestConsumerOpenID1DHSHA1 < Test::Unit::TestCase
107
+ include TestDiffieHellmanResponseParametersMixin
108
+ class << self
109
+ attr_reader :session_cls, :message_namespace
110
+ end
111
+
112
+ @session_cls = Consumer::DiffieHellmanSHA1Session
113
+ @message_namespace = OPENID1_NS
114
+ end
115
+
116
+ class TestConsumerOpenID2DHSHA1 < Test::Unit::TestCase
117
+ include TestDiffieHellmanResponseParametersMixin
118
+ class << self
119
+ attr_reader :session_cls, :message_namespace
120
+ end
121
+
122
+ @session_cls = Consumer::DiffieHellmanSHA1Session
123
+ @message_namespace = OPENID2_NS
124
+ end
125
+
126
+ class TestConsumerOpenID2DHSHA256 < Test::Unit::TestCase
127
+ include TestDiffieHellmanResponseParametersMixin
128
+ class << self
129
+ attr_reader :session_cls, :message_namespace
130
+ end
131
+
132
+ @session_cls = Consumer::DiffieHellmanSHA256Session
133
+ @message_namespace = OPENID2_NS
134
+ end
135
+
136
+ class TestConsumerNoEncryptionSession < Test::Unit::TestCase
137
+ def setup
138
+ @sess = Consumer::NoEncryptionSession.new
139
+ end
140
+
141
+ def test_empty_request
142
+ assert_equal(@sess.get_request, {})
143
+ end
144
+
145
+ def test_get_secret
146
+ secret = 'shhh!' * 4
147
+ mac_key = Util.to_base64(secret)
148
+ msg = Message.from_openid_args({'mac_key' => mac_key})
149
+ assert_equal(secret, @sess.extract_secret(msg))
150
+ end
151
+ end
152
+
153
+ class TestCreateAssociationRequest < Test::Unit::TestCase
154
+ def setup
155
+ @server_url = 'http://invalid/'
156
+ @assoc_manager = Consumer::AssociationManager.new(nil, @server_url)
157
+ class << @assoc_manager
158
+ def compatibility_mode=(val)
159
+ @compatibility_mode = val
160
+ end
161
+ end
162
+ @assoc_type = 'HMAC-SHA1'
163
+ end
164
+
165
+ def test_no_encryption_sends_type
166
+ session_type = 'no-encryption'
167
+ session, args = @assoc_manager.send(:create_associate_request,
168
+ @assoc_type,
169
+ session_type)
170
+
171
+ assert(session.is_a?(Consumer::NoEncryptionSession))
172
+ expected = Message.from_openid_args(
173
+ {'ns' => OPENID2_NS,
174
+ 'session_type' => session_type,
175
+ 'mode' => 'associate',
176
+ 'assoc_type' => @assoc_type,
177
+ })
178
+
179
+ assert_equal(expected, args)
180
+ end
181
+
182
+ def test_no_encryption_compatibility
183
+ @assoc_manager.compatibility_mode = true
184
+ session_type = 'no-encryption'
185
+ session, args = @assoc_manager.send(:create_associate_request,
186
+ @assoc_type,
187
+ session_type)
188
+
189
+ assert(session.is_a?(Consumer::NoEncryptionSession))
190
+ assert_equal(Message.from_openid_args({'mode' => 'associate',
191
+ 'assoc_type' => @assoc_type,
192
+ }), args)
193
+ end
194
+
195
+ def test_dh_sha1_compatibility
196
+ @assoc_manager.compatibility_mode = true
197
+ session_type = 'DH-SHA1'
198
+ session, args = @assoc_manager.send(:create_associate_request,
199
+ @assoc_type,
200
+ session_type)
201
+
202
+
203
+ assert(session.is_a?(Consumer::DiffieHellmanSHA1Session))
204
+
205
+ # This is a random base-64 value, so just check that it's
206
+ # present.
207
+ assert_not_nil(args.get_arg(OPENID1_NS, 'dh_consumer_public'))
208
+ args.del_arg(OPENID1_NS, 'dh_consumer_public')
209
+
210
+ # OK, session_type is set here and not for no-encryption
211
+ # compatibility
212
+ expected = Message.from_openid_args({'mode' => 'associate',
213
+ 'session_type' => 'DH-SHA1',
214
+ 'assoc_type' => @assoc_type,
215
+ })
216
+ assert_equal(expected, args)
217
+ end
218
+ end
219
+
220
+ class TestAssociationManagerExpiresIn < Test::Unit::TestCase
221
+ def expires_in_msg(val)
222
+ msg = Message.from_openid_args({'expires_in' => val})
223
+ Consumer::AssociationManager.extract_expires_in(msg)
224
+ end
225
+
226
+ def test_parse_fail
227
+ ['',
228
+ '-2',
229
+ ' 1',
230
+ ' ',
231
+ '0x00',
232
+ 'foosball',
233
+ '1\n',
234
+ '100,000,000,000',
235
+ ].each do |x|
236
+ assert_raises(ProtocolError) {expires_in_msg(x)}
237
+ end
238
+ end
239
+
240
+ def test_parse
241
+ ['0',
242
+ '1',
243
+ '1000',
244
+ '9999999',
245
+ '01',
246
+ ].each do |n|
247
+ assert_equal(n.to_i, expires_in_msg(n))
248
+ end
249
+ end
250
+ end
251
+
252
+ class TestAssociationManagerCreateSession < Test::Unit::TestCase
253
+ def test_invalid
254
+ assert_raises(ArgumentError) {
255
+ Consumer::AssociationManager.create_session('monkeys')
256
+ }
257
+ end
258
+
259
+ def test_sha256
260
+ sess = Consumer::AssociationManager.create_session('DH-SHA256')
261
+ assert(sess.is_a?(Consumer::DiffieHellmanSHA256Session))
262
+ end
263
+ end
264
+
265
+ module NegotiationTestMixin
266
+ include TestUtil
267
+ def mk_message(args)
268
+ args['ns'] = @openid_ns
269
+ Message.from_openid_args(args)
270
+ end
271
+
272
+ def call_negotiate(responses, negotiator=nil)
273
+ store = nil
274
+ compat = self.class::Compat
275
+ assoc_manager = Consumer::AssociationManager.new(store, @server_url,
276
+ compat, negotiator)
277
+ class << assoc_manager
278
+ attr_accessor :responses
279
+
280
+ def request_association(assoc_type, session_type)
281
+ m = @responses.shift
282
+ if m.is_a?(Message)
283
+ raise ServerError.from_message(m)
284
+ else
285
+ return m
286
+ end
287
+ end
288
+ end
289
+ assoc_manager.responses = responses
290
+ assoc_manager.negotiate_association
291
+ end
292
+ end
293
+
294
+ # Test the session type negotiation behavior of an OpenID 2
295
+ # consumer.
296
+ class TestOpenID2SessionNegotiation < Test::Unit::TestCase
297
+ include NegotiationTestMixin
298
+
299
+ Compat = false
300
+
301
+ def setup
302
+ @server_url = 'http://invalid/'
303
+ @openid_ns = OPENID2_NS
304
+ end
305
+
306
+ # Test the case where the response to an associate request is a
307
+ # server error or is otherwise undecipherable.
308
+ def test_bad_response
309
+ assert_log_matches('Server error when requesting an association') {
310
+ assert_equal(call_negotiate([mk_message({})]), nil)
311
+ }
312
+ end
313
+
314
+ # Test the case where the association type (assoc_type) returned
315
+ # in an unsupported-type response is absent.
316
+ def test_empty_assoc_type
317
+ msg = mk_message({'error' => 'Unsupported type',
318
+ 'error_code' => 'unsupported-type',
319
+ 'session_type' => 'new-session-type',
320
+ })
321
+
322
+ assert_log_matches('Unsupported association type',
323
+ "Server #{@server_url} responded with unsupported "\
324
+ "association session but did not supply a fallback."
325
+ ) {
326
+ assert_equal(call_negotiate([msg]), nil)
327
+ }
328
+
329
+ end
330
+
331
+ # Test the case where the session type (session_type) returned
332
+ # in an unsupported-type response is absent.
333
+ def test_empty_session_type
334
+ msg = mk_message({'error' => 'Unsupported type',
335
+ 'error_code' => 'unsupported-type',
336
+ 'assoc_type' => 'new-assoc-type',
337
+ })
338
+
339
+ assert_log_matches('Unsupported association type',
340
+ "Server #{@server_url} responded with unsupported "\
341
+ "association session but did not supply a fallback."
342
+ ) {
343
+ assert_equal(call_negotiate([msg]), nil)
344
+ }
345
+ end
346
+
347
+ # Test the case where an unsupported-type response specifies a
348
+ # preferred (assoc_type, session_type) combination that is not
349
+ # allowed by the consumer's SessionNegotiator.
350
+ def test_not_allowed
351
+ negotiator = AssociationNegotiator.new([])
352
+ negotiator.instance_eval{
353
+ @allowed_types = [['assoc_bogus', 'session_bogus']]
354
+ }
355
+ msg = mk_message({'error' => 'Unsupported type',
356
+ 'error_code' => 'unsupported-type',
357
+ 'assoc_type' => 'not-allowed',
358
+ 'session_type' => 'not-allowed',
359
+ })
360
+
361
+ assert_log_matches('Unsupported association type',
362
+ 'Server sent unsupported session/association type:') {
363
+ assert_equal(call_negotiate([msg], negotiator), nil)
364
+ }
365
+ end
366
+
367
+ # Test the case where an unsupported-type response triggers a
368
+ # retry to get an association with the new preferred type.
369
+ def test_unsupported_with_retry
370
+ msg = mk_message({'error' => 'Unsupported type',
371
+ 'error_code' => 'unsupported-type',
372
+ 'assoc_type' => 'HMAC-SHA1',
373
+ 'session_type' => 'DH-SHA1',
374
+ })
375
+
376
+ assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
377
+
378
+ assert_log_matches('Unsupported association type') {
379
+ assert_equal(assoc, call_negotiate([msg, assoc]))
380
+ }
381
+ end
382
+
383
+ # Test the case where an unsupported-typ response triggers a
384
+ # retry, but the retry fails and nil is returned instead.
385
+ def test_unsupported_with_retry_and_fail
386
+ msg = mk_message({'error' => 'Unsupported type',
387
+ 'error_code' => 'unsupported-type',
388
+ 'assoc_type' => 'HMAC-SHA1',
389
+ 'session_type' => 'DH-SHA1',
390
+ })
391
+
392
+ assert_log_matches('Unsupported association type',
393
+ "Server #{@server_url} refused") {
394
+ assert_equal(call_negotiate([msg, msg]), nil)
395
+ }
396
+ end
397
+
398
+ # Test the valid case, wherein an association is returned on the
399
+ # first attempt to get one.
400
+ def test_valid
401
+ assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
402
+
403
+ assert_log_matches() {
404
+ assert_equal(call_negotiate([assoc]), assoc)
405
+ }
406
+ end
407
+ end
408
+
409
+
410
+ # Tests for the OpenID 1 consumer association session behavior. See
411
+ # the docs for TestOpenID2SessionNegotiation. Notice that this
412
+ # class is not a subclass of the OpenID 2 tests. Instead, it uses
413
+ # many of the same inputs but inspects the log messages logged with
414
+ # oidutil.log. See the calls to self.failUnlessLogMatches. Some of
415
+ # these tests pass openid2-style messages to the openid 1
416
+ # association processing logic to be sure it ignores the extra data.
417
+ class TestOpenID1SessionNegotiation < Test::Unit::TestCase
418
+ include NegotiationTestMixin
419
+
420
+ Compat = true
421
+
422
+ def setup
423
+ @server_url = 'http://invalid/'
424
+ @openid_ns = OPENID1_NS
425
+ end
426
+
427
+ def test_bad_response
428
+ assert_log_matches('Server error when requesting an association') {
429
+ response = call_negotiate([mk_message({})])
430
+ assert_equal(nil, response)
431
+ }
432
+ end
433
+
434
+ def test_empty_assoc_type
435
+ msg = mk_message({'error' => 'Unsupported type',
436
+ 'error_code' => 'unsupported-type',
437
+ 'session_type' => 'new-session-type',
438
+ })
439
+
440
+ assert_log_matches('Server error when requesting an association') {
441
+ response = call_negotiate([msg])
442
+ assert_equal(nil, response)
443
+ }
444
+ end
445
+
446
+ def test_empty_session_type
447
+ msg = mk_message({'error' => 'Unsupported type',
448
+ 'error_code' => 'unsupported-type',
449
+ 'assoc_type' => 'new-assoc-type',
450
+ })
451
+
452
+ assert_log_matches('Server error when requesting an association') {
453
+ response = call_negotiate([msg])
454
+ assert_equal(nil, response)
455
+ }
456
+ end
457
+
458
+ def test_not_allowed
459
+ negotiator = AssociationNegotiator.new([])
460
+ negotiator.instance_eval{
461
+ @allowed_types = [['assoc_bogus', 'session_bogus']]
462
+ }
463
+
464
+ msg = mk_message({'error' => 'Unsupported type',
465
+ 'error_code' => 'unsupported-type',
466
+ 'assoc_type' => 'not-allowed',
467
+ 'session_type' => 'not-allowed',
468
+ })
469
+
470
+ assert_log_matches('Server error when requesting an association') {
471
+ response = call_negotiate([msg])
472
+ assert_equal(nil, response)
473
+ }
474
+ end
475
+
476
+ def test_unsupported_with_retry
477
+ msg = mk_message({'error' => 'Unsupported type',
478
+ 'error_code' => 'unsupported-type',
479
+ 'assoc_type' => 'HMAC-SHA1',
480
+ 'session_type' => 'DH-SHA1',
481
+ })
482
+
483
+ assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
484
+
485
+
486
+ assert_log_matches('Server error when requesting an association') {
487
+ response = call_negotiate([msg, assoc])
488
+ assert_equal(nil, response)
489
+ }
490
+ end
491
+
492
+ def test_valid
493
+ assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
494
+ assert_log_matches() {
495
+ response = call_negotiate([assoc])
496
+ assert_equal(assoc, response)
497
+ }
498
+ end
499
+ end
500
+
501
+
502
+ class TestExtractAssociation < Test::Unit::TestCase
503
+ include ProtocolErrorMixin
504
+
505
+ # An OpenID associate response (without the namespace)
506
+ DEFAULTS = {
507
+ 'expires_in' => '1000',
508
+ 'assoc_handle' => 'a handle',
509
+ 'assoc_type' => 'a type',
510
+ 'session_type' => 'a session type',
511
+ }
512
+
513
+ def setup
514
+ @assoc_manager = Consumer::AssociationManager.new(nil, nil)
515
+ end
516
+
517
+ # Make tests that ensure that an association response that is
518
+ # missing required fields will raise an Message::KeyNotFound.
519
+ #
520
+ # According to 'Association Session Response' subsection 'Common
521
+ # Response Parameters', the following fields are required for
522
+ # OpenID 2.0:
523
+ #
524
+ # * ns
525
+ # * session_type
526
+ # * assoc_handle
527
+ # * assoc_type
528
+ # * expires_in
529
+ #
530
+ # In OpenID 1, everything except 'session_type' and 'ns' are
531
+ # required.
532
+ MISSING_FIELD_SETS = ([["no_fields", []]] +
533
+ (DEFAULTS.keys.map do |f|
534
+ fields = DEFAULTS.keys
535
+ fields.delete(f)
536
+ ["missing_#{f}", fields]
537
+ end)
538
+ )
539
+
540
+ [OPENID1_NS, OPENID2_NS].each do |ns|
541
+ MISSING_FIELD_SETS.each do |name, fields|
542
+ # OpenID 1 is allowed to be missing session_type
543
+ if ns != OPENID1_NS and name != 'missing_session_type'
544
+ test = lambda do
545
+ msg = Message.new(ns)
546
+ fields.each do |field|
547
+ msg.set_arg(ns, field, DEFAULTS[field])
548
+ end
549
+ assert_raises(Message::KeyNotFound) do
550
+ @assoc_manager.send(:extract_association, msg, nil)
551
+ end
552
+ end
553
+ define_method("test_#{name}", test)
554
+ end
555
+ end
556
+ end
557
+
558
+ # assert that extracting a response that contains the given
559
+ # response session type when the request was made for the given
560
+ # request session type will raise a ProtocolError indicating
561
+ # session type mismatch
562
+ def assert_session_mismatch(req_type, resp_type, ns)
563
+ # Create an association session that has "req_type" as its
564
+ # session_type and no allowed_assoc_types
565
+ assoc_session_class = Class.new do
566
+ @session_type = req_type
567
+ def self.session_type
568
+ @session_type
569
+ end
570
+ def self.allowed_assoc_types
571
+ []
572
+ end
573
+ end
574
+ assoc_session = assoc_session_class.new
575
+
576
+ # Build an OpenID 1 or 2 association response message that has
577
+ # the specified association session type
578
+ msg = Message.new(ns)
579
+ msg.update_args(ns, DEFAULTS)
580
+ msg.set_arg(ns, 'session_type', resp_type)
581
+
582
+ # The request type and response type have been chosen to produce
583
+ # a session type mismatch.
584
+ assert_protocol_error('Session type mismatch') {
585
+ @assoc_manager.send(:extract_association, msg, assoc_session)
586
+ }
587
+ end
588
+
589
+ [['no-encryption', '', OPENID2_NS],
590
+ ['DH-SHA1', 'no-encryption', OPENID2_NS],
591
+ ['DH-SHA256', 'no-encryption', OPENID2_NS],
592
+ ['no-encryption', 'DH-SHA1', OPENID2_NS],
593
+ ['DH-SHA1', 'DH-SHA256', OPENID1_NS],
594
+ ['DH-SHA256', 'DH-SHA1', OPENID1_NS],
595
+ ['no-encryption', 'DH-SHA1', OPENID1_NS],
596
+ ].each do |req_type, resp_type, ns|
597
+ test = lambda { assert_session_mismatch(req_type, resp_type, ns) }
598
+ name = "test_mismatch_req_#{req_type}_resp_#{resp_type}_#{ns}"
599
+ define_method(name, test)
600
+ end
601
+
602
+ def test_openid1_no_encryption_fallback
603
+ # A DH-SHA1 session
604
+ assoc_session = Consumer::DiffieHellmanSHA1Session.new
605
+
606
+ # An OpenID 1 no-encryption association response
607
+ msg = Message.from_openid_args({
608
+ 'expires_in' => '1000',
609
+ 'assoc_handle' => 'a handle',
610
+ 'assoc_type' => 'HMAC-SHA1',
611
+ 'mac_key' => 'X' * 20,
612
+ })
613
+
614
+ # Should succeed
615
+ assoc = @assoc_manager.send(:extract_association, msg, assoc_session)
616
+ assert_equal('a handle', assoc.handle)
617
+ assert_equal('HMAC-SHA1', assoc.assoc_type)
618
+ assert(assoc.expires_in.between?(999, 1000))
619
+ assert('X' * 20, assoc.secret)
620
+ end
621
+ end
622
+
623
+ class GetOpenIDSessionTypeTest < Test::Unit::TestCase
624
+ include TestUtil
625
+
626
+ SERVER_URL = 'http://invalid/'
627
+
628
+ def do_test(expected_session_type, session_type_value)
629
+ # Create a Message with just 'session_type' in it, since
630
+ # that's all this function will use. 'session_type' may be
631
+ # absent if it's set to None.
632
+ args = {}
633
+ if !session_type_value.nil?
634
+ args['session_type'] = session_type_value
635
+ end
636
+ message = Message.from_openid_args(args)
637
+ assert(message.is_openid1)
638
+
639
+ assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
640
+ actual_session_type = assoc_manager.send(:get_openid1_session_type,
641
+ message)
642
+ error_message = ("Returned session type parameter #{session_type_value}"\
643
+ "was expected to yield session type "\
644
+ "#{expected_session_type}, but yielded "\
645
+ "#{actual_session_type}")
646
+ assert_equal(expected_session_type, actual_session_type, error_message)
647
+ end
648
+
649
+
650
+ [['nil', 'no-encryption', nil],
651
+ ['empty', 'no-encryption', ''],
652
+ ['dh_sha1', 'DH-SHA1', 'DH-SHA1'],
653
+ ['dh_sha256', 'DH-SHA256', 'DH-SHA256'],
654
+ ].each {|name, expected, input|
655
+ # Define a test method that will check what session type will be
656
+ # used if the OpenID 1 response to an associate call sets the
657
+ # 'session_type' field to `session_type_value`
658
+ test = lambda {assert_log_matches() { do_test(expected, input) } }
659
+ define_method("test_#{name}", &test)
660
+ }
661
+
662
+ # This one's different because it expects log messages
663
+ def test_explicit_no_encryption
664
+ assert_log_matches("WARNING: #{SERVER_URL} sent 'no-encryption'"){
665
+ do_test('no-encryption', 'no-encryption')
666
+ }
667
+ end
668
+ end
669
+
670
+ class ExtractAssociationTest < Test::Unit::TestCase
671
+ include ProtocolErrorMixin
672
+
673
+ SERVER_URL = 'http://invalid/'
674
+
675
+ def setup
676
+ @session_type = 'testing-session'
677
+
678
+ # This must something that works for Association::from_expires_in
679
+ @assoc_type = 'HMAC-SHA1'
680
+
681
+ @assoc_handle = 'testing-assoc-handle'
682
+
683
+ # These arguments should all be valid
684
+ @assoc_response =
685
+ Message.from_openid_args({
686
+ 'expires_in' => '1000',
687
+ 'assoc_handle' => @assoc_handle,
688
+ 'assoc_type' => @assoc_type,
689
+ 'session_type' => @session_type,
690
+ 'ns' => OPENID2_NS,
691
+ })
692
+ assoc_session_cls = Class.new do
693
+ class << self
694
+ attr_accessor :allowed_assoc_types, :session_type
695
+ end
696
+
697
+ attr_reader :extract_secret_called, :secret
698
+ def initialize
699
+ @extract_secret_called = false
700
+ @secret = 'shhhhh!'
701
+ end
702
+
703
+ def extract_secret(_)
704
+ @extract_secret_called = true
705
+ @secret
706
+ end
707
+ end
708
+ @assoc_session = assoc_session_cls.new
709
+ @assoc_session.class.allowed_assoc_types = [@assoc_type]
710
+ @assoc_session.class.session_type = @session_type
711
+
712
+ @assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
713
+ end
714
+
715
+ def call_extract
716
+ @assoc_manager.send(:extract_association,
717
+ @assoc_response, @assoc_session)
718
+ end
719
+
720
+ # Handle a full successful association response
721
+ def test_works_with_good_fields
722
+ assoc = call_extract
723
+ assert(@assoc_session.extract_secret_called)
724
+ assert_equal(@assoc_session.secret, assoc.secret)
725
+ assert_equal(1000, assoc.lifetime)
726
+ assert_equal(@assoc_handle, assoc.handle)
727
+ assert_equal(@assoc_type, assoc.assoc_type)
728
+ end
729
+
730
+ def test_bad_assoc_type
731
+ # Make sure that the assoc type in the response is not valid
732
+ # for the given session.
733
+ @assoc_session.class.allowed_assoc_types = []
734
+ assert_protocol_error('Unsupported assoc_type for sess') {call_extract}
735
+ end
736
+
737
+ def test_bad_expires_in
738
+ # Invalid value for expires_in should cause failure
739
+ @assoc_response.set_arg(OPENID_NS, 'expires_in', 'forever')
740
+ assert_protocol_error('Invalid expires_in') {call_extract}
741
+ end
742
+ end
743
+
744
+ class TestExtractAssociationDiffieHellman < Test::Unit::TestCase
745
+ include ProtocolErrorMixin
746
+
747
+ SECRET = 'x' * 20
748
+
749
+ def setup
750
+ @assoc_manager = Consumer::AssociationManager.new(nil, nil)
751
+ end
752
+
753
+ def setup_dh
754
+ sess, message = @assoc_manager.send(:create_associate_request,
755
+ 'HMAC-SHA1', 'DH-SHA1')
756
+
757
+ server_dh = DiffieHellman.new
758
+ cons_dh = sess.instance_variable_get('@dh')
759
+
760
+ enc_mac_key = server_dh.xor_secret(CryptUtil.method(:sha1),
761
+ cons_dh.public, SECRET)
762
+
763
+ server_resp = {
764
+ 'dh_server_public' => CryptUtil.num_to_base64(server_dh.public),
765
+ 'enc_mac_key' => Util.to_base64(enc_mac_key),
766
+ 'assoc_type' => 'HMAC-SHA1',
767
+ 'assoc_handle' => 'handle',
768
+ 'expires_in' => '1000',
769
+ 'session_type' => 'DH-SHA1',
770
+ }
771
+ if @assoc_manager.instance_variable_get(:@compatibility_mode)
772
+ server_resp['ns'] = OPENID2_NS
773
+ end
774
+ return [sess, Message.from_openid_args(server_resp)]
775
+ end
776
+
777
+ def test_success
778
+ sess, server_resp = setup_dh
779
+ ret = @assoc_manager.send(:extract_association, server_resp, sess)
780
+ assert(!ret.nil?)
781
+ assert_equal(ret.assoc_type, 'HMAC-SHA1')
782
+ assert_equal(ret.secret, SECRET)
783
+ assert_equal(ret.handle, 'handle')
784
+ assert_equal(ret.lifetime, 1000)
785
+ end
786
+
787
+ def test_openid2success
788
+ # Use openid 1 type in endpoint so _setUpDH checks
789
+ # compatibility mode state properly
790
+ @assoc_manager.instance_variable_set('@compatibility_mode', true)
791
+ test_success()
792
+ end
793
+
794
+ def test_bad_dh_values
795
+ sess, server_resp = setup_dh
796
+ server_resp.set_arg(OPENID_NS, 'enc_mac_key', '\x00\x00\x00')
797
+ assert_protocol_error('Malformed response for') {
798
+ @assoc_manager.send(:extract_association, server_resp, sess)
799
+ }
800
+ end
801
+ end
802
+
803
+ class TestAssocManagerGetAssociation < Test::Unit::TestCase
804
+ include FetcherMixin
805
+ include TestUtil
806
+
807
+ attr_reader :negotiate_association
808
+
809
+ def setup
810
+ @server_url = 'http://invalid/'
811
+ @store = Store::Memory.new
812
+ @assoc_manager = Consumer::AssociationManager.new(@store, @server_url)
813
+ @assoc_manager.extend(Const)
814
+ @assoc = Association.new('handle', 'secret', Time.now, 10000,
815
+ 'HMAC-SHA1')
816
+ end
817
+
818
+ def set_negotiate_response(assoc)
819
+ @assoc_manager.const(:negotiate_association, assoc)
820
+ end
821
+
822
+ def test_not_in_store_no_response
823
+ set_negotiate_response(nil)
824
+ assert_equal(nil, @assoc_manager.get_association)
825
+ end
826
+
827
+ def test_not_in_store_negotiate_assoc
828
+ # Not stored beforehand:
829
+ stored_assoc = @store.get_association(@server_url, @assoc.handle)
830
+ assert_equal(nil, stored_assoc)
831
+
832
+ # Returned from associate call:
833
+ set_negotiate_response(@assoc)
834
+ assert_equal(@assoc, @assoc_manager.get_association)
835
+
836
+ # It should have been stored:
837
+ stored_assoc = @store.get_association(@server_url, @assoc.handle)
838
+ assert_equal(@assoc, stored_assoc)
839
+ end
840
+
841
+ def test_in_store_no_response
842
+ set_negotiate_response(nil)
843
+ @store.store_association(@server_url, @assoc)
844
+ assert_equal(@assoc, @assoc_manager.get_association)
845
+ end
846
+
847
+ def test_request_assoc_with_status_error
848
+ fetcher_class = Class.new do
849
+ define_method(:fetch) do |*args|
850
+ MockResponse.new(500, '')
851
+ end
852
+ end
853
+ with_fetcher(fetcher_class.new) do
854
+ assert_log_matches('Got HTTP status error when requesting') {
855
+ result = @assoc_manager.send(:request_association, 'HMAC-SHA1',
856
+ 'no-encryption')
857
+ assert(result.nil?)
858
+ }
859
+ end
860
+ end
861
+ end
862
+
863
+ class TestAssocManagerRequestAssociation < Test::Unit::TestCase
864
+ include FetcherMixin
865
+ include TestUtil
866
+
867
+ def setup
868
+ @assoc_manager = Consumer::AssociationManager.new(nil, 'http://invalid/')
869
+ @assoc_type = 'HMAC-SHA1'
870
+ @session_type = 'no-encryption'
871
+ @message = Message.new(OPENID2_NS)
872
+ @message.update_args(OPENID_NS, {
873
+ 'assoc_type' => @assoc_type,
874
+ 'session_type' => @session_type,
875
+ 'assoc_handle' => 'kaboodle',
876
+ 'expires_in' => '1000',
877
+ 'mac_key' => 'X' * 20,
878
+ })
879
+ end
880
+
881
+ def make_request
882
+ kv = @message.to_kvform
883
+ fetcher_class = Class.new do
884
+ define_method(:fetch) do |*args|
885
+ MockResponse.new(200, kv)
886
+ end
887
+ end
888
+ with_fetcher(fetcher_class.new) do
889
+ @assoc_manager.send(:request_association, @assoc_type, @session_type)
890
+ end
891
+ end
892
+
893
+ # The association we get is from valid processing of our result,
894
+ # and that no errors are raised
895
+ def test_success
896
+ assert_equal('kaboodle', make_request.handle)
897
+ end
898
+
899
+ # A missing parameter gets translated into a log message and
900
+ # causes the method to return nil
901
+ def test_missing_fields
902
+ @message.del_arg(OPENID_NS, 'assoc_type')
903
+ assert_log_matches('Missing required par') {
904
+ assert_equal(nil, make_request)
905
+ }
906
+ end
907
+
908
+ # A bad value results in a log message and causes the method to
909
+ # return nil
910
+ def test_protocol_error
911
+ @message.set_arg(OPENID_NS, 'expires_in', 'goats')
912
+ assert_log_matches('Protocol error processing') {
913
+ assert_equal(nil, make_request)
914
+ }
915
+ end
916
+ end
917
+
918
+ end