summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Test
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.Test')
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs2
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs2
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestChannel.cs2
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs6
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs6
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs2
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs66
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs410
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs470
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());
+ }
+ }
+}