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
@@ -0,0 +1,275 @@
1
+ require 'openid/extension'
2
+ require 'openid/util'
3
+ require 'openid/message'
4
+
5
+ module OpenID
6
+ module SReg
7
+ DATA_FIELDS = {
8
+ 'fullname'=>'Full Name',
9
+ 'nickname'=>'Nickname',
10
+ 'dob'=>'Date of Birth',
11
+ 'email'=>'E-mail Address',
12
+ 'gender'=>'Gender',
13
+ 'postcode'=>'Postal Code',
14
+ 'country'=>'Country',
15
+ 'language'=>'Language',
16
+ 'timezone'=>'Time Zone',
17
+ }
18
+
19
+ NS_URI_1_0 = 'http://openid.net/sreg/1.0'
20
+ NS_URI_1_1 = 'http://openid.net/extensions/sreg/1.1'
21
+ NS_URI = NS_URI_1_1
22
+
23
+ begin
24
+ Message.register_namespace_alias(NS_URI_1_1, 'sreg')
25
+ rescue NamespaceAliasRegistrationError => e
26
+ Util.log(e)
27
+ end
28
+
29
+ # raise ArgumentError if fieldname is not in the defined sreg fields
30
+ def OpenID.check_sreg_field_name(fieldname)
31
+ unless DATA_FIELDS.member? fieldname
32
+ raise ArgumentError, "#{fieldname} is not a defined simple registration field"
33
+ end
34
+ end
35
+
36
+ # Does the given endpoint advertise support for simple registration?
37
+ def OpenID.supports_sreg?(endpoint)
38
+ endpoint.uses_extension(NS_URI_1_1) || endpoint.uses_extension(NS_URI_1_0)
39
+ end
40
+
41
+ # Extract the simple registration namespace URI from the given
42
+ # OpenID message. Handles OpenID 1 and 2, as well as both sreg
43
+ # namespace URIs found in the wild, as well as missing namespace
44
+ # definitions (for OpenID 1)
45
+ def OpenID.get_sreg_ns(message)
46
+ [NS_URI_1_1, NS_URI_1_0].each{|ns|
47
+ if message.namespaces.get_alias(ns)
48
+ return ns
49
+ end
50
+ }
51
+ # try to add an alias, since we didn't find one
52
+ ns = NS_URI_1_1
53
+ begin
54
+ message.namespaces.add_alias(ns, 'sreg')
55
+ rescue IndexError
56
+ raise NamespaceError
57
+ end
58
+ return ns
59
+ end
60
+
61
+ # The simple registration namespace was not found and could not
62
+ # be created using the expected name (there's another extension
63
+ # using the name 'sreg')
64
+ #
65
+ # This is not <em>illegal</em>, for OpenID 2, although it probably
66
+ # indicates a problem, since it's not expected that other extensions
67
+ # will re-use the alias that is in use for OpenID 1.
68
+ #
69
+ # If this is an OpenID 1 request, then there is no recourse. This
70
+ # should not happen unless some code has modified the namespaces for
71
+ # the message that is being processed.
72
+ class NamespaceError < ArgumentError
73
+ end
74
+
75
+ # An object to hold the state of a simple registration request.
76
+ class Request < Extension
77
+ attr_reader :optional, :required, :ns_uri
78
+ attr_accessor :policy_url
79
+ def initialize(required = nil, optional = nil, policy_url = nil, ns_uri = NS_URI)
80
+ super()
81
+
82
+ @policy_url = policy_url
83
+ @ns_uri = ns_uri
84
+ @ns_alias = 'sreg'
85
+ @required = []
86
+ @optional = []
87
+
88
+ if required
89
+ request_fields(required, true, true)
90
+ end
91
+ if optional
92
+ request_fields(optional, false, true)
93
+ end
94
+ end
95
+
96
+ # Create a simple registration request that contains the
97
+ # fields that were requested in the OpenID request with the
98
+ # given arguments
99
+ # Takes an OpenID::CheckIDRequest, returns an OpenID::Sreg::Request
100
+ # return nil if the extension was not requested.
101
+ def self.from_openid_request(request)
102
+ # Since we're going to mess with namespace URI mapping, don't
103
+ # mutate the object that was passed in.
104
+ message = request.message.copy
105
+ ns_uri = OpenID::get_sreg_ns(message)
106
+ args = message.get_args(ns_uri)
107
+ return nil if args == {}
108
+ req = new(nil,nil,nil,ns_uri)
109
+ req.parse_extension_args(args)
110
+ return req
111
+ end
112
+
113
+ # Parse the unqualified simple registration request
114
+ # parameters and add them to this object.
115
+ #
116
+ # This method is essentially the inverse of
117
+ # getExtensionArgs. This method restores the serialized simple
118
+ # registration request fields.
119
+ #
120
+ # If you are extracting arguments from a standard OpenID
121
+ # checkid_* request, you probably want to use fromOpenIDRequest,
122
+ # which will extract the sreg namespace and arguments from the
123
+ # OpenID request. This method is intended for cases where the
124
+ # OpenID server needs more control over how the arguments are
125
+ # parsed than that method provides.
126
+ def parse_extension_args(args, strict = false)
127
+ required_items = args['required']
128
+ unless required_items.nil? or required_items.empty?
129
+ required_items.split(',').each{|field_name|
130
+ begin
131
+ request_field(field_name, true, strict)
132
+ rescue ArgumentError
133
+ raise if strict
134
+ end
135
+ }
136
+ end
137
+
138
+ optional_items = args['optional']
139
+ unless optional_items.nil? or optional_items.empty?
140
+ optional_items.split(',').each{|field_name|
141
+ begin
142
+ request_field(field_name, false, strict)
143
+ rescue ArgumentError
144
+ raise if strict
145
+ end
146
+ }
147
+ end
148
+ @policy_url = args['policy_url']
149
+ end
150
+
151
+ # A list of all of the simple registration fields that were
152
+ # requested, whether they were required or optional.
153
+ def all_requested_fields
154
+ @required + @optional
155
+ end
156
+
157
+ # Have any simple registration fields been requested?
158
+ def were_fields_requested?
159
+ !all_requested_fields.empty?
160
+ end
161
+
162
+ # Request the specified field from the OpenID user
163
+ # field_name: the unqualified simple registration field name
164
+ # required: whether the given field should be presented
165
+ # to the user as being a required to successfully complete
166
+ # the request
167
+ # strict: whether to raise an exception when a field is
168
+ # added to a request more than once
169
+ # Raises ArgumentError if the field_name is not a simple registration
170
+ # field, or if strict is set and a field is added more than once
171
+ def request_field(field_name, required=false, strict=false)
172
+ OpenID::check_sreg_field_name(field_name)
173
+
174
+ if strict
175
+ if (@required + @optional).member? field_name
176
+ raise ArgumentError, 'That field has already been requested'
177
+ end
178
+ else
179
+ return if @required.member? field_name
180
+ if @optional.member? field_name
181
+ if required
182
+ @optional.delete field_name
183
+ else
184
+ return
185
+ end
186
+ end
187
+ end
188
+ if required
189
+ @required << field_name
190
+ else
191
+ @optional << field_name
192
+ end
193
+ end
194
+
195
+ # Add the given list of fields to the request.
196
+ def request_fields(field_names, required = false, strict = false)
197
+ raise ArgumentError unless field_names[0].is_a?(String)
198
+ field_names.each{|fn|request_field(fn, required, strict)}
199
+ end
200
+
201
+ # Get a hash of unqualified simple registration arguments
202
+ # representing this request.
203
+ # This method is essentially the inverse of parse_extension_args.
204
+ # This method serializes the simple registration request fields.
205
+ def get_extension_args
206
+ args = {}
207
+ args['required'] = @required.join(',') unless @required.empty?
208
+ args['optional'] = @optional.join(',') unless @optional.empty?
209
+ args['policy_url'] = @policy_url unless @policy_url.nil?
210
+ return args
211
+ end
212
+
213
+ def member?(field_name)
214
+ all_requested_fields.member?(field_name)
215
+ end
216
+
217
+ end
218
+
219
+ # Represents the data returned in a simple registration response
220
+ # inside of an OpenID id_res response. This object will be
221
+ # created by the OpenID server, added to the id_res response
222
+ # object, and then extracted from the id_res message by the Consumer.
223
+ class Response < Extension
224
+ attr_reader :ns_uri, :data
225
+
226
+ def initialize(data = {}, ns_uri=NS_URI)
227
+ @ns_alias = 'sreg'
228
+ @data = data
229
+ @ns_uri = ns_uri
230
+ end
231
+
232
+ # Take a Request and a hash of simple registration
233
+ # values and create a Response object containing that data.
234
+ def self.extract_response(request, data)
235
+ arf = request.all_requested_fields
236
+ resp_data = data.reject{|k,v| !arf.member?(k) || v.nil? }
237
+ new(resp_data, request.ns_uri)
238
+ end
239
+
240
+ # Create an Response object from an
241
+ # OpenID::Consumer::SuccessResponse from consumer.complete
242
+ # If you set the signed_only parameter to false, unsigned data from
243
+ # the id_res message from the server will be processed.
244
+ def self.from_success_response(success_response, signed_only = true)
245
+ ns_uri = OpenID::get_sreg_ns(success_response.message)
246
+ if signed_only
247
+ args = success_response.get_signed_ns(ns_uri)
248
+ else
249
+ args = success_response.message.get_args(ns_uri)
250
+ end
251
+ args.reject!{|k,v| !DATA_FIELDS.member?(k) }
252
+ new(args, ns_uri)
253
+ end
254
+
255
+ # Get the fields to put in the simple registration namespace
256
+ # when adding them to an id_res message.
257
+ def get_extension_args
258
+ return @data
259
+ end
260
+
261
+ # Read-only hashlike interface.
262
+ # Raises an exception if the field name is bad
263
+ def [](field_name)
264
+ OpenID::check_sreg_field_name(field_name)
265
+ data[field_name]
266
+ end
267
+
268
+ def empty?
269
+ @data.empty?
270
+ end
271
+ # XXX is there more to a hashlike interface I should add?
272
+ end
273
+ end
274
+ end
275
+
@@ -0,0 +1,11 @@
1
+ class String
2
+ def starts_with?(other)
3
+ head = self[0, other.length]
4
+ head == other
5
+ end
6
+
7
+ def ends_with?(other)
8
+ tail = self[-1 * other.length, other.length]
9
+ tail == other
10
+ end
11
+ end
@@ -1,20 +1,15 @@
1
- require "uri"
2
- require "openid/util"
1
+ require 'net/http'
2
+ require 'openid'
3
+ require 'openid/util'
3
4
 
4
- # Try to use net/https, falling back to no SSL support if it is not available
5
5
  begin
6
6
  require 'net/https'
7
7
  rescue LoadError
8
- OpenID::Util.log('WARNING: no SSL support found. Will not be able to fetch HTTPS URLs!')
9
- HAS_OPENSSL = false
10
- require 'net/http'
11
- else
12
- HAS_OPENSSL = true
8
+ OpenID::Util.log('WARNING: no SSL support found. Will not be able ' +
9
+ 'to fetch HTTPS URLs!')
10
+ require 'net/http'
13
11
  end
14
12
 
15
- # Not all versions of Ruby 1.8.4 have the version of post_connection_check
16
- # that properly handles wildcard hostnames. This version of
17
- # post_connection_check is copied from post April 2006 release of 1.8.4.
18
13
  module Net
19
14
  class HTTP
20
15
  def post_connection_check(hostname)
@@ -47,114 +42,158 @@ module Net
47
42
  end
48
43
 
49
44
  module OpenID
45
+ # Our HTTPResponse class extends Net::HTTPResponse with an additional
46
+ # method, final_url.
47
+ class HTTPResponse
48
+ attr_accessor :final_url
50
49
 
51
- # Base Object used by consumer to send http messages
52
- class Fetcher
50
+ attr_accessor :_response
53
51
 
54
- # Fetch the content of url, following redirects, and return the
55
- # final url and page data. Return nil on failure.
56
- def get(url)
57
- raise NotImplementedError
52
+ def self._from_net_response(response, final_url, headers=nil)
53
+ me = self.new
54
+ me._response = response
55
+ me.final_url = final_url
56
+ return me
58
57
  end
59
-
60
- # Post the body string to url. Return the resulting url and page data.
61
- # Return nil on failure
62
- def post(url, body)
63
- raise NotImplementedError
58
+
59
+ def method_missing(method, *args)
60
+ @_response.send(method, *args)
61
+ end
62
+
63
+ def body=(s)
64
+ @_response.instance_variable_set('@body', s)
65
+ # XXX Hack to work around ruby's HTTP library behavior. @body
66
+ # is only returned if it has been read from the response
67
+ # object's socket, but since we're not using a socket in this
68
+ # case, we need to set the @read flag to true to avoid a bug in
69
+ # Net::HTTPResponse.stream_check when @socket is nil.
70
+ @_response.instance_variable_set('@read', true)
64
71
  end
65
-
66
72
  end
67
-
68
- # Implemetation of OpenID::Fetcher that uses ruby's Net::HTTP
69
- class StandardFetcher < Fetcher
70
-
71
- attr_accessor :ca_path
72
-
73
- def initialize(read_timeout=20, open_timeout=20)
74
- @read_timeout = read_timeout
75
- @open_timeout = open_timeout
76
- @ca_path = nil
73
+
74
+ class FetchingError < OpenIDError
75
+ end
76
+
77
+ class HTTPRedirectLimitReached < FetchingError
78
+ end
79
+
80
+ class SSLFetchingError < FetchingError
81
+ end
82
+
83
+ @fetcher = nil
84
+
85
+ def self.fetch(url, body=nil, headers=nil,
86
+ redirect_limit=StandardFetcher::REDIRECT_LIMIT)
87
+ return fetcher.fetch(url, body, headers, redirect_limit)
88
+ end
89
+
90
+ def self.fetcher
91
+ if @fetcher.nil?
92
+ @fetcher = StandardFetcher.new
77
93
  end
78
-
79
- def get(url)
80
- resp, final_url = do_get(url)
81
- if resp.nil?
82
- nil
83
- else
84
- [final_url, resp.body]
85
- end
94
+
95
+ return @fetcher
96
+ end
97
+
98
+ def self.fetcher=(fetcher)
99
+ @fetcher = fetcher
100
+ end
101
+
102
+ class StandardFetcher
103
+
104
+ USER_AGENT = "ruby-openid/#{OpenID::VERSION} (#{PLATFORM})"
105
+
106
+ REDIRECT_LIMIT = 5
107
+
108
+ attr_accessor :ca_file
109
+
110
+ def initialize
111
+ @ca_file = nil
86
112
  end
87
-
88
- def post(url, body)
89
- begin
90
- uri = URI.parse(url)
91
- http = get_http_obj(uri)
92
- resp = http.post(uri.request_uri, body,
93
- {"Content-type"=>"application/x-www-form-urlencoded"})
94
- rescue
95
- nil
113
+
114
+ def supports_ssl?(conn)
115
+ return conn.respond_to?(:use_ssl=)
116
+ end
117
+
118
+ def make_http(uri)
119
+ Net::HTTP.new(uri.host, uri.port)
120
+ end
121
+
122
+ def set_verified(conn, verify)
123
+ if verify
124
+ conn.verify_mode = OpenSSL::SSL::VERIFY_PEER
96
125
  else
97
- [uri.to_s, resp.body]
126
+ conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
98
127
  end
99
128
  end
100
129
 
101
- protected
102
-
103
- # return a Net::HTTP object ready for use
104
- def get_http_obj(uri)
105
- http = Net::HTTP.new(uri.host, uri.port)
106
- http.read_timeout = @read_timeout
107
- http.open_timeout = @open_timeout
130
+ def make_connection(uri)
131
+ conn = make_http(uri)
132
+
133
+ if !conn.is_a?(Net::HTTP)
134
+ raise RuntimeError, sprintf("Expected Net::HTTP object from make_http; got %s",
135
+ conn.class)
136
+ end
108
137
 
109
138
  if uri.scheme == 'https'
139
+ if supports_ssl?(conn)
110
140
 
111
- if HAS_OPENSSL
112
- http.use_ssl = true
141
+ conn.use_ssl = true
113
142
 
114
- if @ca_path
115
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
116
- http.ca_file = @ca_path
143
+ if @ca_file
144
+ set_verified(conn, true)
145
+ conn.ca_file = @ca_file
117
146
  else
118
- OpenID::Util.log('WARNING: making https request without verifying server certificate.')
119
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
147
+ Util.log("WARNING: making https request to #{uri} without verifying " +
148
+ "server certificate; no CA path was specified.")
149
+ set_verified(conn, false)
120
150
  end
121
-
151
+ else
152
+ raise RuntimeError, "SSL support not found; cannot fetch #{uri}"
122
153
  end
123
-
124
154
  end
125
155
 
126
- return http
156
+ return conn
127
157
  end
128
-
129
- # do a GET following redirects limit deep
130
-
131
- def do_get(url, limit=5)
132
- if limit == 0
133
- return nil
134
- end
158
+
159
+ def fetch(url, body=nil, headers=nil, redirect_limit=REDIRECT_LIMIT)
160
+ unparsed_url = url.dup
161
+ url = URI::parse(url)
162
+
163
+ headers ||= {}
164
+ headers['User-agent'] ||= USER_AGENT
165
+
166
+ conn = make_connection(url)
167
+ response = nil
168
+
135
169
  begin
136
- u = URI.parse(url)
137
- http = get_http_obj(u)
138
- http.start {
139
- if HAS_OPENSSL and u.is_a?(URI::HTTPS) and @ca_path
140
- # do the post_connection_check, which verifies that
141
- # the host matches the cert
142
- http.post_connection_check(u.host)
170
+ response = conn.start {
171
+ # Check the certificate against the URL's hostname
172
+ if supports_ssl?(conn) and conn.use_ssl?
173
+ conn.post_connection_check(url.host)
174
+ end
175
+
176
+ if body.nil?
177
+ conn.request_get(url.request_uri, headers)
178
+ else
179
+ headers["Content-type"] ||= "application/x-www-form-urlencoded"
180
+ conn.request_post(url.request_uri, body, headers)
143
181
  end
144
182
  }
145
- resp = http.get(u.request_uri)
146
- rescue
147
- nil
148
- else
149
- case resp
150
- when Net::HTTPSuccess then [resp, URI.parse(url).to_s]
151
- when Net::HTTPRedirection then do_get(resp["location"], limit-1)
152
- else
153
- nil
183
+ rescue OpenSSL::SSL::SSLError => why
184
+ raise SSLFetchingError, "Error connecting to SSL URL #{url}: #{why}"
185
+ end
186
+
187
+ case response
188
+ when Net::HTTPRedirection
189
+ if redirect_limit <= 0
190
+ raise HTTPRedirectLimitReached.new(
191
+ "Too many redirects, not fetching #{response['location']}")
154
192
  end
193
+ return fetch(response['location'], body, headers, redirect_limit - 1)
194
+ else
195
+ return HTTPResponse._from_net_response(response, unparsed_url)
155
196
  end
156
197
  end
157
-
158
198
  end
159
-
160
199
  end
@@ -0,0 +1,133 @@
1
+
2
+ module OpenID
3
+
4
+ module Util
5
+
6
+ def Util.seq_to_kv(seq, strict=false)
7
+ # Represent a sequence of pairs of strings as newline-terminated
8
+ # key:value pairs. The pairs are generated in the order given.
9
+ #
10
+ # @param seq: The pairs
11
+ #
12
+ # returns a string representation of the sequence
13
+ err = lambda { |msg|
14
+ msg = "seq_to_kv warning: #{msg}: #{seq.inspect}"
15
+ if strict
16
+ raise ArgumentError, msg
17
+ else
18
+ Util.log(msg)
19
+ end
20
+ }
21
+
22
+ lines = []
23
+ seq.each { |k, v|
24
+ if !k.is_a?(String)
25
+ err.call("Converting key to string: #{k.inspect}")
26
+ k = k.to_s
27
+ end
28
+
29
+ if !k.index("\n").nil?
30
+ raise ArgumentError, "Invalid input for seq_to_kv: key contains newline: #{k.inspect}"
31
+ end
32
+
33
+ if !k.index(":").nil?
34
+ raise ArgumentError, "Invalid input for seq_to_kv: key contains colon: #{k.inspect}"
35
+ end
36
+
37
+ if k.strip() != k
38
+ err.call("Key has whitespace at beginning or end: #{k.inspect}")
39
+ end
40
+
41
+ if !v.is_a?(String)
42
+ err.call("Converting value to string: #{v.inspect}")
43
+ v = v.to_s
44
+ end
45
+
46
+ if !v.index("\n").nil?
47
+ raise ArgumentError, "Invalid input for seq_to_kv: value contains newline: #{v.inspect}"
48
+ end
49
+
50
+ if v.strip() != v
51
+ err.call("Value has whitespace at beginning or end: #{v.inspect}")
52
+ end
53
+
54
+ lines << k + ":" + v + "\n"
55
+ }
56
+
57
+ return lines.join("")
58
+ end
59
+
60
+ def Util.kv_to_seq(data, strict=false)
61
+ # After one parse, seq_to_kv and kv_to_seq are inverses, with no
62
+ # warnings:
63
+ #
64
+ # seq = kv_to_seq(s)
65
+ # seq_to_kv(kv_to_seq(seq)) == seq
66
+ err = lambda { |msg|
67
+ msg = "kv_to_seq warning: #{msg}: #{data.inspect}"
68
+ if strict
69
+ raise ArgumentError, msg
70
+ else
71
+ Util.log(msg)
72
+ end
73
+ }
74
+
75
+ lines = data.split("\n")
76
+ if data.length == 0
77
+ return []
78
+ end
79
+
80
+ if data[-1].chr != "\n"
81
+ err.call("Does not end in a newline")
82
+ # We don't expect the last element of lines to be an empty
83
+ # string because split() doesn't behave that way.
84
+ end
85
+
86
+ pairs = []
87
+ line_num = 0
88
+ lines.each { |line|
89
+ line_num += 1
90
+
91
+ # Ignore blank lines
92
+ if line.strip() == ""
93
+ next
94
+ end
95
+
96
+ pair = line.split(':', 2)
97
+ if pair.length == 2
98
+ k, v = pair
99
+ k_s = k.strip()
100
+ if k_s != k
101
+ msg = "In line #{line_num}, ignoring leading or trailing whitespace in key #{k.inspect}"
102
+ err.call(msg)
103
+ end
104
+
105
+ if k_s.length == 0
106
+ err.call("In line #{line_num}, got empty key")
107
+ end
108
+
109
+ v_s = v.strip()
110
+ if v_s != v
111
+ msg = "In line #{line_num}, ignoring leading or trailing whitespace in value #{v.inspect}"
112
+ err.call(msg)
113
+ end
114
+
115
+ pairs << [k_s, v_s]
116
+ else
117
+ err.call("Line #{line_num} does not contain a colon")
118
+ end
119
+ }
120
+
121
+ return pairs
122
+ end
123
+
124
+ def Util.dict_to_kv(d)
125
+ return seq_to_kv(d.entries.sort)
126
+ end
127
+
128
+ def Util.kv_to_dict(s)
129
+ seq = kv_to_seq(s)
130
+ return Hash[*seq.flatten]
131
+ end
132
+ end
133
+ end