class OpenID::GoogleDiscovery

Handles the bulk of Google’s modified discovery prototcol See groups.google.com/group/google-federated-login-api/web/openid-discovery-for-hosted-domains

Constants

NAMESPACES

Public Instance Methods

discover_site(domain) click to toggle source

Handles discovery for a domain

# File lib/gapps_openid.rb, line 137
def discover_site(domain)
  OpenID.logger.debug("Discovering domain #{domain}") unless OpenID.logger.nil?
  url = fetch_host_meta(domain)
  if url.nil?
    OpenID.logger.debug("#{domain} is not a Google Apps domain, aborting") unless OpenID.logger.nil?
    return nil # Not a Google Apps domain
  end
  xrds, secure = fetch_secure_xrds(domain, url)
  
  unless xrds.nil?
    # TODO - Need to propogate secure discovery info up through stack
    endpoints = OpenID::OpenIDServiceEndpoint.from_xrds(domain, xrds)
    return [domain, OpenID.get_op_or_user_services(endpoints)]
  end
  return nil
end
discover_user(domain, claimed_id) click to toggle source

Handles discovery for a user’s claimed ID.

# File lib/gapps_openid.rb, line 110
def discover_user(domain, claimed_id)
  OpenID.logger.debug("Discovering user identity #{claimed_id} for domain #{domain}") unless OpenID.logger.nil?
  url = fetch_host_meta(domain)
  if url.nil?
    OpenID.logger.debug("#{domain} is not a Google Apps domain, aborting") unless OpenID.logger.nil?
    return nil # Not a Google Apps domain
  end

  xrds, signed = fetch_secure_xrds(domain, url)

  unless xrds.nil?
    # TODO - Need to propogate secure discovery info up through stack
    user_url, authority = get_user_xrds_url(xrds, claimed_id)
    user_xrds, signed = fetch_secure_xrds(domain, user_url, false)
  
    # No user xrds -- likely that identifier was just OP identifier
    if user_xrds.nil?
      endpoints = OpenID::OpenIDServiceEndpoint.from_xrds(domain, xrds)
      return [claimed_id, OpenID.get_op_or_user_services(endpoints)]
    end
  
    endpoints = OpenID::OpenIDServiceEndpoint.from_xrds(claimed_id, user_xrds)
    return [claimed_id, OpenID.get_op_or_user_services(endpoints)]
  end
end
fetch_host_meta(domain) click to toggle source

Kickstart the discovery process by checking against Google’s well-known location for hosted domains. This gives us the location of the site’s XRDS doc

# File lib/gapps_openid.rb, line 156
def fetch_host_meta(domain) 
  cached_value = get_cache(domain)
  return cached_value unless cached_value.nil?
  
  host_meta_url = "https://www.google.com/accounts/o8/.well-known/host-meta?hd=#{CGI::escape(domain)}"
  http_resp = fetch_url(host_meta_url)
  return nil if http_resp.nil?

  matches = %rLink: <(.*)>/.match( http_resp.body )
  if matches.nil? 
    OpenID.logger.debug("No link tag found at #{host_meta_url}") unless OpenID.logger.nil?
    return nil
  end
  put_cache(domain, matches[1])
  return matches[1]
end
fetch_secure_xrds(authority, url, cache=true) click to toggle source

Fetches the XRDS and verifies the signature and authority for the doc

# File lib/gapps_openid.rb, line 183
def fetch_secure_xrds(authority, url, cache=true) 
  return if url.nil?

  OpenID.logger.debug("Retrieving XRDS from #{url}") unless OpenID.logger.nil?
 
  cached_xrds = get_cache("XRDS_#{url}")
  return cached_xrds unless cached_xrds.nil?

  http_resp = fetch_url(url)
  return nil if http_resp.nil?

  body = http_resp.body
  put_cache("XRDS_#{url}", body)

  signature = http_resp["Signature"]
  signed_by = SimpleSign.verify(body, signature)

  if signed_by.nil?
    put_cache("XRDS_#{url}", body) if cache
    return [body, false]      
  elsif signed_by.casecmp(authority) || signed_by.casecmp('hosted-id.google.com')
    put_cache("XRDS_#{url}", body) if cache
    return [body, true]
  else
    OpenID.logger.warn("Expected signature from #{authority} but found #{signed_by}") unless OpenID.logger.nil?        
    return nil # Signed, but not by the right domain.
  end
end
fetch_url(url) click to toggle source
# File lib/gapps_openid.rb, line 173
def fetch_url(url)
  http_resp = OpenID.fetch(url)
  if http_resp.code != "200" and http_resp.code != "206"
    OpenID.logger.debug("Received #{http_resp.code} when fetching #{url}") unless OpenID.logger.nil?
    return nil
  end
  return http_resp
end
get_cache(key) click to toggle source
# File lib/gapps_openid.rb, line 231
def get_cache(key)
  return nil if OpenID.cache.nil?
  return OpenID.cache.read("__GAPPS_OPENID__#{key}")
end
get_user_xrds_url(xrds, claimed_id) click to toggle source

Process the URITemplate in the XRDS to derive the location of the claimed id’s XRDS

# File lib/gapps_openid.rb, line 213
def get_user_xrds_url(xrds, claimed_id)
  types_to_match = ['http://www.iana.org/assignments/relation/describedby']
  services = OpenID::Yadis::apply_filter(claimed_id, xrds)
  services.each do | service | 
    if service.match_types(types_to_match) 
      template = REXML::XPath.first(service.service_element, '//openid:URITemplate', NAMESPACES)
      authority = REXML::XPath.first(service.service_element, '//openid:NextAuthority', NAMESPACES)
      url = template.text.gsub('{%uri}', CGI::escape(claimed_id))
      return [url, authority.text]
    end
  end
end
perform_discovery(uri) click to toggle source

Main entry point for discovery. Attempts to detect whether or not the URI is a raw domain name (‘mycompany.com’) vs. a user’s claimed ID (‘mycompany.com/openid?id=12345’) and performs the site or user discovery appropriately

# File lib/gapps_openid.rb, line 88
def perform_discovery(uri)
  OpenID.logger.debug("Performing discovery for #{uri}") unless OpenID.logger.nil?
  begin
    domain = uri
    parsed_uri = URI::parse(uri)
    domain = parsed_uri.host unless parsed_uri.host.nil?
    if site_identifier?(parsed_uri)
      return discover_site(domain)
    end
    return discover_user(domain, uri)
  rescue Exception => e
    # If we fail, just return nothing and fallback on default discovery mechanisms
    OpenID.logger.warn("Unexpected exception performing discovery for id #{uri}: #{e}") unless OpenID.logger.nil?
    return nil
  end
end
put_cache(key, item) click to toggle source
# File lib/gapps_openid.rb, line 226
def put_cache(key, item)
  return if OpenID.cache.nil?
  OpenID.cache.write("__GAPPS_OPENID__#{key}", item)
end
site_identifier?(parsed_uri) click to toggle source
# File lib/gapps_openid.rb, line 105
def site_identifier?(parsed_uri)
  return parsed_uri.scheme.nil? || parsed_uri.path.nil? || parsed_uri.path.strip.empty?
end