//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Net.Http; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.Xrds; using DotNetOpenAuth.Yadis; using Validation; /// /// The discovery service for XRI identifiers that uses an XRI proxy resolver for discovery. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xri", Justification = "Acronym")] public class XriDiscoveryProxyService : IIdentifierDiscoveryService, IRequireHostFactories { /// /// The magic URL that will provide us an XRDS document for a given XRI identifier. /// /// /// We use application/xrd+xml instead of application/xrds+xml because it gets /// xri.net to automatically give us exactly the right XRD element for community i-names /// automatically, saving us having to choose which one to use out of the result. /// The ssl=true parameter tells the proxy resolver to accept only SSL connections /// when resolving community i-names. /// private const string XriResolverProxyTemplate = "https://{1}/{0}?_xrd_r=application/xrd%2Bxml;sep=false"; /// /// Initializes a new instance of the class. /// public XriDiscoveryProxyService() { } /// /// Gets or sets the host factories used by this instance. /// public IHostFactories HostFactories { get; set; } #region IDiscoveryService Members /// /// Performs discovery on the specified identifier. /// /// The identifier to perform discovery on. /// The cancellation token. /// /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. /// public async Task DiscoverAsync(Identifier identifier, CancellationToken cancellationToken) { Requires.NotNull(identifier, "identifier"); Verify.Operation(this.HostFactories != null, Strings.HostFactoriesRequired); var xriIdentifier = identifier as XriIdentifier; if (xriIdentifier == null) { return new IdentifierDiscoveryServiceResult(Enumerable.Empty()); } var xrds = await DownloadXrdsAsync(xriIdentifier, this.HostFactories, cancellationToken); var endpoints = xrds.XrdElements.CreateServiceEndpoints(xriIdentifier); return new IdentifierDiscoveryServiceResult(endpoints); } #endregion /// /// Downloads the XRDS document for this XRI. /// /// The identifier. /// The host factories. /// The cancellation token. /// /// The XRDS document. /// private static async Task DownloadXrdsAsync(XriIdentifier identifier, IHostFactories hostFactories, CancellationToken cancellationToken) { Requires.NotNull(identifier, "identifier"); Requires.NotNull(hostFactories, "hostFactories"); XrdsDocument doc; using (var xrdsResponse = await Yadis.RequestAsync(GetXrdsUrl(identifier), identifier.IsDiscoverySecureEndToEnd, hostFactories, cancellationToken)) { xrdsResponse.EnsureSuccessStatusCode(); var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings(); ErrorUtilities.VerifyProtocol(xrdsResponse.Content != null, "XRDS request \"{0}\" returned no response.", GetXrdsUrl(identifier)); await xrdsResponse.Content.LoadIntoBufferAsync(); using (var xrdsStream = await xrdsResponse.Content.ReadAsStreamAsync()) { doc = new XrdsDocument(XmlReader.Create(xrdsStream, readerSettings)); } } ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); return doc; } /// /// Gets the URL from which this XRI's XRDS document may be downloaded. /// /// The identifier. /// The URI to HTTP GET from to get the services. private static Uri GetXrdsUrl(XriIdentifier identifier) { ErrorUtilities.VerifyProtocol(OpenIdElement.Configuration.XriResolver.Enabled, OpenIdStrings.XriResolutionDisabled); string xriResolverProxy = XriResolverProxyTemplate; if (identifier.IsDiscoverySecureEndToEnd) { // Indicate to xri.net that we require SSL to be used for delegated resolution // of community i-names. xriResolverProxy += ";https=true"; } return new Uri( string.Format( CultureInfo.InvariantCulture, xriResolverProxy, identifier, OpenIdElement.Configuration.XriResolver.Proxy.Name)); } } }