Class: OpenID::GoogleDiscovery

Inherits:
Object
  • Object
show all
Defined in:
lib/gapps_openid.rb

Overview

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

Constant Summary collapse

NAMESPACES =
{
  'xrds' => 'xri://$xrd*($v*2.0)',
  'xrd' => 'xri://$xrds',
  'openid' => 'http://namespace.google.com/openid/xmlns'
}

Instance Method Summary collapse

Instance Method Details

#discover_site(domain) ⇒ Object

Handles discovery for a domain



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# 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) ⇒ Object

Handles discovery for a user’s claimed ID.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 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) ⇒ Object

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



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# 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

#fetch_secure_xrds(authority, url, cache = true) ⇒ Object

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



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# 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) ⇒ Object



173
174
175
176
177
178
179
180
# 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) ⇒ Object



231
232
233
234
# 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) ⇒ Object

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



213
214
215
216
217
218
219
220
221
222
223
224
# 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) ⇒ Object

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



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# 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) ⇒ Object



226
227
228
229
# 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) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
# 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