//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.OpenId.DiscoveryServices { using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.RelyingParty; using NUnit.Framework; [TestFixture] public class UriDiscoveryServiceTests : OpenIdTestBase { [Test] public void DiscoveryWithRedirects() { Identifier claimedId = this.GetMockIdentifier(ProtocolVersion.V20, false); // Add a couple of chained redirect pages that lead to the claimedId. Uri userSuppliedUri = new Uri("https://localhost/someSecurePage"); Uri insecureMidpointUri = new Uri("http://localhost/insecureStop"); this.MockResponder.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); this.MockResponder.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); // don't require secure SSL discovery for this test. Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, false); Assert.AreEqual(1, this.Discover(userSuppliedIdentifier).Count()); } [Test] public void DiscoverRequireSslWithSecureRedirects() { Identifier claimedId = this.GetMockIdentifier(ProtocolVersion.V20, true); // Add a couple of chained redirect pages that lead to the claimedId. // All redirects should be secure. Uri userSuppliedUri = new Uri("https://localhost/someSecurePage"); Uri secureMidpointUri = new Uri("https://localhost/secureStop"); this.MockResponder.RegisterMockRedirect(userSuppliedUri, secureMidpointUri); this.MockResponder.RegisterMockRedirect(secureMidpointUri, new Uri(claimedId.ToString())); Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); Assert.AreEqual(1, this.Discover(userSuppliedIdentifier).Count()); } [TestCase, ExpectedException(typeof(ProtocolException))] public void DiscoverRequireSslWithInsecureRedirect() { Identifier claimedId = this.GetMockIdentifier(ProtocolVersion.V20, true); // Add a couple of chained redirect pages that lead to the claimedId. // Include an insecure HTTP jump in those redirects to verify that // the ultimate endpoint is never found as a result of high security profile. Uri userSuppliedUri = new Uri("https://localhost/someSecurePage"); Uri insecureMidpointUri = new Uri("http://localhost/insecureStop"); this.MockResponder.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); this.MockResponder.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); this.Discover(userSuppliedIdentifier); } [Test] public void DiscoveryRequireSslWithInsecureXrdsInSecureHtmlHead() { var insecureXrdsSource = this.GetMockIdentifier(ProtocolVersion.V20, false); Uri secureClaimedUri = new Uri("https://localhost/secureId"); string html = string.Format("", insecureXrdsSource); this.MockResponder.RegisterMockResponse(secureClaimedUri, "text/html", html); Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); Assert.AreEqual(0, this.Discover(userSuppliedIdentifier).Count()); } [Test] public void DiscoveryRequireSslWithInsecureXrdsInSecureHttpHeader() { var insecureXrdsSource = this.GetMockIdentifier(ProtocolVersion.V20, false); string html = ""; WebHeaderCollection headers = new WebHeaderCollection { { "X-XRDS-Location", insecureXrdsSource } }; this.MockResponder.RegisterMockResponse(VanityUriSsl, VanityUriSsl, "text/html", headers, html); Identifier userSuppliedIdentifier = new UriIdentifier(VanityUriSsl, true); Assert.AreEqual(0, this.Discover(userSuppliedIdentifier).Count()); } [Test] public void DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags() { var insecureXrdsSource = this.GetMockIdentifier(ProtocolVersion.V20, false); string html = string.Format( @" ", HttpUtility.HtmlEncode(insecureXrdsSource), HttpUtility.HtmlEncode(OPUriSsl.AbsoluteUri), HttpUtility.HtmlEncode(OPLocalIdentifiersSsl[1].AbsoluteUri)); this.MockResponder.RegisterMockResponse(VanityUriSsl, "text/html", html); Identifier userSuppliedIdentifier = new UriIdentifier(VanityUriSsl, true); // We verify that the XRDS was ignored and the LINK tags were used // because the XRDS OP-LocalIdentifier uses different local identifiers. Assert.AreEqual(OPLocalIdentifiersSsl[1].AbsoluteUri, this.Discover(userSuppliedIdentifier).Single().ProviderLocalIdentifier.ToString()); } [Test] public void DiscoveryRequiresSslIgnoresInsecureEndpointsInXrds() { var insecureEndpoint = GetServiceEndpoint(0, ProtocolVersion.V20, 10, false); var secureEndpoint = GetServiceEndpoint(1, ProtocolVersion.V20, 20, true); UriIdentifier secureClaimedId = new UriIdentifier(VanityUriSsl, true); this.MockResponder.RegisterMockXrdsResponse(secureClaimedId, new IdentifierDiscoveryResult[] { insecureEndpoint, secureEndpoint }); Assert.AreEqual(secureEndpoint.ProviderLocalIdentifier, this.Discover(secureClaimedId).Single().ProviderLocalIdentifier); } [Test] public void XrdsDirectDiscovery_10() { this.FailDiscoverXrds("xrds-irrelevant"); this.DiscoverXrds("xrds10", ProtocolVersion.V10, null, "http://a/b"); this.DiscoverXrds("xrds11", ProtocolVersion.V11, null, "http://a/b"); this.DiscoverXrds("xrds1020", ProtocolVersion.V10, null, "http://a/b"); } [Test] public void XrdsDirectDiscovery_20() { this.DiscoverXrds("xrds20", ProtocolVersion.V20, null, "http://a/b"); this.DiscoverXrds("xrds2010a", ProtocolVersion.V20, null, "http://a/b"); this.DiscoverXrds("xrds2010b", ProtocolVersion.V20, null, "http://a/b"); } [Test] public void HtmlDiscover_11() { this.DiscoverHtml("html10prov", ProtocolVersion.V11, null, "http://a/b"); this.DiscoverHtml("html10both", ProtocolVersion.V11, "http://c/d", "http://a/b"); this.FailDiscoverHtml("html10del"); // Verify that HTML discovery generates the 1.x endpoints when appropriate this.DiscoverHtml("html2010", ProtocolVersion.V11, "http://g/h", "http://e/f"); this.DiscoverHtml("html1020", ProtocolVersion.V11, "http://g/h", "http://e/f"); this.DiscoverHtml("html2010combinedA", ProtocolVersion.V11, "http://c/d", "http://a/b"); this.DiscoverHtml("html2010combinedB", ProtocolVersion.V11, "http://c/d", "http://a/b"); this.DiscoverHtml("html2010combinedC", ProtocolVersion.V11, "http://c/d", "http://a/b"); } [Test] public void HtmlDiscover_20() { this.DiscoverHtml("html20prov", ProtocolVersion.V20, null, "http://a/b"); this.DiscoverHtml("html20both", ProtocolVersion.V20, "http://c/d", "http://a/b"); this.FailDiscoverHtml("html20del"); this.DiscoverHtml("html2010", ProtocolVersion.V20, "http://c/d", "http://a/b"); this.DiscoverHtml("html1020", ProtocolVersion.V20, "http://c/d", "http://a/b"); this.DiscoverHtml("html2010combinedA", ProtocolVersion.V20, "http://c/d", "http://a/b"); this.DiscoverHtml("html2010combinedB", ProtocolVersion.V20, "http://c/d", "http://a/b"); this.DiscoverHtml("html2010combinedC", ProtocolVersion.V20, "http://c/d", "http://a/b"); this.FailDiscoverHtml("html20relative"); } [Test] public void XrdsDiscoveryFromHead() { this.MockResponder.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), "application/xrds+xml", LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml")); this.DiscoverXrds("XrdsReferencedInHead.html", ProtocolVersion.V10, null, "http://a/b"); } [Test] public void XrdsDiscoveryFromHttpHeader() { WebHeaderCollection headers = new WebHeaderCollection(); headers.Add("X-XRDS-Location", new Uri("http://localhost/xrds1020.xml").AbsoluteUri); this.MockResponder.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), "application/xrds+xml", LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml")); this.DiscoverXrds("XrdsReferencedInHttpHeader.html", ProtocolVersion.V10, null, "http://a/b", headers); } /// /// Verifies HTML discovery proceeds if an XRDS document is referenced that doesn't contain OpenID endpoints. /// [Test] public void HtmlDiscoveryProceedsIfXrdsIsEmpty() { this.MockResponder.RegisterMockResponse(new Uri("http://localhost/xrds-irrelevant.xml"), "application/xrds+xml", LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds-irrelevant.xml")); this.DiscoverHtml("html20provWithEmptyXrds", ProtocolVersion.V20, null, "http://a/b"); } /// /// Verifies HTML discovery proceeds if the XRDS that is referenced cannot be found. /// [Test] public void HtmlDiscoveryProceedsIfXrdsIsBadOrMissing() { this.DiscoverHtml("html20provWithBadXrds", ProtocolVersion.V20, null, "http://a/b"); } /// /// Verifies that a dual identifier yields only one service endpoint by default. /// [Test] public void DualIdentifierOffByDefault() { this.MockResponder.RegisterMockResponse(VanityUri, "application/xrds+xml", LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds20dual.xml")); var results = this.Discover(VanityUri).ToList(); Assert.AreEqual(1, results.Count(r => r.ClaimedIdentifier == r.Protocol.ClaimedIdentifierForOPIdentifier), "OP Identifier missing from discovery results."); Assert.AreEqual(1, results.Count, "Unexpected additional services discovered."); } /// /// Verifies that a dual identifier yields two service endpoints when that feature is turned on. /// [Test] public void DualIdentifier() { this.MockResponder.RegisterMockResponse(VanityUri, "application/xrds+xml", LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds20dual.xml")); var rp = this.CreateRelyingParty(true); rp.Channel.WebRequestHandler = this.RequestHandler; rp.SecuritySettings.AllowDualPurposeIdentifiers = true; var results = rp.Discover(VanityUri).ToList(); Assert.AreEqual(1, results.Count(r => r.ClaimedIdentifier == r.Protocol.ClaimedIdentifierForOPIdentifier), "OP Identifier missing from discovery results."); Assert.AreEqual(1, results.Count(r => r.ClaimedIdentifier == VanityUri), "Claimed identifier missing from discovery results."); Assert.AreEqual(2, results.Count, "Unexpected additional services discovered."); } private void Discover(string url, ProtocolVersion version, Identifier expectedLocalId, string providerEndpoint, bool expectSreg, bool useRedirect) { this.Discover(url, version, expectedLocalId, providerEndpoint, expectSreg, useRedirect, null); } private void Discover(string url, ProtocolVersion version, Identifier expectedLocalId, string providerEndpoint, bool expectSreg, bool useRedirect, WebHeaderCollection headers) { Protocol protocol = Protocol.Lookup(version); Uri baseUrl = new Uri("http://localhost/"); UriIdentifier claimedId = new Uri(baseUrl, url); UriIdentifier userSuppliedIdentifier = new Uri(baseUrl, "Discovery/htmldiscovery/redirect.aspx?target=" + url); if (expectedLocalId == null) { expectedLocalId = claimedId; } Identifier idToDiscover = useRedirect ? userSuppliedIdentifier : claimedId; string contentType; if (url.EndsWith("html")) { contentType = "text/html"; } else if (url.EndsWith("xml")) { contentType = "application/xrds+xml"; } else { throw new InvalidOperationException(); } this.MockResponder.RegisterMockResponse(new Uri(idToDiscover), claimedId, contentType, headers ?? new WebHeaderCollection(), LoadEmbeddedFile(url)); IdentifierDiscoveryResult expected = IdentifierDiscoveryResult.CreateForClaimedIdentifier( claimedId, expectedLocalId, new ProviderEndpointDescription(new Uri(providerEndpoint), new string[] { protocol.ClaimedIdentifierServiceTypeURI }), // services aren't checked by Equals null, null); IdentifierDiscoveryResult se = this.Discover(idToDiscover).FirstOrDefault(ep => ep.Equals(expected)); Assert.IsNotNull(se, url + " failed to be discovered."); // Do extra checking of service type URIs, which aren't included in // the ServiceEndpoint.Equals method. Assert.AreEqual(expectSreg ? 2 : 1, se.Capabilities.Count); Assert.IsTrue(se.Capabilities.Contains(protocol.ClaimedIdentifierServiceTypeURI)); Assert.AreEqual(expectSreg, se.IsExtensionSupported()); } private void DiscoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId, string providerEndpoint) { this.DiscoverXrds(page, version, expectedLocalId, providerEndpoint, null); } private void DiscoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId, string providerEndpoint, WebHeaderCollection headers) { if (!page.Contains(".")) { page += ".xml"; } this.Discover("/Discovery/xrdsdiscovery/" + page, version, expectedLocalId, providerEndpoint, true, false, headers); this.Discover("/Discovery/xrdsdiscovery/" + page, version, expectedLocalId, providerEndpoint, true, true, headers); } private void DiscoverHtml(string page, ProtocolVersion version, Identifier expectedLocalId, string providerEndpoint, bool useRedirect) { this.Discover("/Discovery/htmldiscovery/" + page, version, expectedLocalId, providerEndpoint, false, useRedirect); } private void DiscoverHtml(string scenario, ProtocolVersion version, Identifier expectedLocalId, string providerEndpoint) { string page = scenario + ".html"; this.DiscoverHtml(page, version, expectedLocalId, providerEndpoint, false); this.DiscoverHtml(page, version, expectedLocalId, providerEndpoint, true); } private void FailDiscover(string url) { UriIdentifier userSuppliedId = new Uri(new Uri("http://localhost"), url); this.MockResponder.RegisterMockResponse(new Uri(userSuppliedId), userSuppliedId, "text/html", LoadEmbeddedFile(url)); Assert.AreEqual(0, this.Discover(userSuppliedId).Count()); // ... but that no endpoint info is discoverable } private void FailDiscoverHtml(string scenario) { this.FailDiscover("/Discovery/htmldiscovery/" + scenario + ".html"); } private void FailDiscoverXrds(string scenario) { this.FailDiscover("/Discovery/xrdsdiscovery/" + scenario + ".xml"); } } }