diff options
Diffstat (limited to 'src/DotNetOpenAuth.Test')
9 files changed, 956 insertions, 10 deletions
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs index 2f82c06..e10cda5 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs @@ -68,7 +68,7 @@ namespace DotNetOpenAuth.Test.Mocks { return request.Message; } - protected override IDictionary<string, string> ReadFromResponseInternal(Response response) { + protected override IDictionary<string, string> ReadFromResponseInternal(DirectWebResponse response) { Channel_Accessor accessor = Channel_Accessor.AttachShadow(this.wrappedChannel); return accessor.ReadFromResponseInternal(response); } diff --git a/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs index 515766e..cc63f22 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.Test.Mocks { return base.ReadFromRequest(request); } - protected override IDictionary<string, string> ReadFromResponseInternal(Response response) { + protected override IDictionary<string, string> ReadFromResponseInternal(DirectWebResponse response) { throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs index 0f7f4b8..11dc97e 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs @@ -19,7 +19,7 @@ namespace DotNetOpenAuth.Test.Mocks { : base(messageTypeProvider, bindingElements) { } - protected override IDictionary<string, string> ReadFromResponseInternal(Response response) { + protected override IDictionary<string, string> ReadFromResponseInternal(DirectWebResponse response) { throw new NotImplementedException("ReadFromResponseInternal"); } diff --git a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs index 7117bc1..af971a0 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs @@ -12,13 +12,13 @@ namespace DotNetOpenAuth.Test.Mocks { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.ChannelElements; - internal class TestWebRequestHandler : IWebRequestHandler { + internal class TestWebRequestHandler : IDirectWebRequestHandler { private StringBuilder postEntity; /// <summary> /// Gets or sets the callback used to provide the mock response for the mock request. /// </summary> - internal Func<HttpWebRequest, Response> Callback { get; set; } + internal Func<HttpWebRequest, DirectWebResponse> Callback { get; set; } /// <summary> /// Gets the stream that was written out as if on an HTTP request. @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// <returns> /// An instance of <see cref="Response"/> describing the response. /// </returns> - public Response GetResponse(HttpWebRequest request) { + public DirectWebResponse GetResponse(HttpWebRequest request) { if (this.Callback == null) { throw new InvalidOperationException("Set the Callback property first."); } diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs index b1fe7c4..73542b4 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs @@ -111,7 +111,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { writer.Flush(); ms.Seek(0, SeekOrigin.Begin); Channel_Accessor channelAccessor = Channel_Accessor.AttachShadow(this.channel); - IDictionary<string, string> deserializedFields = channelAccessor.ReadFromResponseInternal(new Response { ResponseStream = ms }); + IDictionary<string, string> deserializedFields = channelAccessor.ReadFromResponseInternal(new DirectWebResponse { ResponseStream = ms }); Assert.AreEqual(fields.Count, deserializedFields.Count); foreach (string key in fields.Keys) { Assert.AreEqual(fields[key], deserializedFields[key]); @@ -228,7 +228,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { HttpMethods = scheme, }; - Response rawResponse = null; + DirectWebResponse rawResponse = null; this.webRequestHandler.Callback = (req) => { Assert.IsNotNull(req); HttpRequestInfo reqInfo = ConvertToRequestInfo(req, this.webRequestHandler.RequestEntityStream); @@ -246,7 +246,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { { "Location", request.Location.AbsoluteUri }, { "Timestamp", XmlConvert.ToString(request.Timestamp, XmlDateTimeSerializationMode.Utc) }, }; - rawResponse = new Response { + rawResponse = new DirectWebResponse { Body = MessagingUtilities.CreateQueryString(responseFields), }; return rawResponse; diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs index cd4b4b6..304473c 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs @@ -79,7 +79,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { { "var1", "value1" }, { "var2", "value2" }, }; - Response response = new Response { + var response = new DirectWebResponse { ResponseStream = new MemoryStream(KeyValueFormEncoding.GetBytes(fields)), }; Assert.IsTrue(MessagingUtilities.AreEquivalent(fields, this.accessor.ReadFromResponseInternal(response))); diff --git a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs new file mode 100644 index 0000000..8c3bc3b --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using DotNetOpenAuth.OpenId; + +namespace DotNetOpenAuth.Test.OpenId { + [TestClass] + public class IdentifierTests { + string uri = "http://www.yahoo.com/"; + string uriNoScheme = "www.yahoo.com"; + string uriHttps = "https://www.yahoo.com/"; + string xri = "=arnott*andrew"; + + [TestMethod] + public void Parse() { + Assert.IsInstanceOfType(typeof(UriIdentifier), Identifier.Parse(uri)); + Assert.IsInstanceOfType(typeof(XriIdentifier), Identifier.Parse(xri)); + } + + /// <summary> + /// Tests conformance with 2.0 spec section 7.2#2 + /// </summary> + [TestMethod] + public void ParseEndUserSuppliedXriIdentifer() { + List<char> symbols = new List<char>(XriIdentifier.GlobalContextSymbols); + symbols.Add('('); + List<string> prefixes = new List<string>(); + prefixes.AddRange(symbols.Select(s => s.ToString())); + prefixes.AddRange(symbols.Select(s => "xri://" + s.ToString())); + foreach (string prefix in prefixes) { + var id = Identifier.Parse(prefix + "andrew"); + Assert.IsInstanceOfType(typeof(XriIdentifier), id); + } + } + + /// <summary> + /// Verifies conformance with 2.0 spec section 7.2#3 + /// </summary> + [TestMethod] + public void ParseEndUserSuppliedUriIdentifier() { + // verify a fully-qualified Uri + var id = Identifier.Parse(uri); + Assert.IsInstanceOfType(typeof(UriIdentifier), id); + Assert.AreEqual(uri, ((UriIdentifier)id).Uri.AbsoluteUri); + // verify an HTTPS Uri + id = Identifier.Parse(uriHttps); + Assert.IsInstanceOfType(typeof(UriIdentifier), id); + Assert.AreEqual(uriHttps, ((UriIdentifier)id).Uri.AbsoluteUri); + // verify that if the scheme is missing it is added automatically + id = Identifier.Parse(uriNoScheme); + Assert.IsInstanceOfType(typeof(UriIdentifier), id); + Assert.AreEqual(uri, ((UriIdentifier)id).Uri.AbsoluteUri); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void ParseNull() { + Identifier.Parse(null); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void ParseEmpty() { + Identifier.Parse(string.Empty); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs new file mode 100644 index 0000000..e6e9149 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs @@ -0,0 +1,410 @@ +using System; +using System.Linq; +using System.Net; +using System.Web; +using DotNetOpenAuth.OpenId.RelyingParty; +using DotNetOpenAuth.Test.Mocks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using DotNetOpenAuth.OpenId; + +namespace DotNetOpenAuth.Test.OpenId { + [TestClass] + public class UriIdentifierTests : OpenIdTestBase { + string goodUri = "http://blog.nerdbank.net/"; + string relativeUri = "host/path"; + string badUri = "som%-)830w8vf/?.<>,ewackedURI"; + + [TestInitialize] + public override void SetUp() { + if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost")) + UntrustedWebRequest.WhitelistHosts.Add("localhost"); + } + + [TestCleanup] + public override void Cleanup() { + Mocks.MockHttpRequest.Reset(); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullUri() { + new UriIdentifier((Uri)null); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullString() { + new UriIdentifier((string)null); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorBlank() { + new UriIdentifier(string.Empty); + } + + [TestMethod, ExpectedException(typeof(UriFormatException))] + public void CtorBadUri() { + new UriIdentifier(badUri); + } + + [TestMethod] + public void CtorGoodUri() { + var uri = new UriIdentifier(goodUri); + Assert.AreEqual(new Uri(goodUri), uri.Uri); + Assert.IsFalse(uri.SchemeImplicitlyPrepended); + Assert.IsFalse(uri.IsDiscoverySecureEndToEnd); + } + + [TestMethod] + public void CtorStringNoSchemeSecure() { + var uri = new UriIdentifier("host/path", true); + Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri); + Assert.IsTrue(uri.IsDiscoverySecureEndToEnd); + } + + [TestMethod] + public void CtorStringHttpsSchemeSecure() { + var uri = new UriIdentifier("https://host/path", true); + Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri); + Assert.IsTrue(uri.IsDiscoverySecureEndToEnd); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void CtorStringHttpSchemeSecure() { + new UriIdentifier("http://host/path", true); + } + + [TestMethod] + public void CtorUriHttpsSchemeSecure() { + var uri = new UriIdentifier(new Uri("https://host/path"), true); + Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri); + Assert.IsTrue(uri.IsDiscoverySecureEndToEnd); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void CtorUriHttpSchemeSecure() { + new UriIdentifier(new Uri("http://host/path"), true); + } + + /// <summary> + /// Verifies that the fragment is not stripped from an Identifier. + /// </summary> + /// <remarks> + /// Although fragments should be stripped from user supplied identifiers, + /// they should NOT be stripped from claimed identifiers. So the UriIdentifier + /// class, which serves both identifier types, must not do the stripping. + /// </remarks> + [TestMethod] + public void DoesNotStripFragment() { + Uri original = new Uri("http://a/b#c"); + UriIdentifier identifier = new UriIdentifier(original); + Assert.AreEqual(original.Fragment, identifier.Uri.Fragment); + } + + [TestMethod] + public void IsValid() { + Assert.IsTrue(UriIdentifier.IsValidUri(goodUri)); + Assert.IsFalse(UriIdentifier.IsValidUri(badUri)); + Assert.IsTrue(UriIdentifier.IsValidUri(relativeUri), "URL lacking http:// prefix should have worked anyway."); + } + + [TestMethod] + public void TrimFragment() { + Identifier noFragment = UriIdentifier.Parse("http://a/b"); + Identifier fragment = UriIdentifier.Parse("http://a/b#c"); + Assert.AreSame(noFragment, noFragment.TrimFragment()); + Assert.AreEqual(noFragment, fragment.TrimFragment()); + } + + [TestMethod] + public void ToStringTest() { + Assert.AreEqual(goodUri, new UriIdentifier(goodUri).ToString()); + } + + [TestMethod] + public void EqualsTest() { + Assert.AreEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri)); + // This next test is an interesting side-effect of passing off to Uri.Equals. But it's probably ok. + Assert.AreEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri + "#frag")); + Assert.AreNotEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri + "a")); + Assert.AreNotEqual(null, new UriIdentifier(goodUri)); + Assert.AreNotEqual(goodUri, new UriIdentifier(goodUri)); + } + + [TestMethod] + public void UnicodeTest() { + string unicodeUrl = "http://nerdbank.org/opaffirmative/崎村.aspx"; + Assert.IsTrue(UriIdentifier.IsValidUri(unicodeUrl)); + Identifier id; + Assert.IsTrue(UriIdentifier.TryParse(unicodeUrl, out id)); + Assert.AreEqual("/opaffirmative/%E5%B4%8E%E6%9D%91.aspx", ((UriIdentifier)id).Uri.AbsolutePath); + Assert.AreEqual(Uri.EscapeUriString(unicodeUrl), id.ToString()); + } + + void discover(string url, ProtocolVersion version, Identifier expectedLocalId, bool expectSreg, bool useRedirect) { + discover(url, version, expectedLocalId, expectSreg, useRedirect, null); + } + void discover(string url, ProtocolVersion version, Identifier expectedLocalId, bool expectSreg, bool useRedirect, WebHeaderCollection headers) { + Protocol protocol = Protocol.Lookup(version); + UriIdentifier claimedId = TestSupport.GetFullUrl(url); + UriIdentifier userSuppliedIdentifier = TestSupport.GetFullUrl( + "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(); + } + MockHttpRequest.RegisterMockResponse(new Uri(idToDiscover), claimedId, contentType, + headers ?? new WebHeaderCollection(), TestSupport.LoadEmbeddedFile(url)); + + ServiceEndpoint se = idToDiscover.Discover().FirstOrDefault(); + Assert.IsNotNull(se, url + " failed to be discovered."); + Assert.AreSame(protocol, se.Protocol); + Assert.AreEqual(claimedId, se.ClaimedIdentifier); + Assert.AreEqual(expectedLocalId, se.ProviderLocalIdentifier); + Assert.AreEqual(expectSreg ? 2 : 1, se.ProviderSupportedServiceTypeUris.Length); + Assert.IsTrue(Array.IndexOf(se.ProviderSupportedServiceTypeUris, protocol.ClaimedIdentifierServiceTypeURI) >= 0); + Assert.AreEqual(expectSreg, se.IsExtensionSupported(new ClaimsRequest())); + } + void discoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId) { + discoverXrds(page, version, expectedLocalId, null); + } + void discoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId, WebHeaderCollection headers) { + if (!page.Contains(".")) page += ".xml"; + discover("/Discovery/xrdsdiscovery/" + page, version, expectedLocalId, true, false, headers); + discover("/Discovery/xrdsdiscovery/" + page, version, expectedLocalId, true, true, headers); + } + void discoverHtml(string page, ProtocolVersion version, Identifier expectedLocalId, bool useRedirect) { + discover("/Discovery/htmldiscovery/" + page, version, expectedLocalId, false, useRedirect); + } + void discoverHtml(string scenario, ProtocolVersion version, Identifier expectedLocalId) { + string page = scenario + ".html"; + discoverHtml(page, version, expectedLocalId, false); + discoverHtml(page, version, expectedLocalId, true); + } + void failDiscover(string url) { + UriIdentifier userSuppliedId = TestSupport.GetFullUrl(url); + + Mocks.MockHttpRequest.RegisterMockResponse(new Uri(userSuppliedId), userSuppliedId, "text/html", + TestSupport.LoadEmbeddedFile(url)); + + Assert.AreEqual(0, userSuppliedId.Discover().Count()); // ... but that no endpoint info is discoverable + } + void failDiscoverHtml(string scenario) { + failDiscover("/Discovery/htmldiscovery/" + scenario + ".html"); + } + void failDiscoverXrds(string scenario) { + failDiscover("/Discovery/xrdsdiscovery/" + scenario + ".xml"); + } + [TestMethod] + public void HtmlDiscover_11() { + discoverHtml("html10prov", ProtocolVersion.V11, null); + discoverHtml("html10both", ProtocolVersion.V11, "http://c/d"); + failDiscoverHtml("html10del"); + } + [TestMethod] + public void HtmlDiscover_20() { + discoverHtml("html20prov", ProtocolVersion.V20, null); + discoverHtml("html20both", ProtocolVersion.V20, "http://c/d"); + failDiscoverHtml("html20del"); + discoverHtml("html2010", ProtocolVersion.V20, "http://c/d"); + discoverHtml("html1020", ProtocolVersion.V20, "http://c/d"); + discoverHtml("html2010combinedA", ProtocolVersion.V20, "http://c/d"); + discoverHtml("html2010combinedB", ProtocolVersion.V20, "http://c/d"); + discoverHtml("html2010combinedC", ProtocolVersion.V20, "http://c/d"); + failDiscoverHtml("html20relative"); + } + [TestMethod] + public void XrdsDiscoveryFromHead() { + Mocks.MockHttpRequest.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), + "application/xrds+xml", TestSupport.LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml")); + discoverXrds("XrdsReferencedInHead.html", ProtocolVersion.V10, null); + } + [TestMethod] + public void XrdsDiscoveryFromHttpHeader() { + WebHeaderCollection headers = new WebHeaderCollection(); + headers.Add("X-XRDS-Location", TestSupport.GetFullUrl("http://localhost/xrds1020.xml").AbsoluteUri); + Mocks.MockHttpRequest.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), + "application/xrds+xml", TestSupport.LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml")); + discoverXrds("XrdsReferencedInHttpHeader.html", ProtocolVersion.V10, null, headers); + } + [TestMethod] + public void XrdsDirectDiscovery_10() { + failDiscoverXrds("xrds-irrelevant"); + discoverXrds("xrds10", ProtocolVersion.V10, null); + discoverXrds("xrds11", ProtocolVersion.V11, null); + discoverXrds("xrds1020", ProtocolVersion.V10, null); + } + [TestMethod] + public void XrdsDirectDiscovery_20() { + discoverXrds("xrds20", ProtocolVersion.V20, null); + discoverXrds("xrds2010a", ProtocolVersion.V20, null); + discoverXrds("xrds2010b", ProtocolVersion.V20, null); + } + + [TestMethod] + public void NormalizeCase() { + // only the host name can be normalized in casing safely. + Identifier id = "http://HOST:80/PaTH?KeY=VaLUE#fRag"; + Assert.AreEqual("http://host/PaTH?KeY=VaLUE#fRag", id.ToString()); + // make sure https is preserved, along with port 80, which is NON-default for https + id = "https://HOST:80/PaTH?KeY=VaLUE#fRag"; + Assert.AreEqual("https://host:80/PaTH?KeY=VaLUE#fRag", id.ToString()); + } + + [TestMethod] + public void HttpSchemePrepended() { + UriIdentifier id = new UriIdentifier("www.yahoo.com"); + Assert.AreEqual("http://www.yahoo.com/", id.ToString()); + Assert.IsTrue(id.SchemeImplicitlyPrepended); + } + + ////[TestMethod, Ignore("The spec says http:// must be prepended in this case, but that just creates an invalid URI. Our UntrustedWebRequest will stop disallowed schemes.")] + public void CtorDisallowedScheme() { + UriIdentifier id = new UriIdentifier(new Uri("ftp://host/path")); + Assert.AreEqual("http://ftp://host/path", id.ToString()); + Assert.IsTrue(id.SchemeImplicitlyPrepended); + } + + [TestMethod] + public void DiscoveryWithRedirects() { + MockHttpRequest.Reset(); + Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20); + + // Add a couple of chained redirect pages that lead to the claimedId. + Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true); + Uri insecureMidpointUri = TestSupport.GetFullUrl("/insecureStop"); + MockHttpRequest.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); + MockHttpRequest.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); + + // don't require secure SSL discovery for this test. + Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, false); + Assert.AreEqual(1, userSuppliedIdentifier.Discover().Count()); + } + + [TestMethod] + public void TryRequireSslAdjustsIdentifier() { + Identifier secureId; + // Try Parse and ctor without explicit scheme + var id = Identifier.Parse("www.yahoo.com"); + Assert.AreEqual("http://www.yahoo.com/", id.ToString()); + Assert.IsTrue(id.TryRequireSsl(out secureId)); + Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd); + Assert.AreEqual("https://www.yahoo.com/", secureId.ToString()); + + id = new UriIdentifier("www.yahoo.com"); + Assert.AreEqual("http://www.yahoo.com/", id.ToString()); + Assert.IsTrue(id.TryRequireSsl(out secureId)); + Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd); + Assert.AreEqual("https://www.yahoo.com/", secureId.ToString()); + + // Try Parse and ctor with explicit http:// scheme + id = Identifier.Parse("http://www.yahoo.com"); + Assert.IsFalse(id.TryRequireSsl(out secureId)); + Assert.IsFalse(secureId.IsDiscoverySecureEndToEnd); + Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); + Assert.AreEqual(0, secureId.Discover().Count()); + + id = new UriIdentifier("http://www.yahoo.com"); + Assert.IsFalse(id.TryRequireSsl(out secureId)); + Assert.IsFalse(secureId.IsDiscoverySecureEndToEnd); + Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); + Assert.AreEqual(0, secureId.Discover().Count()); + } + + [TestMethod] + public void DiscoverRequireSslWithSecureRedirects() { + MockHttpRequest.Reset(); + Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, true); + + // Add a couple of chained redirect pages that lead to the claimedId. + // All redirects should be secure. + Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true); + Uri secureMidpointUri = TestSupport.GetFullUrl("/secureStop", null, true); + MockHttpRequest.RegisterMockRedirect(userSuppliedUri, secureMidpointUri); + MockHttpRequest.RegisterMockRedirect(secureMidpointUri, new Uri(claimedId.ToString())); + + Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); + Assert.AreEqual(1, userSuppliedIdentifier.Discover().Count()); + } + + [TestMethod, ExpectedException(typeof(OpenIdException))] + public void DiscoverRequireSslWithInsecureRedirect() { + MockHttpRequest.Reset(); + Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, 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 = TestSupport.GetFullUrl("/someSecurePage", null, true); + Uri insecureMidpointUri = TestSupport.GetFullUrl("/insecureStop"); + MockHttpRequest.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); + MockHttpRequest.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); + + Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); + userSuppliedIdentifier.Discover(); + } + + [TestMethod] + public void DiscoveryRequireSslWithInsecureXrdsInSecureHtmlHead() { + var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false); + Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true); + + string html = string.Format("<html><head><meta http-equiv='X-XRDS-Location' content='{0}'/></head><body></body></html>", + insecureXrdsSource); + MockHttpRequest.RegisterMockResponse(secureClaimedUri, "text/html", html); + + Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); + Assert.AreEqual(0, userSuppliedIdentifier.Discover().Count()); + } + + [TestMethod] + public void DiscoveryRequireSslWithInsecureXrdsInSecureHttpHeader() { + var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false); + Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true); + + string html = "<html><head></head><body></body></html>"; + WebHeaderCollection headers = new WebHeaderCollection { + { "X-XRDS-Location", insecureXrdsSource } + }; + MockHttpRequest.RegisterMockResponse(secureClaimedUri, secureClaimedUri, "text/html", headers, html); + + Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); + Assert.AreEqual(0, userSuppliedIdentifier.Discover().Count()); + } + + [TestMethod] + public void DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags() { + var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false); + Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true); + + Identifier localIdForLinkTag = TestSupport.GetDelegateUrl(TestSupport.Scenarios.AlwaysDeny, true); + string html = string.Format(@" + <html><head> + <meta http-equiv='X-XRDS-Location' content='{0}'/> <!-- this one will be insecure and ignored --> + <link rel='openid2.provider' href='{1}' /> + <link rel='openid2.local_id' href='{2}' /> + </head><body></body></html>", + HttpUtility.HtmlEncode(insecureXrdsSource), + HttpUtility.HtmlEncode(TestSupport.GetFullUrl("/" + TestSupport.ProviderPage, null, true).AbsoluteUri), + HttpUtility.HtmlEncode(localIdForLinkTag.ToString())); + MockHttpRequest.RegisterMockResponse(secureClaimedUri, "text/html", html); + + Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); + Assert.AreEqual(localIdForLinkTag, userSuppliedIdentifier.Discover().Single().ProviderLocalIdentifier); + } + + [TestMethod] + public void DiscoveryRequiresSslIgnoresInsecureEndpointsInXrds() { + var insecureEndpoint = TestSupport.GetServiceEndpoint(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, 10, false); + var secureEndpoint = TestSupport.GetServiceEndpoint(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20, 20, true); + UriIdentifier secureClaimedId = new UriIdentifier(TestSupport.GetFullUrl("/claimedId", null, true), true); + MockHttpRequest.RegisterMockXrdsResponse(secureClaimedId, new ServiceEndpoint[] { insecureEndpoint, secureEndpoint }); + Assert.AreEqual(secureEndpoint.ProviderLocalIdentifier, secureClaimedId.Discover().Single().ProviderLocalIdentifier); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs new file mode 100644 index 0000000..1edc086 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs @@ -0,0 +1,470 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DotNetOpenAuth.OpenId.RelyingParty; +using DotNetOpenAuth.Test.Mocks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using DotNetOpenAuth.OpenId; + +namespace DotNetOpenAuth.Test.OpenId { + [TestClass] + public class XriIdentifierTests : OpenIdTestBase { + string goodXri = "=Andrew*Arnott"; + string badXri = "some\\wacky%^&*()non-XRI"; + + [TestCleanup] + public void TearDown() { + MockHttpRequest.Reset(); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNull() { + new XriIdentifier(null); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorBlank() { + new XriIdentifier(string.Empty); + } + + [TestMethod, ExpectedException(typeof(FormatException))] + public void CtorBadXri() { + new XriIdentifier(badXri); + } + + [TestMethod] + public void CtorGoodXri() { + var xri = new XriIdentifier(goodXri); + Assert.AreEqual(goodXri, xri.OriginalXri); + Assert.AreEqual(goodXri, xri.CanonicalXri); // assumes 'goodXri' is canonical already + Assert.IsFalse(xri.IsDiscoverySecureEndToEnd); + } + + [TestMethod] + public void CtorGoodXriSecure() { + var xri = new XriIdentifier(goodXri, true); + Assert.AreEqual(goodXri, xri.OriginalXri); + Assert.AreEqual(goodXri, xri.CanonicalXri); // assumes 'goodXri' is canonical already + Assert.IsTrue(xri.IsDiscoverySecureEndToEnd); + } + + [TestMethod] + public void IsValid() { + Assert.IsTrue(XriIdentifier.IsValidXri(goodXri)); + Assert.IsFalse(XriIdentifier.IsValidXri(badXri)); + } + + /// <summary> + /// Verifies 2.0 spec section 7.2#1 + /// </summary> + [TestMethod] + public void StripXriScheme() { + var xri = new XriIdentifier("xri://" + goodXri); + Assert.AreEqual("xri://" + goodXri, xri.OriginalXri); + Assert.AreEqual(goodXri, xri.CanonicalXri); + } + + [TestMethod] + public void TrimFragment() { + Identifier xri = new XriIdentifier(goodXri); + Assert.AreSame(xri, xri.TrimFragment()); + } + + [TestMethod] + public void ToStringTest() { + Assert.AreEqual(goodXri, new XriIdentifier(goodXri).ToString()); + } + + [TestMethod] + public void EqualsTest() { + Assert.AreEqual(new XriIdentifier(goodXri), new XriIdentifier(goodXri)); + Assert.AreNotEqual(new XriIdentifier(goodXri), new XriIdentifier(goodXri + "a")); + Assert.AreNotEqual(null, new XriIdentifier(goodXri)); + Assert.AreNotEqual(goodXri, new XriIdentifier(goodXri)); + } + +#if DISCOVERY // TODO: Add discovery and then re-enable this code block + private ServiceEndpoint verifyCanonicalId(Identifier iname, string expectedClaimedIdentifier) { + ServiceEndpoint se = iname.Discover().FirstOrDefault(); + if (expectedClaimedIdentifier != null) { + Assert.IsNotNull(se); + Assert.AreEqual(expectedClaimedIdentifier, se.ClaimedIdentifier.ToString(), "i-name {0} discovery resulted in unexpected CanonicalId", iname); + Assert.IsTrue(se.ProviderSupportedServiceTypeUris.Length > 0); + } else { + Assert.IsNull(se); + } + return se; + } +#endif + + [TestMethod] + public void Discover() { + string xrds = @"<?xml version='1.0' encoding='UTF-8'?> +<XRD version='2.0' xmlns='xri://$xrd*($v*2.0)'> + <Query>*Arnott</Query> + <Status ceid='off' cid='verified' code='100'/> + <Expires>2008-07-14T02:03:24.000Z</Expires> + <ProviderID>xri://=</ProviderID> + <LocalID>!9b72.7dd1.50a9.5ccd</LocalID> + <CanonicalID>=!9B72.7DD1.50A9.5CCD</CanonicalID> + + <Service priority='10'> + <ProviderID>xri://!!1008</ProviderID> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='default' select='false'/> + <Path select='true'>(+contact)</Path> + <Path match='null' select='false'/> + <URI append='qxri' priority='1'>http://1id.com/contact/</URI> + + </Service> + <Service priority='10'> + <ProviderID>xri://!!1008</ProviderID> + <Type select='true'>xri://+i-service*(+forwarding)*($v*1.0)</Type> + <Type match='null' select='false'/> + <URI append='qxri' priority='1'>http://1id.com/</URI> + </Service> + + <Service priority='10'> + <ProviderID>xri://!!1008</ProviderID> + <Type select='true'>http://openid.net/signon/1.0</Type> + <URI append='none' priority='10'>http://1id.com/sso</URI> + </Service> +</XRD>"; + Dictionary<string, string> mocks = new Dictionary<string, string> { + { "https://xri.net/=Arnott?_xrd_r=application/xrd%2Bxml;sep=false", xrds }, + { "https://xri.net/=!9B72.7DD1.50A9.5CCD?_xrd_r=application/xrd%2Bxml;sep=false", xrds }, + }; + MockHttpRequest.RegisterMockXrdsResponses(mocks); + + string expectedCanonicalId = "=!9B72.7DD1.50A9.5CCD"; + ServiceEndpoint se = verifyCanonicalId("=Arnott", expectedCanonicalId); + Assert.AreEqual(Protocol.V10, se.Protocol); + Assert.AreEqual("http://1id.com/sso", se.ProviderEndpoint.ToString()); + Assert.AreEqual(se.ClaimedIdentifier, se.ProviderLocalIdentifier); + Assert.AreEqual("=Arnott", se.FriendlyIdentifierForDisplay); + } + + [TestMethod] + public void DiscoverCommunityInameCanonicalIDs() { + string llliResponse = @"<?xml version='1.0' encoding='UTF-8'?> +<XRD version='2.0' xmlns='xri://$xrd*($v*2.0)'> + <Query>*llli</Query> + <Status ceid='off' cid='verified' code='100'/> + <Expires>2008-07-14T02:21:06.000Z</Expires> + <ProviderID>xri://@</ProviderID> + <LocalID>!72cd.a072.157e.a9c6</LocalID> + <CanonicalID>@!72CD.A072.157E.A9C6</CanonicalID> + <Service priority='10'> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>http://openid.net/signon/1.0</Type> + <URI append='none' priority='1'>https://login.llli.org/server/</URI> + </Service> + <Service priority='1'> + <ProviderID>xri://!!1003!103</ProviderID> + <Type match='null' select='false'/> + <Type select='true'>xri://+i-service*(+forwarding)*($v*1.0)</Type> + <Path match='default'/> + <Path>(+index)</Path> + <URI append='qxri' priority='1'>http://linksafe-forward.ezibroker.net/forwarding/</URI> + </Service> + <Service priority='10'> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://$res*auth*($v*2.0)</Type> + <MediaType>application/xrds+xml;trust=none</MediaType> + <URI priority='10'>http://resolve.ezibroker.net/resolve/@llli/</URI> + </Service> + <Service priority='10'> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='null'/> + <Path select='true'>(+contact)</Path> + <Path match='null'/> + <URI append='authority' priority='1'>http://linksafe-contact.ezibroker.net/contact/</URI> + </Service> +</XRD> +"; + string llliAreaResponse = @"<?xml version='1.0' encoding='UTF-8'?> +<XRD xmlns='xri://$xrd*($v*2.0)'> + <Query>*area</Query> + <Status cid='verified' code='100'>SUCCESS</Status> + <ServerStatus code='100'>SUCCESS</ServerStatus> + <Expires>2008-07-15T01:21:07.000Z</Expires> + <ProviderID>xri://!!1003</ProviderID> + <LocalID>0000.0000.3B9A.CA0C</LocalID> + <CanonicalID>@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C</CanonicalID> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>http://openid.net/signon/1.0</Type> + <URI append='none' priority='1'>https://login.llli.org/server/</URI> + </Service> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='null'/> + <Path select='true'>(+contact)</Path> + <Path match='null'/> + <URI append='authority' priority='1'>http://linksafe-contact.ezibroker.net/contact/</URI> + </Service> + <Service priority='1'> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://+i-service*(+forwarding)*($v*1.0)</Type> + <Type match='null' select='false'/> + <Path>(+index)</Path> + <Path match='default'/> + <URI append='qxri' priority='1'>http://linksafe-forward.ezibroker.net/forwarding/</URI> + </Service> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://$res*auth*($v*2.0)</Type> + <MediaType>application/xrds+xml;trust=none</MediaType> + <URI>http://resolve.ezibroker.net/resolve/@llli*area/</URI> + </Service> +</XRD>"; + string llliAreaCanadaUnattachedResponse = @"<?xml version='1.0' encoding='UTF-8'?> +<XRD xmlns='xri://$xrd*($v*2.0)'> + <Query>*canada.unattached</Query> + <Status cid='verified' code='100'>SUCCESS</Status> + <ServerStatus code='100'>SUCCESS</ServerStatus> + <Expires>2008-07-15T01:21:08.000Z</Expires> + <ProviderID>xri://!!1003</ProviderID> + <LocalID>0000.0000.3B9A.CA41</LocalID> + <CanonicalID>@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C!0000.0000.3B9A.CA41</CanonicalID> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>http://openid.net/signon/1.0</Type> + <URI append='none' priority='1'>https://login.llli.org/server/</URI> + </Service> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='null'/> + <Path select='true'>(+contact)</Path> + <Path match='null'/> + <URI append='authority' priority='1'>http://linksafe-contact.ezibroker.net/contact/</URI> + </Service> + <Service priority='1'> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://+i-service*(+forwarding)*($v*1.0)</Type> + <Type match='null' select='false'/> + <Path>(+index)</Path> + <Path match='default'/> + <URI append='qxri' priority='1'>http://linksafe-forward.ezibroker.net/forwarding/</URI> + </Service> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://$res*auth*($v*2.0)</Type> + <MediaType>application/xrds+xml;trust=none</MediaType> + <URI>http://resolve.ezibroker.net/resolve/@llli*area*canada.unattached/</URI> + </Service> +</XRD>"; + string llliAreaCanadaUnattachedAdaResponse = @"<?xml version='1.0' encoding='UTF-8'?> +<XRD xmlns='xri://$xrd*($v*2.0)'> + <Query>*ada</Query> + <Status cid='verified' code='100'>SUCCESS</Status> + <ServerStatus code='100'>SUCCESS</ServerStatus> + <Expires>2008-07-15T01:21:10.000Z</Expires> + <ProviderID>xri://!!1003</ProviderID> + <LocalID>0000.0000.3B9A.CA01</LocalID> + <CanonicalID>@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C!0000.0000.3B9A.CA41!0000.0000.3B9A.CA01</CanonicalID> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>http://openid.net/signon/1.0</Type> + <URI append='none' priority='1'>https://login.llli.org/server/</URI> + </Service> + <Service> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='null'/> + <Path select='true'>(+contact)</Path> + <Path match='null'/> + <URI append='authority' priority='1'>http://linksafe-contact.ezibroker.net/contact/</URI> + </Service> + <Service priority='1'> + <ProviderID>xri://!!1003!103</ProviderID> + <Type select='true'>xri://+i-service*(+forwarding)*($v*1.0)</Type> + <Type match='null' select='false'/> + <Path>(+index)</Path> + <Path match='default'/> + <URI append='qxri' priority='1'>http://linksafe-forward.ezibroker.net/forwarding/</URI> + </Service> +</XRD>"; + string webResponse = @"<?xml version='1.0' encoding='UTF-8'?> +<XRD version='2.0' xmlns='xri://$xrd*($v*2.0)'> + <Query>*Web</Query> + <Status ceid='off' cid='verified' code='100'/> + <Expires>2008-07-14T02:21:12.000Z</Expires> + <ProviderID>xri://=</ProviderID> + <LocalID>!91f2.8153.f600.ae24</LocalID> + <CanonicalID>=!91F2.8153.F600.AE24</CanonicalID> + <Service priority='10'> + <Type select='true'>xri://+i-service*(+locator)*($v*1.0)</Type> + <Path select='true'>(+locator)</Path> + <MediaType match='default' select='false'/> + <URI append='qxri'>http://locator.fullxri.com/locator/</URI> + </Service> + <Service priority='10'> + <ProviderID>xri://=web</ProviderID> + <Type select='true'>xri://$res*auth*($v*2.0)</Type> + <Type select='true'>xri://$res*auth*($v*2.0)</Type> + <MediaType select='true'>application/xrds+xml</MediaType> + <URI append='qxri' priority='1'>https://resolve.freexri.com/ns/=web/</URI> + <URI append='qxri' priority='2'>http://resolve.freexri.com/ns/=web/</URI> + </Service> + <Service priority='10'> + <Type select='true'>http://openid.net/signon/1.0</Type> + <Type select='true'>http://specs.openid.net/auth/2.0/signon</Type> + <Path select='true'>(+login)</Path> + <Path match='default' select='false'/> + <MediaType match='default' select='false'/> + <URI append='none' priority='2'>http://authn.fullxri.com/authentication/</URI> + <URI append='none' priority='1'>https://authn.fullxri.com/authentication/</URI> + </Service> + <Service priority='10'> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='null' select='false'/> + <Path select='true'>(+contact)</Path> + <Path match='null' select='false'/> + <MediaType match='default' select='false'/> + <URI append='qxri'>http://contact.fullxri.com/contact/</URI> + </Service> + <KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'> + <X509Data> + <X509Certificate> +MIIExzCCA6+gAwIBAgIJAM+MlFr0Sth6MA0GCSqGSIb3DQEBBQUAMIGdMR8wHQYD +VQQDExZTdXBlcnZpbGxhaW46IFRoZSBSb290MQswCQYDVQQGEwJVUzERMA8GA1UE +CBMITmV3IFlvcmsxDzANBgNVBAcTBkdvdGhhbTEgMB4GA1UEChMXU3VwZXJ2aWxs +YWluIFVuaXZlcnNpdHkxJzAlBgkqhkiG9w0BCQEWGHBlbmd1aW5Ac3VwZXJ2aWxs +YWluLmVkdTAeFw0wNjA4MTcxOTU5NTNaFw0xMTA4MTYxOTU5NTNaMIGdMR8wHQYD +VQQDExZTdXBlcnZpbGxhaW46IFRoZSBSb290MQswCQYDVQQGEwJVUzERMA8GA1UE +CBMITmV3IFlvcmsxDzANBgNVBAcTBkdvdGhhbTEgMB4GA1UEChMXU3VwZXJ2aWxs +YWluIFVuaXZlcnNpdHkxJzAlBgkqhkiG9w0BCQEWGHBlbmd1aW5Ac3VwZXJ2aWxs +YWluLmVkdTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL6uFqas4dK6 +A2wTZL0viRQNJrPyFnFBDSZGib/2ijhgzed/vvmZIBM9sFpwahcuR5hvyKUe37/c +/RSZXoNDi/eiNOx4qb0l9UB6bd8qvc4V1PnLE7L+ZYcmwrvTKm4x8qXMgEv1wca2 +FPsreHNPdLiTUZ8v0tDTWi3Mgi7y47VTzJaTkcfmO1nL6xAtln5sLdH0PbMM3LAp +T1d3nwI3VdbhqqZ+6+OKEuC8gk5iH4lfrbr6C9bYS6vzIKrotHpZ3N2aIC3NMjJD +PMw/mfCuADfRNlHXgZW+0zyUkwGTMDea8qgsoAMWJGdeTIw8I1I3RhnbgLzdsNQl +b/1ZXx1uJRUCAwEAAaOCAQYwggECMB0GA1UdDgQWBBQe+xSjYTrlfraJARjMxscb +j36jvDCB0gYDVR0jBIHKMIHHgBQe+xSjYTrlfraJARjMxscbj36jvKGBo6SBoDCB +nTEfMB0GA1UEAxMWU3VwZXJ2aWxsYWluOiBUaGUgUm9vdDELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCE5ldyBZb3JrMQ8wDQYDVQQHEwZHb3RoYW0xIDAeBgNVBAoTF1N1 +cGVydmlsbGFpbiBVbml2ZXJzaXR5MScwJQYJKoZIhvcNAQkBFhhwZW5ndWluQHN1 +cGVydmlsbGFpbi5lZHWCCQDPjJRa9ErYejAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 +DQEBBQUAA4IBAQC4SPBDGYAxfbXd8N5OvG0drM7a5hjXfcCZpiILlPSRpxp79yh7 +I5vVWxBxUfolwbei7PTBVy7CE27SUbSICeqWjcDCfjNjiZk6mLS80rm/TdLrHSyM ++Ujlw9MGcBGaLI+sdziDUMtTQDpeAyQTaGVbh1mx5874Hlo1VXqGYNo0RwR+iLfs +x48VuO6GbWVyxtktkE2ypz1KLWiyI056YynydRvuBCBHeRqGUixPlH9CrmeSCP2S +sfbiKnMOGXjIYbvbsTAMdW2iqg6IWa/fgxhvZoAXChM9bkhisJQc0qD0J5TJQwgr +uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy + </X509Certificate> + </X509Data> + </KeyInfo> +</XRD>"; + MockHttpRequest.RegisterMockXrdsResponses(new Dictionary<string, string> { + { "https://xri.net/@llli?_xrd_r=application/xrd%2Bxml;sep=false", llliResponse }, + { "https://xri.net/@llli*area?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaResponse }, + { "https://xri.net/@llli*area*canada.unattached?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaCanadaUnattachedResponse }, + { "https://xri.net/@llli*area*canada.unattached*ada?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaCanadaUnattachedAdaResponse }, + { "https://xri.net/=Web?_xrd_r=application/xrd%2Bxml;sep=false", webResponse }, + }); + verifyCanonicalId("@llli", "@!72CD.A072.157E.A9C6"); + verifyCanonicalId("@llli*area", "@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C"); + verifyCanonicalId("@llli*area*canada.unattached", "@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C!0000.0000.3B9A.CA41"); + verifyCanonicalId("@llli*area*canada.unattached*ada", "@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C!0000.0000.3B9A.CA41!0000.0000.3B9A.CA01"); + verifyCanonicalId("=Web", "=!91F2.8153.F600.AE24"); + } + + [TestMethod] + public void DiscoveryCommunityInameDelegateWithoutCanonicalID() { + MockHttpRequest.RegisterMockXrdsResponses(new Dictionary<string, string> { + { "https://xri.net/=Web*andrew.arnott?_xrd_r=application/xrd%2Bxml;sep=false", @"<?xml version='1.0' encoding='UTF-8'?> +<XRD xmlns='xri://$xrd*($v*2.0)'> + <Query>*andrew.arnott</Query> + <Status cid='absent' code='100'>Success</Status> + <ServerStatus code='100'>Success</ServerStatus> + <Expires>2008-07-14T03:30:59.722Z</Expires> + <ProviderID>=!91F2.8153.F600.AE24</ProviderID> + <Service> + <Type select='true'>http://openid.net/signon/1.0</Type> + <Path select='true'>(+login)</Path> + <Path match='default'/> + <MediaType match='default'/> + <URI append='none' priority='2'>http://www.myopenid.com/server</URI> + <openid:Delegate xmlns:openid='http://openid.net/xmlns/1.0'>http://blog.nerdbank.net</openid:Delegate> + </Service> + <Service> + <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='null'/> + <Path select='true'>(+contact)</Path> + <Path match='null'/> + <MediaType match='default'/> + <URI append='qxri'>http://contact.freexri.com/contact/</URI> + </Service> + <Service> + <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID> + <Type select='true'>xri://+i-service*(+forwarding)*($v*1.0)</Type> + <Path select='true'>(+index)</Path> + <Path match='default'/> + <MediaType match='default'/> + <URI append='qxri'>http://forwarding.freexri.com/forwarding/</URI> + </Service> + <Service> + <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID> + <Type select='true'>http://openid.net/signon/1.0</Type> + <Path select='true'>(+login)</Path> + <Path match='default'/> + <MediaType match='default'/> + <URI append='none' priority='2'>http://authn.freexri.com/authentication/</URI> + <URI append='none' priority='1'>https://authn.freexri.com/authentication/</URI> + </Service> + <ServedBy>OpenXRI</ServedBy> +</XRD>" }, + { "https://xri.net/@id*andrewarnott?_xrd_r=application/xrd%2Bxml;sep=false", @"<?xml version='1.0' encoding='UTF-8'?> +<XRD xmlns='xri://$xrd*($v*2.0)'> + <Query>*andrewarnott</Query> + <Status cid='absent' code='100'>Success</Status> + <ServerStatus code='100'>Success</ServerStatus> + <Expires>2008-07-14T03:31:00.466Z</Expires> + <ProviderID>@!B1E8.C27B.E41C.25C3</ProviderID> + <Service> + <Type select='true'>http://openid.net/signon/1.0</Type> + <Path select='true'>(+login)</Path> + <Path match='default'/> + <MediaType match='default'/> + <URI append='none' priority='2'>http://www.myopenid.com/server</URI> + <openid:Delegate xmlns:openid='http://openid.net/xmlns/1.0'>http://blog.nerdbank.net</openid:Delegate> + </Service> + <Service> + <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID> + <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type> + <Type match='null'/> + <Path select='true'>(+contact)</Path> + <Path match='null'/> + <MediaType match='default'/> + <URI append='qxri'>http://contact.freexri.com/contact/</URI> + </Service> + <Service> + <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID> + <Type select='true'>xri://+i-service*(+forwarding)*($v*1.0)</Type> + <Path select='true'>(+index)</Path> + <Path match='default'/> + <MediaType match='default'/> + <URI append='qxri'>http://forwarding.freexri.com/forwarding/</URI> + </Service> + <ServedBy>OpenXRI</ServedBy> +</XRD>" }, + }); + // Consistent with spec section 7.3.2.3, we do not permit + // delegation on XRI discovery when there is no CanonicalID present. + verifyCanonicalId("=Web*andrew.arnott", null); + verifyCanonicalId("@id*andrewarnott", null); + } + + ////[TestMethod, Ignore("XRI parsing and normalization is not implemented (yet).")] + public void NormalizeCase() { + Identifier id = "=!9B72.7dd1.50a9.5ccd"; + Assert.AreEqual("=!9B72.7DD1.50A9.5CCD", id.ToString()); + } + } +} |