//-----------------------------------------------------------------------
//
// 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));
}
}
}