pelle-ruby-openid 2.1.8

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