summaryrefslogtreecommitdiffstats
path: root/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb')
-rw-r--r--vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb498
1 files changed, 498 insertions, 0 deletions
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb
new file mode 100644
index 000000000..01fe03656
--- /dev/null
+++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb
@@ -0,0 +1,498 @@
+# Functions to discover OpenID endpoints from identifiers.
+
+require 'uri'
+require 'openid/util'
+require 'openid/fetchers'
+require 'openid/urinorm'
+require 'openid/message'
+require 'openid/yadis/discovery'
+require 'openid/yadis/xrds'
+require 'openid/yadis/xri'
+require 'openid/yadis/services'
+require 'openid/yadis/filters'
+require 'openid/consumer/html_parse'
+require 'openid/yadis/xrires'
+
+module OpenID
+
+ OPENID_1_0_NS = 'http://openid.net/xmlns/1.0'
+ OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server'
+ OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon'
+ OPENID_1_1_TYPE = 'http://openid.net/signon/1.1'
+ OPENID_1_0_TYPE = 'http://openid.net/signon/1.0'
+
+ OPENID_1_0_MESSAGE_NS = OPENID1_NS
+ OPENID_2_0_MESSAGE_NS = OPENID2_NS
+
+ # Object representing an OpenID service endpoint.
+ class OpenIDServiceEndpoint
+
+ # OpenID service type URIs, listed in order of preference. The
+ # ordering of this list affects yadis and XRI service discovery.
+ OPENID_TYPE_URIS = [
+ OPENID_IDP_2_0_TYPE,
+
+ OPENID_2_0_TYPE,
+ OPENID_1_1_TYPE,
+ OPENID_1_0_TYPE,
+ ]
+
+ # the verified identifier.
+ attr_accessor :claimed_id
+
+ # For XRI, the persistent identifier.
+ attr_accessor :canonical_id
+
+ attr_accessor :server_url, :type_uris, :local_id, :used_yadis
+
+ def initialize
+ @claimed_id = nil
+ @server_url = nil
+ @type_uris = []
+ @local_id = nil
+ @canonical_id = nil
+ @used_yadis = false # whether this came from an XRDS
+ @display_identifier = nil
+ end
+
+ def display_identifier
+ return @display_identifier if @display_identifier
+
+ return @claimed_id if @claimed_id.nil?
+
+ begin
+ parsed_identifier = URI.parse(@claimed_id)
+ rescue URI::InvalidURIError
+ raise ProtocolError, "Claimed identifier #{claimed_id} is not a valid URI"
+ end
+
+ return @claimed_id if not parsed_identifier.fragment
+
+ disp = parsed_identifier
+ disp.fragment = nil
+
+ return disp.to_s
+ end
+
+ def display_identifier=(display_identifier)
+ @display_identifier = display_identifier
+ end
+
+ def uses_extension(extension_uri)
+ return @type_uris.member?(extension_uri)
+ end
+
+ def preferred_namespace
+ if (@type_uris.member?(OPENID_IDP_2_0_TYPE) or
+ @type_uris.member?(OPENID_2_0_TYPE))
+ return OPENID_2_0_MESSAGE_NS
+ else
+ return OPENID_1_0_MESSAGE_NS
+ end
+ end
+
+ def supports_type(type_uri)
+ # Does this endpoint support this type?
+ #
+ # I consider C{/server} endpoints to implicitly support C{/signon}.
+ (
+ @type_uris.member?(type_uri) or
+ (type_uri == OPENID_2_0_TYPE and is_op_identifier())
+ )
+ end
+
+ def compatibility_mode
+ return preferred_namespace() != OPENID_2_0_MESSAGE_NS
+ end
+
+ def is_op_identifier
+ return @type_uris.member?(OPENID_IDP_2_0_TYPE)
+ end
+
+ def parse_service(yadis_url, uri, type_uris, service_element)
+ # Set the state of this object based on the contents of the
+ # service element.
+ @type_uris = type_uris
+ @server_url = uri
+ @used_yadis = true
+
+ if !is_op_identifier()
+ # XXX: This has crappy implications for Service elements that
+ # contain both 'server' and 'signon' Types. But that's a
+ # pathological configuration anyway, so I don't think I care.
+ @local_id = OpenID.find_op_local_identifier(service_element,
+ @type_uris)
+ @claimed_id = yadis_url
+ end
+ end
+
+ def get_local_id
+ # Return the identifier that should be sent as the
+ # openid.identity parameter to the server.
+ if @local_id.nil? and @canonical_id.nil?
+ return @claimed_id
+ else
+ return (@local_id or @canonical_id)
+ end
+ end
+
+ def self.from_basic_service_endpoint(endpoint)
+ # Create a new instance of this class from the endpoint object
+ # passed in.
+ #
+ # @return: nil or OpenIDServiceEndpoint for this endpoint object"""
+
+ type_uris = endpoint.match_types(OPENID_TYPE_URIS)
+
+ # If any Type URIs match and there is an endpoint URI specified,
+ # then this is an OpenID endpoint
+ if (!type_uris.nil? and !type_uris.empty?) and !endpoint.uri.nil?
+ openid_endpoint = self.new
+ openid_endpoint.parse_service(
+ endpoint.yadis_url,
+ endpoint.uri,
+ endpoint.type_uris,
+ endpoint.service_element)
+ else
+ openid_endpoint = nil
+ end
+
+ return openid_endpoint
+ end
+
+ def self.from_html(uri, html)
+ # Parse the given document as HTML looking for an OpenID <link
+ # rel=...>
+ #
+ # @rtype: [OpenIDServiceEndpoint]
+
+ discovery_types = [
+ [OPENID_2_0_TYPE, 'openid2.provider', 'openid2.local_id'],
+ [OPENID_1_1_TYPE, 'openid.server', 'openid.delegate'],
+ ]
+
+ link_attrs = OpenID.parse_link_attrs(html)
+ services = []
+ discovery_types.each { |type_uri, op_endpoint_rel, local_id_rel|
+
+ op_endpoint_url = OpenID.find_first_href(link_attrs, op_endpoint_rel)
+
+ if !op_endpoint_url
+ next
+ end
+
+ service = self.new
+ service.claimed_id = uri
+ service.local_id = OpenID.find_first_href(link_attrs, local_id_rel)
+ service.server_url = op_endpoint_url
+ service.type_uris = [type_uri]
+
+ services << service
+ }
+
+ return services
+ end
+
+ def self.from_xrds(uri, xrds)
+ # Parse the given document as XRDS looking for OpenID services.
+ #
+ # @rtype: [OpenIDServiceEndpoint]
+ #
+ # @raises L{XRDSError}: When the XRDS does not parse.
+ return Yadis::apply_filter(uri, xrds, self)
+ end
+
+ def self.from_discovery_result(discoveryResult)
+ # Create endpoints from a DiscoveryResult.
+ #
+ # @type discoveryResult: L{DiscoveryResult}
+ #
+ # @rtype: list of L{OpenIDServiceEndpoint}
+ #
+ # @raises L{XRDSError}: When the XRDS does not parse.
+ if discoveryResult.is_xrds()
+ meth = self.method('from_xrds')
+ else
+ meth = self.method('from_html')
+ end
+
+ return meth.call(discoveryResult.normalized_uri,
+ discoveryResult.response_text)
+ end
+
+ def self.from_op_endpoint_url(op_endpoint_url)
+ # Construct an OP-Identifier OpenIDServiceEndpoint object for
+ # a given OP Endpoint URL
+ #
+ # @param op_endpoint_url: The URL of the endpoint
+ # @rtype: OpenIDServiceEndpoint
+ service = self.new
+ service.server_url = op_endpoint_url
+ service.type_uris = [OPENID_IDP_2_0_TYPE]
+ return service
+ end
+
+ def to_s
+ return sprintf("<%s server_url=%s claimed_id=%s " +
+ "local_id=%s canonical_id=%s used_yadis=%s>",
+ self.class, @server_url, @claimed_id,
+ @local_id, @canonical_id, @used_yadis)
+ end
+ end
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ def self.discover_xri(iname)
+ endpoints = []
+ iname = self.normalize_xri(iname)
+
+ begin
+ canonical_id, services = Yadis::XRI::ProxyResolver.new().query(
+ iname, OpenIDServiceEndpoint::OPENID_TYPE_URIS)
+
+ 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
+
+ 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
+
+ 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
+
+ def self.discover(identifier)
+ if Yadis::XRI::identifier_scheme(identifier) == :xri
+ normalized_identifier, services = discover_xri(identifier)
+ else
+ return discover_uri(identifier)
+ end
+ end
+end