ruby-openid 1.1.4 → 2.0.1

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