ruby-openid2 3.0.0
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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +136 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +54 -0
- data/LICENSE.txt +210 -0
- data/README.md +81 -0
- data/SECURITY.md +15 -0
- data/lib/hmac/hmac.rb +110 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid/association.rb +246 -0
- data/lib/openid/consumer/associationmanager.rb +354 -0
- data/lib/openid/consumer/checkid_request.rb +179 -0
- data/lib/openid/consumer/discovery.rb +516 -0
- data/lib/openid/consumer/discovery_manager.rb +144 -0
- data/lib/openid/consumer/html_parse.rb +142 -0
- data/lib/openid/consumer/idres.rb +513 -0
- data/lib/openid/consumer/responses.rb +147 -0
- data/lib/openid/consumer/session.rb +36 -0
- data/lib/openid/consumer.rb +406 -0
- data/lib/openid/cryptutil.rb +112 -0
- data/lib/openid/dh.rb +84 -0
- data/lib/openid/extension.rb +38 -0
- data/lib/openid/extensions/ax.rb +552 -0
- data/lib/openid/extensions/oauth.rb +88 -0
- data/lib/openid/extensions/pape.rb +170 -0
- data/lib/openid/extensions/sreg.rb +268 -0
- data/lib/openid/extensions/ui.rb +49 -0
- data/lib/openid/fetchers.rb +277 -0
- data/lib/openid/kvform.rb +113 -0
- data/lib/openid/kvpost.rb +62 -0
- data/lib/openid/message.rb +555 -0
- data/lib/openid/protocolerror.rb +7 -0
- data/lib/openid/server.rb +1571 -0
- data/lib/openid/store/filesystem.rb +260 -0
- data/lib/openid/store/interface.rb +73 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +79 -0
- data/lib/openid/store/nonce.rb +72 -0
- data/lib/openid/trustroot.rb +597 -0
- data/lib/openid/urinorm.rb +72 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +5 -0
- data/lib/openid/yadis/accept.rb +141 -0
- data/lib/openid/yadis/constants.rb +16 -0
- data/lib/openid/yadis/discovery.rb +151 -0
- data/lib/openid/yadis/filters.rb +192 -0
- data/lib/openid/yadis/htmltokenizer.rb +290 -0
- data/lib/openid/yadis/parsehtml.rb +50 -0
- data/lib/openid/yadis/services.rb +44 -0
- data/lib/openid/yadis/xrds.rb +160 -0
- data/lib/openid/yadis/xri.rb +86 -0
- data/lib/openid/yadis/xrires.rb +87 -0
- data/lib/openid.rb +27 -0
- data/lib/ruby-openid.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +331 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,516 @@
|
|
1
|
+
# Functions to discover OpenID endpoints from identifiers.
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require_relative "../util"
|
5
|
+
require_relative "../fetchers"
|
6
|
+
require_relative "../urinorm"
|
7
|
+
require_relative "../message"
|
8
|
+
require_relative "../yadis/discovery"
|
9
|
+
require_relative "../yadis/xrds"
|
10
|
+
require_relative "../yadis/xri"
|
11
|
+
require_relative "../yadis/services"
|
12
|
+
require_relative "../yadis/filters"
|
13
|
+
require_relative "../consumer/html_parse"
|
14
|
+
require_relative "../yadis/xrires"
|
15
|
+
|
16
|
+
module OpenID
|
17
|
+
OPENID_1_0_NS = "http://openid.net/xmlns/1.0"
|
18
|
+
OPENID_IDP_2_0_TYPE = "http://specs.openid.net/auth/2.0/server"
|
19
|
+
OPENID_2_0_TYPE = "http://specs.openid.net/auth/2.0/signon"
|
20
|
+
OPENID_1_1_TYPE = "http://openid.net/signon/1.1"
|
21
|
+
OPENID_1_0_TYPE = "http://openid.net/signon/1.0"
|
22
|
+
|
23
|
+
OPENID_1_0_MESSAGE_NS = OPENID1_NS
|
24
|
+
OPENID_2_0_MESSAGE_NS = OPENID2_NS
|
25
|
+
|
26
|
+
# Object representing an OpenID service endpoint.
|
27
|
+
class OpenIDServiceEndpoint
|
28
|
+
# OpenID service type URIs, listed in order of preference. The
|
29
|
+
# ordering of this list affects yadis and XRI service discovery.
|
30
|
+
OPENID_TYPE_URIS = [
|
31
|
+
OPENID_IDP_2_0_TYPE,
|
32
|
+
|
33
|
+
OPENID_2_0_TYPE,
|
34
|
+
OPENID_1_1_TYPE,
|
35
|
+
OPENID_1_0_TYPE,
|
36
|
+
]
|
37
|
+
|
38
|
+
# the verified identifier.
|
39
|
+
attr_accessor :claimed_id
|
40
|
+
|
41
|
+
# For XRI, the persistent identifier.
|
42
|
+
attr_accessor :canonical_id
|
43
|
+
|
44
|
+
attr_accessor :server_url, :type_uris, :local_id, :used_yadis
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@claimed_id = nil
|
48
|
+
@server_url = nil
|
49
|
+
@type_uris = []
|
50
|
+
@local_id = nil
|
51
|
+
@canonical_id = nil
|
52
|
+
@used_yadis = false # whether this came from an XRDS
|
53
|
+
@display_identifier = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def display_identifier
|
57
|
+
return @display_identifier if @display_identifier
|
58
|
+
|
59
|
+
return @claimed_id if @claimed_id.nil?
|
60
|
+
|
61
|
+
begin
|
62
|
+
parsed_identifier = URI.parse(@claimed_id)
|
63
|
+
rescue URI::InvalidURIError
|
64
|
+
raise ProtocolError, "Claimed identifier #{claimed_id} is not a valid URI"
|
65
|
+
end
|
66
|
+
|
67
|
+
return @claimed_id unless parsed_identifier.fragment
|
68
|
+
|
69
|
+
disp = parsed_identifier
|
70
|
+
disp.fragment = nil
|
71
|
+
|
72
|
+
disp.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_writer :display_identifier
|
76
|
+
|
77
|
+
def uses_extension(extension_uri)
|
78
|
+
@type_uris.member?(extension_uri)
|
79
|
+
end
|
80
|
+
|
81
|
+
def preferred_namespace
|
82
|
+
if @type_uris.member?(OPENID_IDP_2_0_TYPE) or
|
83
|
+
@type_uris.member?(OPENID_2_0_TYPE)
|
84
|
+
OPENID_2_0_MESSAGE_NS
|
85
|
+
else
|
86
|
+
OPENID_1_0_MESSAGE_NS
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def supports_type(type_uri)
|
91
|
+
# Does this endpoint support this type?
|
92
|
+
#
|
93
|
+
# I consider C{/server} endpoints to implicitly support C{/signon}.
|
94
|
+
(
|
95
|
+
@type_uris.member?(type_uri) or
|
96
|
+
(type_uri == OPENID_2_0_TYPE and is_op_identifier)
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def compatibility_mode
|
101
|
+
preferred_namespace != OPENID_2_0_MESSAGE_NS
|
102
|
+
end
|
103
|
+
|
104
|
+
def is_op_identifier
|
105
|
+
@type_uris.member?(OPENID_IDP_2_0_TYPE)
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse_service(yadis_url, uri, type_uris, service_element)
|
109
|
+
# Set the state of this object based on the contents of the
|
110
|
+
# service element.
|
111
|
+
@type_uris = type_uris
|
112
|
+
@server_url = uri
|
113
|
+
@used_yadis = true
|
114
|
+
|
115
|
+
return if is_op_identifier
|
116
|
+
|
117
|
+
# XXX: This has crappy implications for Service elements that
|
118
|
+
# contain both 'server' and 'signon' Types. But that's a
|
119
|
+
# pathological configuration anyway, so I don't think I care.
|
120
|
+
@local_id = OpenID.find_op_local_identifier(
|
121
|
+
service_element,
|
122
|
+
@type_uris,
|
123
|
+
)
|
124
|
+
@claimed_id = yadis_url
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_local_id
|
128
|
+
# Return the identifier that should be sent as the
|
129
|
+
# openid.identity parameter to the server.
|
130
|
+
if @local_id.nil? and @canonical_id.nil?
|
131
|
+
@claimed_id
|
132
|
+
else
|
133
|
+
(@local_id or @canonical_id)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_session_value
|
138
|
+
Hash[*instance_variables.flat_map { |name| [name, instance_variable_get(name)] }]
|
139
|
+
end
|
140
|
+
|
141
|
+
def ==(other)
|
142
|
+
to_session_value == other.to_session_value
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.from_session_value(value)
|
146
|
+
return value unless value.is_a?(Hash)
|
147
|
+
|
148
|
+
new.tap do |endpoint|
|
149
|
+
value.each do |name, val|
|
150
|
+
endpoint.instance_variable_set(name, val)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.from_basic_service_endpoint(endpoint)
|
156
|
+
# Create a new instance of this class from the endpoint object
|
157
|
+
# passed in.
|
158
|
+
#
|
159
|
+
# @return: nil or OpenIDServiceEndpoint for this endpoint object"""
|
160
|
+
|
161
|
+
type_uris = endpoint.match_types(OPENID_TYPE_URIS)
|
162
|
+
|
163
|
+
# If any Type URIs match and there is an endpoint URI specified,
|
164
|
+
# then this is an OpenID endpoint
|
165
|
+
if (!type_uris.nil? and !type_uris.empty?) and !endpoint.uri.nil?
|
166
|
+
openid_endpoint = new
|
167
|
+
openid_endpoint.parse_service(
|
168
|
+
endpoint.yadis_url,
|
169
|
+
endpoint.uri,
|
170
|
+
endpoint.type_uris,
|
171
|
+
endpoint.service_element,
|
172
|
+
)
|
173
|
+
else
|
174
|
+
openid_endpoint = nil
|
175
|
+
end
|
176
|
+
|
177
|
+
openid_endpoint
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.from_html(uri, html)
|
181
|
+
# Parse the given document as HTML looking for an OpenID <link
|
182
|
+
# rel=...>
|
183
|
+
#
|
184
|
+
# @rtype: [OpenIDServiceEndpoint]
|
185
|
+
|
186
|
+
discovery_types = [
|
187
|
+
[OPENID_2_0_TYPE, "openid2.provider", "openid2.local_id"],
|
188
|
+
[OPENID_1_1_TYPE, "openid.server", "openid.delegate"],
|
189
|
+
]
|
190
|
+
|
191
|
+
link_attrs = OpenID.parse_link_attrs(html)
|
192
|
+
services = []
|
193
|
+
discovery_types.each do |type_uri, op_endpoint_rel, local_id_rel|
|
194
|
+
op_endpoint_url = OpenID.find_first_href(link_attrs, op_endpoint_rel)
|
195
|
+
|
196
|
+
next unless op_endpoint_url
|
197
|
+
|
198
|
+
service = new
|
199
|
+
service.claimed_id = uri
|
200
|
+
service.local_id = OpenID.find_first_href(link_attrs, local_id_rel)
|
201
|
+
service.server_url = op_endpoint_url
|
202
|
+
service.type_uris = [type_uri]
|
203
|
+
|
204
|
+
services << service
|
205
|
+
end
|
206
|
+
|
207
|
+
services
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.from_xrds(uri, xrds)
|
211
|
+
# Parse the given document as XRDS looking for OpenID services.
|
212
|
+
#
|
213
|
+
# @rtype: [OpenIDServiceEndpoint]
|
214
|
+
#
|
215
|
+
# @raises L{XRDSError}: When the XRDS does not parse.
|
216
|
+
Yadis.apply_filter(uri, xrds, self)
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.from_discovery_result(discovery_result)
|
220
|
+
# Create endpoints from a DiscoveryResult.
|
221
|
+
#
|
222
|
+
# @type discoveryResult: L{DiscoveryResult}
|
223
|
+
#
|
224
|
+
# @rtype: list of L{OpenIDServiceEndpoint}
|
225
|
+
#
|
226
|
+
# @raises L{XRDSError}: When the XRDS does not parse.
|
227
|
+
meth = if discovery_result.is_xrds
|
228
|
+
method(:from_xrds)
|
229
|
+
else
|
230
|
+
method(:from_html)
|
231
|
+
end
|
232
|
+
|
233
|
+
meth.call(
|
234
|
+
discovery_result.normalized_uri,
|
235
|
+
discovery_result.response_text,
|
236
|
+
)
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.from_op_endpoint_url(op_endpoint_url)
|
240
|
+
# Construct an OP-Identifier OpenIDServiceEndpoint object for
|
241
|
+
# a given OP Endpoint URL
|
242
|
+
#
|
243
|
+
# @param op_endpoint_url: The URL of the endpoint
|
244
|
+
# @rtype: OpenIDServiceEndpoint
|
245
|
+
service = new
|
246
|
+
service.server_url = op_endpoint_url
|
247
|
+
service.type_uris = [OPENID_IDP_2_0_TYPE]
|
248
|
+
service
|
249
|
+
end
|
250
|
+
|
251
|
+
def to_s
|
252
|
+
format(
|
253
|
+
"<%s server_url=%s claimed_id=%s " +
|
254
|
+
"local_id=%s canonical_id=%s used_yadis=%s>",
|
255
|
+
self.class,
|
256
|
+
@server_url,
|
257
|
+
@claimed_id,
|
258
|
+
@local_id,
|
259
|
+
@canonical_id,
|
260
|
+
@used_yadis,
|
261
|
+
)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.find_op_local_identifier(service_element, type_uris)
|
266
|
+
# Find the OP-Local Identifier for this xrd:Service element.
|
267
|
+
#
|
268
|
+
# This considers openid:Delegate to be a synonym for xrd:LocalID
|
269
|
+
# if both OpenID 1.X and OpenID 2.0 types are present. If only
|
270
|
+
# OpenID 1.X is present, it returns the value of
|
271
|
+
# openid:Delegate. If only OpenID 2.0 is present, it returns the
|
272
|
+
# value of xrd:LocalID. If there is more than one LocalID tag and
|
273
|
+
# the values are different, it raises a DiscoveryFailure. This is
|
274
|
+
# also triggered when the xrd:LocalID and openid:Delegate tags are
|
275
|
+
# different.
|
276
|
+
|
277
|
+
# XXX: Test this function on its own!
|
278
|
+
|
279
|
+
# Build the list of tags that could contain the OP-Local
|
280
|
+
# Identifier
|
281
|
+
local_id_tags = []
|
282
|
+
if type_uris.member?(OPENID_1_1_TYPE) or
|
283
|
+
type_uris.member?(OPENID_1_0_TYPE)
|
284
|
+
# local_id_tags << Yadis::nsTag(OPENID_1_0_NS, 'openid', 'Delegate')
|
285
|
+
service_element.add_namespace("openid", OPENID_1_0_NS)
|
286
|
+
local_id_tags << "openid:Delegate"
|
287
|
+
end
|
288
|
+
|
289
|
+
if type_uris.member?(OPENID_2_0_TYPE)
|
290
|
+
# local_id_tags.append(Yadis::nsTag(XRD_NS_2_0, 'xrd', 'LocalID'))
|
291
|
+
service_element.add_namespace("xrd", Yadis::XRD_NS_2_0)
|
292
|
+
local_id_tags << "xrd:LocalID"
|
293
|
+
end
|
294
|
+
|
295
|
+
# Walk through all the matching tags and make sure that they all
|
296
|
+
# have the same value
|
297
|
+
local_id = nil
|
298
|
+
local_id_tags.each do |local_id_tag|
|
299
|
+
service_element.each_element(local_id_tag) do |local_id_element|
|
300
|
+
if local_id.nil?
|
301
|
+
local_id = local_id_element.text
|
302
|
+
elsif local_id != local_id_element.text
|
303
|
+
format = "More than one %s tag found in one service element"
|
304
|
+
message = format(format, local_id_tag)
|
305
|
+
raise DiscoveryFailure.new(message, nil)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
local_id
|
311
|
+
end
|
312
|
+
|
313
|
+
def self.normalize_xri(xri)
|
314
|
+
# Normalize an XRI, stripping its scheme if present
|
315
|
+
m = %r{^xri://(.*)}.match(xri)
|
316
|
+
xri = m[1] if m
|
317
|
+
xri
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.normalize_url(url)
|
321
|
+
# Normalize a URL, converting normalization failures to
|
322
|
+
# DiscoveryFailure
|
323
|
+
|
324
|
+
normalized = URINorm.urinorm(url)
|
325
|
+
rescue URI::Error => e
|
326
|
+
raise DiscoveryFailure.new("Error normalizing #{url}: #{e.message}", nil)
|
327
|
+
else
|
328
|
+
defragged = URI.parse(normalized)
|
329
|
+
defragged.fragment = nil
|
330
|
+
defragged.normalize.to_s
|
331
|
+
end
|
332
|
+
|
333
|
+
def self.best_matching_service(service, preferred_types)
|
334
|
+
# Return the index of the first matching type, or something higher
|
335
|
+
# if no type matches.
|
336
|
+
#
|
337
|
+
# This provides an ordering in which service elements that contain
|
338
|
+
# a type that comes earlier in the preferred types list come
|
339
|
+
# before service elements that come later. If a service element
|
340
|
+
# has more than one type, the most preferred one wins.
|
341
|
+
preferred_types.each_with_index do |value, index|
|
342
|
+
return index if service.type_uris.member?(value)
|
343
|
+
end
|
344
|
+
|
345
|
+
preferred_types.length
|
346
|
+
end
|
347
|
+
|
348
|
+
def self.arrange_by_type(service_list, preferred_types)
|
349
|
+
# Rearrange service_list in a new list so services are ordered by
|
350
|
+
# types listed in preferred_types. Return the new list.
|
351
|
+
|
352
|
+
# Build a list with the service elements in tuples whose
|
353
|
+
# comparison will prefer the one with the best matching service
|
354
|
+
prio_services = []
|
355
|
+
|
356
|
+
service_list.each_with_index do |s, index|
|
357
|
+
prio_services << [best_matching_service(s, preferred_types), index, s]
|
358
|
+
end
|
359
|
+
|
360
|
+
prio_services.sort!
|
361
|
+
|
362
|
+
# Now that the services are sorted by priority, remove the sort
|
363
|
+
# keys from the list.
|
364
|
+
(0...prio_services.length).each do |i|
|
365
|
+
prio_services[i] = prio_services[i][2]
|
366
|
+
end
|
367
|
+
|
368
|
+
prio_services
|
369
|
+
end
|
370
|
+
|
371
|
+
def self.get_op_or_user_services(openid_services)
|
372
|
+
# Extract OP Identifier services. If none found, return the rest,
|
373
|
+
# sorted with most preferred first according to
|
374
|
+
# OpenIDServiceEndpoint.openid_type_uris.
|
375
|
+
#
|
376
|
+
# openid_services is a list of OpenIDServiceEndpoint objects.
|
377
|
+
#
|
378
|
+
# Returns a list of OpenIDServiceEndpoint objects.
|
379
|
+
|
380
|
+
op_services = arrange_by_type(openid_services, [OPENID_IDP_2_0_TYPE])
|
381
|
+
|
382
|
+
openid_services = arrange_by_type(
|
383
|
+
openid_services,
|
384
|
+
OpenIDServiceEndpoint::OPENID_TYPE_URIS,
|
385
|
+
)
|
386
|
+
|
387
|
+
if !op_services.empty?
|
388
|
+
op_services
|
389
|
+
else
|
390
|
+
openid_services
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def self.discover_yadis(uri)
|
395
|
+
# Discover OpenID services for a URI. Tries Yadis and falls back
|
396
|
+
# on old-style <link rel='...'> discovery if Yadis fails.
|
397
|
+
#
|
398
|
+
# @param uri: normalized identity URL
|
399
|
+
# @type uri: str
|
400
|
+
#
|
401
|
+
# @return: (claimed_id, services)
|
402
|
+
# @rtype: (str, list(OpenIDServiceEndpoint))
|
403
|
+
#
|
404
|
+
# @raises DiscoveryFailure: when discovery fails.
|
405
|
+
|
406
|
+
# Might raise a yadis.discover.DiscoveryFailure if no document
|
407
|
+
# came back for that URI at all. I don't think falling back to
|
408
|
+
# OpenID 1.0 discovery on the same URL will help, so don't bother
|
409
|
+
# to catch it.
|
410
|
+
response = Yadis.discover(uri)
|
411
|
+
|
412
|
+
yadis_url = response.normalized_uri
|
413
|
+
body = response.response_text
|
414
|
+
|
415
|
+
begin
|
416
|
+
openid_services = OpenIDServiceEndpoint.from_xrds(yadis_url, body)
|
417
|
+
rescue Yadis::XRDSError
|
418
|
+
# Does not parse as a Yadis XRDS file
|
419
|
+
openid_services = []
|
420
|
+
end
|
421
|
+
|
422
|
+
if openid_services.empty?
|
423
|
+
# Either not an XRDS or there are no OpenID services.
|
424
|
+
|
425
|
+
if response.is_xrds
|
426
|
+
# if we got the Yadis content-type or followed the Yadis
|
427
|
+
# header, re-fetch the document without following the Yadis
|
428
|
+
# header, with no Accept header.
|
429
|
+
return discover_no_yadis(uri)
|
430
|
+
end
|
431
|
+
|
432
|
+
# Try to parse the response as HTML.
|
433
|
+
# <link rel="...">
|
434
|
+
openid_services = OpenIDServiceEndpoint.from_html(yadis_url, body)
|
435
|
+
end
|
436
|
+
|
437
|
+
[yadis_url, get_op_or_user_services(openid_services)]
|
438
|
+
end
|
439
|
+
|
440
|
+
def self.discover_xri(iname)
|
441
|
+
endpoints = []
|
442
|
+
iname = normalize_xri(iname)
|
443
|
+
|
444
|
+
begin
|
445
|
+
canonical_id, services = Yadis::XRI::ProxyResolver.new.query(iname)
|
446
|
+
|
447
|
+
raise Yadis::XRDSError.new(format("No CanonicalID found for XRI %s", iname)) if canonical_id.nil?
|
448
|
+
|
449
|
+
flt = Yadis.make_filter(OpenIDServiceEndpoint)
|
450
|
+
|
451
|
+
services.each do |service_element|
|
452
|
+
endpoints += flt.get_service_endpoints(iname, service_element)
|
453
|
+
end
|
454
|
+
rescue Yadis::XRDSError, Yadis::XRI::XRIHTTPError => e
|
455
|
+
Util.log("xrds error on " + iname + ": " + e.to_s)
|
456
|
+
end
|
457
|
+
|
458
|
+
endpoints.each do |endpoint|
|
459
|
+
# Is there a way to pass this through the filter to the endpoint
|
460
|
+
# constructor instead of tacking it on after?
|
461
|
+
endpoint.canonical_id = canonical_id
|
462
|
+
endpoint.claimed_id = canonical_id
|
463
|
+
endpoint.display_identifier = iname
|
464
|
+
end
|
465
|
+
|
466
|
+
# FIXME: returned xri should probably be in some normal form
|
467
|
+
[iname, get_op_or_user_services(endpoints)]
|
468
|
+
end
|
469
|
+
|
470
|
+
def self.discover_no_yadis(uri)
|
471
|
+
http_resp = OpenID.fetch(uri)
|
472
|
+
if http_resp.code != "200" and http_resp.code != "206"
|
473
|
+
raise DiscoveryFailure.new(
|
474
|
+
'HTTP Response status from identity URL host is not "200". ' \
|
475
|
+
"Got status #{http_resp.code.inspect}",
|
476
|
+
http_resp,
|
477
|
+
)
|
478
|
+
end
|
479
|
+
|
480
|
+
claimed_id = http_resp.final_url
|
481
|
+
openid_services = OpenIDServiceEndpoint.from_html(
|
482
|
+
claimed_id, http_resp.body
|
483
|
+
)
|
484
|
+
[claimed_id, openid_services]
|
485
|
+
end
|
486
|
+
|
487
|
+
def self.discover_uri(uri)
|
488
|
+
# Hack to work around URI parsing for URls with *no* scheme.
|
489
|
+
uri = "http://" + uri if uri.index("://").nil?
|
490
|
+
|
491
|
+
begin
|
492
|
+
parsed = URI.parse(uri)
|
493
|
+
rescue URI::InvalidURIError => e
|
494
|
+
raise DiscoveryFailure.new("URI is not valid: #{e.message}", nil)
|
495
|
+
end
|
496
|
+
|
497
|
+
if !parsed.scheme.nil? and !parsed.scheme.empty? && !%w[http https].member?(parsed.scheme)
|
498
|
+
raise DiscoveryFailure.new(
|
499
|
+
"URI scheme #{parsed.scheme} is not HTTP or HTTPS", nil
|
500
|
+
)
|
501
|
+
end
|
502
|
+
|
503
|
+
uri = normalize_url(uri)
|
504
|
+
claimed_id, openid_services = discover_yadis(uri)
|
505
|
+
claimed_id = normalize_url(claimed_id)
|
506
|
+
[claimed_id, openid_services]
|
507
|
+
end
|
508
|
+
|
509
|
+
def self.discover(identifier)
|
510
|
+
if Yadis::XRI.identifier_scheme(identifier) == :xri
|
511
|
+
discover_xri(identifier)
|
512
|
+
else
|
513
|
+
discover_uri(identifier)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module OpenID
|
2
|
+
class Consumer
|
3
|
+
# A set of discovered services, for tracking which providers have
|
4
|
+
# been attempted for an OpenID identifier
|
5
|
+
class DiscoveredServices
|
6
|
+
attr_reader :current
|
7
|
+
|
8
|
+
def initialize(starting_url, yadis_url, services)
|
9
|
+
@starting_url = starting_url
|
10
|
+
@yadis_url = yadis_url
|
11
|
+
@services = services.dup
|
12
|
+
@current = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def next
|
16
|
+
@current = @services.shift
|
17
|
+
end
|
18
|
+
|
19
|
+
def for_url?(url)
|
20
|
+
[@starting_url, @yadis_url].member?(url)
|
21
|
+
end
|
22
|
+
|
23
|
+
def started?
|
24
|
+
!@current.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty?
|
28
|
+
@services.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_session_value
|
32
|
+
services = @services.map { |s| s.respond_to?(:to_session_value) ? s.to_session_value : s }
|
33
|
+
current_val = @current.respond_to?(:to_session_value) ? @current.to_session_value : @current
|
34
|
+
|
35
|
+
{
|
36
|
+
"starting_url" => @starting_url,
|
37
|
+
"yadis_url" => @yadis_url,
|
38
|
+
"services" => services,
|
39
|
+
"current" => current_val,
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
to_session_value == other.to_session_value
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.from_session_value(value)
|
48
|
+
return value unless value.is_a?(Hash)
|
49
|
+
|
50
|
+
services = value["services"].map { |s| OpenID::OpenIDServiceEndpoint.from_session_value(s) }
|
51
|
+
current = OpenID::OpenIDServiceEndpoint.from_session_value(value["current"])
|
52
|
+
|
53
|
+
obj = new(value["starting_url"], value["yadis_url"], services)
|
54
|
+
obj.instance_variable_set(:@current, current)
|
55
|
+
obj
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Manages calling discovery and tracking which endpoints have
|
60
|
+
# already been attempted.
|
61
|
+
class DiscoveryManager
|
62
|
+
def initialize(session, url, session_key_suffix = nil)
|
63
|
+
@url = url
|
64
|
+
|
65
|
+
@session = OpenID::Consumer::Session.new(session, DiscoveredServices)
|
66
|
+
@session_key_suffix = session_key_suffix || "auth"
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_next_service
|
70
|
+
manager = get_manager
|
71
|
+
if !manager.nil? && manager.empty?
|
72
|
+
destroy_manager
|
73
|
+
manager = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
if manager.nil?
|
77
|
+
yadis_url, services = yield @url
|
78
|
+
manager = create_manager(yadis_url, services)
|
79
|
+
end
|
80
|
+
|
81
|
+
if !manager.nil?
|
82
|
+
service = manager.next
|
83
|
+
store(manager)
|
84
|
+
else
|
85
|
+
service = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
service
|
89
|
+
end
|
90
|
+
|
91
|
+
def cleanup(force = false)
|
92
|
+
manager = get_manager(force)
|
93
|
+
if !manager.nil?
|
94
|
+
service = manager.current
|
95
|
+
destroy_manager(force)
|
96
|
+
else
|
97
|
+
service = nil
|
98
|
+
end
|
99
|
+
service
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
def get_manager(force = false)
|
105
|
+
manager = load
|
106
|
+
return manager if force || manager.nil? || manager.for_url?(@url)
|
107
|
+
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_manager(yadis_url, services)
|
112
|
+
manager = get_manager
|
113
|
+
raise StandardError, "There is already a manager for #{yadis_url}" unless manager.nil?
|
114
|
+
return if services.empty?
|
115
|
+
|
116
|
+
manager = DiscoveredServices.new(@url, yadis_url, services)
|
117
|
+
store(manager)
|
118
|
+
manager
|
119
|
+
end
|
120
|
+
|
121
|
+
def destroy_manager(force = false)
|
122
|
+
return if get_manager(force).nil?
|
123
|
+
|
124
|
+
destroy!
|
125
|
+
end
|
126
|
+
|
127
|
+
def session_key
|
128
|
+
"OpenID::Consumer::DiscoveredServices::" + @session_key_suffix
|
129
|
+
end
|
130
|
+
|
131
|
+
def store(manager)
|
132
|
+
@session[session_key] = manager
|
133
|
+
end
|
134
|
+
|
135
|
+
def load
|
136
|
+
@session[session_key]
|
137
|
+
end
|
138
|
+
|
139
|
+
def destroy!
|
140
|
+
@session[session_key] = nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|