//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.OpenId { using System; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.RelyingParty; using NUnit.Framework; [TestFixture] public class UriIdentifierTests : OpenIdTestBase { private string goodUri = "http://blog.nerdbank.net/"; private string relativeUri = "host/path"; private string badUri = "som%-)830w8vf/?.<>,ewackedURI"; [SetUp] public override void SetUp() { base.SetUp(); } [Test, ExpectedException(typeof(ArgumentNullException))] public void CtorNullUri() { new UriIdentifier((Uri)null); } [Test, ExpectedException(typeof(ArgumentNullException))] public void CtorNullString() { new UriIdentifier((string)null); } [Test, ExpectedException(typeof(ArgumentException))] public void CtorBlank() { new UriIdentifier(string.Empty); } [Test, ExpectedException(typeof(UriFormatException))] public void CtorBadUri() { new UriIdentifier(this.badUri); } [Test] public void CtorGoodUri() { var uri = new UriIdentifier(this.goodUri); Assert.AreEqual(new Uri(this.goodUri), uri.Uri); Assert.IsFalse(uri.SchemeImplicitlyPrepended); Assert.IsFalse(uri.IsDiscoverySecureEndToEnd); } [Test] public void CtorStringNoSchemeSecure() { var uri = new UriIdentifier("host/path", true); Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri); Assert.IsTrue(uri.IsDiscoverySecureEndToEnd); } [Test] public void CtorStringHttpsSchemeSecure() { var uri = new UriIdentifier("https://host/path", true); Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri); Assert.IsTrue(uri.IsDiscoverySecureEndToEnd); } [Test, ExpectedException(typeof(ArgumentException))] public void CtorStringHttpSchemeSecure() { new UriIdentifier("http://host/path", true); } [Test] 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); } [Test, ExpectedException(typeof(ArgumentException))] public void CtorUriHttpSchemeSecure() { new UriIdentifier(new Uri("http://host/path"), true); } /// /// Verifies that the fragment is not stripped from an Identifier. /// /// /// 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. /// [Test] public void DoesNotStripFragment() { Uri original = new Uri("http://a/b#c"); UriIdentifier identifier = new UriIdentifier(original); Assert.AreEqual(original.Fragment, identifier.Uri.Fragment); } [Test] public void IsValid() { Assert.IsTrue(UriIdentifier.IsValidUri(this.goodUri)); Assert.IsFalse(UriIdentifier.IsValidUri(this.badUri)); Assert.IsTrue(UriIdentifier.IsValidUri(this.relativeUri), "URL lacking http:// prefix should have worked anyway."); } [Test] 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.ToString(), fragment.TrimFragment().ToString()); // Try the problematic ones TestAsFullAndPartialTrust(fullTrust => { Identifier noFrag = UriIdentifier.Parse("http://a/b./c"); Identifier frag = UriIdentifier.Parse("http://a/b./c#d"); Assert.AreSame(noFrag, noFrag.TrimFragment()); Assert.AreEqual(noFrag.ToString(), frag.TrimFragment().ToString()); }); } [Test] public void ToStringTest() { Assert.AreEqual(this.goodUri, new UriIdentifier(this.goodUri).ToString()); TestAsFullAndPartialTrust(fullTrust => { Assert.AreEqual("http://abc/D./e.?Qq#Ff", new UriIdentifier("HTTP://ABC/D./e.?Qq#Ff").ToString()); Assert.AreEqual("http://abc/D./e.?Qq", new UriIdentifier("HTTP://ABC/D./e.?Qq").ToString()); Assert.AreEqual("http://abc/D./e.#Ff", new UriIdentifier("HTTP://ABC/D./e.#Ff").ToString()); Assert.AreEqual("http://abc/", new UriIdentifier("HTTP://ABC").ToString()); Assert.AreEqual("http://abc/?q", new UriIdentifier("HTTP://ABC?q").ToString()); Assert.AreEqual("http://abc/#f", new UriIdentifier("HTTP://ABC#f").ToString()); Assert.AreEqual("http://blog.nerdbank.net/", new UriIdentifier("blog.nerdbank.net").ToString()); Assert.AreEqual("http://blog.nerdbank.net/a", new UriIdentifier("BLOG.nerdbank.net/a").ToString()); Assert.AreEqual("https://blog.nerdbank.net/", new UriIdentifier("blog.nerdbank.net", true).ToString()); }); } [Test] public void EqualsTest() { TestAsFullAndPartialTrust(fulltrust => { Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri)); // This next test is an interesting side-effect of passing off to Uri.Equals. But it's probably ok. Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "#frag")); Assert.AreEqual(new UriIdentifier("http://a/b./c."), new UriIdentifier("http://a/b./c.#frag")); Assert.AreNotEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "a")); Assert.AreNotEqual(null, new UriIdentifier(this.goodUri)); Assert.IsTrue(new UriIdentifier(this.goodUri).Equals(this.goodUri)); Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", true), Identifier.Parse("http://www.foo.com/abc", true)); Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", true), Identifier.Parse("http://www.foo.com/abc", false)); Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", false), Identifier.Parse("http://www.foo.com/abc", false)); Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", true), Identifier.Parse("http://www.foo.com/ABC", true)); Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", true), Identifier.Parse("http://www.foo.com/ABC", false)); Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", false), Identifier.Parse("http://www.foo.com/ABC", false)); Assert.AreNotEqual(Identifier.Parse("http://a/b./c."), Identifier.Parse("http://a/b/c.")); Assert.AreEqual(Identifier.Parse("http://a/b./c."), Identifier.Parse("http://a/b./c.")); }); } [Test] 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()); } [Test] 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()); } /// /// Verifies that URIs that contain base64 encoded path segments (such as Yahoo) are properly preserved. /// /// /// Yahoo includes a base64 encoded part as their last path segment, /// which may end with a period. The default .NET Uri parser trims off /// trailing periods, which breaks OpenID unless special precautions are taken. /// [Test] public void TrailingPeriodsNotTrimmed() { TestAsFullAndPartialTrust(fullTrust => { string claimedIdentifier = "https://me.yahoo.com/a/AsDf.#asdf"; Identifier id = claimedIdentifier; Assert.AreEqual(claimedIdentifier, id.OriginalString); Assert.AreEqual(claimedIdentifier, id.ToString()); UriIdentifier idUri = new UriIdentifier(claimedIdentifier); Assert.AreEqual(claimedIdentifier, idUri.OriginalString); Assert.AreEqual(claimedIdentifier, idUri.ToString()); if (fullTrust) { Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri); } Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString()); Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString); Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed."); idUri = new UriIdentifier(new Uri(claimedIdentifier)); Assert.AreEqual(claimedIdentifier, idUri.OriginalString); Assert.AreEqual(claimedIdentifier, idUri.ToString()); if (fullTrust) { Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri); } Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString()); Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString); Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed."); claimedIdentifier = "https://me.yahoo.com:443/a/AsDf.#asdf"; id = claimedIdentifier; Assert.AreEqual(claimedIdentifier, id.OriginalString); Assert.AreEqual("https://me.yahoo.com/a/AsDf.#asdf", id.ToString()); }); } [Test] public void HttpSchemePrepended() { UriIdentifier id = new UriIdentifier("www.yahoo.com"); Assert.AreEqual("http://www.yahoo.com/", id.ToString()); Assert.IsTrue(id.SchemeImplicitlyPrepended); } ////[Test, 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); } [Test] public async Task 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.IsTrue(secureId.IsDiscoverySecureEndToEnd, "Although the TryRequireSsl failed, the created identifier should retain the Ssl status."); Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); Assert.AreEqual(0, (await DiscoverAsync(secureId)).Count(), "Since TryRequireSsl failed, the created Identifier should never discover anything."); id = new UriIdentifier("http://www.yahoo.com"); Assert.IsFalse(id.TryRequireSsl(out secureId)); Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd); Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); Assert.AreEqual(0, (await DiscoverAsync(secureId)).Count()); } /// /// Verifies that unicode hostnames are handled. /// [Test] public void UnicodeHostSupport() { var id = new UriIdentifier("http://server崎/村"); Assert.AreEqual("server崎", id.Uri.Host); } /// /// Verifies SimpleUri behavior /// [Test] public void SimpleUri() { Assert.AreEqual("http://abc/D./e.?Qq#Ff", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.?Qq#Ff").ToString()); Assert.AreEqual("http://abc/D./e.?Qq", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.?Qq").ToString()); Assert.AreEqual("http://abc/D./e.#Ff", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.#Ff").ToString()); Assert.AreEqual("http://abc/", new UriIdentifier.SimpleUri("HTTP://ABC/").ToString()); Assert.AreEqual("http://abc/", new UriIdentifier.SimpleUri("HTTP://ABC").ToString()); Assert.AreEqual("http://abc/?q", new UriIdentifier.SimpleUri("HTTP://ABC?q").ToString()); Assert.AreEqual("http://abc/#f", new UriIdentifier.SimpleUri("HTTP://ABC#f").ToString()); Assert.AreEqual("http://abc/a//b", new UriIdentifier.SimpleUri("http://abc/a//b").ToString()); Assert.AreEqual("http://abc/a%2Fb/c", new UriIdentifier.SimpleUri("http://abc/a%2fb/c").ToString()); Assert.AreEqual("http://abc/A/c", new UriIdentifier.SimpleUri("http://abc/%41/c").ToString()); } private static void TestAsFullAndPartialTrust(Action action) { // Test a bunch of interesting URLs both with scheme substitution on and off. Assert.IsTrue(UriIdentifier.SchemeSubstitutionTestHook, "Expected scheme substitution to be working."); action(true); UriIdentifier.SchemeSubstitutionTestHook = false; try { action(false); } finally { UriIdentifier.SchemeSubstitutionTestHook = true; } } } }