Module: OpenID

Defined in:
lib/openid/util.rb,
lib/openid.rb,
lib/openid/dh.rb,
lib/openid/kvform.rb,
lib/openid/kvpost.rb,
lib/openid/server.rb,
lib/openid/message.rb,
lib/openid/urinorm.rb,
lib/openid/version.rb,
lib/openid/consumer.rb,
lib/openid/fetchers.rb,
lib/openid/cryptutil.rb,
lib/openid/extension.rb,
lib/openid/trustroot.rb,
lib/openid/yadis/xri.rb,
lib/openid/yadis/xrds.rb,
lib/openid/association.rb,
lib/openid/store/nonce.rb,
lib/openid/store/memory.rb,
lib/openid/yadis/accept.rb,
lib/openid/yadis/xrires.rb,
lib/openid/extensions/ax.rb,
lib/openid/extensions/ui.rb,
lib/openid/protocolerror.rb,
lib/openid/yadis/filters.rb,
lib/openid/consumer/idres.rb,
lib/openid/store/memcache.rb,
lib/openid/yadis/services.rb,
lib/openid/extensions/pape.rb,
lib/openid/extensions/sreg.rb,
lib/openid/store/interface.rb,
lib/openid/yadis/constants.rb,
lib/openid/yadis/discovery.rb,
lib/openid/yadis/parsehtml.rb,
lib/openid/consumer/session.rb,
lib/openid/extensions/oauth.rb,
lib/openid/store/filesystem.rb,
lib/openid/consumer/discovery.rb,
lib/openid/consumer/responses.rb,
lib/openid/consumer/html_parse.rb,
lib/openid/consumer/checkid_request.rb,
lib/openid/consumer/discovery_manager.rb,
lib/openid/consumer/associationmanager.rb

Overview

This file contains functions and classes used for extracting endpoint information out of a Yadis XRD file using the REXML XML parser.

Defined Under Namespace

Modules: AX, CryptUtil, Nonce, OAuth, PAPE, SReg, Server, Store, TrustRoot, UI, URINorm, Util, Version, Yadis Classes: AssertionError, Association, AssociationNegotiator, Consumer, DiffieHellman, DiscoveryFailure, Extension, FetchingError, HTTPRedirectLimitReached, HTTPResponse, HTTPStatusError, InvalidOpenIDNamespace, KVFormError, KVPostNetworkError, Message, NamespaceAliasRegistrationError, NamespaceMap, OpenIDError, OpenIDServiceEndpoint, ProtocolError, RealmVerificationRedirected, SSLFetchingError, ServerError, StandardFetcher, TypeURIMismatch, UndefinedOpenIDNamespace

Constant Summary collapse

IDENTIFIER_SELECT =
"http://specs.openid.net/auth/2.0/identifier_select"
SREG_URI =

URI for Simple Registration extension, the only commonly deployed OpenID 1.x extension, and so a special case.

"http://openid.net/sreg/1.0"
OPENID1_NS =

The OpenID 1.x namespace URIs

"http://openid.net/signon/1.0"
OPENID11_NS =
"http://openid.net/signon/1.1"
OPENID1_NAMESPACES =
[OPENID1_NS, OPENID11_NS]
OPENID2_NS =

The OpenID 2.0 namespace URI

"http://specs.openid.net/auth/2.0"
NULL_NAMESPACE =

The namespace consisting of pairs with keys that are prefixed with “openid.” but not in another namespace.

:null_namespace
OPENID_NS =

The null namespace, when it is an allowed OpenID namespace

:openid_namespace
BARE_NS =

The top-level namespace, excluding all pairs with keys that start with “openid.”

:bare_namespace
OPENID1_URL_LIMIT =

Limit, in bytes, of identity provider and return_to URLs, including response payload. See OpenID 1.1 specification, Appendix D.

2047
OPENID_PROTOCOL_FIELDS =

All OpenID protocol fields. Used to check namespace aliases.

%w[
  ns
  mode
  error
  return_to
  contact
  reference
  signed
  assoc_type
  session_type
  dh_modulus
  dh_gen
  dh_consumer_public
  claimed_id
  identity
  realm
  invalidate_handle
  op_endpoint
  response_nonce
  sig
  assoc_handle
  trust_root
  openid
]
NO_DEFAULT =

Sentinel used for Message implementation to indicate that getArg should raise an exception instead of returning a default.

:no_default
DefaultNegotiator =
AssociationNegotiator.new([
  %w[HMAC-SHA1 DH-SHA1],
  %w[HMAC-SHA1 no-encryption],
  %w[HMAC-SHA256 DH-SHA256],
  %w[HMAC-SHA256 no-encryption],
])
EncryptedNegotiator =
AssociationNegotiator.new([
  %w[HMAC-SHA1 DH-SHA1],
  %w[HMAC-SHA256 DH-SHA256],
])
OPENID_1_0_NS =
"http://openid.net/xmlns/1.0"
OPENID_IDP_2_0_TYPE =
"http://specs.openid.net/auth/2.0/server"
OPENID_2_0_TYPE =
"http://specs.openid.net/auth/2.0/signon"
OPENID_1_1_TYPE =
"http://openid.net/signon/1.1"
OPENID_1_0_TYPE =
"http://openid.net/signon/1.0"
OPENID_1_0_MESSAGE_NS =
OPENID1_NS
OPENID_2_0_MESSAGE_NS =
OPENID2_NS
REMOVED_RE =

Stuff to remove before we start looking for tags

%r{
  # Comments
  <!--.*?-->

  # CDATA blocks
| <!\[CDATA\[.*?\]\]>

  # script blocks
| <script\b

  # make sure script is not an XML namespace
  (?!:)

  [^>]*>.*?</script>

}mix

Class Method Summary collapse

Class Method Details

.arrange_by_type(service_list, preferred_types) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/openid/consumer/discovery.rb', line 348

def self.arrange_by_type(service_list, preferred_types)
  # Rearrange service_list in a new list so services are ordered by
  # types listed in preferred_types.  Return the new list.

  # Build a list with the service elements in tuples whose
  # comparison will prefer the one with the best matching service
  prio_services = []

  service_list.each_with_index do |s, index|
    prio_services << [best_matching_service(s, preferred_types), index, s]
  end

  prio_services.sort!

  # Now that the services are sorted by priority, remove the sort
  # keys from the list.
  (0...prio_services.length).each do |i|
    prio_services[i] = prio_services[i][2]
  end

  prio_services
end

.best_matching_service(service, preferred_types) ⇒ Object



333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/openid/consumer/discovery.rb', line 333

def self.best_matching_service(service, preferred_types)
  # Return the index of the first matching type, or something higher
  # if no type matches.
  #
  # This provides an ordering in which service elements that contain
  # a type that comes earlier in the preferred types list come
  # before service elements that come later. If a service element
  # has more than one type, the most preferred one wins.
  preferred_types.each_with_index do |value, index|
    return index if service.type_uris.member?(value)
  end

  preferred_types.length
end

.check_sreg_field_name(fieldname) ⇒ Object

raise ArgumentError if fieldname is not in the defined sreg fields

Raises:

  • (ArgumentError)


30
31
32
33
34
# File 'lib/openid/extensions/sreg.rb', line 30

def OpenID.check_sreg_field_name(fieldname)
  return if DATA_FIELDS.member?(fieldname)

  raise ArgumentError, "#{fieldname} is not a defined simple registration field"
end

.discover(identifier) ⇒ Object



509
510
511
512
513
514
515
# File 'lib/openid/consumer/discovery.rb', line 509

def self.discover(identifier)
  if Yadis::XRI.identifier_scheme(identifier) == :xri
    discover_xri(identifier)
  else
    discover_uri(identifier)
  end
end

.discover_no_yadis(uri) ⇒ Object



470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/openid/consumer/discovery.rb', line 470

def self.discover_no_yadis(uri)
  http_resp = OpenID.fetch(uri)
  if http_resp.code != "200" and http_resp.code != "206"
    raise DiscoveryFailure.new(
      'HTTP Response status from identity URL host is not "200". ' \
        "Got status #{http_resp.code.inspect}",
      http_resp,
    )
  end

  claimed_id = http_resp.final_url
  openid_services = OpenIDServiceEndpoint.from_html(
    claimed_id, http_resp.body
  )
  [claimed_id, openid_services]
end

.discover_uri(uri) ⇒ Object



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/openid/consumer/discovery.rb', line 487

def self.discover_uri(uri)
  # Hack to work around URI parsing for URls with *no* scheme.
  uri = "http://" + uri if uri.index("://").nil?

  begin
    parsed = URI.parse(uri)
  rescue URI::InvalidURIError => e
    raise DiscoveryFailure.new("URI is not valid: #{e.message}", nil)
  end

  if !parsed.scheme.nil? and !parsed.scheme.empty? && !%w[http https].member?(parsed.scheme)
    raise DiscoveryFailure.new(
      "URI scheme #{parsed.scheme} is not HTTP or HTTPS", nil
    )
  end

  uri = normalize_url(uri)
  claimed_id, openid_services = discover_yadis(uri)
  claimed_id = normalize_url(claimed_id)
  [claimed_id, openid_services]
end

.discover_xri(iname) ⇒ Object



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/openid/consumer/discovery.rb', line 440

def self.discover_xri(iname)
  endpoints = []
  iname = normalize_xri(iname)

  begin
    canonical_id, services = Yadis::XRI::ProxyResolver.new.query(iname)

    raise Yadis::XRDSError.new(format("No CanonicalID found for XRI %s", iname)) if canonical_id.nil?

    flt = Yadis.make_filter(OpenIDServiceEndpoint)

    services.each do |service_element|
      endpoints += flt.get_service_endpoints(iname, service_element)
    end
  rescue Yadis::XRDSError, Yadis::XRI::XRIHTTPError => e
    Util.log("xrds error on " + iname + ": " + e.to_s)
  end

  endpoints.each do |endpoint|
    # Is there a way to pass this through the filter to the endpoint
    # constructor instead of tacking it on after?
    endpoint.canonical_id = canonical_id
    endpoint.claimed_id = canonical_id
    endpoint.display_identifier = iname
  end

  # FIXME: returned xri should probably be in some normal form
  [iname, get_op_or_user_services(endpoints)]
end

.discover_yadis(uri) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/openid/consumer/discovery.rb', line 394

def self.discover_yadis(uri)
  # Discover OpenID services for a URI. Tries Yadis and falls back
  # on old-style <link rel='...'> discovery if Yadis fails.
  #
  # @param uri: normalized identity URL
  # @type uri: str
  #
  # @return: (claimed_id, services)
  # @rtype: (str, list(OpenIDServiceEndpoint))
  #
  # @raises DiscoveryFailure: when discovery fails.

  # Might raise a yadis.discover.DiscoveryFailure if no document
  # came back for that URI at all.  I don't think falling back to
  # OpenID 1.0 discovery on the same URL will help, so don't bother
  # to catch it.
  response = Yadis.discover(uri)

  yadis_url = response.normalized_uri
  body = response.response_text

  begin
    openid_services = OpenIDServiceEndpoint.from_xrds(yadis_url, body)
  rescue Yadis::XRDSError
    # Does not parse as a Yadis XRDS file
    openid_services = []
  end

  if openid_services.empty?
    # Either not an XRDS or there are no OpenID services.

    if response.is_xrds
      # if we got the Yadis content-type or followed the Yadis
      # header, re-fetch the document without following the Yadis
      # header, with no Accept header.
      return discover_no_yadis(uri)
    end

    # Try to parse the response as HTML.
    # <link rel="...">
    openid_services = OpenIDServiceEndpoint.from_html(yadis_url, body)
  end

  [yadis_url, get_op_or_user_services(openid_services)]
end

.fetch(url, body = nil, headers = nil, redirect_limit = StandardFetcher::REDIRECT_LIMIT) ⇒ Object



94
95
96
97
# File 'lib/openid/fetchers.rb', line 94

def self.fetch(url, body = nil, headers = nil,
  redirect_limit = StandardFetcher::REDIRECT_LIMIT)
  fetcher.fetch(url, body, headers, redirect_limit)
end

.fetcherObject



99
100
101
102
103
# File 'lib/openid/fetchers.rb', line 99

def self.fetcher
  @fetcher = StandardFetcher.new if @fetcher.nil?

  @fetcher
end

.fetcher=(fetcher) ⇒ Object



105
106
107
# File 'lib/openid/fetchers.rb', line 105

def self.fetcher=(fetcher)
  @fetcher = fetcher
end

.fetcher_use_env_http_proxyObject

Set the default fetcher to use the HTTP proxy defined in the environment variable ‘http_proxy’.



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/openid/fetchers.rb', line 111

def self.fetcher_use_env_http_proxy
  proxy_string = ENV["http_proxy"]
  return unless proxy_string

  proxy_uri = URI.parse(proxy_string)
  @fetcher = StandardFetcher.new(
    proxy_uri.host,
    proxy_uri.port,
    proxy_uri.user,
    proxy_uri.password,
  )
end

.find_first_href(link_attrs_list, target_rel) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
# File 'lib/openid/consumer/html_parse.rb', line 131

def self.find_first_href(link_attrs_list, target_rel)
  # Return the value of the href attribute for the first link tag in
  # the list that has target_rel as a relationship.

  # XXX: TESTME
  matches = find_links_rel(link_attrs_list, target_rel)
  return if !matches or matches.empty?

  first = matches[0]
  first["href"]
end


116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/openid/consumer/html_parse.rb', line 116

def self.find_links_rel(link_attrs_list, target_rel)
  # Filter the list of link attributes on whether it has target_rel
  # as a relationship.

  # XXX: TESTME
  matches_target = ->(attrs) { link_has_rel(attrs, target_rel) }
  result = []

  link_attrs_list.each do |item|
    result << item if matches_target.call(item)
  end

  result
end

.find_op_local_identifier(service_element, type_uris) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/openid/consumer/discovery.rb', line 265

def self.find_op_local_identifier(service_element, type_uris)
  # Find the OP-Local Identifier for this xrd:Service element.
  #
  # This considers openid:Delegate to be a synonym for xrd:LocalID
  # if both OpenID 1.X and OpenID 2.0 types are present. If only
  # OpenID 1.X is present, it returns the value of
  # openid:Delegate. If only OpenID 2.0 is present, it returns the
  # value of xrd:LocalID. If there is more than one LocalID tag and
  # the values are different, it raises a DiscoveryFailure. This is
  # also triggered when the xrd:LocalID and openid:Delegate tags are
  # different.

  # XXX: Test this function on its own!

  # Build the list of tags that could contain the OP-Local
  # Identifier
  local_id_tags = []
  if type_uris.member?(OPENID_1_1_TYPE) or
      type_uris.member?(OPENID_1_0_TYPE)
    # local_id_tags << Yadis::nsTag(OPENID_1_0_NS, 'openid', 'Delegate')
    service_element.add_namespace("openid", OPENID_1_0_NS)
    local_id_tags << "openid:Delegate"
  end

  if type_uris.member?(OPENID_2_0_TYPE)
    # local_id_tags.append(Yadis::nsTag(XRD_NS_2_0, 'xrd', 'LocalID'))
    service_element.add_namespace("xrd", Yadis::XRD_NS_2_0)
    local_id_tags << "xrd:LocalID"
  end

  # Walk through all the matching tags and make sure that they all
  # have the same value
  local_id = nil
  local_id_tags.each do |local_id_tag|
    service_element.each_element(local_id_tag) do |local_id_element|
      if local_id.nil?
        local_id = local_id_element.text
      elsif local_id != local_id_element.text
        format = "More than one %s tag found in one service element"
        message = format(format, local_id_tag)
        raise DiscoveryFailure.new(message, nil)
      end
    end
  end

  local_id
end

.get_op_or_user_services(openid_services) ⇒ Object



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/openid/consumer/discovery.rb', line 371

def self.get_op_or_user_services(openid_services)
  # Extract OP Identifier services.  If none found, return the rest,
  # sorted with most preferred first according to
  # OpenIDServiceEndpoint.openid_type_uris.
  #
  # openid_services is a list of OpenIDServiceEndpoint objects.
  #
  # Returns a list of OpenIDServiceEndpoint objects.

  op_services = arrange_by_type(openid_services, [OPENID_IDP_2_0_TYPE])

  openid_services = arrange_by_type(
    openid_services,
    OpenIDServiceEndpoint::OPENID_TYPE_URIS,
  )

  if !op_services.empty?
    op_services
  else
    openid_services
  end
end

.get_secret_size(assoc_type) ⇒ Object



7
8
9
10
11
12
13
14
15
# File 'lib/openid/association.rb', line 7

def self.get_secret_size(assoc_type)
  if assoc_type == "HMAC-SHA1"
    20
  elsif assoc_type == "HMAC-SHA256"
    32
  else
    raise ArgumentError("Unsupported association type: #{assoc_type}")
  end
end

.get_sreg_ns(message) ⇒ Object

Extract the simple registration namespace URI from the given OpenID message. Handles OpenID 1 and 2, as well as both sreg namespace URIs found in the wild, as well as missing namespace definitions (for OpenID 1)



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/openid/extensions/sreg.rb', line 45

def OpenID.get_sreg_ns(message)
  [NS_URI_1_1, NS_URI_1_0].each do |ns|
    return ns if message.namespaces.get_alias(ns)
  end
  # try to add an alias, since we didn't find one
  ns = NS_URI_1_1
  begin
    message.namespaces.add_alias(ns, "sreg")
  rescue IndexError
    raise NamespaceError
  end
  ns
end


108
109
110
111
112
113
114
# File 'lib/openid/consumer/html_parse.rb', line 108

def self.link_has_rel(link_attrs, target_rel)
  # Does this link have target_rel as a relationship?

  # XXX: TESTME
  rel_attr = link_attrs["rel"]
  (rel_attr and rel_matches(rel_attr, target_rel))
end

.make_kv_post(request_message, server_url) ⇒ Object

Send the message to the server via HTTP POST and receive and parse a response in KV Form



54
55
56
57
58
59
60
61
# File 'lib/openid/kvpost.rb', line 54

def self.make_kv_post(request_message, server_url)
  begin
    http_response = fetch(server_url, request_message.to_url_encoded)
  rescue Exception
    raise KVPostNetworkError.new("Unable to contact OpenID server: #{$!}")
  end
  Message.from_http_response(http_response, server_url)
end

.normalize_url(url) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/openid/consumer/discovery.rb', line 320

def self.normalize_url(url)
  # Normalize a URL, converting normalization failures to
  # DiscoveryFailure

  normalized = URINorm.urinorm(url)
rescue URI::Error => e
  raise DiscoveryFailure.new("Error normalizing #{url}: #{e.message}", nil)
else
  defragged = URI.parse(normalized)
  defragged.fragment = nil
  defragged.normalize.to_s
end

.normalize_xri(xri) ⇒ Object



313
314
315
316
317
318
# File 'lib/openid/consumer/discovery.rb', line 313

def self.normalize_xri(xri)
  # Normalize an XRI, stripping its scheme if present
  m = %r{^xri://(.*)}.match(xri)
  xri = m[1] if m
  xri
end

.openid_unescape(s) ⇒ Object



22
23
24
# File 'lib/openid/consumer/html_parse.rb', line 22

def self.openid_unescape(s)
  s.gsub("&amp;", "&").gsub("&lt;", "<").gsub("&gt;", ">").gsub("&quot;", '"')
end


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/openid/consumer/html_parse.rb', line 34

def self.parse_link_attrs(html)
  begin
    stripped = html.gsub(REMOVED_RE, "")
  rescue ArgumentError
    begin
      stripped = html.encode("UTF-8", "binary", invalid: :replace, undef: :replace, replace: "").gsub(
        REMOVED_RE, ""
      )
    rescue Encoding::UndefinedConversionError, Encoding::ConverterNotFoundError
      # needed for a problem in JRuby where it can't handle the conversion.
      # see details here: https://github.com/jruby/jruby/issues/829
      stripped = html.encode("UTF-8", "ASCII", invalid: :replace, undef: :replace, replace: "").gsub(
        REMOVED_RE, ""
      )
    end
  end
  parser = HTMLTokenizer.new(stripped)

  links = []
  # to keep track of whether or not we are in the head element
  in_head = false
  in_html = false
  saw_head = false

  begin
    while el = parser.getTag(
      "head",
      "/head",
      "link",
      "body",
      "/body",
      "html",
      "/html",
    )

      # we are leaving head or have reached body, so we bail
      return links if ["/head", "body", "/body", "/html"].member?(el.tag_name)

      # enforce html > head > link
      in_html = true if el.tag_name == "html"
      next unless in_html

      if el.tag_name == "head"
        if saw_head
          return links # only allow one head
        end

        saw_head = true
        in_head = true unless el.to_s[-2] == 47 # tag ends with a /: a short tag
      end
      next unless in_head

      return links if el.tag_name == "html"

      links << unescape_hash(el.attr_hash) if el.tag_name == "link"

    end
  rescue Exception # just stop parsing if there's an error
  end
  links
end

.rel_matches(rel_attr, target_rel) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/openid/consumer/html_parse.rb', line 96

def self.rel_matches(rel_attr, target_rel)
  # Does this target_rel appear in the rel_str?
  # XXX: TESTME
  rels = rel_attr.strip.split
  rels.each do |rel|
    rel = rel.downcase
    return true if rel == target_rel
  end

  false
end

.supports_sreg?(endpoint) ⇒ Boolean

Does the given endpoint advertise support for simple registration?

Returns:

  • (Boolean)


37
38
39
# File 'lib/openid/extensions/sreg.rb', line 37

def OpenID.supports_sreg?(endpoint)
  endpoint.uses_extension(NS_URI_1_1) || endpoint.uses_extension(NS_URI_1_0)
end

.unescape_hash(h) ⇒ Object



26
27
28
29
30
31
32
# File 'lib/openid/consumer/html_parse.rb', line 26

def self.unescape_hash(h)
  newh = {}
  h.map do |k, v|
    newh[k] = openid_unescape(v)
  end
  newh
end