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
# 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
# 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
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
# 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
# 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
# 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
# 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
# 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
# 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
# File lib/openid/fetchers.rb, line 92 def self.fetcher if @fetcher.nil? @fetcher = StandardFetcher.new end return @fetcher end
# File lib/openid/fetchers.rb, line 100 def self.fetcher=(fetcher) @fetcher = fetcher end
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
# 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
# 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
# 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
# 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
# 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
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
# 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
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
# 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
# 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
# File lib/openid/consumer/html_parse.rb, line 23 def OpenID.openid_unescape(s) s.gsub('&','&').gsub('<','<').gsub('>','>').gsub('"','"') end
# 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
# 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
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
# 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