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/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

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, UI, 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

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
VERSION =
"2.9.2"
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>

/mix

Class Method Summary collapse

Class Method Details

.arrange_by_type(service_list, preferred_types) ⇒ Object



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

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



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

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



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

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

.discover_no_yadis(uri) ⇒ Object



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

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



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

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



437
438
439
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
# File 'lib/openid/consumer/discovery.rb', line 437

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

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

    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, Yadis::XRI::XRIHTTPError => 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



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
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/openid/consumer/discovery.rb', line 391

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



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

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


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

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



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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/openid/consumer/discovery.rb', line 261

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



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

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


105
106
107
108
109
110
111
# File 'lib/openid/consumer/html_parse.rb', line 105

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



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

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



309
310
311
312
313
314
# File 'lib/openid/consumer/discovery.rb', line 309

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
80
81
82
83
84
85
86
87
88
89
# File 'lib/openid/consumer/html_parse.rb', line 36

def OpenID.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
      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



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/openid/consumer/html_parse.rb', line 91

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