diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs | 55 | ||||
-rw-r--r-- | src/DotNetOpenAuth.Test/OpenId/RealmTests.cs | 41 | ||||
-rw-r--r-- | src/DotNetOpenAuth/GlobalSuppressions.cs | 4 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs | 22 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuth/ServiceProvider.cs | 4 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs | 2 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OpenId/Identifier.cs | 90 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs | 87 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OpenId/Realm.cs | 367 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OpenId/UriIdentifier.cs | 308 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OpenId/XriIdentifier.cs | 234 | ||||
-rw-r--r-- | src/DotNetOpenAuth/UriUtil.cs | 7 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Util.cs | 22 |
13 files changed, 843 insertions, 400 deletions
diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs index 0f970f9..dd7b3d9 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs @@ -9,42 +9,41 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { using System.Collections.Generic; using System.Linq; using System.Text; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Messages; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] public class CheckIdRequestTests : OpenIdTestBase { - private Uri ProviderEndpoint; + private Uri providerEndpoint; private CheckIdRequest immediatev1; private CheckIdRequest setupv1; private CheckIdRequest immediatev2; private CheckIdRequest setupv2; - [TestInitialize] public override void SetUp() { base.SetUp(); - ProviderEndpoint = new Uri("http://host"); + this.providerEndpoint = new Uri("http://host"); - immediatev1 = new CheckIdRequest(Protocol.V11.Version, ProviderEndpoint, true); - setupv1 = new CheckIdRequest(Protocol.V11.Version, ProviderEndpoint, false); + this.immediatev1 = new CheckIdRequest(Protocol.V11.Version, this.providerEndpoint, true); + this.setupv1 = new CheckIdRequest(Protocol.V11.Version, this.providerEndpoint, false); - immediatev2 = new CheckIdRequest(Protocol.V20.Version, ProviderEndpoint, true); - setupv2 = new CheckIdRequest(Protocol.V20.Version, ProviderEndpoint, false); + this.immediatev2 = new CheckIdRequest(Protocol.V20.Version, this.providerEndpoint, true); + this.setupv2 = new CheckIdRequest(Protocol.V20.Version, this.providerEndpoint, false); // Prepare all message versions so that they SHOULD be valid by default. // In particular, V1 messages require ReturnTo. - immediatev1.ReturnTo = new Uri("http://returnto/"); - setupv1.ReturnTo = new Uri("http://returnto/"); + this.immediatev1.ReturnTo = new Uri("http://returnto/"); + this.setupv1.ReturnTo = new Uri("http://returnto/"); try { - immediatev1.EnsureValidMessage(); - setupv1.EnsureValidMessage(); - immediatev2.EnsureValidMessage(); - setupv2.EnsureValidMessage(); + this.immediatev1.EnsureValidMessage(); + this.setupv1.EnsureValidMessage(); + this.immediatev2.EnsureValidMessage(); + this.setupv2.EnsureValidMessage(); } catch (ProtocolException ex) { Assert.Inconclusive("All messages ought to be valid before tests run, but got: {0}", ex.Message); } @@ -56,8 +55,8 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { /// </summary> [TestMethod, ExpectedException(typeof(ProtocolException))] public void ClaimedIdentifierWithoutIdentity() { - setupv2.ClaimedIdentifier = "http://andrew.arnott.myopenid.com/"; - setupv2.EnsureValidMessage(); + this.setupv2.ClaimedIdentifier = "http://andrew.arnott.myopenid.com/"; + this.setupv2.EnsureValidMessage(); } /// <summary> @@ -66,8 +65,8 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { /// </summary> [TestMethod, ExpectedException(typeof(ProtocolException))] public void LocalIdentifierWithoutClaimedIdentifier() { - setupv2.LocalIdentifier = "http://andrew.arnott.myopenid.com/"; - setupv2.EnsureValidMessage(); + this.setupv2.LocalIdentifier = "http://andrew.arnott.myopenid.com/"; + this.setupv2.EnsureValidMessage(); } /// <summary> @@ -76,8 +75,8 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { /// </summary> [TestMethod] public void LocalIdentifierWithoutClaimedIdentifierV1() { - setupv1.LocalIdentifier = "http://andrew.arnott.myopenid.com/"; - setupv1.EnsureValidMessage(); + this.setupv1.LocalIdentifier = "http://andrew.arnott.myopenid.com/"; + this.setupv1.EnsureValidMessage(); } /// <summary> @@ -86,9 +85,9 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { /// </summary> [TestMethod, ExpectedException(typeof(ProtocolException))] public void RealmReturnToMismatchV2() { - setupv2.Realm = "http://somehost/"; - setupv2.ReturnTo = new Uri("http://someotherhost/"); - setupv2.EnsureValidMessage(); + this.setupv2.Realm = "http://somehost/"; + this.setupv2.ReturnTo = new Uri("http://someotherhost/"); + this.setupv2.EnsureValidMessage(); } /// <summary> @@ -97,9 +96,9 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { /// </summary> [TestMethod, ExpectedException(typeof(ProtocolException))] public void RealmReturnToMismatchV1() { - setupv1.Realm = "http://somehost/"; - setupv1.ReturnTo = new Uri("http://someotherhost/"); - setupv1.EnsureValidMessage(); + this.setupv1.Realm = "http://somehost/"; + this.setupv1.ReturnTo = new Uri("http://someotherhost/"); + this.setupv1.EnsureValidMessage(); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs b/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs index 5d59ded..e062e12 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs @@ -4,18 +4,15 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.Test -{ +namespace DotNetOpenAuth.Test { using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; using DotNetOpenAuth.OpenId; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] - public class RealmTests - { + public class RealmTests { [TestMethod] - public void ValidRealmsTest() - { + public void ValidRealmsTest() { // Just create these. If any are determined to be invalid, // an exception should be thrown that would fail this test. new Realm("http://www.myopenid.com"); @@ -31,8 +28,7 @@ namespace DotNetOpenAuth.Test [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void InvalidRealmNullString() - { + public void InvalidRealmNullString() { new Realm((string)null); } @@ -44,29 +40,25 @@ namespace DotNetOpenAuth.Test [TestMethod] [ExpectedException(typeof(UriFormatException))] - public void InvalidRealmEmpty() - { + public void InvalidRealmEmpty() { new Realm(string.Empty); } [TestMethod] [ExpectedException(typeof(UriFormatException))] - public void InvalidRealmBadProtocol() - { + public void InvalidRealmBadProtocol() { new Realm("asdf://www.microsoft.com/"); } [TestMethod] [ExpectedException(typeof(UriFormatException))] - public void InvalidRealmNoScheme() - { + public void InvalidRealmNoScheme() { new Realm("www.guy.com"); } [TestMethod] [ExpectedException(typeof(UriFormatException))] - public void InvalidRealmBadWildcard1() - { + public void InvalidRealmBadWildcard1() { new Realm("http://*www.my.com"); } @@ -84,21 +76,18 @@ namespace DotNetOpenAuth.Test [TestMethod] [ExpectedException(typeof(UriFormatException))] - public void InvalidRealmTwoWildcards1() - { + public void InvalidRealmTwoWildcards1() { new Realm("http://**.my.com"); } [TestMethod] [ExpectedException(typeof(UriFormatException))] - public void InvalidRealmTwoWildcards2() - { + public void InvalidRealmTwoWildcards2() { new Realm("http://*.*.my.com"); } [TestMethod] - public void IsSaneTest() - { + public void IsSaneTest() { Assert.IsTrue(new Realm("http://www.myopenid.com").IsSane); Assert.IsTrue(new Realm("http://myopenid.com").IsSane); Assert.IsTrue(new Realm("http://localhost").IsSane); @@ -110,8 +99,7 @@ namespace DotNetOpenAuth.Test } [TestMethod] - public void IsUrlWithinRealmTests() - { + public void IsUrlWithinRealmTests() { /* * The openid.return_to URL MUST descend from the openid.trust_root, or the * Identity Provider SHOULD return an error. Namely, the URL scheme and port @@ -207,8 +195,7 @@ namespace DotNetOpenAuth.Test } [TestMethod] - public void EqualsTest() - { + public void EqualsTest() { Realm testRealm1a = new Realm("http://www.yahoo.com"); Realm testRealm1b = new Realm("http://www.yahoo.com"); Realm testRealm2 = new Realm("http://www.yahoo.com/b"); diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs index aed15eb..4113c73 100644 --- a/src/DotNetOpenAuth/GlobalSuppressions.cs +++ b/src/DotNetOpenAuth/GlobalSuppressions.cs @@ -25,3 +25,7 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes", Scope = "member", Target = "Mono.Math.BigInteger.#op_Multiply(Mono.Math.BigInteger,Mono.Math.BigInteger)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "sign", Scope = "member", Target = "Mono.Math.BigInteger.#.ctor(Mono.Math.BigInteger+Sign,System.UInt32)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "notUsed", Scope = "member", Target = "Mono.Math.BigInteger.#isProbablePrime(System.Int32)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "returnto", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "openid", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "claimedid", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xri", Scope = "type", Target = "DotNetOpenAuth.OpenId.XriIdentifier")] diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs index ae52679..94812d8 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs @@ -197,6 +197,28 @@ namespace DotNetOpenAuth.Messaging.Reflection { internal Version Version { get { return this.version; } } /// <summary> + /// Implements the operator ==. + /// </summary> + /// <param name="first">The first object to compare.</param> + /// <param name="second">The second object to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator ==(MessageTypeAndVersion first, MessageTypeAndVersion second) { + // structs cannot be null, so this is safe + return first.Equals(second); + } + + /// <summary> + /// Implements the operator !=. + /// </summary> + /// <param name="first">The first object to compare.</param> + /// <param name="second">The second object to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator !=(MessageTypeAndVersion first, MessageTypeAndVersion second) { + // structs cannot be null, so this is safe + return !first.Equals(second); + } + + /// <summary> /// Indicates whether this instance and a specified object are equal. /// </summary> /// <param name="obj">Another object to compare to.</param> diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index 824aa20..2ede84b 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -228,9 +228,7 @@ namespace DotNetOpenAuth.OAuth { /// Null if the Consumer did not request a callback. /// </returns> public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) { - if (request == null) { - throw new ArgumentNullException("request"); - } + ErrorUtilities.VerifyArgumentNotNull(request, "request"); if (request.Callback != null) { var authorization = new UserAuthorizationResponse(request.Callback) { diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs index 6d564b5..b14ac04 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs @@ -99,7 +99,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { message = new AssociateUnencryptedResponse(associateUnencryptedRequest); } - // TODO: recognize more message types here + //// TODO: recognize more message types here if (message != null) { message.SetAsIncoming(); diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs index 2eff359..ae40d4e 100644 --- a/src/DotNetOpenAuth/OpenId/Identifier.cs +++ b/src/DotNetOpenAuth/OpenId/Identifier.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> @@ -17,63 +18,82 @@ namespace DotNetOpenAuth.OpenId { [Serializable] public abstract class Identifier { /// <summary> - /// Constructs an <see cref="Identifier"/>. + /// Initializes a new instance of the <see cref="Identifier"/> class. /// </summary> /// <param name="isDiscoverySecureEndToEnd"> /// Whether the derived class is prepared to guarantee end-to-end discovery /// and initial redirect for authentication is performed using SSL. /// </param> protected Identifier(bool isDiscoverySecureEndToEnd) { - IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd; + this.IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd; } /// <summary> - /// Whether this Identifier will ensure SSL is used throughout the discovery phase - /// and initial redirect of authentication. + /// Gets a value indicating whether this Identifier will ensure SSL is + /// used throughout the discovery phase and initial redirect of authentication. /// </summary> /// <remarks> - /// If this is False, a value of True may be obtained by calling <see cref="TryRequireSsl"/>. + /// If this is <c>false</c>, a value of <c>true</c> may be obtained by calling + /// <see cref="TryRequireSsl"/>. /// </remarks> protected internal bool IsDiscoverySecureEndToEnd { get; private set; } /// <summary> /// Converts the string representation of an Identifier to its strong type. /// </summary> - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates"), SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads")] + /// <param name="identifier">The identifier.</param> + /// <returns>The particular Identifier instance to represent the value given.</returns> + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Our named alternate is Parse.")] + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "TODO")] public static implicit operator Identifier(string identifier) { - if (identifier == null) return null; + if (identifier == null) { + return null; + } return Parse(identifier); } + /// <summary> - /// Returns a strongly-typed Identifier for a given Uri. + /// Converts a given Uri to a strongly-typed Identifier. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + /// <param name="identifier">The identifier to convert.</param> + /// <returns>The result of the conversion.</returns> + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "TODO")] public static implicit operator Identifier(Uri identifier) { - if (identifier == null) return null; + if (identifier == null) { + return null; + } return new UriIdentifier(identifier); } + /// <summary> /// Converts an Identifier to its string representation. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")] + /// <param name="identifier">The identifier to convert to a string.</param> + /// <returns>The result of the conversion.</returns> + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "TODO")] public static implicit operator string(Identifier identifier) { - if (identifier == null) return null; + if (identifier == null) { + return null; + } return identifier.ToString(); } + /// <summary> /// Parses an identifier string and automatically determines /// whether it is an XRI or URI. /// </summary> /// <param name="identifier">Either a URI or XRI identifier.</param> - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")] + /// <returns>An <see cref="Identifier"/> instance for the given value.</returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "TODO")] public static Identifier Parse(string identifier) { - if (string.IsNullOrEmpty(identifier)) throw new ArgumentNullException("identifier"); + ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); if (XriIdentifier.IsValidXri(identifier)) { return new XriIdentifier(identifier); } else { return new UriIdentifier(identifier); } } + /// <summary> /// Attempts to parse a string for an OpenId Identifier. /// </summary> @@ -91,10 +111,15 @@ namespace DotNetOpenAuth.OpenId { return false; } } + /// <summary> - /// Gets whether a given string represents a valid Identifier format. + /// Checks the validity of a given string representation of some Identifier. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")] + /// <param name="identifier">The identifier.</param> + /// <returns> + /// <c>true</c> if the specified identifier is valid; otherwise, <c>false</c>. + /// </returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "TODO")] public static bool IsValid(string identifier) { return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier); } @@ -112,27 +137,48 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Tests equality between two <see cref="Identifier"/>s. /// </summary> + /// <param name="id1">The first Identifier.</param> + /// <param name="id2">The second Identifier.</param> + /// <returns> + /// <c>true</c> if the two instances should be considered equal; <c>false</c> otherwise. + /// </returns> public static bool operator ==(Identifier id1, Identifier id2) { - if ((object)id1 == null ^ (object)id2 == null) return false; - if ((object)id1 == null) return true; - return id1.Equals(id2); + return id1.EqualsNullSafe(id2); } + /// <summary> /// Tests inequality between two <see cref="Identifier"/>s. /// </summary> + /// <param name="id1">The first Identifier.</param> + /// <param name="id2">The second Identifier.</param> + /// <returns> + /// <c>true</c> if the two instances should be considered unequal; <c>false</c> if they are equal. + /// </returns> public static bool operator !=(Identifier id1, Identifier id2) { - return !(id1 == id2); + return !id1.EqualsNullSafe(id2); } + /// <summary> /// Tests equality between two <see cref="Identifier"/>s. /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> public override bool Equals(object obj) { Debug.Fail("This should be overridden in every derived class."); return base.Equals(obj); } + /// <summary> /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable. /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> public override int GetHashCode() { Debug.Fail("This should be overridden in every derived class."); return base.GetHashCode(); @@ -140,9 +186,11 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Returns an <see cref="Identifier"/> that has no URI fragment. - /// Quietly returns the original <see cref="Identifier"/> if it is not + /// Quietly returns the original <see cref="Identifier"/> if it is not /// a <see cref="UriIdentifier"/> or no fragment exists. /// </summary> + /// <returns>A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance..</returns> internal abstract Identifier TrimFragment(); /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs index 9a6d0ac..a6f1384 100644 --- a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs @@ -1,16 +1,31 @@ -namespace DotNetOpenAuth.OpenId { +//----------------------------------------------------------------------- +// <copyright file="NoDiscoveryIdentifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// Wraps an existing Identifier and prevents it from performing discovery. /// </summary> - class NoDiscoveryIdentifier : Identifier { - Identifier wrappedIdentifier; + internal class NoDiscoveryIdentifier : Identifier { + /// <summary> + /// The wrapped identifier. + /// </summary> + private Identifier wrappedIdentifier; + + /// <summary> + /// Initializes a new instance of the <see cref="NoDiscoveryIdentifier"/> class. + /// </summary> + /// <param name="wrappedIdentifier">The ordinary Identifier whose discovery is being masked.</param> internal NoDiscoveryIdentifier(Identifier wrappedIdentifier) : base(false) { - if (wrappedIdentifier == null) throw new ArgumentNullException("wrappedIdentifier"); + ErrorUtilities.VerifyArgumentNotNull(wrappedIdentifier, "wrappedIdentifier"); this.wrappedIdentifier = wrappedIdentifier; } @@ -20,25 +35,67 @@ return new ServiceEndpoint[0]; } #endif - - internal override Identifier TrimFragment() { - return new NoDiscoveryIdentifier(wrappedIdentifier.TrimFragment()); + /// <summary> + /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + return this.wrappedIdentifier.ToString(); } - internal override bool TryRequireSsl(out Identifier secureIdentifier) { - return wrappedIdentifier.TryRequireSsl(out secureIdentifier); + /// <summary> + /// Tests equality between two <see cref="Identifier"/>s. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + return this.wrappedIdentifier.Equals(obj); } - public override string ToString() { - return wrappedIdentifier.ToString(); + /// <summary> + /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.wrappedIdentifier.GetHashCode(); } - public override bool Equals(object obj) { - return wrappedIdentifier.Equals(obj); + /// <summary> + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. + /// </summary> + /// <returns> + /// A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance.. + /// </returns> + internal override Identifier TrimFragment() { + return new NoDiscoveryIdentifier(this.wrappedIdentifier.TrimFragment()); } - public override int GetHashCode() { - return wrappedIdentifier.GetHashCode(); + /// <summary> + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. + /// </summary> + /// <param name="secureIdentifier">The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> + internal override bool TryRequireSsl(out Identifier secureIdentifier) { + return this.wrappedIdentifier.TryRequireSsl(out secureIdentifier); } } } diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs index ff38291..4b0266e 100644 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ b/src/DotNetOpenAuth/OpenId/Realm.cs @@ -12,6 +12,7 @@ namespace DotNetOpenAuth.OpenId { using System.Globalization; using System.Text.RegularExpressions; using System.Xml; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Provider; /// <summary> @@ -23,113 +24,113 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> public class Realm { /// <summary> - /// Implicitly converts the string-form of a URI to a <see cref="Realm"/> object. + /// A regex used to detect a wildcard that is being used in the realm. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")] - [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads")] - public static implicit operator Realm(string uri) { - return uri != null ? new Realm(uri) : null; - } + private const string WildcardDetectionPattern = @"^(\w+://)\*\."; /// <summary> - /// Implicitly converts a <see cref="Uri"/> to a <see cref="Realm"/> object. + /// A (more or less) comprehensive list of top-level (i.e. ".com") domains, + /// for use by <see cref="IsSane"/> in order to disallow overly-broad realms + /// that allow all web sites ending with '.com', for example. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")] - public static implicit operator Realm(Uri uri) { - return uri != null ? new Realm(uri.AbsoluteUri) : null; - } + private static readonly string[] topLevelDomains = { "com", "edu", "gov", "int", "mil", "net", "org", "biz", "info", "name", "museum", "coop", "aero", "ac", "ad", "ae", + "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", + "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", + "cu", "cv", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "fi", "fj", "fk", "fm", "fo", + "fr", "ga", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", + "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", + "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "mg", "mh", "mk", "ml", "mm", + "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", + "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru", "rw", "sa", + "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", + "tj", "tk", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", + "vn", "vu", "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" }; /// <summary> - /// Implicitly converts a <see cref="Realm"/> object to its <see cref="String"/> form. + /// The Uri of the realm, with the wildcard (if any) removed. /// </summary> - public static implicit operator string(Realm realm) { - return realm != null ? realm.ToString() : null; - } + private Uri uri; /// <summary> - /// Instantiates a <see cref="Realm"/> from its string representation. + /// Initializes a new instance of the <see cref="Realm"/> class. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads"), SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] + /// <param name="realmUrl">The realm URL to use in the new instance.</param> + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "TODO")] public Realm(string realmUrl) { - if (realmUrl == null) throw new ArgumentNullException("realmUrl"); - DomainWildcard = Regex.IsMatch(realmUrl, wildcardDetectionPattern); - uri = new Uri(Regex.Replace(realmUrl, wildcardDetectionPattern, m => m.Groups[1].Value)); - if (!uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && - !uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) - throw new UriFormatException(string.Format(CultureInfo.CurrentCulture, - OpenIdStrings.InvalidScheme, uri.Scheme)); + ErrorUtilities.VerifyArgumentNotNull(realmUrl, "realmUrl"); + this.DomainWildcard = Regex.IsMatch(realmUrl, WildcardDetectionPattern); + this.uri = new Uri(Regex.Replace(realmUrl, WildcardDetectionPattern, m => m.Groups[1].Value)); + if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && + !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { + throw new UriFormatException( + string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme)); + } } /// <summary> - /// Instantiates a <see cref="Realm"/> from its <see cref="Uri"/> representation. + /// Initializes a new instance of the <see cref="Realm"/> class. /// </summary> + /// <param name="realmUrl">The realm URL of the Relying Party.</param> public Realm(Uri realmUrl) { - if (realmUrl == null) throw new ArgumentNullException("realmUrl"); - uri = realmUrl; - if (!uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && - !uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) - throw new UriFormatException(string.Format(CultureInfo.CurrentCulture, - OpenIdStrings.InvalidScheme, uri.Scheme)); + ErrorUtilities.VerifyArgumentNotNull(realmUrl, "realmUrl"); + this.uri = realmUrl; + if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && + !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { + throw new UriFormatException( + string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme)); + } } /// <summary> - /// Instantiates a <see cref="Realm"/> from its <see cref="UriBuilder"/> representation. + /// Initializes a new instance of the <see cref="Realm"/> class. /// </summary> + /// <param name="realmUriBuilder">The realm URI builder.</param> /// <remarks> - /// This is useful because UriBuilder can construct a host with a wildcard + /// This is useful because UriBuilder can construct a host with a wildcard /// in the Host property, but once there it can't be converted to a Uri. /// </remarks> internal Realm(UriBuilder realmUriBuilder) - : this(safeUriBuilderToString(realmUriBuilder)) { } - static string safeUriBuilderToString(UriBuilder realmUriBuilder) { - if (realmUriBuilder == null) throw new ArgumentNullException("realmUriBuilder"); - // Note: we MUST use ToString. Uri property throws if wildcard is present. - return realmUriBuilder.ToString(); - } - - Uri uri; - const string wildcardDetectionPattern = @"^(\w+://)\*\."; + : this(SafeUriBuilderToString(realmUriBuilder)) { } /// <summary> - /// Whether a '*.' prefix to the hostname is used in the realm to allow - /// subdomains or hosts to be added to the URL. + /// Gets a value indicating whether a '*.' prefix to the hostname is + /// used in the realm to allow subdomains or hosts to be added to the URL. /// </summary> public bool DomainWildcard { get; private set; } - + /// <summary> /// Gets the host component of this instance. /// </summary> - public string Host { get { return uri.Host; } } - + public string Host { get { return this.uri.Host; } } + /// <summary> /// Gets the scheme name for this URI. /// </summary> - public string Scheme { get { return uri.Scheme; } } - + public string Scheme { get { return this.uri.Scheme; } } + /// <summary> /// Gets the port number of this URI. /// </summary> - public int Port { get { return uri.Port; } } - + public int Port { get { return this.uri.Port; } } + /// <summary> /// Gets the absolute path of the URI. /// </summary> - public string AbsolutePath { get { return uri.AbsolutePath; } } - + public string AbsolutePath { get { return this.uri.AbsolutePath; } } + /// <summary> /// Gets the System.Uri.AbsolutePath and System.Uri.Query properties separated /// by a question mark (?). /// </summary> - public string PathAndQuery { get { return uri.PathAndQuery; } } - + public string PathAndQuery { get { return this.uri.PathAndQuery; } } + /// <summary> /// Gets the realm URL. If the realm includes a wildcard, it is not included here. /// </summary> - internal Uri NoWildcardUri { get { return uri; } } - + internal Uri NoWildcardUri { get { return this.uri; } } + /// <summary> - /// Produces the Realm URL. If the realm URL had a wildcard in it, - /// the wildcard is replaced with a "www." prefix. + /// Gets the Realm discovery URL, where the wildcard (if present) is replaced with "www.". /// </summary> /// <remarks> /// See OpenID 2.0 spec section 9.2.1 for the explanation on the addition of @@ -137,31 +138,18 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> internal Uri UriWithWildcardChangedToWww { get { - if (DomainWildcard) { - UriBuilder builder = new UriBuilder(NoWildcardUri); + if (this.DomainWildcard) { + UriBuilder builder = new UriBuilder(this.NoWildcardUri); builder.Host = "www." + builder.Host; return builder.Uri; } else { - return NoWildcardUri; + return this.NoWildcardUri; } } } - static string[] topLevelDomains = { "com", "edu", "gov", "int", "mil", "net", "org", "biz", "info", "name", "museum", "coop", "aero", "ac", "ad", "ae", - "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", - "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", - "cu", "cv", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "fi", "fj", "fk", "fm", "fo", - "fr", "ga", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", - "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", - "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "mg", "mh", "mk", "ml", "mm", - "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", - "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru", "rw", "sa", - "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", - "tj", "tk", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", - "vn", "vu", "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" }; - /// <summary> - /// This method checks the to see if a trust root represents a reasonable (sane) set of URLs. + /// Gets a value indicating whether this realm represents a reasonable (sane) set of URLs. /// </summary> /// <remarks> /// 'http://*.com/', for example is not a reasonable pattern, as it cannot meaningfully @@ -171,22 +159,26 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> internal bool IsSane { get { - if (Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) + if (this.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { return true; + } - string[] host_parts = Host.Split('.'); + string[] host_parts = this.Host.Split('.'); string tld = host_parts[host_parts.Length - 1]; - if (Array.IndexOf(topLevelDomains, tld) < 0) + if (Array.IndexOf(topLevelDomains, tld) < 0) { return false; + } if (tld.Length == 2) { - if (host_parts.Length == 1) + if (host_parts.Length == 1) { return false; + } - if (host_parts[host_parts.Length - 2].Length <= 3) + if (host_parts[host_parts.Length - 2].Length <= 3) { return host_parts.Length > 2; + } } else { return host_parts.Length > 1; } @@ -196,12 +188,86 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Implicitly converts the string-form of a URI to a <see cref="Realm"/> object. + /// </summary> + /// <param name="uri">The URI that the new Realm instance will represent.</param> + /// <returns>The result of the conversion.</returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "TODO")] + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "TODO")] + public static implicit operator Realm(string uri) { + return uri != null ? new Realm(uri) : null; + } + + /// <summary> + /// Implicitly converts a <see cref="Uri"/> to a <see cref="Realm"/> object. + /// </summary> + /// <param name="uri">The URI to convert to a realm.</param> + /// <returns>The result of the conversion.</returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "TODO")] + public static implicit operator Realm(Uri uri) { + return uri != null ? new Realm(uri.AbsoluteUri) : null; + } + + /// <summary> + /// Implicitly converts a <see cref="Realm"/> object to its <see cref="String"/> form. + /// </summary> + /// <param name="realm">The realm to convert to a string value.</param> + /// <returns>The result of the conversion.</returns> + public static implicit operator string(Realm realm) { + return realm != null ? realm.ToString() : null; + } + + /// <summary> + /// Checks whether one <see cref="Realm"/> is equal to another. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + Realm other = obj as Realm; + if (other == null) { + return false; + } + return this.uri.Equals(other.uri) && this.DomainWildcard == other.DomainWildcard; + } + + /// <summary> + /// Returns the hash code used for storing this object in a hash table. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.uri.GetHashCode() + (this.DomainWildcard ? 1 : 0); + } + + /// <summary> + /// Returns the string form of this <see cref="Realm"/>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + if (this.DomainWildcard) { + UriBuilder builder = new UriBuilder(this.uri); + builder.Host = "*." + builder.Host; + return builder.ToStringWithImpliedPorts(); + } else { + return this.uri.AbsoluteUri; + } + } + + /// <summary> /// Validates a URL against this trust root. /// </summary> /// <param name="url">A string specifying URL to check.</param> /// <returns>Whether the given URL is within this trust root.</returns> internal bool Contains(string url) { - return Contains(new Uri(url)); + return this.Contains(new Uri(url)); } /// <summary> @@ -210,26 +276,29 @@ namespace DotNetOpenAuth.OpenId { /// <param name="url">The URL to check.</param> /// <returns>Whether the given URL is within this trust root.</returns> internal bool Contains(Uri url) { - if (url.Scheme != Scheme) + if (url.Scheme != this.Scheme) { return false; + } - if (url.Port != Port) + if (url.Port != this.Port) { return false; + } - if (!DomainWildcard) { - if (url.Host != Host) { + if (!this.DomainWildcard) { + if (url.Host != this.Host) { return false; } } else { - Debug.Assert(!string.IsNullOrEmpty(Host), "The host part of the Regex should evaluate to at least one char for successful parsed trust roots."); - string[] host_parts = Host.Split('.'); + Debug.Assert(!string.IsNullOrEmpty(this.Host), "The host part of the Regex should evaluate to at least one char for successful parsed trust roots."); + string[] host_parts = this.Host.Split('.'); string[] url_parts = url.Host.Split('.'); // If the domain containing the wildcard has more parts than the URL to match against, // it naturally can't be valid. // Unless *.example.com actually matches example.com too. - if (host_parts.Length > url_parts.Length) + if (host_parts.Length > url_parts.Length) { return false; + } // Compare last part first and move forward. // Maybe could be done by using EndsWith, but piecewies helps ensure that @@ -245,95 +314,89 @@ namespace DotNetOpenAuth.OpenId { // If path matches or is specified to root ... // (deliberately case sensitive to protect security on case sensitive systems) - if (PathAndQuery.Equals(url.PathAndQuery, StringComparison.Ordinal) - || PathAndQuery.Equals("/", StringComparison.Ordinal)) + if (this.PathAndQuery.Equals(url.PathAndQuery, StringComparison.Ordinal) + || this.PathAndQuery.Equals("/", StringComparison.Ordinal)) { return true; + } // If trust root has a longer path, the return URL must be invalid. - if (PathAndQuery.Length > url.PathAndQuery.Length) + if (this.PathAndQuery.Length > url.PathAndQuery.Length) { return false; + } // The following code assures that http://example.com/directory isn't below http://example.com/dir, // but makes sure http://example.com/dir/ectory is below http://example.com/dir - int path_len = PathAndQuery.Length; + int path_len = this.PathAndQuery.Length; string url_prefix = url.PathAndQuery.Substring(0, path_len); - if (PathAndQuery != url_prefix) + if (this.PathAndQuery != url_prefix) { return false; + } // If trust root includes a query string ... - if (PathAndQuery.Contains("?")) { + if (this.PathAndQuery.Contains("?")) { // ... make sure return URL begins with a new argument return url.PathAndQuery[path_len] == '&'; } // Or make sure a query string is introduced or a path below trust root - return PathAndQuery.EndsWith("/", StringComparison.Ordinal) + return this.PathAndQuery.EndsWith("/", StringComparison.Ordinal) || url.PathAndQuery[path_len] == '?' || url.PathAndQuery[path_len] == '/'; } #if DISCOVERY // TODO: Add discovery and then re-enable this code block - /// <summary> - /// Searches for an XRDS document at the realm URL, and if found, searches - /// for a description of a relying party endpoints (OpenId login pages). - /// </summary> - /// <param name="allowRedirects"> - /// Whether redirects may be followed when discovering the Realm. - /// This may be true when creating an unsolicited assertion, but must be - /// false when performing return URL verification per 2.0 spec section 9.2.1. - /// </param> - /// <returns>The details of the endpoints if found, otherwise null.</returns> - internal IEnumerable<DotNetOpenId.Provider.RelyingPartyReceivingEndpoint> Discover(bool allowRedirects) { - // Attempt YADIS discovery - DiscoveryResult yadisResult = Yadis.Yadis.Discover(UriWithWildcardChangedToWww, false); - if (yadisResult != null) { - if (!allowRedirects && yadisResult.NormalizedUri != yadisResult.RequestUri) { - // Redirect occurred when it was not allowed. - throw new OpenIdException(string.Format(CultureInfo.CurrentCulture, - Strings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri)); - } - if (yadisResult.IsXrds) { - try { - XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); - return xrds.FindRelyingPartyReceivingEndpoints(); - } catch (XmlException ex) { - throw new OpenIdException(Strings.InvalidXRDSDocument, ex); - } - } - } - return new RelyingPartyReceivingEndpoint[0]; - } + /////// <summary> + /////// Searches for an XRDS document at the realm URL, and if found, searches + /////// for a description of a relying party endpoints (OpenId login pages). + /////// </summary> + /////// <param name="allowRedirects"> + /////// Whether redirects may be followed when discovering the Realm. + /////// This may be true when creating an unsolicited assertion, but must be + /////// false when performing return URL verification per 2.0 spec section 9.2.1. + /////// </param> + /////// <returns>The details of the endpoints if found, otherwise null.</returns> + ////internal IEnumerable<DotNetOpenId.Provider.RelyingPartyReceivingEndpoint> Discover(bool allowRedirects) { + //// // Attempt YADIS discovery + //// DiscoveryResult yadisResult = Yadis.Yadis.Discover(UriWithWildcardChangedToWww, false); + //// if (yadisResult != null) { + //// if (!allowRedirects && yadisResult.NormalizedUri != yadisResult.RequestUri) { + //// // Redirect occurred when it was not allowed. + //// throw new OpenIdException(string.Format(CultureInfo.CurrentCulture, + //// Strings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri)); + //// } + //// if (yadisResult.IsXrds) { + //// try { + //// XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); + //// return xrds.FindRelyingPartyReceivingEndpoints(); + //// } catch (XmlException ex) { + //// throw new OpenIdException(Strings.InvalidXRDSDocument, ex); + //// } + //// } + //// } + //// return new RelyingPartyReceivingEndpoint[0]; + ////} #endif /// <summary> - /// Checks whether one <see cref="Realm"/> is equal to another. + /// Calls <see cref="UriBuilder.ToString"/> if the argument is non-null. + /// Otherwise throws <see cref="ArgumentNullException"/>. /// </summary> - public override bool Equals(object obj) { - Realm other = obj as Realm; - if (other == null) return false; - return uri.Equals(other.uri) && DomainWildcard == other.DomainWildcard; - } - - /// <summary> - /// Returns the hash code used for storing this object in a hash table. - /// </summary> - /// <returns></returns> - public override int GetHashCode() { - return uri.GetHashCode() + (DomainWildcard ? 1 : 0); - } - - /// <summary> - /// Returns the string form of this <see cref="Realm"/>. - /// </summary> - public override string ToString() { - if (DomainWildcard) { - UriBuilder builder = new UriBuilder(uri); - builder.Host = "*." + builder.Host; - return builder.ToStringWithImpliedPorts(); - } else { - return uri.AbsoluteUri; - } + /// <param name="realmUriBuilder">The realm URI builder.</param> + /// <returns>The result of UriBuilder.ToString()</returns> + /// <remarks> + /// This simple method is worthwhile because it checks for null + /// before dereferencing the UriBuilder. Since this is called from + /// within a constructor's base(...) call, this avoids a <see cref="NullReferenceException"/> + /// when we should be throwing an <see cref="ArgumentNullException"/>. + /// </remarks> + private static string SafeUriBuilderToString(UriBuilder realmUriBuilder) { + ErrorUtilities.VerifyArgumentNotNull(realmUriBuilder, "realmUriBuilder"); + + // Note: we MUST use ToString. Uri property throws if wildcard is present. + // TODO: I now know that Uri.ToString and Uri.AbsoluteUri are very different + // for some strings. Do we have to worry about that here? + return realmUriBuilder.ToString(); } } }
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index d4cba3a..865b895 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -1,8 +1,16 @@ -namespace DotNetOpenAuth.OpenId { +//----------------------------------------------------------------------- +// <copyright file="UriIdentifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using System.Web.UI.HtmlControls; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> @@ -10,84 +18,133 @@ /// </summary> [Serializable] public sealed class UriIdentifier : Identifier { - static readonly string[] allowedSchemes = { "http", "https" }; /// <summary> - /// Converts a <see cref="UriIdentifier"/> instance to a <see cref="Uri"/> instance. + /// The allowed protocol schemes in a URI Identifier. /// </summary> - public static implicit operator Uri(UriIdentifier identifier) { - if (identifier == null) return null; - return identifier.Uri; - } + private static readonly string[] allowedSchemes = { "http", "https" }; + /// <summary> - /// Converts a <see cref="Uri"/> instance to a <see cref="UriIdentifier"/> instance. + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. /// </summary> - public static implicit operator UriIdentifier(Uri identifier) { - if (identifier == null) return null; - return new UriIdentifier(identifier); - } - + /// <param name="uri">The value this identifier will represent.</param> internal UriIdentifier(string uri) : this(uri, false) { } + + /// <summary> + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <param name="uri">The value this identifier will represent.</param> + /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> internal UriIdentifier(string uri, bool requireSslDiscovery) : base(requireSslDiscovery) { - if (string.IsNullOrEmpty(uri)) throw new ArgumentNullException("uri"); + ErrorUtilities.VerifyNonZeroLength(uri, "uri"); Uri canonicalUri; bool schemePrepended; - if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended)) + if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended)) { throw new UriFormatException(); + } if (requireSslDiscovery && canonicalUri.Scheme != Uri.UriSchemeHttps) { throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement); } - Uri = canonicalUri; - SchemeImplicitlyPrepended = schemePrepended; + this.Uri = canonicalUri; + this.SchemeImplicitlyPrepended = schemePrepended; } + + /// <summary> + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <param name="uri">The value this identifier will represent.</param> internal UriIdentifier(Uri uri) : this(uri, false) { } + + /// <summary> + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <param name="uri">The value this identifier will represent.</param> + /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> internal UriIdentifier(Uri uri, bool requireSslDiscovery) : base(requireSslDiscovery) { - if (uri == null) throw new ArgumentNullException("uri"); - if (!TryCanonicalize(new UriBuilder(uri), out uri)) + ErrorUtilities.VerifyArgumentNotNull(uri, "uri"); + if (!TryCanonicalize(new UriBuilder(uri), out uri)) { throw new UriFormatException(); + } if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) { throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement); } - Uri = uri; - SchemeImplicitlyPrepended = false; + this.Uri = uri; + this.SchemeImplicitlyPrepended = false; } + /// <summary> + /// Gets the URI this instance represents. + /// </summary> internal Uri Uri { get; private set; } + /// <summary> - /// Gets whether the scheme was missing when this Identifier was - /// created and added automatically as part of the normalization - /// process. + /// Gets a value indicating whether the scheme was missing when this + /// Identifier was created and added automatically as part of the + /// normalization process. /// </summary> internal bool SchemeImplicitlyPrepended { get; private set; } - static bool isAllowedScheme(string uri) { - if (string.IsNullOrEmpty(uri)) return false; - return Array.FindIndex(allowedSchemes, s => uri.StartsWith( - s + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase)) >= 0; + /// <summary> + /// Converts a <see cref="UriIdentifier"/> instance to a <see cref="Uri"/> instance. + /// </summary> + /// <param name="identifier">The identifier to convert to an ordinary <see cref="Uri"/> instance.</param> + /// <returns>The result of the conversion.</returns> + public static implicit operator Uri(UriIdentifier identifier) { + if (identifier == null) { + return null; + } + return identifier.Uri; } - static bool isAllowedScheme(Uri uri) { - if (uri == null) return false; - return Array.FindIndex(allowedSchemes, s => - uri.Scheme.Equals(s, StringComparison.OrdinalIgnoreCase)) >= 0; + + /// <summary> + /// Converts a <see cref="Uri"/> instance to a <see cref="UriIdentifier"/> instance. + /// </summary> + /// <param name="identifier">The <see cref="Uri"/> instance to turn into a <see cref="UriIdentifier"/>.</param> + /// <returns>The result of the conversion.</returns> + public static implicit operator UriIdentifier(Uri identifier) { + if (identifier == null) { + return null; + } + return new UriIdentifier(identifier); } - static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) { - canonicalUri = null; - schemePrepended = false; - try { - // Assume http:// scheme if an allowed scheme isn't given, and strip - // fragments off. Consistent with spec section 7.2#3 - if (!isAllowedScheme(uri)) { - uri = (forceHttpsDefaultScheme ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) + - Uri.SchemeDelimiter + uri; - schemePrepended = true; - } - // Use a UriBuilder because it helps to normalize the URL as well. - return TryCanonicalize(new UriBuilder(uri), out canonicalUri); - } catch (UriFormatException) { - // We try not to land here with checks in the try block, but just in case. + + /// <summary> + /// Tests equality between this URI and another URI. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + UriIdentifier other = obj as UriIdentifier; + if (other == null) { return false; } + return this.Uri == other.Uri; + } + + /// <summary> + /// Returns the hash code of this XRI. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return Uri.GetHashCode(); + } + + /// <summary> + /// Returns the string form of the URI. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + return Uri.AbsoluteUri; } #if UNUSED static bool TryCanonicalize(string uri, out string canonicalUri) { @@ -98,29 +155,41 @@ } #endif /// <summary> - /// Removes the fragment from a URL and sets the host to lowercase. + /// Determines whether a URI is a valid OpenID Identifier (of any kind). /// </summary> + /// <param name="uri">The URI to test for OpenID validity.</param> + /// <returns> + /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>. + /// </returns> /// <remarks> - /// This does NOT standardize an OpenID URL for storage in a database, as - /// it does nothing to convert the URL to a Claimed Identifier, besides the fact - /// that it only deals with URLs whereas OpenID 2.0 supports XRIs. - /// For this, you should lookup the value stored in IAuthenticationResponse.ClaimedIdentifier. + /// A valid URI is absolute (not relative) and uses an http(s) scheme. /// </remarks> - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] - static bool TryCanonicalize(UriBuilder uriBuilder, out Uri canonicalUri) { - uriBuilder.Host = uriBuilder.Host.ToLowerInvariant(); - canonicalUri = uriBuilder.Uri; - return true; - } internal static bool IsValidUri(string uri) { Uri normalized; bool schemePrepended; return TryCanonicalize(uri, out normalized, false, out schemePrepended); } + + /// <summary> + /// Determines whether a URI is a valid OpenID Identifier (of any kind). + /// </summary> + /// <param name="uri">The URI to test for OpenID validity.</param> + /// <returns> + /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// A valid URI is absolute (not relative) and uses an http(s) scheme. + /// </remarks> internal static bool IsValidUri(Uri uri) { - if (uri == null) return false; - if (!uri.IsAbsoluteUri) return false; - if (!isAllowedScheme(uri)) return false; + if (uri == null) { + return false; + } + if (!uri.IsAbsoluteUri) { + return false; + } + if (!IsAllowedScheme(uri)) { + return false; + } return true; } @@ -217,10 +286,20 @@ } #endif + /// <summary> + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. + /// </summary> + /// <returns> + /// A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance.. + /// </returns> internal override Identifier TrimFragment() { // If there is no fragment, we have no need to rebuild the Identifier. - if (Uri.Fragment == null || Uri.Fragment.Length == 0) + if (Uri.Fragment == null || Uri.Fragment.Length == 0) { return this; + } // Strip the fragment. UriBuilder builder = new UriBuilder(Uri); @@ -228,6 +307,18 @@ return builder.Uri; } + /// <summary> + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. + /// </summary> + /// <param name="secureIdentifier">The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> internal override bool TryRequireSsl(out Identifier secureIdentifier) { // If this Identifier is already secure, reuse it. if (IsDiscoverySecureEndToEnd) { @@ -243,7 +334,7 @@ } // Otherwise, try to make this Identifier secure by normalizing to HTTPS instead of HTTP. - if (SchemeImplicitlyPrepended) { + if (this.SchemeImplicitlyPrepended) { UriBuilder newIdentifierUri = new UriBuilder(this.Uri); newIdentifierUri.Scheme = Uri.UriSchemeHttps; if (newIdentifierUri.Port == 80) { @@ -259,26 +350,95 @@ } /// <summary> - /// Tests equality between this URI and another URI. + /// Determines whether the given URI is using a scheme in the list of allowed schemes. /// </summary> - public override bool Equals(object obj) { - UriIdentifier other = obj as UriIdentifier; - if (other == null) return false; - return this.Uri == other.Uri; + /// <param name="uri">The URI whose scheme is to be checked.</param> + /// <returns> + /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>. + /// <c>false</c> is also returned if <paramref name="uri"/> is null. + /// </returns> + private static bool IsAllowedScheme(string uri) { + if (string.IsNullOrEmpty(uri)) { + return false; + } + return Array.FindIndex( + allowedSchemes, + s => uri.StartsWith(s + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase)) >= 0; } /// <summary> - /// Returns the hash code of this XRI. + /// Determines whether the given URI is using a scheme in the list of allowed schemes. /// </summary> - public override int GetHashCode() { - return Uri.GetHashCode(); + /// <param name="uri">The URI whose scheme is to be checked.</param> + /// <returns> + /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>. + /// <c>false</c> is also returned if <paramref name="uri"/> is null. + /// </returns> + private static bool IsAllowedScheme(Uri uri) { + if (uri == null) { + return false; + } + return Array.FindIndex( + allowedSchemes, + s => uri.Scheme.Equals(s, StringComparison.OrdinalIgnoreCase)) >= 0; } /// <summary> - /// Returns the string form of the URI. + /// Tries to canonicalize a user-supplied identifier. + /// This does NOT convert a user-supplied identifier to a Claimed Identifier! /// </summary> - public override string ToString() { - return Uri.AbsoluteUri; + /// <param name="uri">The user-supplied identifier.</param> + /// <param name="canonicalUri">The resulting canonical URI.</param> + /// <param name="forceHttpsDefaultScheme">If set to <c>true</c> and the user-supplied identifier lacks a scheme, the "https://" scheme will be prepended instead of the standard "http://" one.</param> + /// <param name="schemePrepended">if set to <c>true</c> [scheme prepended].</param> + /// <returns> + /// <c>true</c> if the identifier was valid and could be canonicalized. + /// <c>false</c> if the identifier is outside the scope of allowed inputs and should be rejected. + /// </returns> + /// <remarks> + /// Canonicalization is done by adding a scheme in front of an + /// identifier if it isn't already present. Other trivial changes that do not + /// require network access are also done, such as lower-casing the hostname in the URI. + /// </remarks> + private static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) { + ErrorUtilities.VerifyNonZeroLength(uri, "uri"); + + canonicalUri = null; + schemePrepended = false; + try { + // Assume http:// scheme if an allowed scheme isn't given, and strip + // fragments off. Consistent with spec section 7.2#3 + if (!IsAllowedScheme(uri)) { + uri = (forceHttpsDefaultScheme ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) + + Uri.SchemeDelimiter + uri; + schemePrepended = true; + } + + // Use a UriBuilder because it helps to normalize the URL as well. + return TryCanonicalize(new UriBuilder(uri), out canonicalUri); + } catch (UriFormatException) { + // We try not to land here with checks in the try block, but just in case. + return false; + } + } + + /// <summary> + /// Removes the fragment from a URL and sets the host to lowercase. + /// </summary> + /// <param name="uriBuilder">The URI builder with the value to canonicalize.</param> + /// <param name="canonicalUri">The resulting canonical URI.</param> + /// <returns><c>true</c> if the canonicalization was successful; <c>false</c> otherwise.</returns> + /// <remarks> + /// This does NOT standardize an OpenID URL for storage in a database, as + /// it does nothing to convert the URL to a Claimed Identifier, besides the fact + /// that it only deals with URLs whereas OpenID 2.0 supports XRIs. + /// For this, you should lookup the value stored in IAuthenticationResponse.ClaimedIdentifier. + /// </remarks> + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The user will see the result of this operation and they want to see it in lower case.")] + private static bool TryCanonicalize(UriBuilder uriBuilder, out Uri canonicalUri) { + uriBuilder.Host = uriBuilder.Host.ToLowerInvariant(); + canonicalUri = uriBuilder.Uri; + return true; } } } diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs index 663bb49..4e92eff 100644 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs @@ -1,8 +1,15 @@ -namespace DotNetOpenAuth.OpenId { +//----------------------------------------------------------------------- +// <copyright file="XriIdentifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Globalization; using System.Xml; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> @@ -10,130 +17,203 @@ /// </summary> [Serializable] public sealed class XriIdentifier : Identifier { + /// <summary> + /// An XRI always starts with one of these symbols. + /// </summary> internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' }; - const string xriScheme = "xri://"; + /// <summary> + /// The scheme and separator "xri://" + /// </summary> + private const string XriScheme = "xri://"; + + /// <summary> + /// The magic URL that will provide us an XRDS document for a given XRI identifier. + /// </summary> + /// <remarks> + /// We use application/xrd+xml instead of application/xrds+xml because it gets + /// xri.net to automatically give us exactly the right XRD element for community i-names + /// automatically, saving us having to choose which one to use out of the result. + /// The ssl=true parameter tells the proxy resolver to accept only SSL connections + /// when resolving community i-names. + /// </remarks> + private const string XriResolverProxyTemplate = "https://xri.net/{0}?_xrd_r=application/xrd%2Bxml;sep=false"; + + /// <summary> + /// The XRI proxy resolver to use for finding XRDS documents from an XRI. + /// </summary> + private readonly string xriResolverProxy; + + /// <summary> + /// Initializes a new instance of the <see cref="XriIdentifier"/> class. + /// </summary> + /// <param name="xri">The string value of the XRI.</param> internal XriIdentifier(string xri) : this(xri, false) { } + + /// <summary> + /// Initializes a new instance of the <see cref="XriIdentifier"/> class. + /// </summary> + /// <param name="xri">The XRI that this Identifier will represent.</param> + /// <param name="requireSsl"> + /// If set to <c>true</c>, discovery and the initial authentication redirect will + /// only succeed if it can be done entirely using SSL. + /// </param> internal XriIdentifier(string xri, bool requireSsl) : base(requireSsl) { - if (!IsValidXri(xri)) - throw new FormatException(string.Format(CultureInfo.CurrentCulture, - OpenIdStrings.InvalidXri, xri)); - xriResolverProxy = xriResolverProxyTemplate; + if (!IsValidXri(xri)) { + throw new FormatException( + string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidXri, xri)); + } + this.xriResolverProxy = XriResolverProxyTemplate; if (requireSsl) { // Indicate to xri.net that we require SSL to be used for delegated resolution // of community i-names. - xriResolverProxy += ";https=true"; + this.xriResolverProxy += ";https=true"; } - OriginalXri = xri; - CanonicalXri = canonicalizeXri(xri); + this.OriginalXri = xri; + this.CanonicalXri = CanonicalizeXri(xri); } /// <summary> - /// The original XRI supplied to the constructor. + /// Gets the original XRI supplied to the constructor. /// </summary> internal string OriginalXri { get; private set; } + /// <summary> - /// The canonical form of the XRI string. + /// Gets the canonical form of the XRI string. /// </summary> internal string CanonicalXri { get; private set; } /// <summary> - /// Tests whether a given string represents a valid XRI format. + /// Gets the URL from which this XRI's XRDS document may be downloaded. /// </summary> - internal static bool IsValidXri(string xri) { - if (string.IsNullOrEmpty(xri)) throw new ArgumentNullException("xri"); - // TODO: better validation code here - return xri.IndexOfAny(GlobalContextSymbols) == 0 - || xri.StartsWith("(", StringComparison.Ordinal) - || xri.StartsWith(xriScheme, StringComparison.OrdinalIgnoreCase); + private Uri XrdsUrl { + get { return new Uri(string.Format(CultureInfo.InvariantCulture, this.xriResolverProxy, this)); } } /// <summary> - /// Takes any valid form of XRI string and returns the canonical form of the same XRI. + /// Tests equality between this XRI and another XRI. /// </summary> - static string canonicalizeXri(string xri) { - xri = xri.Trim(); - if (xri.StartsWith(xriScheme, StringComparison.OrdinalIgnoreCase)) - xri = xri.Substring(xriScheme.Length); - return xri; + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + XriIdentifier other = obj as XriIdentifier; + if (other == null) { + return false; + } + return this.CanonicalXri == other.CanonicalXri; } /// <summary> - /// The magic URL that will provide us an XRDS document for a given XRI identifier. - /// </summary> - /// <remarks> - /// We use application/xrd+xml instead of application/xrds+xml because it gets - /// xri.net to automatically give us exactly the right XRD element for community i-names - /// automatically, saving us having to choose which one to use out of the result. - /// The ssl=true parameter tells the proxy resolver to accept only SSL connections - /// when resolving community i-names. - /// </remarks> - const string xriResolverProxyTemplate = "https://xri.net/{0}?_xrd_r=application/xrd%2Bxml;sep=false"; - readonly string xriResolverProxy; - /// <summary> - /// Resolves the XRI to a URL from which an XRDS document may be downloaded. + /// Returns the hash code of this XRI. /// </summary> - private Uri XrdsUrl { - get { - return new Uri(string.Format(CultureInfo.InvariantCulture, - xriResolverProxy, this)); - } - } - -#if DISCOVERY // TODO: Add discovery and then re-enable this code block - XrdsDocument downloadXrds() { - var xrdsResponse = UntrustedWebRequest.Request(XrdsUrl); - XrdsDocument doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); - if (!doc.IsXrdResolutionSuccessful) { - throw new OpenIdException(Strings.XriResolutionFailed); - } - return doc; + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.CanonicalXri.GetHashCode(); } - internal override IEnumerable<ServiceEndpoint> Discover() { - return downloadXrds().CreateServiceEndpoints(this); + /// <summary> + /// Returns the canonical string form of the XRI. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + return this.CanonicalXri; } /// <summary> - /// Performs discovery on THIS identifier, but generates <see cref="ServiceEndpoint"/> - /// instances that treat another given identifier as the user-supplied identifier. + /// Tests whether a given string represents a valid XRI format. /// </summary> - internal IEnumerable<ServiceEndpoint> Discover(XriIdentifier userSuppliedIdentifier) { - return downloadXrds().CreateServiceEndpoints(userSuppliedIdentifier); - } -#endif + /// <param name="xri">The value to test for XRI validity.</param> + /// <returns> + /// <c>true</c> if the given string constitutes a valid XRI; otherwise, <c>false</c>. + /// </returns> + internal static bool IsValidXri(string xri) { + ErrorUtilities.VerifyNonZeroLength(xri, "xri"); - internal override Identifier TrimFragment() { - return this; + // TODO: better validation code here + return xri.IndexOfAny(GlobalContextSymbols) == 0 + || xri.StartsWith("(", StringComparison.Ordinal) + || xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase); } - internal override bool TryRequireSsl(out Identifier secureIdentifier) { - secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true); - return true; - } +#if DISCOVERY // TODO: Add discovery and then re-enable this code block + ////private XrdsDocument downloadXrds() { + //// var xrdsResponse = UntrustedWebRequest.Request(XrdsUrl); + //// XrdsDocument doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); + //// if (!doc.IsXrdResolutionSuccessful) { + //// throw new OpenIdException(Strings.XriResolutionFailed); + //// } + //// return doc; + ////} + + ////internal override IEnumerable<ServiceEndpoint> Discover() { + //// return downloadXrds().CreateServiceEndpoints(this); + ////} + + /////// <summary> + /////// Performs discovery on THIS identifier, but generates <see cref="ServiceEndpoint"/> + /////// instances that treat another given identifier as the user-supplied identifier. + /////// </summary> + ////internal IEnumerable<ServiceEndpoint> Discover(XriIdentifier userSuppliedIdentifier) { + //// return downloadXrds().CreateServiceEndpoints(userSuppliedIdentifier); + ////} +#endif /// <summary> - /// Tests equality between this XRI and another XRI. + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. /// </summary> - public override bool Equals(object obj) { - XriIdentifier other = obj as XriIdentifier; - if (other == null) return false; - return this.CanonicalXri == other.CanonicalXri; + /// <returns> + /// A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance.. + /// </returns> + /// <remarks> + /// XRI Identifiers never have a fragment part, and thus this method + /// always returns this same instance. + /// </remarks> + internal override Identifier TrimFragment() { + return this; } /// <summary> - /// Returns the hash code of this XRI. + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. /// </summary> - public override int GetHashCode() { - return CanonicalXri.GetHashCode(); + /// <param name="secureIdentifier">The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> + internal override bool TryRequireSsl(out Identifier secureIdentifier) { + secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true); + return true; } /// <summary> - /// Returns the canonical string form of the XRI. + /// Takes any valid form of XRI string and returns the canonical form of the same XRI. /// </summary> - public override string ToString() { - return CanonicalXri; + /// <param name="xri">The xri to canonicalize.</param> + /// <returns>The canonicalized form of the XRI.</returns> + /// <remarks>The canonical form, per the OpenID spec, is no scheme and no whitespace on either end.</remarks> + private static string CanonicalizeXri(string xri) { + xri = xri.Trim(); + if (xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase)) { + xri = xri.Substring(XriScheme.Length); + } + return xri; } } } diff --git a/src/DotNetOpenAuth/UriUtil.cs b/src/DotNetOpenAuth/UriUtil.cs index 3385e4e..a5d0df9 100644 --- a/src/DotNetOpenAuth/UriUtil.cs +++ b/src/DotNetOpenAuth/UriUtil.cs @@ -8,9 +8,9 @@ namespace DotNetOpenAuth { using System; using System.Collections.Specialized; using System.Linq; + using System.Text.RegularExpressions; using System.Web; using DotNetOpenAuth.Messaging; - using System.Text.RegularExpressions; /// <summary> /// Utility methods for working with URIs. @@ -49,7 +49,9 @@ namespace DotNetOpenAuth { /// Equivalent to UriBuilder.ToString() but omits port # if it may be implied. /// Equivalent to UriBuilder.Uri.ToString(), but doesn't throw an exception if the Host has a wildcard. /// </summary> - public static string ToStringWithImpliedPorts(this UriBuilder builder) { + /// <param name="builder">The UriBuilder to render as a string.</param> + /// <returns>The string version of the Uri.</returns> + internal static string ToStringWithImpliedPorts(this UriBuilder builder) { ErrorUtilities.VerifyArgumentNotNull(builder, "builder"); // We only check for implied ports on HTTP and HTTPS schemes since those @@ -58,6 +60,7 @@ namespace DotNetOpenAuth { (builder.Port == 443 && string.Equals(builder.Scheme, "https", StringComparison.OrdinalIgnoreCase))) { // An implied port may be removed. string url = builder.ToString(); + // Be really careful to only remove the first :80 or :443 so we are guaranteed // we're removing only the port (and not something in the query string that // looks like a port. diff --git a/src/DotNetOpenAuth/Util.cs b/src/DotNetOpenAuth/Util.cs index a4fdb9d..a043c7a 100644 --- a/src/DotNetOpenAuth/Util.cs +++ b/src/DotNetOpenAuth/Util.cs @@ -27,5 +27,27 @@ namespace DotNetOpenAuth { return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", assemblyFullName, official ? "official" : "private"); } } + + /// <summary> + /// Tests for equality between two objects. Safely handles the case where one or both are null. + /// </summary> + /// <typeparam name="T">The type of objects been checked for equality.</typeparam> + /// <param name="first">The first object.</param> + /// <param name="second">The second object.</param> + /// <returns><c>true</c> if the two objects are equal; <c>false</c> otherwise.</returns> + internal static bool EqualsNullSafe<T>(this T first, T second) where T : class { + // If one is null and the other is not... + if (object.ReferenceEquals(first, null) ^ object.ReferenceEquals(second, null)) { + return false; + } + + // If both are null... (we only check one because we already know both are either null or non-null) + if (object.ReferenceEquals(first, null)) { + return true; + } + + // Neither are null. Delegate to the Equals method. + return first.Equals(second); + } } } |