module OpenID

Copyright 2006-2007 JanRain, Inc.

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

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

Constants

BARE_NS

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

DefaultNegotiator
EncryptedNegotiator
IDENTIFIER_SELECT
NO_DEFAULT

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

NULL_NAMESPACE

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

OPENID11_NS
OPENID1_NAMESPACES
OPENID1_NS

The OpenID 1.x namespace URIs

OPENID1_URL_LIMIT

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

OPENID2_NS

The OpenID 2.0 namespace URI

OPENID_1_0_MESSAGE_NS
OPENID_1_0_NS
OPENID_1_0_TYPE
OPENID_1_1_TYPE
OPENID_2_0_MESSAGE_NS
OPENID_2_0_TYPE
OPENID_IDP_2_0_TYPE
OPENID_NS

The null namespace, when it is an allowed OpenID namespace

OPENID_PROTOCOL_FIELDS

All OpenID protocol fields. Used to check namespace aliases.

REMOVED_RE

Stuff to remove before we start looking for tags

SREG_URI

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

VERSION

Public Class Methods

arrange_by_type(service_list, preferred_types) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

raise ArgumentError if fieldname is not in the defined sreg fields

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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 => 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) click to toggle source
# 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) click to toggle source
# 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
fetcher() click to toggle source
# File lib/openid/fetchers.rb, line 92
def self.fetcher
  if @fetcher.nil?
    @fetcher = StandardFetcher.new
  end

  return @fetcher
end
fetcher=(fetcher) click to toggle source
# File lib/openid/fetchers.rb, line 100
def self.fetcher=(fetcher)
  @fetcher = fetcher
end
fetcher_use_env_http_proxy() click to toggle source

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

# 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) click to toggle source
# 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
find_op_local_identifier(service_element, type_uris) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

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)

# 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
make_kv_post(request_message, server_url) click to toggle source

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

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/openid/consumer/html_parse.rb, line 23
def OpenID.openid_unescape(s)
  s.gsub('&amp;','&').gsub('&lt;','<').gsub('&gt;','>').gsub('&quot;','"')
end
rel_matches(rel_attr, target_rel) click to toggle source
# 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) click to toggle source

Does the given endpoint advertise support for simple registration?

# 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) click to toggle source
# 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