ruby-openid 1.1.4 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-openid might be problematic. Click here for more details.

Files changed (207) hide show
  1. data/INSTALL +0 -9
  2. data/README +21 -22
  3. data/UPGRADE +117 -0
  4. data/admin/runtests.rb +36 -0
  5. data/examples/README +13 -21
  6. data/examples/active_record_openid_store/README +8 -3
  7. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +4 -8
  8. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  9. data/examples/active_record_openid_store/lib/association.rb +2 -0
  10. data/examples/active_record_openid_store/lib/openid_ar_store.rb +22 -47
  11. data/examples/active_record_openid_store/test/store_test.rb +78 -48
  12. data/examples/discover +46 -0
  13. data/examples/{rails_server → rails_openid}/README +0 -0
  14. data/examples/{rails_server → rails_openid}/Rakefile +0 -0
  15. data/examples/{rails_server → rails_openid}/app/controllers/application.rb +0 -0
  16. data/examples/rails_openid/app/controllers/consumer_controller.rb +115 -0
  17. data/examples/{rails_server → rails_openid}/app/controllers/login_controller.rb +10 -2
  18. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  19. data/examples/{rails_server → rails_openid}/app/helpers/application_helper.rb +0 -0
  20. data/examples/{rails_server → rails_openid}/app/helpers/login_helper.rb +0 -0
  21. data/examples/{rails_server → rails_openid}/app/helpers/server_helper.rb +0 -0
  22. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  23. data/examples/rails_openid/app/views/consumer/start.rhtml +8 -0
  24. data/examples/{rails_server → rails_openid}/app/views/layouts/server.rhtml +0 -0
  25. data/examples/{rails_server → rails_openid}/app/views/login/index.rhtml +1 -1
  26. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  27. data/examples/{rails_server → rails_openid}/config/boot.rb +0 -0
  28. data/examples/{rails_server → rails_openid}/config/database.yml +0 -0
  29. data/examples/{rails_server → rails_openid}/config/environment.rb +0 -0
  30. data/examples/{rails_server → rails_openid}/config/environments/development.rb +0 -0
  31. data/examples/{rails_server → rails_openid}/config/environments/production.rb +0 -0
  32. data/examples/{rails_server → rails_openid}/config/environments/test.rb +0 -0
  33. data/examples/{rails_server → rails_openid}/config/routes.rb +2 -1
  34. data/examples/{rails_server → rails_openid}/doc/README_FOR_APP +0 -0
  35. data/examples/{rails_server → rails_openid}/public/404.html +0 -0
  36. data/examples/{rails_server → rails_openid}/public/500.html +0 -0
  37. data/examples/{rails_server → rails_openid}/public/dispatch.cgi +0 -0
  38. data/examples/{rails_server → rails_openid}/public/dispatch.fcgi +0 -0
  39. data/examples/{rails_server → rails_openid}/public/dispatch.rb +0 -0
  40. data/examples/{rails_server → rails_openid}/public/favicon.ico +0 -0
  41. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  42. data/examples/{rails_server → rails_openid}/public/javascripts/controls.js +0 -0
  43. data/examples/{rails_server → rails_openid}/public/javascripts/dragdrop.js +0 -0
  44. data/examples/{rails_server → rails_openid}/public/javascripts/effects.js +0 -0
  45. data/examples/{rails_server → rails_openid}/public/javascripts/prototype.js +0 -0
  46. data/examples/{rails_server → rails_openid}/public/robots.txt +0 -0
  47. data/examples/{rails_server → rails_openid}/script/about +0 -0
  48. data/examples/{rails_server → rails_openid}/script/breakpointer +0 -0
  49. data/examples/{rails_server → rails_openid}/script/console +0 -0
  50. data/examples/{rails_server → rails_openid}/script/destroy +0 -0
  51. data/examples/{rails_server → rails_openid}/script/generate +0 -0
  52. data/examples/{rails_server → rails_openid}/script/performance/benchmarker +0 -0
  53. data/examples/{rails_server → rails_openid}/script/performance/profiler +0 -0
  54. data/examples/{rails_server → rails_openid}/script/plugin +0 -0
  55. data/examples/{rails_server → rails_openid}/script/process/reaper +0 -0
  56. data/examples/{rails_server → rails_openid}/script/process/spawner +0 -0
  57. data/examples/{rails_server → rails_openid}/script/process/spinner +0 -0
  58. data/examples/{rails_server → rails_openid}/script/runner +0 -0
  59. data/examples/{rails_server → rails_openid}/script/server +0 -0
  60. data/examples/{rails_server → rails_openid}/test/functional/login_controller_test.rb +0 -0
  61. data/examples/{rails_server → rails_openid}/test/functional/server_controller_test.rb +0 -0
  62. data/examples/{rails_server → rails_openid}/test/test_helper.rb +0 -0
  63. data/lib/{hmac.rb → hmac/hmac.rb} +0 -0
  64. data/lib/{hmac-sha1.rb → hmac/sha1.rb} +1 -1
  65. data/lib/{hmac-sha2.rb → hmac/sha2.rb} +1 -1
  66. data/lib/openid/association.rb +213 -73
  67. data/lib/openid/consumer/associationmanager.rb +338 -0
  68. data/lib/openid/consumer/checkid_request.rb +175 -0
  69. data/lib/openid/consumer/discovery.rb +480 -0
  70. data/lib/openid/consumer/discovery_manager.rb +123 -0
  71. data/lib/openid/consumer/html_parse.rb +136 -0
  72. data/lib/openid/consumer/idres.rb +525 -0
  73. data/lib/openid/consumer/responses.rb +133 -0
  74. data/lib/openid/consumer.rb +280 -807
  75. data/lib/openid/cryptutil.rb +85 -0
  76. data/lib/openid/dh.rb +60 -23
  77. data/lib/openid/extension.rb +31 -0
  78. data/lib/openid/extensions/ax.rb +506 -0
  79. data/lib/openid/extensions/pape.rb +182 -0
  80. data/lib/openid/extensions/sreg.rb +275 -0
  81. data/lib/openid/extras.rb +11 -0
  82. data/lib/openid/fetchers.rb +132 -93
  83. data/lib/openid/kvform.rb +133 -0
  84. data/lib/openid/kvpost.rb +56 -0
  85. data/lib/openid/message.rb +534 -0
  86. data/lib/openid/protocolerror.rb +6 -0
  87. data/lib/openid/server.rb +1215 -666
  88. data/lib/openid/store/filesystem.rb +271 -0
  89. data/lib/openid/store/interface.rb +75 -0
  90. data/lib/openid/store/memory.rb +84 -0
  91. data/lib/openid/store/nonce.rb +68 -0
  92. data/lib/openid/trustroot.rb +314 -87
  93. data/lib/openid/urinorm.rb +37 -34
  94. data/lib/openid/util.rb +42 -220
  95. data/lib/openid/yadis/accept.rb +148 -0
  96. data/lib/openid/yadis/constants.rb +21 -0
  97. data/lib/openid/yadis/discovery.rb +153 -0
  98. data/lib/openid/yadis/filters.rb +205 -0
  99. data/lib/openid/{htmltokenizer.rb → yadis/htmltokenizer.rb} +1 -54
  100. data/lib/openid/yadis/parsehtml.rb +36 -0
  101. data/lib/openid/yadis/services.rb +42 -0
  102. data/lib/openid/yadis/xrds.rb +171 -0
  103. data/lib/openid/yadis/xri.rb +90 -0
  104. data/lib/openid/yadis/xrires.rb +106 -0
  105. data/lib/openid.rb +1 -4
  106. data/test/data/accept.txt +124 -0
  107. data/test/data/dh.txt +29 -0
  108. data/test/data/example-xrds.xml +14 -0
  109. data/test/data/linkparse.txt +587 -0
  110. data/test/data/n2b64 +650 -0
  111. data/test/data/test1-discover.txt +137 -0
  112. data/test/data/test1-parsehtml.txt +128 -0
  113. data/test/data/test_discover/openid.html +11 -0
  114. data/test/data/test_discover/openid2.html +11 -0
  115. data/test/data/test_discover/openid2_xrds.xml +12 -0
  116. data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  117. data/test/data/test_discover/openid_1_and_2.html +11 -0
  118. data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
  119. data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  120. data/test/data/test_discover/openid_and_yadis.html +12 -0
  121. data/test/data/test_discover/openid_no_delegate.html +10 -0
  122. data/test/data/test_discover/yadis_0entries.xml +12 -0
  123. data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
  124. data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
  125. data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
  126. data/test/data/test_discover/yadis_another_delegate.xml +14 -0
  127. data/test/data/test_discover/yadis_idp.xml +12 -0
  128. data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
  129. data/test/data/test_discover/yadis_no_delegate.xml +11 -0
  130. data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  131. data/test/data/test_xrds/README +12 -0
  132. data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
  133. data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
  134. data/test/data/test_xrds/delegated-20060809.xrds +34 -0
  135. data/test/data/test_xrds/no-xrd.xml +7 -0
  136. data/test/data/test_xrds/not-xrds.xml +2 -0
  137. data/test/data/test_xrds/prefixsometimes.xrds +34 -0
  138. data/test/data/test_xrds/ref.xrds +109 -0
  139. data/test/data/test_xrds/sometimesprefix.xrds +34 -0
  140. data/test/data/test_xrds/spoof1.xrds +25 -0
  141. data/test/data/test_xrds/spoof2.xrds +25 -0
  142. data/test/data/test_xrds/spoof3.xrds +37 -0
  143. data/test/data/test_xrds/status222.xrds +9 -0
  144. data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
  145. data/test/data/trustroot.txt +147 -0
  146. data/test/discoverdata.rb +131 -0
  147. data/test/test_accept.rb +170 -0
  148. data/test/test_association.rb +266 -0
  149. data/test/test_associationmanager.rb +899 -0
  150. data/test/test_ax.rb +587 -0
  151. data/test/test_checkid_request.rb +297 -0
  152. data/test/test_consumer.rb +257 -0
  153. data/test/test_cryptutil.rb +117 -0
  154. data/test/test_dh.rb +86 -0
  155. data/test/test_discover.rb +772 -0
  156. data/test/test_discovery_manager.rb +262 -0
  157. data/test/test_extras.rb +35 -0
  158. data/test/test_fetchers.rb +472 -0
  159. data/test/test_filters.rb +270 -0
  160. data/test/test_idres.rb +816 -0
  161. data/test/test_kvform.rb +165 -0
  162. data/test/test_kvpost.rb +65 -0
  163. data/test/test_linkparse.rb +101 -0
  164. data/test/test_message.rb +1058 -0
  165. data/test/test_nonce.rb +89 -0
  166. data/test/test_openid_yadis.rb +178 -0
  167. data/test/test_pape.rb +233 -0
  168. data/test/test_parsehtml.rb +80 -0
  169. data/test/test_responses.rb +63 -0
  170. data/test/test_server.rb +2270 -0
  171. data/test/test_sreg.rb +479 -0
  172. data/test/test_stores.rb +269 -0
  173. data/test/test_trustroot.rb +112 -0
  174. data/test/{urinorm.rb → test_urinorm.rb} +6 -3
  175. data/test/test_util.rb +144 -0
  176. data/test/test_xrds.rb +160 -0
  177. data/test/test_xri.rb +48 -0
  178. data/test/test_xrires.rb +63 -0
  179. data/test/test_yadis_discovery.rb +207 -0
  180. data/test/testutil.rb +116 -0
  181. data/test/util.rb +47 -50
  182. metadata +233 -143
  183. data/examples/consumer.rb +0 -290
  184. data/examples/rails_openid_login_generator/openid_login_generator-0.1.gem +0 -0
  185. data/examples/rails_server/app/controllers/server_controller.rb +0 -190
  186. data/examples/rails_server/app/views/server/decide.rhtml +0 -11
  187. data/examples/rails_server/public/images/rails.png +0 -0
  188. data/lib/hmac-md5.rb +0 -11
  189. data/lib/hmac-rmd160.rb +0 -11
  190. data/lib/openid/discovery.rb +0 -122
  191. data/lib/openid/filestore.rb +0 -315
  192. data/lib/openid/parse.rb +0 -23
  193. data/lib/openid/service.rb +0 -147
  194. data/lib/openid/stores.rb +0 -178
  195. data/test/assoc.rb +0 -38
  196. data/test/consumer.rb +0 -376
  197. data/test/data/brian.xrds +0 -16
  198. data/test/data/brianellin.mylid.xrds +0 -42
  199. data/test/dh.rb +0 -20
  200. data/test/extensions.rb +0 -30
  201. data/test/linkparse.rb +0 -305
  202. data/test/runtests.rb +0 -22
  203. data/test/server2.rb +0 -1053
  204. data/test/service.rb +0 -47
  205. data/test/storetestcase.rb +0 -172
  206. data/test/teststore.rb +0 -47
  207. data/test/trustroot.rb +0 -117
data/lib/openid/util.rb CHANGED
@@ -1,147 +1,52 @@
1
1
  require "base64"
2
2
  require "cgi"
3
- require "digest/sha1"
4
- require "hmac-sha1"
5
3
  require "uri"
4
+ require "logger"
6
5
 
7
- require "openid/urinorm"
6
+ require "openid/extras"
8
7
 
9
8
  srand(Time.now.to_f)
10
9
 
11
- class Object
12
-
13
- def instance_variable_hash
14
- h = {}
15
- self.instance_variables.each { |k| h[k] = self.instance_variable_get(k) }
16
- return h
10
+ # See OpenID::Consumer or OpenID::Server modules, as well as the store classes
11
+ module OpenID
12
+ class AssertionError < Exception
17
13
  end
18
14
 
19
- end
20
-
21
- class String
15
+ VERSION = "2.0.1"
22
16
 
23
- def starts_with?(other)
24
- other = other.to_str
25
- head = self[0, other.length]
26
- head == other
17
+ # Exceptions that are raised by the library are subclasses of this
18
+ # exception type, so if you want to catch all exceptions raised by
19
+ # the library, you can catch OpenIDError
20
+ class OpenIDError < StandardError
27
21
  end
28
22
 
29
- end
30
-
31
-
32
- module OpenID
33
-
34
- # Code returned when either the of the
35
- # OpenID::OpenIDConsumer.begin_auth or OpenID::OpenIDConsumer.complete_auth
36
- # methods return successfully.
37
- SUCCESS = 'success'
38
-
39
- # Code OpenID::OpenIDConsumer.complete_auth
40
- # returns when the value it received indicated an invalid login.
41
- FAILURE = 'failure'
42
-
43
- # Code returned by OpenIDConsumer.complete_auth when the user
44
- # cancels the operation from the server.
45
- CANCEL = 'cancel'
46
-
47
- # Code returned by OpenID::OpenIDConsumer.complete_auth when the
48
- # OpenIDConsumer instance is in immediate mode and ther server sends back a
49
- # URL for the user to login with.
50
- SETUP_NEEDED = 'setup needed'
51
-
52
- # Code returned by OpenID::OpenIDConsumer.begin_auth when it is unable
53
- # to fetch the URL given by the user.
54
- HTTP_FAILURE = 'http failure'
55
-
56
- # Code returned by OpenID::OpenIDConsumer.begin_auth when the page fetched
57
- # from the OpenID URL doesn't contain the necessary link tags to function
58
- # as an identity page.
59
- PARSE_ERROR = 'parse error'
60
-
61
23
  module Util
62
24
 
63
- HAS_URANDOM = File.chardev? '/dev/urandom'
64
-
65
- def Util.get_openid_params(query)
66
- params = {}
67
- query.each do |k,v|
68
- params[k] = v if k.index("openid.") == 0
25
+ BASE64_CHARS = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
26
+ 'abcdefghijklmnopqrstuvwxyz0123456789+/')
27
+ BASE64_RE = Regexp.compile("
28
+ \\A
29
+ ([#{BASE64_CHARS}]{4})*
30
+ ([#{BASE64_CHARS}]{2}==|
31
+ [#{BASE64_CHARS}]{3}=)?
32
+ \\Z", Regexp::EXTENDED)
33
+
34
+ def Util.assert(value, message=nil)
35
+ if not value
36
+ raise AssertionError, message or value
69
37
  end
70
- params
71
38
  end
72
39
 
73
- def Util.hmac_sha1(key, text)
74
- HMAC::SHA1.digest(key, text)
75
- end
76
-
77
- def Util.sha1(s)
78
- Digest::SHA1.digest(s)
79
- end
80
-
81
40
  def Util.to_base64(s)
82
41
  Base64.encode64(s).gsub("\n", "")
83
42
  end
84
43
 
85
44
  def Util.from_base64(s)
86
- Base64.decode64(s)
87
- end
88
-
89
- def Util.kvform(hash)
90
- form = ""
91
- hash.each do |k,v|
92
- form << "#{k}:#{v}\n"
93
- end
94
- form
95
- end
96
-
97
- def Util.parsekv(s)
98
- s.strip!
99
- form = {}
100
- s.split("\n").each do |line|
101
- pair = line.split(":", 2)
102
- if pair.length == 2
103
- k, v = pair
104
- form[k.strip] = v.strip
105
- end
106
- end
107
- form
108
- end
109
-
110
- def Util.num_to_str(n)
111
- bits = n.to_s(2)
112
- prepend = (8 - bits.length % 8)
113
- bits = ('0' * prepend) + bits
114
- [bits].pack('B*')
115
- end
116
-
117
- def Util.str_to_num(s)
118
- # taken from openid-ruby 0.0.1
119
- s = "\000" * (4 - (s.length % 4)) + s
120
- num = 0
121
- s.unpack('N*').each do |x|
122
- num <<= 32
123
- num |= x
124
- end
125
- num
126
- end
127
-
128
- def Util.num_to_base64(l)
129
- return to_base64(num_to_str(l))
130
- end
131
-
132
- def Util.base64_to_num(s)
133
- return str_to_num(from_base64(s))
134
- end
135
-
136
- def Util.random_string(length, chars=nil)
137
- s = ""
138
-
139
- unless chars.nil?
140
- length.times { s << chars[Util.rand(chars.length)] }
141
- else
142
- length.times { s << Util.rand(256).chr }
45
+ without_newlines = s.gsub(/[\r\n]+/, '')
46
+ if !BASE64_RE.match(without_newlines)
47
+ raise ArgumentError, "Malformed input: #{s.inspect}"
143
48
  end
144
- s
49
+ Base64.decode64(without_newlines)
145
50
  end
146
51
 
147
52
  def Util.urlencode(args)
@@ -152,123 +57,40 @@ module OpenID
152
57
  end
153
58
  a.join("&")
154
59
  end
155
-
60
+
156
61
  def Util.parse_query(qs)
157
62
  query = {}
158
63
  CGI::parse(qs).each {|k,v| query[k] = v[0]}
159
64
  return query
160
65
  end
161
-
66
+
162
67
  def Util.append_args(url, args)
163
68
  url = url.dup
164
- url if args.length == 0
165
- url << (url.include?("?") ? "&" : "?")
166
- url << Util.urlencode(args)
167
- end
168
-
169
- def Util.strxor(s1, s2)
170
- raise ArgumentError if s1.length != s2.length
171
- length = [s1.length, s2.length].min - 1
172
- a = (0..length).collect {|i| (s1[i]^s2[i]).chr}
173
- a.join("")
174
- end
175
-
176
- # Sign the given fields from the reply with the specified key.
177
- # Return [signed, sig]
178
- def Util.sign_reply(reply, key, signed_fields, prefix="openid.")
179
- token = []
180
- signed_fields.each do |sf|
181
- token << [sf+":"+reply[prefix+sf].to_s+"\n"]
182
- end
183
- text = token.join("")
184
- signed = Util.to_base64(Util.hmac_sha1(key, text))
185
- return [signed_fields.join(","), signed]
186
- end
69
+ return url if args.length == 0
187
70
 
188
- # This code is taken from this post[http://blade.nagaokaut.ac.jp/cgi-bin/scat.\rb/ruby/ruby-talk/19098]
189
- # by Eric Lee Green.
190
- # This implementation is much faster than x ** n % q
191
- def Util.powermod(x, n, q)
192
- counter=0
193
- n_p=n
194
- y_p=1
195
- z_p=x
196
- while n_p != 0
197
- if n_p[0]==1
198
- y_p=(y_p*z_p) % q
199
- end
200
- n_p = n_p >> 1
201
- z_p = (z_p * z_p) % q
202
- counter += 1
71
+ if args.respond_to?('each_pair')
72
+ args = args.sort
203
73
  end
204
- return y_p
205
- end
206
-
207
- # Generate a random number less than max. Uses urandom if available.
208
- def Util.rand(max)
209
- unless Util::HAS_URANDOM
210
- return Kernel::rand(max)
211
- end
212
-
213
- start = 0
214
- stop = max
215
- step = 1
216
- r = ((stop-start)/step).to_i
217
74
 
218
- # figure out how many bytes we need
219
- rbytes = Util::num_to_str(r)
220
- nbytes = rbytes.length
221
- nbytes -= 1 if rbytes[0].chr == "\000"
222
-
223
- bytes = "\000" + Util::get_random_bytes(nbytes)
224
- n = Util::str_to_num(bytes)
225
-
226
- return start + (n % r) * step
227
- end
228
-
229
- # change the message below to do whatever you like for logging
230
- def Util.log(message)
231
- STDERR.puts('OpenID Log: ' + message)
75
+ url << (url.include?("?") ? "&" : "?")
76
+ url << Util.urlencode(args)
232
77
  end
233
78
 
79
+ @@logger = Logger.new(STDERR)
80
+ @@logger.progname = "OpenID"
234
81
 
235
- def Util.get_random_bytes(n)
236
- bytes = ""
237
-
238
- if Util::HAS_URANDOM
239
- f = File.open("/dev/urandom")
240
- while n != 0
241
- _bytes = f.read(n)
242
- n -= _bytes.length
243
- bytes << _bytes
244
- end
245
- else
246
- bytes = Util.random_string(n)
247
- end
248
-
249
- return bytes
82
+ def Util.logger=(logger)
83
+ @@logger = logger
250
84
  end
251
85
 
252
- def Util.normalize_url(url)
253
- url = url.strip
254
-
255
- unless url.starts_with?('http://') or url.starts_with?('https://')
256
- url = 'http://' + url
257
- end
258
-
259
- begin
260
- return Util.urinorm(url)
261
- rescue URI::InvalidURIError
262
- return nil
263
- end
86
+ def Util.logger
87
+ @@logger
264
88
  end
265
89
 
266
- def Util.urls_equal?(url1, url2)
267
- url1 = Util.normalize_url(url1)
268
- return false if url1.nil?
269
- return url1 == Util.normalize_url(url2)
90
+ # change the message below to do whatever you like for logging
91
+ def Util.log(message)
92
+ logger.info(message)
270
93
  end
271
-
272
94
  end
273
95
 
274
96
  end
@@ -0,0 +1,148 @@
1
+ module OpenID
2
+
3
+ module Yadis
4
+
5
+ # Generate an accept header value
6
+ #
7
+ # [str or (str, float)] -> str
8
+ def self.generate_accept_header(*elements)
9
+ parts = []
10
+ elements.each { |element|
11
+ if element.is_a?(String)
12
+ qs = "1.0"
13
+ mtype = element
14
+ else
15
+ mtype, q = element
16
+ q = q.to_f
17
+ if q > 1 or q <= 0
18
+ raise ArgumentError.new("Invalid preference factor: #{q}")
19
+ end
20
+ qs = sprintf("%0.1f", q)
21
+ end
22
+
23
+ parts << [qs, mtype]
24
+ }
25
+
26
+ parts.sort!
27
+ chunks = []
28
+ parts.each { |q, mtype|
29
+ if q == '1.0'
30
+ chunks << mtype
31
+ else
32
+ chunks << sprintf("%s; q=%s", mtype, q)
33
+ end
34
+ }
35
+
36
+ return chunks.join(', ')
37
+ end
38
+
39
+ def self.parse_accept_header(value)
40
+ # Parse an accept header, ignoring any accept-extensions
41
+ #
42
+ # returns a list of tuples containing main MIME type, MIME
43
+ # subtype, and quality markdown.
44
+ #
45
+ # str -> [(str, str, float)]
46
+ chunks = value.split(',', -1).collect { |v| v.strip }
47
+ accept = []
48
+ chunks.each { |chunk|
49
+ parts = chunk.split(";", -1).collect { |s| s.strip }
50
+
51
+ mtype = parts.shift
52
+ if mtype.index('/').nil?
53
+ # This is not a MIME type, so ignore the bad data
54
+ next
55
+ end
56
+
57
+ main, sub = mtype.split('/', 2)
58
+
59
+ q = nil
60
+ parts.each { |ext|
61
+ if !ext.index('=').nil?
62
+ k, v = ext.split('=', 2)
63
+ if k == 'q'
64
+ q = v.to_f
65
+ end
66
+ end
67
+ }
68
+
69
+ q = 1.0 if q.nil?
70
+
71
+ accept << [q, main, sub]
72
+ }
73
+
74
+ accept.sort!
75
+ accept.reverse!
76
+
77
+ return accept.collect { |q, main, sub| [main, sub, q] }
78
+ end
79
+
80
+ def self.match_types(accept_types, have_types)
81
+ # Given the result of parsing an Accept: header, and the
82
+ # available MIME types, return the acceptable types with their
83
+ # quality markdowns.
84
+ #
85
+ # For example:
86
+ #
87
+ # >>> acceptable = parse_accept_header('text/html, text/plain; q=0.5')
88
+ # >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])
89
+ # [('text/html', 1.0), ('text/plain', 0.5)]
90
+ #
91
+ # Type signature: ([(str, str, float)], [str]) -> [(str, float)]
92
+ if accept_types.nil? or accept_types == []
93
+ # Accept all of them
94
+ default = 1
95
+ else
96
+ default = 0
97
+ end
98
+
99
+ match_main = {}
100
+ match_sub = {}
101
+ accept_types.each { |main, sub, q|
102
+ if main == '*'
103
+ default = [default, q].max
104
+ next
105
+ elsif sub == '*'
106
+ match_main[main] = [match_main.fetch(main, 0), q].max
107
+ else
108
+ match_sub[[main, sub]] = [match_sub.fetch([main, sub], 0), q].max
109
+ end
110
+ }
111
+
112
+ accepted_list = []
113
+ order_maintainer = 0
114
+ have_types.each { |mtype|
115
+ main, sub = mtype.split('/', 2)
116
+ if match_sub.member?([main, sub])
117
+ q = match_sub[[main, sub]]
118
+ else
119
+ q = match_main.fetch(main, default)
120
+ end
121
+
122
+ if q != 0
123
+ accepted_list << [1 - q, order_maintainer, q, mtype]
124
+ order_maintainer += 1
125
+ end
126
+ }
127
+
128
+ accepted_list.sort!
129
+ return accepted_list.collect { |_, _, q, mtype| [mtype, q] }
130
+ end
131
+
132
+ def self.get_acceptable(accept_header, have_types)
133
+ # Parse the accept header and return a list of available types
134
+ # in preferred order. If a type is unacceptable, it will not be
135
+ # in the resulting list.
136
+ #
137
+ # This is a convenience wrapper around matchTypes and
138
+ # parse_accept_header
139
+ #
140
+ # (str, [str]) -> [str]
141
+ accepted = self.parse_accept_header(accept_header)
142
+ preferred = self.match_types(accepted, have_types)
143
+ return preferred.collect { |mtype, _| mtype }
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,21 @@
1
+
2
+ require 'openid/yadis/accept'
3
+
4
+ module OpenID
5
+
6
+ module Yadis
7
+
8
+ YADIS_HEADER_NAME = 'X-XRDS-Location'
9
+ YADIS_CONTENT_TYPE = 'application/xrds+xml'
10
+
11
+ # A value suitable for using as an accept header when performing
12
+ # YADIS discovery, unless the application has special requirements
13
+ YADIS_ACCEPT_HEADER = generate_accept_header(
14
+ ['text/html', 0.3],
15
+ ['application/xhtml+xml', 0.5],
16
+ [YADIS_CONTENT_TYPE, 1.0]
17
+ )
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,153 @@
1
+
2
+ require 'openid/util'
3
+ require 'openid/fetchers'
4
+ require 'openid/yadis/constants'
5
+ require 'openid/yadis/parsehtml'
6
+
7
+ module OpenID
8
+
9
+ # Raised when a error occurs in the discovery process
10
+ class DiscoveryFailure < OpenIDError
11
+ attr_accessor :identity_url, :http_response
12
+
13
+ def initialize(message, http_response)
14
+ super(message)
15
+ @identity_url = nil
16
+ @http_response = http_response
17
+ end
18
+ end
19
+
20
+ module Yadis
21
+
22
+ # Contains the result of performing Yadis discovery on a URI
23
+ class DiscoveryResult
24
+
25
+ # The result of following redirects from the request_uri
26
+ attr_accessor :normalize_uri
27
+
28
+ # The URI from which the response text was returned (set to
29
+ # nil if there was no XRDS document found)
30
+ attr_accessor :xrds_uri
31
+
32
+ # The content-type returned with the response_text
33
+ attr_accessor :content_type
34
+
35
+ # The document returned from the xrds_uri
36
+ attr_accessor :response_text
37
+
38
+ attr_accessor :request_uri, :normalized_uri
39
+
40
+ def initialize(request_uri)
41
+ # Initialize the state of the object
42
+ #
43
+ # sets all attributes to None except the request_uri
44
+ @request_uri = request_uri
45
+ @normalized_uri = nil
46
+ @xrds_uri = nil
47
+ @content_type = nil
48
+ @response_text = nil
49
+ end
50
+
51
+ # Was the Yadis protocol's indirection used?
52
+ def used_yadis_location?
53
+ return @normalized_uri != @xrds_uri
54
+ end
55
+
56
+ # Is the response text supposed to be an XRDS document?
57
+ def is_xrds
58
+ return (used_yadis_location?() or
59
+ @content_type == YADIS_CONTENT_TYPE)
60
+ end
61
+ end
62
+
63
+ # Discover services for a given URI.
64
+ #
65
+ # uri: The identity URI as a well-formed http or https URI. The
66
+ # well-formedness and the protocol are not checked, but the
67
+ # results of this function are undefined if those properties do
68
+ # not hold.
69
+ #
70
+ # returns a DiscoveryResult object
71
+ #
72
+ # Raises DiscoveryFailure when the HTTP response does not have
73
+ # a 200 code.
74
+ def self.discover(uri)
75
+ result = DiscoveryResult.new(uri)
76
+ begin
77
+ resp = OpenID.fetch(uri, nil, {'Accept' => YADIS_ACCEPT_HEADER})
78
+ rescue Exception
79
+ raise DiscoveryFailure.new("Failed to fetch identity URL.",$!)
80
+ end
81
+ if resp.code != "200"
82
+ raise DiscoveryFailure.new(
83
+ "HTTP Response status from identity URL host is not \"200\""\
84
+ ". Got status #{resp.code.inspect}", resp)
85
+ end
86
+
87
+ # Note the URL after following redirects
88
+ result.normalized_uri = resp.final_url
89
+
90
+ # Attempt to find out where to go to discover the document or if
91
+ # we already have it
92
+ result.content_type = resp['content-type']
93
+
94
+ result.xrds_uri = self.where_is_yadis?(resp)
95
+
96
+ if result.xrds_uri and result.used_yadis_location?
97
+ begin
98
+ resp = OpenID.fetch(result.xrds_uri)
99
+ rescue
100
+ raise DiscoveryFailure.new("Failed to fetch Yadis URL.", $!)
101
+ end
102
+ if resp.code != "200"
103
+ exc = DiscoveryFailure.new(
104
+ "HTTP Response status from Yadis host is not \"200\". " +
105
+ "Got status #{resp.code.inspect}", resp)
106
+ exc.identity_url = result.normalized_uri
107
+ raise exc
108
+ end
109
+
110
+ result.content_type = resp['content-type']
111
+ end
112
+
113
+ result.response_text = resp.body
114
+ return result
115
+ end
116
+
117
+ # Given a HTTPResponse, return the location of the Yadis
118
+ # document.
119
+ #
120
+ # May be the URL just retrieved, another URL, or None, if I
121
+ # can't find any.
122
+ #
123
+ # [non-blocking]
124
+ def self.where_is_yadis?(resp)
125
+ # Attempt to find out where to go to discover the document or if
126
+ # we already have it
127
+ content_type = resp['content-type']
128
+
129
+ # According to the spec, the content-type header must be an
130
+ # exact match, or else we have to look for an indirection.
131
+ if (!content_type.nil? and
132
+ content_type.split(';', 2)[0].downcase == YADIS_CONTENT_TYPE)
133
+ return resp.final_url
134
+ else
135
+ # Try the header
136
+ yadis_loc = resp[YADIS_HEADER_NAME.downcase]
137
+
138
+ if yadis_loc.nil?
139
+ # Parse as HTML if the header is missing.
140
+ #
141
+ # XXX: do we want to do something with content-type, like
142
+ # have a whitelist or a blacklist (for detecting that it's
143
+ # HTML)?
144
+ yadis_loc = Yadis.html_yadis_location(resp.body)
145
+ end
146
+ end
147
+
148
+ return yadis_loc
149
+ end
150
+
151
+ end
152
+
153
+ end