summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs
blob: bea8752ddec545b530fc9abc0cfe24702150609c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//-----------------------------------------------------------------------
// <copyright file="XriDiscoveryProxyService.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

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;

	/// <summary>
	/// The discovery service for XRI identifiers that uses an XRI proxy resolver for discovery.
	/// </summary>
	[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xri", Justification = "Acronym")]
	public class XriDiscoveryProxyService : IIdentifierDiscoveryService, IRequireHostFactories {
		/// <summary>
		/// The magic URL that will provide us an XRDS document for a given XRI identifier.
		/// </summary>
		/// <remarks>
		/// 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.
		/// </remarks>
		private const string XriResolverProxyTemplate = "https://{1}/{0}?_xrd_r=application/xrd%2Bxml;sep=false";

		/// <summary>
		/// Initializes a new instance of the <see cref="XriDiscoveryProxyService"/> class.
		/// </summary>
		public XriDiscoveryProxyService() {
		}

		/// <summary>
		/// Gets or sets the host factories used by this instance.
		/// </summary>
		public IHostFactories HostFactories { get; set; }

		#region IDiscoveryService Members

		/// <summary>
		/// Performs discovery on the specified identifier.
		/// </summary>
		/// <param name="identifier">The identifier to perform discovery on.</param>
		/// <param name="cancellationToken">The cancellation token.</param>
		/// <returns>
		/// A sequence of service endpoints yielded by discovery.  Must not be null, but may be empty.
		/// </returns>
		public async Task<IdentifierDiscoveryServiceResult> 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<IdentifierDiscoveryResult>());
			}

			var xrds = await DownloadXrdsAsync(xriIdentifier, this.HostFactories, cancellationToken);
			var endpoints = xrds.XrdElements.CreateServiceEndpoints(xriIdentifier);
			return new IdentifierDiscoveryServiceResult(endpoints);
		}

		#endregion

		/// <summary>
		/// Downloads the XRDS document for this XRI.
		/// </summary>
		/// <param name="identifier">The identifier.</param>
		/// <param name="hostFactories">The host factories.</param>
		/// <param name="cancellationToken">The cancellation token.</param>
		/// <returns>
		/// The XRDS document.
		/// </returns>
		private static async Task<XrdsDocument> 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;
		}

		/// <summary>
		/// Gets the URL from which this XRI's XRDS document may be downloaded.
		/// </summary>
		/// <param name="identifier">The identifier.</param>
		/// <returns>The URI to HTTP GET from to get the services.</returns>
		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));
		}
	}
}