entp-ruby-openid 2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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