nov-ruby-openid 2.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. data/CHANGELOG +215 -0
  2. data/CHANGES-2.1.0 +36 -0
  3. data/INSTALL +47 -0
  4. data/LICENSE +210 -0
  5. data/NOTICE +2 -0
  6. data/README +81 -0
  7. data/Rakefile +98 -0
  8. data/UPGRADE +127 -0
  9. data/VERSION +1 -0
  10. data/contrib/google/ruby-openid-apps-discovery-1.0.gem +0 -0
  11. data/contrib/google/ruby-openid-apps-discovery-1.01.gem +0 -0
  12. data/examples/README +32 -0
  13. data/examples/active_record_openid_store/README +58 -0
  14. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  15. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  16. data/examples/active_record_openid_store/init.rb +8 -0
  17. data/examples/active_record_openid_store/lib/association.rb +10 -0
  18. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  19. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  20. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  21. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  22. data/examples/discover +49 -0
  23. data/examples/rails_openid/README +153 -0
  24. data/examples/rails_openid/Rakefile +10 -0
  25. data/examples/rails_openid/app/controllers/application.rb +4 -0
  26. data/examples/rails_openid/app/controllers/consumer_controller.rb +122 -0
  27. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  28. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  29. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  30. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  31. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  32. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  33. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  34. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  35. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  36. data/examples/rails_openid/config/boot.rb +19 -0
  37. data/examples/rails_openid/config/database.yml +74 -0
  38. data/examples/rails_openid/config/environment.rb +54 -0
  39. data/examples/rails_openid/config/environments/development.rb +19 -0
  40. data/examples/rails_openid/config/environments/production.rb +19 -0
  41. data/examples/rails_openid/config/environments/test.rb +19 -0
  42. data/examples/rails_openid/config/routes.rb +24 -0
  43. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  44. data/examples/rails_openid/public/.htaccess +40 -0
  45. data/examples/rails_openid/public/404.html +8 -0
  46. data/examples/rails_openid/public/500.html +8 -0
  47. data/examples/rails_openid/public/dispatch.cgi +12 -0
  48. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  49. data/examples/rails_openid/public/dispatch.rb +12 -0
  50. data/examples/rails_openid/public/favicon.ico +0 -0
  51. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  52. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  53. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  54. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  55. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  56. data/examples/rails_openid/public/robots.txt +1 -0
  57. data/examples/rails_openid/script/about +3 -0
  58. data/examples/rails_openid/script/breakpointer +3 -0
  59. data/examples/rails_openid/script/console +3 -0
  60. data/examples/rails_openid/script/destroy +3 -0
  61. data/examples/rails_openid/script/generate +3 -0
  62. data/examples/rails_openid/script/performance/benchmarker +3 -0
  63. data/examples/rails_openid/script/performance/profiler +3 -0
  64. data/examples/rails_openid/script/plugin +3 -0
  65. data/examples/rails_openid/script/process/reaper +3 -0
  66. data/examples/rails_openid/script/process/spawner +3 -0
  67. data/examples/rails_openid/script/process/spinner +3 -0
  68. data/examples/rails_openid/script/runner +3 -0
  69. data/examples/rails_openid/script/server +3 -0
  70. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  71. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  72. data/examples/rails_openid/test/test_helper.rb +28 -0
  73. data/lib/hmac/hmac.rb +112 -0
  74. data/lib/hmac/sha1.rb +11 -0
  75. data/lib/hmac/sha2.rb +25 -0
  76. data/lib/openid.rb +20 -0
  77. data/lib/openid/association.rb +249 -0
  78. data/lib/openid/consumer.rb +395 -0
  79. data/lib/openid/consumer/associationmanager.rb +344 -0
  80. data/lib/openid/consumer/checkid_request.rb +186 -0
  81. data/lib/openid/consumer/discovery.rb +497 -0
  82. data/lib/openid/consumer/discovery_manager.rb +123 -0
  83. data/lib/openid/consumer/html_parse.rb +134 -0
  84. data/lib/openid/consumer/idres.rb +523 -0
  85. data/lib/openid/consumer/responses.rb +148 -0
  86. data/lib/openid/cryptutil.rb +115 -0
  87. data/lib/openid/dh.rb +89 -0
  88. data/lib/openid/extension.rb +39 -0
  89. data/lib/openid/extensions/ax.rb +539 -0
  90. data/lib/openid/extensions/oauth.rb +91 -0
  91. data/lib/openid/extensions/pape.rb +179 -0
  92. data/lib/openid/extensions/sreg.rb +277 -0
  93. data/lib/openid/extensions/ui.rb +53 -0
  94. data/lib/openid/extras.rb +11 -0
  95. data/lib/openid/fetchers.rb +258 -0
  96. data/lib/openid/kvform.rb +136 -0
  97. data/lib/openid/kvpost.rb +58 -0
  98. data/lib/openid/message.rb +553 -0
  99. data/lib/openid/protocolerror.rb +8 -0
  100. data/lib/openid/server.rb +1544 -0
  101. data/lib/openid/store/filesystem.rb +271 -0
  102. data/lib/openid/store/interface.rb +75 -0
  103. data/lib/openid/store/memcache.rb +107 -0
  104. data/lib/openid/store/memory.rb +84 -0
  105. data/lib/openid/store/nonce.rb +68 -0
  106. data/lib/openid/trustroot.rb +349 -0
  107. data/lib/openid/urinorm.rb +75 -0
  108. data/lib/openid/util.rb +110 -0
  109. data/lib/openid/yadis/accept.rb +148 -0
  110. data/lib/openid/yadis/constants.rb +21 -0
  111. data/lib/openid/yadis/discovery.rb +153 -0
  112. data/lib/openid/yadis/filters.rb +205 -0
  113. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  114. data/lib/openid/yadis/parsehtml.rb +45 -0
  115. data/lib/openid/yadis/services.rb +42 -0
  116. data/lib/openid/yadis/xrds.rb +155 -0
  117. data/lib/openid/yadis/xri.rb +90 -0
  118. data/lib/openid/yadis/xrires.rb +99 -0
  119. data/setup.rb +1551 -0
  120. data/test/data/accept.txt +124 -0
  121. data/test/data/dh.txt +29 -0
  122. data/test/data/example-xrds.xml +14 -0
  123. data/test/data/linkparse.txt +587 -0
  124. data/test/data/n2b64 +650 -0
  125. data/test/data/test1-discover.txt +137 -0
  126. data/test/data/test1-parsehtml.txt +152 -0
  127. data/test/data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/data/test_discover/openid.html +11 -0
  129. data/test/data/test_discover/openid2.html +11 -0
  130. data/test/data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/data/test_discover/openid_utf8.html +11 -0
  138. data/test/data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/data/test_discover/yadis_idp.xml +12 -0
  144. data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/data/test_xrds/README +12 -0
  148. data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/data/test_xrds/no-xrd.xml +7 -0
  152. data/test/data/test_xrds/not-xrds.xml +2 -0
  153. data/test/data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/data/test_xrds/ref.xrds +109 -0
  155. data/test/data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/data/test_xrds/spoof1.xrds +25 -0
  157. data/test/data/test_xrds/spoof2.xrds +25 -0
  158. data/test/data/test_xrds/spoof3.xrds +37 -0
  159. data/test/data/test_xrds/status222.xrds +9 -0
  160. data/test/data/test_xrds/subsegments.xrds +58 -0
  161. data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/data/trustroot.txt +153 -0
  163. data/test/data/urinorm.txt +79 -0
  164. data/test/discoverdata.rb +131 -0
  165. data/test/test_accept.rb +170 -0
  166. data/test/test_association.rb +266 -0
  167. data/test/test_associationmanager.rb +917 -0
  168. data/test/test_ax.rb +690 -0
  169. data/test/test_checkid_request.rb +294 -0
  170. data/test/test_consumer.rb +257 -0
  171. data/test/test_cryptutil.rb +119 -0
  172. data/test/test_dh.rb +86 -0
  173. data/test/test_discover.rb +852 -0
  174. data/test/test_discovery_manager.rb +262 -0
  175. data/test/test_extension.rb +46 -0
  176. data/test/test_extras.rb +35 -0
  177. data/test/test_fetchers.rb +565 -0
  178. data/test/test_filters.rb +270 -0
  179. data/test/test_idres.rb +963 -0
  180. data/test/test_kvform.rb +165 -0
  181. data/test/test_kvpost.rb +65 -0
  182. data/test/test_linkparse.rb +101 -0
  183. data/test/test_message.rb +1116 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +175 -0
  186. data/test/test_openid_yadis.rb +178 -0
  187. data/test/test_pape.rb +247 -0
  188. data/test/test_parsehtml.rb +80 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2457 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +298 -0
  193. data/test/test_trustroot.rb +113 -0
  194. data/test/test_ui.rb +93 -0
  195. data/test/test_urinorm.rb +35 -0
  196. data/test/test_util.rb +145 -0
  197. data/test/test_xrds.rb +169 -0
  198. data/test/test_xri.rb +48 -0
  199. data/test/test_xrires.rb +63 -0
  200. data/test/test_yadis_discovery.rb +220 -0
  201. data/test/testutil.rb +127 -0
  202. data/test/util.rb +53 -0
  203. metadata +336 -0
@@ -0,0 +1,170 @@
1
+
2
+ require 'test/unit'
3
+ require 'openid/yadis/accept'
4
+ require 'openid/extras'
5
+ require 'openid/util'
6
+
7
+ module OpenID
8
+
9
+ class AcceptTest < Test::Unit::TestCase
10
+ include TestDataMixin
11
+
12
+ def getTestData()
13
+ # Read the test data off of disk
14
+ #
15
+ # () -> [(int, str)]
16
+ lines = read_data_file('accept.txt')
17
+ line_no = 1
18
+ return lines.collect { |line|
19
+ pair = [line_no, line]
20
+ line_no += 1
21
+ pair
22
+ }
23
+ end
24
+
25
+ def chunk(lines)
26
+ # Return groups of lines separated by whitespace or comments
27
+ #
28
+ # [(int, str)] -> [[(int, str)]]
29
+ chunks = []
30
+ chunk = []
31
+ lines.each { |lineno, line|
32
+ stripped = line.strip()
33
+ if (stripped == '') or stripped.starts_with?('#')
34
+ if chunk.length > 0
35
+ chunks << chunk
36
+ chunk = []
37
+ end
38
+ else
39
+ chunk << [lineno, stripped]
40
+ end
41
+ }
42
+
43
+ if chunk.length > 0
44
+ chunks << chunk
45
+ end
46
+
47
+ return chunks
48
+ end
49
+
50
+ def parseLines(chunk)
51
+ # Take the given chunk of lines and turn it into a test data
52
+ # dictionary
53
+ #
54
+ # [(int, str)] -> {str:(int, str)}
55
+ items = {}
56
+ chunk.each { |lineno, line|
57
+ header, data = line.split(':', 2)
58
+ header = header.downcase
59
+ items[header] = [lineno, data.strip]
60
+ }
61
+ return items
62
+ end
63
+
64
+ def parseAvailable(available_text)
65
+ # Parse an Available: line's data
66
+ #
67
+ # str -> [str]
68
+ return available_text.split(',', -1).collect { |s| s.strip }
69
+ end
70
+
71
+ def parseExpected(expected_text)
72
+ # Parse an Expected: line's data
73
+ #
74
+ # str -> [(str, float)]
75
+ expected = []
76
+ if expected_text != ''
77
+ expected_text.split(',', -1).each { |chunk|
78
+ chunk = chunk.strip
79
+ mtype, qstuff = chunk.split(';', -1)
80
+ mtype = mtype.strip
81
+ Util.assert(!mtype.index('/').nil?)
82
+ qstuff = qstuff.strip
83
+ q, qstr = qstuff.split('=', -1)
84
+ Util.assert(q == 'q')
85
+ qval = qstr.to_f
86
+ expected << [mtype, qval]
87
+ }
88
+ end
89
+
90
+ return expected
91
+ end
92
+
93
+ def test_accept_headers
94
+ lines = getTestData()
95
+ chunks = chunk(lines)
96
+ data_sets = chunks.collect { |chunk| parseLines(chunk) }
97
+ cases = []
98
+ data_sets.each { |data|
99
+ lnos = []
100
+ lno, header = data['accept']
101
+ lnos << lno
102
+ lno, avail_data = data['available']
103
+ lnos << lno
104
+ begin
105
+ available = parseAvailable(avail_data)
106
+ rescue
107
+ print 'On line', lno
108
+ raise
109
+ end
110
+
111
+ lno, exp_data = data['expected']
112
+ lnos << lno
113
+ begin
114
+ expected = parseExpected(exp_data)
115
+ rescue
116
+ print 'On line', lno
117
+ raise
118
+ end
119
+
120
+ descr = sprintf('MatchAcceptTest for lines %s', lnos)
121
+
122
+ # Test:
123
+ accepted = Yadis.parse_accept_header(header)
124
+ actual = Yadis.match_types(accepted, available)
125
+ assert_equal(expected, actual)
126
+
127
+ assert_equal(Yadis.get_acceptable(header, available),
128
+ expected.collect { |mtype, _| mtype })
129
+ }
130
+ end
131
+
132
+ def test_generate_accept_header
133
+ # TODO: move this into a test case file and write parsing code
134
+ # for it.
135
+
136
+ # Form: [input_array, expected_header_string]
137
+ cases = [
138
+ # Empty input list
139
+ [[], ""],
140
+ # Content type name only; no q value
141
+ [["test"], "test"],
142
+ # q = 1.0 should be omitted from the header
143
+ [[["test", 1.0]], "test"],
144
+ # Test conversion of float to string
145
+ [["test", ["with_q", 0.8]], "with_q; q=0.8, test"],
146
+ # Allow string q values, too
147
+ [["test", ["with_q_str", "0.7"]], "with_q_str; q=0.7, test"],
148
+ # Test q values out of bounds
149
+ [[["test", -1.0]], nil],
150
+ [[["test", 1.1]], nil],
151
+ # Test sorting of types by q value
152
+ [[["middle", 0.5], ["min", 0.1], "max"],
153
+ "min; q=0.1, middle; q=0.5, max"],
154
+
155
+ ].each { |input, expected_header|
156
+
157
+ if expected_header.nil?
158
+ assert_raise(ArgumentError) {
159
+ Yadis.generate_accept_header(*input)
160
+ }
161
+ else
162
+ assert_equal(expected_header, Yadis.generate_accept_header(*input),
163
+ [input, expected_header].inspect)
164
+ end
165
+ }
166
+ end
167
+
168
+ end
169
+
170
+ end
@@ -0,0 +1,266 @@
1
+ require "test/unit"
2
+ require "openid/association"
3
+
4
+ module OpenID
5
+ class AssociationTestCase < Test::Unit::TestCase
6
+ def setup
7
+ # Use this funny way of getting a time so that it does not have
8
+ # fractional seconds, and so can be serialized exactly using our
9
+ # standard code.
10
+ issued = Time.at(Time.now.to_i)
11
+ lifetime = 600
12
+
13
+ @assoc = Association.new('handle', 'secret', issued,
14
+ lifetime, 'HMAC-SHA1')
15
+ end
16
+
17
+ def test_round_trip
18
+ assoc2 = Association.deserialize(@assoc.serialize())
19
+ [:handle, :secret, :lifetime, :assoc_type].each do |attr|
20
+ assert_equal(@assoc.send(attr), assoc2.send(attr))
21
+ end
22
+ end
23
+
24
+ def test_deserialize_failure
25
+ field_list = Util.kv_to_seq(@assoc.serialize)
26
+ kv = Util.seq_to_kv(field_list + [['monkeys', 'funny']])
27
+ assert_raises(ProtocolError) {
28
+ Association.deserialize(kv)
29
+ }
30
+
31
+ bad_version_list = field_list.dup
32
+ bad_version_list[0] = ['version', 'moon']
33
+ bad_version_kv = Util.seq_to_kv(bad_version_list)
34
+ assert_raises(ProtocolError) {
35
+ Association.deserialize(bad_version_kv)
36
+ }
37
+ end
38
+
39
+ def test_serialization_identity
40
+ assoc2 = Association.deserialize(@assoc.serialize)
41
+ assert_equal(@assoc, assoc2)
42
+ end
43
+
44
+ def test_expires_in
45
+ # Allow one second of slop
46
+ assert(@assoc.expires_in.between?(599,600))
47
+ assert(@assoc.expires_in(Time.now.to_i).between?(599,600))
48
+ assert_equal(0,@assoc.expires_in(Time.now.to_i + 10000),"negative expires_in")
49
+ end
50
+
51
+ def test_from_expires_in
52
+ start_time = Time.now
53
+ expires_in = @assoc.expires_in
54
+ assoc = Association.from_expires_in(expires_in,
55
+ @assoc.handle,
56
+ @assoc.secret,
57
+ @assoc.assoc_type)
58
+
59
+ # Allow one second of slop here for code execution time
60
+ assert_in_delta(1, assoc.expires_in, @assoc.expires_in)
61
+ [:handle, :secret, :assoc_type].each do |attr|
62
+ assert_equal(@assoc.send(attr), assoc.send(attr))
63
+ end
64
+
65
+ # Make sure the issued time is near the start
66
+ assert(assoc.issued >= start_time)
67
+ assert_in_delta(1, assoc.issued.to_f, start_time.to_f)
68
+ end
69
+
70
+ def test_sign_sha1
71
+ pairs = [['key1', 'value1'],
72
+ ['key2', 'value2']]
73
+
74
+ [['HMAC-SHA256', "\xfd\xaa\xfe;\xac\xfc*\x988\xad\x05d6-"\
75
+ "\xeaVy\xd5\xa5Z.<\xa9\xed\x18\x82\\$"\
76
+ "\x95x\x1c&"],
77
+ ['HMAC-SHA1', "\xe0\x1bv\x04\xf1G\xc0\xbb\x7f\x9a\x8b"\
78
+ "\xe9\xbc\xee}\\\xe5\xbb7*"],
79
+ ].each do |assoc_type, expected|
80
+ assoc = Association.from_expires_in(3600, "handle", 'very_secret',
81
+ assoc_type)
82
+ sig = assoc.sign(pairs)
83
+ assert_equal(sig, expected)
84
+
85
+ m = Message.new(OPENID2_NS)
86
+ pairs.each { |k, v|
87
+ m.set_arg(OPENID_NS, k, v)
88
+ }
89
+ m.set_arg(BARE_NS, "not_an_openid_arg", "bogus")
90
+
91
+ signed_m = assoc.sign_message(m)
92
+ assert(signed_m.has_key?(OPENID_NS, 'sig'))
93
+ assert_equal(signed_m.get_arg(OPENID_NS, 'signed'),
94
+ 'assoc_handle,key1,key2,ns,signed')
95
+ end
96
+ end
97
+
98
+ def test_sign_message_with_sig
99
+ assoc = Association.from_expires_in(3600, "handle", "very_secret",
100
+ "HMAC-SHA1")
101
+ m = Message.new(OPENID2_NS)
102
+ m.set_arg(OPENID_NS, 'sig', 'noise')
103
+ assert_raises(ArgumentError) {
104
+ assoc.sign_message(m)
105
+ }
106
+ end
107
+
108
+ def test_sign_message_with_signed
109
+ assoc = Association.from_expires_in(3600, "handle", "very_secret",
110
+ "HMAC-SHA1")
111
+ m = Message.new(OPENID2_NS)
112
+ m.set_arg(OPENID_NS, 'signed', 'fields')
113
+ assert_raises(ArgumentError) {
114
+ assoc.sign_message(m)
115
+ }
116
+ end
117
+
118
+ def test_sign_different_assoc_handle
119
+ assoc = Association.from_expires_in(3600, "handle", "very_secret",
120
+ "HMAC-SHA1")
121
+ m = Message.new(OPENID2_NS)
122
+ m.set_arg(OPENID_NS, 'assoc_handle', 'different')
123
+ assert_raises(ArgumentError) {
124
+ assoc.sign_message(m)
125
+ }
126
+ end
127
+
128
+ def test_sign_bad_assoc_type
129
+ @assoc.instance_eval { @assoc_type = 'Cookies' }
130
+ assert_raises(ProtocolError) {
131
+ @assoc.sign([])
132
+ }
133
+ end
134
+
135
+ def test_make_pairs
136
+ msg = Message.new(OPENID2_NS)
137
+ msg.update_args(OPENID2_NS, {
138
+ 'mode' => 'id_res',
139
+ 'identifier' => '=example',
140
+ 'signed' => 'identifier,mode',
141
+ 'sig' => 'cephalopod',
142
+ })
143
+ msg.update_args(BARE_NS, {'xey' => 'value'})
144
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
145
+ "HMAC-SHA1")
146
+ pairs = assoc.make_pairs(msg)
147
+ assert_equal([['identifier', '=example'],
148
+ ['mode', 'id_res']], pairs)
149
+ end
150
+
151
+ def test_check_message_signature_no_signed
152
+ m = Message.new(OPENID2_NS)
153
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
154
+ 'identifier' => '=example',
155
+ 'sig' => 'coyote',
156
+ })
157
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
158
+ "HMAC-SHA1")
159
+ assert_raises(ProtocolError) {
160
+ assoc.check_message_signature(m)
161
+ }
162
+ end
163
+
164
+ def test_check_message_signature_no_sig
165
+ m = Message.new(OPENID2_NS)
166
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
167
+ 'identifier' => '=example',
168
+ 'signed' => 'mode',
169
+ })
170
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
171
+ "HMAC-SHA1")
172
+ assert_raises(ProtocolError) {
173
+ assoc.check_message_signature(m)
174
+ }
175
+ end
176
+
177
+ def test_check_message_signature_bad_sig
178
+ m = Message.new(OPENID2_NS)
179
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
180
+ 'identifier' => '=example',
181
+ 'signed' => 'mode',
182
+ 'sig' => Util.to_base64('coyote'),
183
+ })
184
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
185
+ "HMAC-SHA1")
186
+ assert(!assoc.check_message_signature(m))
187
+ end
188
+
189
+ def test_check_message_signature_good_sig
190
+ m = Message.new(OPENID2_NS)
191
+ m.update_args(OPENID2_NS, {'mode' => 'id_res',
192
+ 'identifier' => '=example',
193
+ 'signed' => 'mode',
194
+ 'sig' => Util.to_base64('coyote'),
195
+ })
196
+ assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
197
+ "HMAC-SHA1")
198
+ class << assoc
199
+ # Override sign, because it's already tested elsewhere
200
+ def sign(pairs)
201
+ "coyote"
202
+ end
203
+ end
204
+
205
+ assert(assoc.check_message_signature(m))
206
+ end
207
+ end
208
+
209
+ class AssociationNegotiatorTestCase < Test::Unit::TestCase
210
+ def assert_equal_under(item1, item2)
211
+ val1 = yield(item1)
212
+ val2 = yield(item2)
213
+ assert_equal(val1, val2)
214
+ end
215
+
216
+ def test_copy
217
+ neg = AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1']])
218
+ neg2 = neg.copy
219
+ assert_equal_under(neg, neg2) {|n| n.instance_eval{@allowed_types} }
220
+ assert(neg.object_id != neg2.object_id)
221
+ end
222
+
223
+ def test_add_allowed
224
+ neg = AssociationNegotiator.new([])
225
+ assert(!neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
226
+ assert(!neg.allowed?('HMAC-SHA1', 'no-encryption'))
227
+ assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
228
+ assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
229
+ neg.add_allowed_type('HMAC-SHA1')
230
+ assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
231
+ assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))
232
+ assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
233
+ assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
234
+ neg.add_allowed_type('HMAC-SHA256', 'DH-SHA256')
235
+ assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
236
+ assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))
237
+ assert(neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
238
+ assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
239
+ assert_equal(neg.get_allowed_type, ['HMAC-SHA1', 'DH-SHA1'])
240
+ end
241
+
242
+ def test_bad_assoc_type
243
+ assert_raises(ProtocolError) {
244
+ AssociationNegotiator.new([['OMG', 'Ponies']])
245
+ }
246
+ end
247
+
248
+ def test_bad_session_type
249
+ assert_raises(ProtocolError) {
250
+ AssociationNegotiator.new([['HMAC-SHA1', 'OMG-Ponies']])
251
+ }
252
+ end
253
+
254
+ def test_default_negotiator
255
+ assert_equal(DefaultNegotiator.get_allowed_type,
256
+ ['HMAC-SHA1', 'DH-SHA1'])
257
+ assert(DefaultNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))
258
+ end
259
+
260
+ def test_encrypted_negotiator
261
+ assert_equal(EncryptedNegotiator.get_allowed_type,
262
+ ['HMAC-SHA1', 'DH-SHA1'])
263
+ assert(!EncryptedNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))
264
+ end
265
+ end
266
+ end
@@ -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