//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web.UI.HtmlControls; using System.Xml; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.Xrds; using DotNetOpenAuth.Yadis; /// /// The discovery service for URI identifiers. /// public class UriDiscoveryService : IIdentifierDiscoveryService { /// /// Initializes a new instance of the class. /// public UriDiscoveryService() { } #region IDiscoveryService Members /// /// Performs discovery on the specified identifier. /// /// The identifier to perform discovery on. /// The means to place outgoing HTTP requests. /// if set to true, no further discovery services will be called for this identifier. /// /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. /// public IEnumerable Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { abortDiscoveryChain = false; var uriIdentifier = identifier as UriIdentifier; if (uriIdentifier == null) { return Enumerable.Empty(); } var endpoints = new List(); // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Discover(requestHandler, uriIdentifier, identifier.IsDiscoverySecureEndToEnd); if (yadisResult != null) { if (yadisResult.IsXrds) { try { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); var xrdsEndpoints = xrds.XrdElements.CreateServiceEndpoints(yadisResult.NormalizedUri, uriIdentifier); // Filter out insecure endpoints if high security is required. if (uriIdentifier.IsDiscoverySecureEndToEnd) { xrdsEndpoints = xrdsEndpoints.Where(se => se.ProviderEndpoint.IsTransportSecure()); } endpoints.AddRange(xrdsEndpoints); } catch (XmlException ex) { Logger.Yadis.Error("Error while parsing the XRDS document. Falling back to HTML discovery.", ex); } } // Failing YADIS discovery of an XRDS document, we try HTML discovery. if (endpoints.Count == 0) { yadisResult.TryRevertToHtmlResponse(); var htmlEndpoints = new List(DiscoverFromHtml(yadisResult.NormalizedUri, uriIdentifier, yadisResult.ResponseText)); if (htmlEndpoints.Any()) { Logger.Yadis.DebugFormat("Total services discovered in HTML: {0}", htmlEndpoints.Count); Logger.Yadis.Debug(htmlEndpoints.ToStringDeferred(true)); endpoints.AddRange(htmlEndpoints.Where(ep => !uriIdentifier.IsDiscoverySecureEndToEnd || ep.ProviderEndpoint.IsTransportSecure())); if (endpoints.Count == 0) { Logger.Yadis.Info("No HTML discovered endpoints met the security requirements."); } } else { Logger.Yadis.Debug("HTML discovery failed to find any endpoints."); } } else { Logger.Yadis.Debug("Skipping HTML discovery because XRDS contained service endpoints."); } } return endpoints; } #endregion /// /// Searches HTML for the HEAD META tags that describe OpenID provider services. /// /// The final URL that provided this HTML document. /// This may not be the same as (this) userSuppliedIdentifier if the /// userSuppliedIdentifier pointed to a 301 Redirect. /// The user supplied identifier. /// The HTML that was downloaded and should be searched. /// /// A sequence of any discovered ServiceEndpoints. /// private static IEnumerable DiscoverFromHtml(Uri claimedIdentifier, UriIdentifier userSuppliedIdentifier, string html) { var linkTags = new List(HtmlParser.HeadTags(html)); foreach (var protocol in Protocol.AllPracticalVersions) { // rel attributes are supposed to be interpreted with case INsensitivity, // and is a space-delimited list of values. (http://www.htmlhelp.com/reference/html40/values.html#linktypes) var serverLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryProviderKey) + @"\b", RegexOptions.IgnoreCase)); if (serverLinkTag == null) { continue; } Uri providerEndpoint = null; if (Uri.TryCreate(serverLinkTag.Href, UriKind.Absolute, out providerEndpoint)) { // See if a LocalId tag of the discovered version exists Identifier providerLocalIdentifier = null; var delegateLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryLocalIdKey) + @"\b", RegexOptions.IgnoreCase)); if (delegateLinkTag != null) { if (Identifier.IsValid(delegateLinkTag.Href)) { providerLocalIdentifier = delegateLinkTag.Href; } else { Logger.Yadis.WarnFormat("Skipping endpoint data because local id is badly formed ({0}).", delegateLinkTag.Href); continue; // skip to next version } } // Choose the TypeURI to match the OpenID version detected. string[] typeURIs = { protocol.ClaimedIdentifierServiceTypeURI }; yield return IdentifierDiscoveryResult.CreateForClaimedIdentifier( claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, new ProviderEndpointDescription(providerEndpoint, typeURIs), (int?)null, (int?)null); } } } } }