Module: OpenID

Defined in:
lib/openid.rb,
lib/openid/dh.rb,
lib/openid/util.rb,
lib/openid/kvform.rb,
lib/openid/kvpost.rb,
lib/openid/server.rb,
lib/openid/message.rb,
lib/openid/urinorm.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/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/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

See OpenID::Consumer or OpenID::Server modules, as well as the store classes

Defined Under Namespace

Modules: AX, CryptUtil, Nonce, OAuth, PAPE, SReg, Server, Store, TrustRoot, URINorm, Util, 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

VERSION =
"2.1.8"
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.

[
 '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([['HMAC-SHA1', 'DH-SHA1'],
['HMAC-SHA1', 'no-encryption'],
['HMAC-SHA256', 'DH-SHA256'],
['HMAC-SHA256', 'no-encryption']])
EncryptedNegotiator =
AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],
['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

/
  # Comments
  <!--.*?-->

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

  # script blocks
| <script\b

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

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

/mixu

Class Method Summary collapse

Class Method Details

.arrange_by_type(service_list, preferred_types) ⇒ Object



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/openid/consumer/discovery.rb', line 329

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 { |s, index|
    prio_services << [best_matching_service(s, preferred_types), index, s]
  }

  prio_services.sort!

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

  return prio_services
end

.best_matching_service(service, preferred_types) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/openid/consumer/discovery.rb', line 312

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 { |value, index|
    if service.type_uris.member?(value)
      return index
    end
  }

  return preferred_types.length
end

.check_sreg_field_name(fieldname) ⇒ Object

raise ArgumentError if fieldname is not in the defined sreg fields



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

def OpenID.check_sreg_field_name(fieldname)
  unless DATA_FIELDS.member? fieldname
    raise ArgumentError, "#{fieldname} is not a defined simple registration field"
  end
end

.discover(identifier) ⇒ Object



491
492
493
494
495
496
497
# File 'lib/openid/consumer/discovery.rb', line 491

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

.discover_no_yadis(uri) ⇒ Object



452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/openid/consumer/discovery.rb', line 452

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)
  return [claimed_id, openid_services]
end

.discover_uri(uri) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/openid/consumer/discovery.rb', line 466

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

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

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

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

.discover_xri(iname) ⇒ Object



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/openid/consumer/discovery.rb', line 419

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

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

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

    flt = Yadis.make_filter(OpenIDServiceEndpoint)

    services.each { |service_element|
      endpoints += flt.get_service_endpoints(iname, service_element)
    }
  rescue Yadis::XRDSError => why
    Util.log('xrds error on ' + iname + ': ' + why.to_s)
  end

  endpoints.each { |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
  }

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

.discover_yadis(uri) ⇒ Object



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/openid/consumer/discovery.rb', line 373

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 self.discover_no_yadis(uri)
    end

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

  return [yadis_url, self.get_op_or_user_services(openid_services)]
end

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



87
88
89
90
# File 'lib/openid/fetchers.rb', line 87

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

.fetcherObject



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

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

  return @fetcher
end

.fetcher=(fetcher) ⇒ Object



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

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’.



106
107
108
109
110
111
112
113
# File 'lib/openid/fetchers.rb', line 106

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



120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/openid/consumer/html_parse.rb', line 120

def OpenID.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)
  if !matches or matches.empty?
    return nil
  end

  first = matches[0]
  return first['href']
end


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/openid/consumer/html_parse.rb', line 103

def OpenID.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
  matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) }
  result = []

  link_attrs_list.each { |item|
    if matchesTarget.call(item)
      result << item
    end
  }

  return result
end

.find_op_local_identifier(service_element, type_uris) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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
# File 'lib/openid/consumer/discovery.rb', line 243

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 { |local_id_tag|
    service_element.each_element(local_id_tag) { |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 = sprintf(format, local_id_tag)
        raise DiscoveryFailure.new(message, nil)
      end
    }
  }

  return local_id
end

.get_op_or_user_services(openid_services) ⇒ Object



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

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?
    return op_services
  else
    return openid_services
  end
end

.get_secret_size(assoc_type) ⇒ Object



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

def self.get_secret_size(assoc_type)
  if assoc_type == 'HMAC-SHA1'
    return 20
  elsif assoc_type == 'HMAC-SHA256'
    return 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
58
59
# File 'lib/openid/extensions/sreg.rb', line 45

def OpenID.get_sreg_ns(message)
  [NS_URI_1_1, NS_URI_1_0].each{|ns|
    if message.namespaces.get_alias(ns)
      return 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
  return ns
end


95
96
97
98
99
100
101
# File 'lib/openid/consumer/html_parse.rb', line 95

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

  # XXX: TESTME
  rel_attr = link_attrs['rel']
  return (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



50
51
52
53
54
55
56
57
# File 'lib/openid/kvpost.rb', line 50

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

.normalize_url(url) ⇒ Object



298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/openid/consumer/discovery.rb', line 298

def self.normalize_url(url)
  # Normalize a URL, converting normalization failures to
  # DiscoveryFailure
  begin
    normalized = URINorm.urinorm(url)
  rescue URI::Error => why
    raise DiscoveryFailure.new("Error normalizing #{url}: #{why.message}", nil)
  else
    defragged = URI::parse(normalized)
    defragged.fragment = nil
    return defragged.normalize.to_s
  end
end

.normalize_xri(xri) ⇒ Object



291
292
293
294
295
296
# File 'lib/openid/consumer/discovery.rb', line 291

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

.openid_unescape(s) ⇒ Object



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

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


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
# File 'lib/openid/consumer/html_parse.rb', line 36

def OpenID.parse_link_attrs(html)
  stripped = html.gsub(REMOVED_RE,'')
  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
      if el.tag_name == 'html'
        in_html = true
      end
      next unless in_html
      if el.tag_name == 'head'
        if saw_head
          return links #only allow one head
        end
        saw_head = true
        unless el.to_s[-2] == 47 # tag ends with a /: a short tag
          in_head = true
        end
      end
      next unless in_head

      return links if el.tag_name == 'html'

      if el.tag_name == 'link'
        links << unescape_hash(el.attr_hash)
      end
      
    end
  rescue Exception # just stop parsing if there's an error
  end
  return links
end

.rel_matches(rel_attr, target_rel) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/openid/consumer/html_parse.rb', line 81

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

  return 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



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

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