Handles the bulk of Google's modified discovery prototcol See groups.google.com/group/google-federated-login-api/web/openid-discovery-for-hosted-domains
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
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
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 = /Link: <(.*)>/.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
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
# 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
# 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
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
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
# 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
# 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