summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj2
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs68
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs105
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/RealmTests.cs224
-rw-r--r--src/DotNetOpenAuth.sln9
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj2
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs154
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs27
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx9
-rw-r--r--src/DotNetOpenAuth/OpenId/Realm.cs339
-rw-r--r--src/DotNetOpenAuth/UriUtil.cs25
11 files changed, 962 insertions, 2 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
index 4469c17..6e294e5 100644
--- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
+++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
@@ -102,11 +102,13 @@
<Compile Include="OpenId\Messages\AssociateUnsuccessfulResponseTests.cs" />
<Compile Include="OpenId\Messages\AssociateUnencryptedResponseTests.cs" />
<Compile Include="OpenId\ChannelElements\OpenIdChannelTests.cs" />
+ <Compile Include="OpenId\Messages\CheckIdRequestTests.cs" />
<Compile Include="OpenId\Messages\DirectErrorResponseTests.cs" />
<Compile Include="OpenId\Messages\IndirectErrorResponseTests.cs" />
<Compile Include="OpenId\OpenIdCoordinator.cs" />
<Compile Include="OpenId\AssociationHandshakeTests.cs" />
<Compile Include="OpenId\OpenIdTestBase.cs" />
+ <Compile Include="OpenId\RealmTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Messaging\ResponseTests.cs" />
<Compile Include="OAuth\AppendixScenarios.cs" />
diff --git a/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs b/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs
index 054a149..7743ccc 100644
--- a/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs
@@ -35,6 +35,74 @@ namespace DotNetOpenAuth.Test.OpenId {
}
/// <summary>
+ /// Verifies that the RP and OP can renegotiate an association type if the RP's
+ /// initial request for an association is for a type the OP doesn't support.
+ /// </summary>
+ [TestMethod, Ignore]
+ public void AssociateRenegotiateBitLength() {
+ // TODO: test where the RP asks for an association type that the OP doesn't support
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Verifies that the RP cannot get caught in an infinite loop if a bad OP
+ /// keeps sending it association retry messages.
+ /// </summary>
+ [TestMethod, Ignore]
+ public void AssociateRenegotiateBitLengthRPStopsAfterOneRetry() {
+ // TODO: code here
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Verifies security settings limit RP's initial associate request
+ /// </summary>
+ [TestMethod, Ignore]
+ public void AssociateRequestDeterminedBySecuritySettings() {
+ // TODO: Code here
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Verifies security settings limit RP's acceptance of OP's counter-suggestion
+ /// </summary>
+ [TestMethod, Ignore]
+ public void AssociateRenegotiateLimitedByRPSecuritySettings() {
+ // TODO: Code here
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Verifies security settings limit OP's set of acceptable association types.
+ /// </summary>
+ [TestMethod, Ignore]
+ public void AssociateLimitedByOPSecuritySettings() {
+ // TODO: Code here
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Verifies the RP can recover with no association after receiving an
+ /// associate error response from the OP when no suggested association
+ /// type is included.
+ /// </summary>
+ [TestMethod, Ignore]
+ public void AssociateContinueAfterOpenIdError() {
+ // TODO: Code here
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Verifies that the RP can recover from an invalid or non-existent
+ /// response from the OP, for example in the HTTP timeout case.
+ /// </summary>
+ [TestMethod, Ignore]
+ public void AssociateContinueAfterHttpError() {
+ // TODO: Code here
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
/// Runs a parameterized association flow test using all supported OpenID versions.
/// </summary>
/// <param name="opEndpoint">The OP endpoint to simulate using.</param>
diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs
new file mode 100644
index 0000000..0f970f9
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs
@@ -0,0 +1,105 @@
+//-----------------------------------------------------------------------
+// <copyright file="CheckIdRequestTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.OpenId.Messages {
+ using System;
+ 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;
+
+ [TestClass]
+ public class CheckIdRequestTests : OpenIdTestBase {
+ 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");
+
+ immediatev1 = new CheckIdRequest(Protocol.V11.Version, ProviderEndpoint, true);
+ setupv1 = new CheckIdRequest(Protocol.V11.Version, ProviderEndpoint, false);
+
+ immediatev2 = new CheckIdRequest(Protocol.V20.Version, ProviderEndpoint, true);
+ setupv2 = new CheckIdRequest(Protocol.V20.Version, 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/");
+
+ try {
+ immediatev1.EnsureValidMessage();
+ setupv1.EnsureValidMessage();
+ immediatev2.EnsureValidMessage();
+ setupv2.EnsureValidMessage();
+ } catch (ProtocolException ex) {
+ Assert.Inconclusive("All messages ought to be valid before tests run, but got: {0}", ex.Message);
+ }
+ }
+
+ /// <summary>
+ /// Tests that having <see cref="CheckIdRequest.ClaimedIdentifier"/> set without
+ /// <see cref="CheckIdRequest.LocalIdentifier"/> set is recognized as an error in OpenID 2.x.
+ /// </summary>
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void ClaimedIdentifierWithoutIdentity() {
+ setupv2.ClaimedIdentifier = "http://andrew.arnott.myopenid.com/";
+ setupv2.EnsureValidMessage();
+ }
+
+ /// <summary>
+ /// Tests that having <see cref="CheckIdRequest.LocalIdentifier"/> set without
+ /// <see cref="CheckIdRequest.ClaimedIdentifier"/> set is recognized as an error in OpenID 2.x.
+ /// </summary>
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void LocalIdentifierWithoutClaimedIdentifier() {
+ setupv2.LocalIdentifier = "http://andrew.arnott.myopenid.com/";
+ setupv2.EnsureValidMessage();
+ }
+
+ /// <summary>
+ /// Tests that having <see cref="CheckIdRequest.LocalIdentifier"/> set without
+ /// <see cref="CheckIdRequest.ClaimedIdentifier"/> set is recognized as valid in OpenID 1.x.
+ /// </summary>
+ [TestMethod]
+ public void LocalIdentifierWithoutClaimedIdentifierV1() {
+ setupv1.LocalIdentifier = "http://andrew.arnott.myopenid.com/";
+ setupv1.EnsureValidMessage();
+ }
+
+ /// <summary>
+ /// Verifies that the validation check throws if the return_to and the realm
+ /// values are not compatible.
+ /// </summary>
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void RealmReturnToMismatchV2() {
+ setupv2.Realm = "http://somehost/";
+ setupv2.ReturnTo = new Uri("http://someotherhost/");
+ setupv2.EnsureValidMessage();
+ }
+
+ /// <summary>
+ /// Verifies that the validation check throws if the return_to and the realm
+ /// values are not compatible.
+ /// </summary>
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void RealmReturnToMismatchV1() {
+ setupv1.Realm = "http://somehost/";
+ setupv1.ReturnTo = new Uri("http://someotherhost/");
+ setupv1.EnsureValidMessage();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs b/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs
new file mode 100644
index 0000000..5d59ded
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OpenId/RealmTests.cs
@@ -0,0 +1,224 @@
+//-----------------------------------------------------------------------
+// <copyright file="RealmTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test
+{
+ using System;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using DotNetOpenAuth.OpenId;
+
+ [TestClass]
+ public class RealmTests
+ {
+ [TestMethod]
+ 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");
+ new Realm("http://www.myopenid.com/");
+ new Realm("http://www.myopenid.com:5221/");
+ new Realm("https://www.myopenid.com");
+ new Realm("http://www.myopenid.com/abc");
+ new Realm("http://www.myopenid.com/abc/");
+ new Realm("http://*.myopenid.com/");
+ new Realm("http://*.com/");
+ new Realm("http://*.guest.myopenid.com/");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public void InvalidRealmNullString()
+ {
+ new Realm((string)null);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public void InvalidRealmNullUri() {
+ new Realm((Uri)null);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmEmpty()
+ {
+ new Realm(string.Empty);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmBadProtocol()
+ {
+ new Realm("asdf://www.microsoft.com/");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmNoScheme()
+ {
+ new Realm("www.guy.com");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmBadWildcard1()
+ {
+ new Realm("http://*www.my.com");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmBadWildcard2() {
+ new Realm("http://www.*.com");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmBadWildcard3() {
+ new Realm("http://www.my.*/");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmTwoWildcards1()
+ {
+ new Realm("http://**.my.com");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(UriFormatException))]
+ public void InvalidRealmTwoWildcards2()
+ {
+ new Realm("http://*.*.my.com");
+ }
+
+ [TestMethod]
+ 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);
+ Assert.IsTrue(new Realm("http://localhost:33532/dab").IsSane);
+ Assert.IsTrue(new Realm("http://www.myopenid.com").IsSane);
+
+ Assert.IsFalse(new Realm("http://*.com").IsSane);
+ Assert.IsFalse(new Realm("http://*.co.uk").IsSane);
+ }
+
+ [TestMethod]
+ 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
+ * MUST match. The path, if present, MUST be equal to or below the value of
+ * openid.trust_root, and the domains on both MUST match, or, the
+ * openid.trust_root value contain a wildcard like http://*.example.com.
+ * The wildcard SHALL only be at the beginning. It is RECOMMENDED Identity
+ * Provider's protect their End Users from requests for things like
+ * http://*.com/ or http://*.co.uk/.
+ */
+
+ // Schemes must match
+ Assert.IsFalse(new Realm("https://www.my.com/").Contains("http://www.my.com/"));
+ Assert.IsFalse(new Realm("http://www.my.com/").Contains("https://www.my.com/"));
+
+ // Ports must match
+ Assert.IsTrue(new Realm("http://www.my.com/").Contains("http://www.my.com:80/boo"));
+ Assert.IsTrue(new Realm("http://www.my.com:80/").Contains("http://www.my.com/boo"));
+ Assert.IsFalse(new Realm("http://www.my.com:79/").Contains("http://www.my.com/boo"));
+ Assert.IsFalse(new Realm("https://www.my.com/").Contains("http://www.my.com:79/boo"));
+
+ // Path must be (at or) below trust root
+ Assert.IsTrue(new Realm("http://www.my.com/").Contains("http://www.my.com/"));
+ Assert.IsTrue(new Realm("http://www.my.com/").Contains("http://www.my.com/boo"));
+ Assert.IsTrue(new Realm("http://www.my.com/p/").Contains("http://www.my.com/p/l"));
+ Assert.IsTrue(new Realm("http://www.my.com/bah").Contains("http://www.my.com/bah/bah"));
+ Assert.IsTrue(new Realm("http://www.my.com/bah").Contains("http://www.my.com/bah/bah"));
+ Assert.IsTrue(new Realm("http://www.my.com/bah.html").Contains("http://www.my.com/bah.html/bah"));
+ Assert.IsFalse(new Realm("http://www.my.com/bah").Contains("http://www.my.com/bahbah"));
+ Assert.IsTrue(new Realm("http://www.my.com/bah").Contains("http://www.my.com/bah?q=a"));
+ Assert.IsTrue(new Realm("http://www.my.com/bah?q=a").Contains("http://www.my.com/bah?q=a"));
+ Assert.IsTrue(new Realm("http://www.my.com/bah?a=b&c=d").Contains("http://www.my.com/bah?a=b&c=d&e=f"));
+ Assert.IsFalse(new Realm("http://www.my.com/bah?a=b&c=d").Contains("http://www.my.com/bah?a=b"));
+
+ // Domains MUST match
+ Assert.IsFalse(new Realm("http://www.my.com/").Contains("http://yours.com/"));
+ Assert.IsFalse(new Realm("http://www.my.com/").Contains("http://www.yours.com/"));
+ Assert.IsFalse(new Realm("http://www.my.com/").Contains("http://q.www.my.com/"));
+ Assert.IsFalse(new Realm("http://www.my.com/").Contains("http://wwww.my.com/"));
+ Assert.IsFalse(new Realm("http://www.my.com/").Contains("http://www.my.com.uk/"));
+ Assert.IsFalse(new Realm("http://www.my.com/").Contains("http://www.my.comm/"));
+
+ // Allow for wildcards
+ Assert.IsTrue(new Realm("http://*.www.my.com/").Contains("http://bah.www.my.com/"));
+ Assert.IsTrue(new Realm("http://*.www.my.com/").Contains("http://bah.WWW.MY.COM/"));
+ Assert.IsTrue(new Realm("http://*.www.my.com/").Contains("http://bah.www.my.com/boo"));
+ Assert.IsTrue(new Realm("http://*.my.com/").Contains("http://bah.www.my.com/boo"));
+ Assert.IsTrue(new Realm("http://*.my.com/").Contains("http://my.com/boo"));
+ Assert.IsFalse(new Realm("http://*.my.com/").Contains("http://ohmeohmy.com/"));
+ Assert.IsFalse(new Realm("http://*.my.com/").Contains("http://me.com/"));
+ Assert.IsFalse(new Realm("http://*.my.com/").Contains("http://my.co/"));
+ Assert.IsFalse(new Realm("http://*.my.com/").Contains("http://com/"));
+ Assert.IsFalse(new Realm("http://*.www.my.com/").Contains("http://my.com/"));
+ Assert.IsFalse(new Realm("http://*.www.my.com/").Contains("http://zzz.my.com/"));
+ // These are tested against by the constructor test, as these are invalid wildcard positions.
+ ////Assert.IsFalse(new Realm("http://*www.my.com/").ValidateUrl("http://bah.www.my.com/"));
+ ////Assert.IsFalse(new Realm("http://*www.my.com/").ValidateUrl("http://wwww.my.com/"));
+
+ // Among those that should return true, mix up character casing to test for case sensitivity.
+ // Host names should be case INSENSITIVE, but paths should probably be case SENSITIVE,
+ // because in some systems they are case sensitive and to ignore this would open
+ // security holes.
+ Assert.IsTrue(new Realm("http://www.my.com/").Contains("http://WWW.MY.COM/"));
+ Assert.IsFalse(new Realm("http://www.my.com/abc").Contains("http://www.my.com/ABC"));
+ }
+
+ [TestMethod]
+ public void ImplicitConversionFromStringTests() {
+ Realm realm = "http://host";
+ Assert.AreEqual("host", realm.Host);
+ realm = (string)null;
+ Assert.IsNull(realm);
+ }
+
+ [TestMethod]
+ public void ImplicitConversionToStringTests() {
+ Realm realm = new Realm("http://host/");
+ string realmString = realm;
+ Assert.AreEqual("http://host/", realmString);
+ realm = null;
+ realmString = realm;
+ Assert.IsNull(realmString);
+ }
+
+ [TestMethod]
+ public void ImplicitConverstionFromUriTests() {
+ Uri uri = new Uri("http://host");
+ Realm realm = uri;
+ Assert.AreEqual(uri.Host, realm.Host);
+ uri = null;
+ realm = uri;
+ Assert.IsNull(realm);
+ }
+
+ [TestMethod]
+ 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");
+ Realm testRealm3 = new Realm("http://*.www.yahoo.com");
+
+ Assert.AreEqual(testRealm1a, testRealm1b);
+ Assert.AreNotEqual(testRealm1a, testRealm2);
+ Assert.AreNotEqual(testRealm1a, null);
+ Assert.AreNotEqual(testRealm1a, testRealm1a.ToString(), "Although the URLs are equal, different object types shouldn't be equal.");
+ Assert.AreNotEqual(testRealm3, testRealm1a, "Wildcard difference ignored by Equals");
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln
index a31ac97..2656006 100644
--- a/src/DotNetOpenAuth.sln
+++ b/src/DotNetOpenAuth.sln
@@ -14,9 +14,14 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specs", "Specs", "{CD57219F-24F4-4136-8741-6063D0D7A031}"
ProjectSection(SolutionItems) = preProject
..\doc\specs\OAuth Core 1.0.htm = ..\doc\specs\OAuth Core 1.0.htm
+ ..\doc\specs\openid-attribute-exchange-1_0.html = ..\doc\specs\openid-attribute-exchange-1_0.html
+ ..\doc\specs\openid-authentication-1_1.html = ..\doc\specs\openid-authentication-1_1.html
+ ..\doc\specs\openid-authentication-2_0.html = ..\doc\specs\openid-authentication-2_0.html
+ ..\doc\specs\openid-provider-authentication-policy-extension-1_0-02.html = ..\doc\specs\openid-provider-authentication-policy-extension-1_0-02.html
+ ..\doc\specs\openid-simple-registration-extension-1_0.html = ..\doc\specs\openid-simple-registration-extension-1_0.html
EndProjectSection
EndProject
-Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Consumer", "..\samples\Consumer", "{F9076F04-17AF-4205-93A2-1D3BEBFCDAEB}"
+Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Consumer", "..\samples\Consumer\", "{F9076F04-17AF-4205-93A2-1D3BEBFCDAEB}"
ProjectSection(WebsiteProperties) = preProject
TargetFramework = "3.5"
ProjectReferences = "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}|DotNetOpenAuth.dll;{AA78D112-D889-414B-A7D4-467B34C7B663}|DotNetOpenAuth.ApplicationBlock.dll;"
@@ -45,7 +50,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{B4C6
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumerWpf", "..\samples\ConsumerWpf\ConsumerWpf.csproj", "{6EC36418-DBC5-4AD1-A402-413604AA7A08}"
EndProject
-Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "ServiceProvider", "..\samples\ServiceProvider", "{EC910270-AAB6-4AC6-9B57-99118CFBE557}"
+Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "ServiceProvider", "..\samples\ServiceProvider\", "{EC910270-AAB6-4AC6-9B57-99118CFBE557}"
ProjectSection(WebsiteProperties) = preProject
TargetFramework = "3.5"
ProjectReferences = "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}|DotNetOpenAuth.dll;"
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 0da6de6..0d143eb 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -163,6 +163,8 @@
<Compile Include="OpenId\ChannelElements\OpenIdChannel.cs" />
<Compile Include="OpenId\ChannelElements\OpenIdMessageFactory.cs" />
<Compile Include="OpenId\Configuration.cs" />
+ <Compile Include="OpenId\Messages\CheckIdRequest.cs" />
+ <Compile Include="OpenId\Realm.cs" />
<Compile Include="OpenId\RelyingPartyDescription.cs" />
<Compile Include="OpenId\DiffieHellmanUtilities.cs" />
<Compile Include="OpenId\DiffieHellman\DHKeyGeneration.cs" />
diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
new file mode 100644
index 0000000..034dd65
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
@@ -0,0 +1,154 @@
+//-----------------------------------------------------------------------
+// <copyright file="CheckIdRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An authentication request from a Relying Party to a Provider.
+ /// </summary>
+ /// <remarks>
+ /// This message type satisfies OpenID 2.0 section 9.1.
+ /// </remarks>
+ [DebuggerDisplay("OpenID {ProtocolVersion} {Mode} {ClaimedIdentifier}")]
+ internal class CheckIdRequest : RequestBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CheckIdRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="providerEndpoint">The Provider endpoint that receives this message.</param>
+ /// <param name="immediate">
+ /// <c>true</c> for asynchronous javascript clients;
+ /// <c>false</c> to allow the Provider to interact with the user in order to complete authentication.
+ /// </param>
+ internal CheckIdRequest(Version version, Uri providerEndpoint, bool immediate) :
+ base(version, providerEndpoint, GetMode(version, immediate), DotNetOpenAuth.Messaging.MessageTransport.Indirect) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Claimed Identifier.
+ /// </summary>
+ /// <remarks>
+ /// <para>"openid.claimed_id" and "openid.identity" SHALL be either both present or both absent.
+ /// If neither value is present, the assertion is not about an identifier,
+ /// and will contain other information in its payload, using extensions (Extensions). </para>
+ /// <para>It is RECOMMENDED that OPs accept XRI identifiers with or without the "xri://" prefix, as specified in the Normalization (Normalization) section. </para>
+ /// </remarks>
+ [MessagePart("openid.claimed_id", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
+ internal string ClaimedIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the OP Local Identifier.
+ /// </summary>
+ /// <value>The OP-Local Identifier. </value>
+ /// <remarks>
+ /// <para>If a different OP-Local Identifier is not specified, the claimed
+ /// identifier MUST be used as the value for openid.identity.</para>
+ /// <para>Note: If this is set to the special value
+ /// "http://specs.openid.net/auth/2.0/identifier_select" then the OP SHOULD
+ /// choose an Identifier that belongs to the end user. This parameter MAY
+ /// be omitted if the request is not about an identifier (for instance if
+ /// an extension is in use that makes the request meaningful without it;
+ /// see openid.claimed_id above). </para>
+ /// </remarks>
+ [MessagePart("openid.identity", IsRequired = false, AllowEmpty = false)]
+ internal string LocalIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the handle of the association the RP would like the Provider
+ /// to use for signing a positive assertion in the response message.
+ /// </summary>
+ /// <value>A handle for an association between the Relying Party and the OP
+ /// that SHOULD be used to sign the response. </value>
+ /// <remarks>
+ /// If no association handle is sent, the transaction will take place in Stateless Mode
+ /// (Verifying Directly with the OpenID Provider).
+ /// </remarks>
+ [MessagePart("openid.assoc_handle", IsRequired = false, AllowEmpty = false)]
+ internal string AssociationHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the URL the Provider should redirect the user agent to following
+ /// the authentication attempt.
+ /// </summary>
+ /// <value>URL to which the OP SHOULD return the User-Agent with the response
+ /// indicating the status of the request.</value>
+ /// <remarks>
+ /// <para>If this value is not sent in the request it signifies that the Relying Party
+ /// does not wish for the end user to be returned. </para>
+ /// <para>The return_to URL MAY be used as a mechanism for the Relying Party to attach
+ /// context about the authentication request to the authentication response.
+ /// This document does not define a mechanism by which the RP can ensure that query
+ /// parameters are not modified by outside parties; such a mechanism can be defined
+ /// by the RP itself. </para>
+ /// </remarks>
+ [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false)]
+ [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
+ internal Uri ReturnTo { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Relying Party discovery URL the Provider may use to verify the
+ /// source of the authentication request.
+ /// </summary>
+ /// <value>
+ /// URL pattern the OP SHOULD ask the end user to trust. See Section 9.2 (Realms).
+ /// This value MUST be sent if openid.return_to is omitted.
+ /// Default: The <see cref="ReturnTo"/> URL.
+ /// </value>
+ [MessagePart("openid.trust_root", IsRequired = false, AllowEmpty = false)]
+ [MessagePart("openid.realm", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
+ internal Realm Realm { get; set; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.ProtocolVersion.Major >= 2) {
+ ErrorUtilities.VerifyProtocol((this.ClaimedIdentifier == null) == (this.LocalIdentifier == null), OpenIdStrings.ClaimedIdAndLocalIdMustBothPresentOrAbsent);
+ }
+
+ if (this.Realm == null) {
+ // Set the default Realm per the spec if it is not explicitly given.
+ this.Realm = this.ReturnTo;
+ } else if (this.ReturnTo != null) {
+ // Verify that the realm and return_to agree.
+ ErrorUtilities.VerifyProtocol(this.Realm.Contains(this.ReturnTo), OpenIdStrings.ReturnToNotUnderRealm, this.ReturnTo, this.Realm);
+ }
+ }
+
+ /// <summary>
+ /// Gets the value of the openid.mode parameter based on the protocol version and immediate flag.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="immediate">
+ /// <c>true</c> for asynchronous javascript clients;
+ /// <c>false</c> to allow the Provider to interact with the user in order to complete authentication.
+ /// </param>
+ /// <returns>checkid_immediate or checkid_setup</returns>
+ private static string GetMode(Version version, bool immediate) {
+ ErrorUtilities.VerifyArgumentNotNull(version, "version");
+
+ Protocol protocol = Protocol.Lookup(version);
+ return immediate ? protocol.Args.Mode.checkid_immediate : protocol.Args.Mode.checkid_setup;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index 4924d68..d6b6de0 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -88,6 +88,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to The openid.claimed_id and openid.identity parameters must both be present or both be absent..
+ /// </summary>
+ internal static string ClaimedIdAndLocalIdMustBothPresentOrAbsent {
+ get {
+ return ResourceManager.GetString("ClaimedIdAndLocalIdMustBothPresentOrAbsent", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The following properties must be set before the Diffie-Hellman algorithm can generate a public key: {0}.
/// </summary>
internal static string DiffieHellmanRequiredPropertiesNotSet {
@@ -115,6 +124,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to The scheme must be http or https but was &apos;{0}&apos;..
+ /// </summary>
+ internal static string InvalidScheme {
+ get {
+ return ResourceManager.GetString("InvalidScheme", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The list of keys do not match the provided dictionary..
/// </summary>
internal static string KeysListAndDictionaryDoNotMatch {
@@ -158,5 +176,14 @@ namespace DotNetOpenAuth.OpenId {
return ResourceManager.GetString("NoSessionTypeFound", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to return_to &apos;{0}&apos; not under realm &apos;{1}&apos;..
+ /// </summary>
+ internal static string ReturnToNotUnderRealm {
+ get {
+ return ResourceManager.GetString("ReturnToNotUnderRealm", resourceCulture);
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index 3df53ac..aae4bad 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -126,6 +126,9 @@
<data name="BadAssociationPrivateData" xml:space="preserve">
<value>The private data supplied does not meet the requirements of any known Association type. Its length may be too short, or it may have been corrupted.</value>
</data>
+ <data name="ClaimedIdAndLocalIdMustBothPresentOrAbsent" xml:space="preserve">
+ <value>The openid.claimed_id and openid.identity parameters must both be present or both be absent.</value>
+ </data>
<data name="DiffieHellmanRequiredPropertiesNotSet" xml:space="preserve">
<value>The following properties must be set before the Diffie-Hellman algorithm can generate a public key: {0}</value>
</data>
@@ -135,6 +138,9 @@
<data name="InvalidKeyValueFormCharacterMissing" xml:space="preserve">
<value>Cannot decode Key-Value Form because a line was found without a '{0}' character. (line {1}: '{2}')</value>
</data>
+ <data name="InvalidScheme" xml:space="preserve">
+ <value>The scheme must be http or https but was '{0}'.</value>
+ </data>
<data name="KeysListAndDictionaryDoNotMatch" xml:space="preserve">
<value>The list of keys do not match the provided dictionary.</value>
</data>
@@ -150,4 +156,7 @@
<data name="NoSessionTypeFound" xml:space="preserve">
<value>Diffie-Hellman session type '{0}' not found for OpenID {1}.</value>
</data>
+ <data name="ReturnToNotUnderRealm" xml:space="preserve">
+ <value>return_to '{0}' not under realm '{1}'.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs
new file mode 100644
index 0000000..80c0188
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Realm.cs
@@ -0,0 +1,339 @@
+//-----------------------------------------------------------------------
+// <copyright file="Realm.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Globalization;
+ using System.Text.RegularExpressions;
+ using System.Xml;
+ using DotNetOpenAuth.OpenId.Provider;
+
+ /// <summary>
+ /// A trust root to validate requests and match return URLs against.
+ /// </summary>
+ /// <remarks>
+ /// This fills the OpenID Authentication 2.0 specification for realms.
+ /// See http://openid.net/specs/openid-authentication-2_0.html#realms
+ /// </remarks>
+ public class Realm {
+ /// <summary>
+ /// Implicitly converts the string-form of a URI to a <see cref="Realm"/> object.
+ /// </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;
+ }
+
+ /// <summary>
+ /// Implicitly converts a <see cref="Uri"/> to a <see cref="Realm"/> object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
+ 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>
+ public static implicit operator string(Realm realm) {
+ return realm != null ? realm.ToString() : null;
+ }
+
+ /// <summary>
+ /// Instantiates a <see cref="Realm"/> from its string representation.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads"), SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
+ 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));
+ }
+
+ /// <summary>
+ /// Instantiates a <see cref="Realm"/> from its <see cref="Uri"/> representation.
+ /// </summary>
+ 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));
+ }
+
+ /// <summary>
+ /// Instantiates a <see cref="Realm"/> from its <see cref="UriBuilder"/> representation.
+ /// </summary>
+ /// <remarks>
+ /// 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+://)\*\.";
+
+ /// <summary>
+ /// 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; } }
+
+ /// <summary>
+ /// Gets the scheme name for this URI.
+ /// </summary>
+ public string Scheme { get { return uri.Scheme; } }
+
+ /// <summary>
+ /// Gets the port number of this URI.
+ /// </summary>
+ public int Port { get { return uri.Port; } }
+
+ /// <summary>
+ /// Gets the absolute path of the URI.
+ /// </summary>
+ public string AbsolutePath { get { return 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; } }
+
+ /// <summary>
+ /// Gets the realm URL. If the realm includes a wildcard, it is not included here.
+ /// </summary>
+ internal Uri NoWildcardUri { get { return uri; } }
+
+ /// <summary>
+ /// Produces the Realm URL. If the realm URL had a wildcard in it,
+ /// the wildcard is replaced with a "www." prefix.
+ /// </summary>
+ /// <remarks>
+ /// See OpenID 2.0 spec section 9.2.1 for the explanation on the addition of
+ /// the "www" prefix.
+ /// </remarks>
+ internal Uri UriWithWildcardChangedToWww {
+ get {
+ if (DomainWildcard) {
+ UriBuilder builder = new UriBuilder(NoWildcardUri);
+ builder.Host = "www." + builder.Host;
+ return builder.Uri;
+ } else {
+ return 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.
+ /// </summary>
+ /// <remarks>
+ /// 'http://*.com/', for example is not a reasonable pattern, as it cannot meaningfully
+ /// specify the site claiming it. This function attempts to find many related examples,
+ /// but it can only work via heuristics. Negative responses from this method should be
+ /// treated as advisory, used only to alert the user to examine the trust root carefully.
+ /// </remarks>
+ internal bool IsSane {
+ get {
+ if (Host.Equals("localhost", StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ string[] host_parts = Host.Split('.');
+
+ string tld = host_parts[host_parts.Length - 1];
+
+ if (Array.IndexOf(topLevelDomains, tld) < 0)
+ return false;
+
+ if (tld.Length == 2) {
+ if (host_parts.Length == 1)
+ return false;
+
+ if (host_parts[host_parts.Length - 2].Length <= 3)
+ return host_parts.Length > 2;
+ } else {
+ return host_parts.Length > 1;
+ }
+
+ return false;
+ }
+ }
+
+ /// <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));
+ }
+
+ /// <summary>
+ /// Validates a URL against this trust root.
+ /// </summary>
+ /// <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)
+ return false;
+
+ if (url.Port != Port)
+ return false;
+
+ if (!DomainWildcard) {
+ if (url.Host != 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('.');
+ 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)
+ return false;
+
+ // Compare last part first and move forward.
+ // Maybe could be done by using EndsWith, but piecewies helps ensure that
+ // *.my.com doesn't match ohmeohmy.com but can still match my.com.
+ for (int i = 0; i < host_parts.Length; i++) {
+ string hostPart = host_parts[host_parts.Length - 1 - i];
+ string urlPart = url_parts[url_parts.Length - 1 - i];
+ if (!string.Equals(hostPart, urlPart, StringComparison.OrdinalIgnoreCase)) {
+ return false;
+ }
+ }
+ }
+
+ // 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))
+ return true;
+
+ // If trust root has a longer path, the return URL must be invalid.
+ if (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;
+ string url_prefix = url.PathAndQuery.Substring(0, path_len);
+
+ if (PathAndQuery != url_prefix)
+ return false;
+
+ // If trust root includes a query string ...
+ if (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)
+ || url.PathAndQuery[path_len] == '?'
+ || url.PathAndQuery[path_len] == '/';
+ }
+
+#if TODO // 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];
+ }
+#endif
+
+ /// <summary>
+ /// Checks whether one <see cref="Realm"/> is equal to another.
+ /// </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;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth/UriUtil.cs b/src/DotNetOpenAuth/UriUtil.cs
index 63f666d..3385e4e 100644
--- a/src/DotNetOpenAuth/UriUtil.cs
+++ b/src/DotNetOpenAuth/UriUtil.cs
@@ -9,6 +9,8 @@ namespace DotNetOpenAuth {
using System.Collections.Specialized;
using System.Linq;
using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using System.Text.RegularExpressions;
/// <summary>
/// Utility methods for working with URIs.
@@ -42,5 +44,28 @@ namespace DotNetOpenAuth {
return string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase);
}
+
+ /// <summary>
+ /// 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) {
+ ErrorUtilities.VerifyArgumentNotNull(builder, "builder");
+
+ // We only check for implied ports on HTTP and HTTPS schemes since those
+ // are the only ones supported by OpenID anyway.
+ if ((builder.Port == 80 && string.Equals(builder.Scheme, "http", StringComparison.OrdinalIgnoreCase)) ||
+ (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.
+ return Regex.Replace(url, @"^(https?://[^:]+):\d+", m => m.Groups[1].Value, RegexOptions.IgnoreCase);
+ } else {
+ // The port must be explicitly given anyway.
+ return builder.ToString();
+ }
+ }
}
}