summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-05-06 21:18:11 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2010-05-06 21:18:11 -0700
commit3376d7a58ffdae11afa3b8ac4a946d491808576a (patch)
tree0093148a9b3cd3f93f8666ba394cbf642383b5ff /src
parent91f6fca9adfd016f913cab905a986d68117f6caa (diff)
parent998cfe308c340b70e1497bff3e25c47a194c7f7c (diff)
downloadDotNetOpenAuth-3376d7a58ffdae11afa3b8ac4a946d491808576a.zip
DotNetOpenAuth-3376d7a58ffdae11afa3b8ac4a946d491808576a.tar.gz
DotNetOpenAuth-3376d7a58ffdae11afa3b8ac4a946d491808576a.tar.bz2
Merge branch 'v3.4' into oauthWRAP
Conflicts: samples/OAuthConsumer/Web.config src/DotNetOpenAuth/DotNetOpenAuth.csproj src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs src/DotNetOpenAuth/Messaging/MessagingStrings.resx src/version.txt
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj6
-rw-r--r--src/DotNetOpenAuth.Test/Logging.config3
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs2
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithBadXrds.html10
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithEmptyXrds.html10
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/DiscoveryServices/UriDiscoveryServiceTests.cs17
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs24
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs2
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs2
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs21
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs128
-rw-r--r--src/DotNetOpenAuth.sln83
-rw-r--r--src/DotNetOpenAuth/ComponentModel/ConverterBase.cs1
-rw-r--r--src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs2
-rw-r--r--src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd27
-rw-r--r--src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs36
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj24
-rw-r--r--src/DotNetOpenAuth/GlobalSuppressions.cs3
-rw-r--r--src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs65
-rw-r--r--src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs8
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/Token.cs22
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs4
-rw-r--r--src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs13
-rw-r--r--src/DotNetOpenAuth/Messaging/Channel.cs41
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs11
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.resx5
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs19
-rw-r--r--src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs9
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs6
-rw-r--r--src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset1
-rw-r--r--src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs59
-rw-r--r--src/DotNetOpenAuth/Mvc/OpenIdHelper.cs432
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs6
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/Association.cs28
-rw-r--r--src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs39
-rw-r--r--src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs12
-rw-r--r--src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs3
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs11
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs6
-rw-r--r--src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs113
-rw-r--r--src/DotNetOpenAuth/OpenId/IAssociationStore.cs17
-rw-r--r--src/DotNetOpenAuth/OpenId/Identifier.cs62
-rw-r--r--src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs20
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx6
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs21
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs13
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs19
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs3
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs74
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs238
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs10
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs163
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs16
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs208
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs318
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js8
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs161
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs110
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs8
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs8
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs32
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs12
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs15
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs13
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.gifbin237 -> 0 bytes
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.pngbin0 -> 457 bytes
-rw-r--r--src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs1
-rw-r--r--src/DotNetOpenAuth/OpenId/UriIdentifier.cs347
-rw-r--r--src/DotNetOpenAuth/Properties/AssemblyInfo.cs47
-rw-r--r--src/DotNetOpenAuth/Reporting.cs71
-rw-r--r--src/DotNetOpenAuth/Util.cs2
-rw-r--r--src/DotNetOpenAuth/XrdsPublisher.cs2
-rw-r--r--src/DotNetOpenAuth/Yadis/DiscoveryResult.cs37
-rw-r--r--src/DotNetOpenAuth/Yadis/Yadis.cs3
76 files changed, 2496 insertions, 887 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
index 3b074a6..ee39ba3 100644
--- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
+++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
@@ -377,6 +377,12 @@
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html20provWithEmptyXrds.html" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html20provWithBadXrds.html" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Test/Logging.config b/src/DotNetOpenAuth.Test/Logging.config
index cd19de2..87da027 100644
--- a/src/DotNetOpenAuth.Test/Logging.config
+++ b/src/DotNetOpenAuth.Test/Logging.config
@@ -30,7 +30,4 @@
<logger name="DotNetOpenAuth.Test">
<level value="Debug" />
</logger>
- <logger name="DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement">
- <level value="WARN" />
- </logger>
</log4net>
diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs
index 8d5ef2a..6160680 100644
--- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs
@@ -21,7 +21,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements {
/// </summary>
[TestCase]
public void SignaturesMatchKnownGood() {
- Protocol protocol = Protocol.Default;
+ Protocol protocol = Protocol.V20;
var settings = new ProviderSecuritySettings();
var store = new AssociationMemoryStore<AssociationRelyingPartyType>();
byte[] associationSecret = Convert.FromBase64String("rsSwv1zPWfjPRQU80hciu8FPDC+GONAMJQ/AvSo1a2M=");
diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithBadXrds.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithBadXrds.html
new file mode 100644
index 0000000..e695116
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithBadXrds.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Untitled Page</title>
+ <meta http-equiv="X-XRDS-Location" content="http://localhost/xrds-notfound.xml"/>
+ <link rel="openid2.provider" href="http://a/b" />
+</head>
+<body>
+</body>
+</html>
diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithEmptyXrds.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithEmptyXrds.html
new file mode 100644
index 0000000..97ad7dc
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20provWithEmptyXrds.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Untitled Page</title>
+ <meta http-equiv="X-XRDS-Location" content="http://localhost/xrds-irrelevant.xml"/>
+ <link rel="openid2.provider" href="http://a/b" />
+</head>
+<body>
+</body>
+</html>
diff --git a/src/DotNetOpenAuth.Test/OpenId/DiscoveryServices/UriDiscoveryServiceTests.cs b/src/DotNetOpenAuth.Test/OpenId/DiscoveryServices/UriDiscoveryServiceTests.cs
index 7b12a1d..1050b4b 100644
--- a/src/DotNetOpenAuth.Test/OpenId/DiscoveryServices/UriDiscoveryServiceTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/DiscoveryServices/UriDiscoveryServiceTests.cs
@@ -179,6 +179,23 @@ namespace DotNetOpenAuth.Test.OpenId.DiscoveryServices {
}
/// <summary>
+ /// Verifies HTML discovery proceeds if an XRDS document is referenced that doesn't contain OpenID endpoints.
+ /// </summary>
+ [TestCase]
+ public void HtmlDiscoveryProceedsIfXrdsIsEmpty() {
+ this.MockResponder.RegisterMockResponse(new Uri("http://localhost/xrds-irrelevant.xml"), "application/xrds+xml", LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds-irrelevant.xml"));
+ this.DiscoverHtml("html20provWithEmptyXrds", ProtocolVersion.V20, null, "http://a/b");
+ }
+
+ /// <summary>
+ /// Verifies HTML discovery proceeds if the XRDS that is referenced cannot be found.
+ /// </summary>
+ [TestCase]
+ public void HtmlDiscoveryProceedsIfXrdsIsBadOrMissing() {
+ this.DiscoverHtml("html20provWithBadXrds", ProtocolVersion.V20, null, "http://a/b");
+ }
+
+ /// <summary>
/// Verifies that a dual identifier yields only one service endpoint by default.
/// </summary>
[TestCase]
diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs
index ffbadc8..1b1dd49 100644
--- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs
@@ -42,6 +42,8 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions {
Assert.IsNull(sreg);
// Make sure we're still able to send an sreg response.
+ // (not really a valid scenario, since OPs don't have public access
+ // to directly create a response without a request.
var sregResponse = new ClaimsResponse();
this.request.AddResponseExtension(sregResponse);
ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request);
@@ -49,12 +51,18 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions {
Assert.AreSame(sregResponse, extensions.Single());
}
+ [TestCase]
+ public void NegativeResponse() {
+ this.request.IsAuthenticated = false;
+ ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request);
+ }
+
/// <summary>
/// Verifies sreg coming in is seen as sreg.
/// </summary>
[TestCase]
public void UnifyExtensionsAsSregWithSreg() {
- var sregInjected = new ClaimsRequest {
+ var sregInjected = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns) {
Nickname = DemandLevel.Request,
};
this.extensions.Add(sregInjected);
@@ -63,7 +71,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions {
Assert.AreEqual(DemandLevel.Request, sreg.Nickname);
Assert.AreEqual(DemandLevel.NoRequest, sreg.FullName);
- var sregResponse = new ClaimsResponse();
+ var sregResponse = sreg.CreateResponse();
this.request.AddResponseExtension(sregResponse);
ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request);
var extensions = this.GetResponseExtensions();
@@ -91,7 +99,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions {
/// </summary>
[TestCase]
public void UnifyExtensionsAsSregWithBothSregAndAX() {
- var sregInjected = new ClaimsRequest {
+ var sregInjected = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns) {
Nickname = DemandLevel.Request,
};
this.extensions.Add(sregInjected);
@@ -103,9 +111,8 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions {
Assert.AreEqual(DemandLevel.Request, sreg.Nickname);
Assert.AreEqual(DemandLevel.NoRequest, sreg.Email);
- var sregResponseInjected = new ClaimsResponse {
- Nickname = "andy",
- };
+ var sregResponseInjected = sreg.CreateResponse();
+ sregResponseInjected.Nickname = "andy";
this.request.AddResponseExtension(sregResponseInjected);
var axResponseInjected = new FetchResponse();
axResponseInjected.Attributes.Add(WellKnownAttributes.Contact.Email, "a@b.com");
@@ -134,9 +141,8 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions {
Assert.AreEqual(DemandLevel.Require, sreg.FullName);
Assert.AreEqual(DemandLevel.NoRequest, sreg.Language);
- var sregResponse = new ClaimsResponse {
- Nickname = "andy",
- };
+ var sregResponse = sreg.CreateResponse();
+ sregResponse.Nickname = "andy";
this.request.AddResponseExtension(sregResponse);
ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request);
var extensions = this.GetResponseExtensions();
diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs
index 181c023..44a629a 100644
--- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOpenAuth.Test.OpenId {
+namespace DotNetOpenAuth.Test.OpenId.Extensions {
using System.Collections.ObjectModel;
using System.Linq;
using DotNetOpenAuth.OpenId;
diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs
index 9074f75..7b528d0 100644
--- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOpenAuth.Test.OpenId {
+namespace DotNetOpenAuth.Test.OpenId.Extensions {
using System.Collections.Generic;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs
index 811b7d1..25b0607 100644
--- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs
@@ -12,6 +12,7 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty {
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.Messages;
using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Test.Mocks;
using NUnit.Framework;
[TestFixture]
@@ -120,6 +121,26 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty {
Assert.IsNull(authResponse.GetCallbackArgument("a"));
}
+ /// <summary>
+ /// Verifies that certain problematic claimed identifiers pass through to the RP response correctly.
+ /// </summary>
+ [TestCase]
+ public void ProblematicClaimedId() {
+ var providerEndpoint = new ProviderEndpointDescription(OpenIdTestBase.OPUri, Protocol.Default.Version);
+ string claimed_id = BaseMockUri + "a./b.";
+ var se = IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimed_id, claimed_id, providerEndpoint, null, null);
+ UriIdentifier identityUri = (UriIdentifier)se.ClaimedIdentifier;
+ var mockId = new MockIdentifier(identityUri, this.MockResponder, new IdentifierDiscoveryResult[] { se });
+
+ var positiveAssertion = this.GetPositiveAssertion();
+ positiveAssertion.ClaimedIdentifier = mockId;
+ positiveAssertion.LocalIdentifier = mockId;
+ var rp = CreateRelyingParty();
+ var authResponse = new PositiveAuthenticationResponse(positiveAssertion, rp);
+ Assert.AreEqual(AuthenticationStatus.Authenticated, authResponse.Status);
+ Assert.AreEqual(claimed_id, authResponse.ClaimedIdentifier.ToString());
+ }
+
private PositiveAssertionResponse GetPositiveAssertion() {
return this.GetPositiveAssertion(false);
}
diff --git a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
index 73c185e..5b015ff 100644
--- a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
@@ -112,22 +112,51 @@ namespace DotNetOpenAuth.Test.OpenId {
Identifier noFragment = UriIdentifier.Parse("http://a/b");
Identifier fragment = UriIdentifier.Parse("http://a/b#c");
Assert.AreSame(noFragment, noFragment.TrimFragment());
- Assert.AreEqual(noFragment, fragment.TrimFragment());
+ Assert.AreEqual(noFragment.ToString(), fragment.TrimFragment().ToString());
+
+ // Try the problematic ones
+ TestAsFullAndPartialTrust(fullTrust => {
+ Identifier noFrag = UriIdentifier.Parse("http://a/b./c");
+ Identifier frag = UriIdentifier.Parse("http://a/b./c#d");
+ Assert.AreSame(noFrag, noFrag.TrimFragment());
+ Assert.AreEqual(noFrag.ToString(), frag.TrimFragment().ToString());
+ });
}
[TestCase]
public void ToStringTest() {
Assert.AreEqual(this.goodUri, new UriIdentifier(this.goodUri).ToString());
+ TestAsFullAndPartialTrust(fullTrust => {
+ Assert.AreEqual("http://abc/D./e.?Qq#Ff", new UriIdentifier("HTTP://ABC/D./e.?Qq#Ff").ToString());
+ Assert.AreEqual("http://abc/D./e.?Qq", new UriIdentifier("HTTP://ABC/D./e.?Qq").ToString());
+ Assert.AreEqual("http://abc/D./e.#Ff", new UriIdentifier("HTTP://ABC/D./e.#Ff").ToString());
+ Assert.AreEqual("http://abc/", new UriIdentifier("HTTP://ABC").ToString());
+ Assert.AreEqual("http://abc/?q", new UriIdentifier("HTTP://ABC?q").ToString());
+ Assert.AreEqual("http://abc/#f", new UriIdentifier("HTTP://ABC#f").ToString());
+ });
}
[TestCase]
public void EqualsTest() {
- Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri));
- // This next test is an interesting side-effect of passing off to Uri.Equals. But it's probably ok.
- Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "#frag"));
- Assert.AreNotEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "a"));
- Assert.AreNotEqual(null, new UriIdentifier(this.goodUri));
- Assert.IsTrue(new UriIdentifier(this.goodUri).Equals(this.goodUri));
+ TestAsFullAndPartialTrust(fulltrust => {
+ Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri));
+ // This next test is an interesting side-effect of passing off to Uri.Equals. But it's probably ok.
+ Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "#frag"));
+ Assert.AreEqual(new UriIdentifier("http://a/b./c."), new UriIdentifier("http://a/b./c.#frag"));
+ Assert.AreNotEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "a"));
+ Assert.AreNotEqual(null, new UriIdentifier(this.goodUri));
+ Assert.IsTrue(new UriIdentifier(this.goodUri).Equals(this.goodUri));
+
+ Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", true), Identifier.Parse("http://www.foo.com/abc", true));
+ Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", true), Identifier.Parse("http://www.foo.com/abc", false));
+ Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", false), Identifier.Parse("http://www.foo.com/abc", false));
+ Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", true), Identifier.Parse("http://www.foo.com/ABC", true));
+ Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", true), Identifier.Parse("http://www.foo.com/ABC", false));
+ Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", false), Identifier.Parse("http://www.foo.com/ABC", false));
+
+ Assert.AreNotEqual(Identifier.Parse("http://a/b./c."), Identifier.Parse("http://a/b/c."));
+ Assert.AreEqual(Identifier.Parse("http://a/b./c."), Identifier.Parse("http://a/b./c."));
+ });
}
[TestCase]
@@ -150,6 +179,51 @@ namespace DotNetOpenAuth.Test.OpenId {
Assert.AreEqual("https://host:80/PaTH?KeY=VaLUE#fRag", id.ToString());
}
+ /// <summary>
+ /// Verifies that URIs that contain base64 encoded path segments (such as Yahoo) are properly preserved.
+ /// </summary>
+ /// <remarks>
+ /// Yahoo includes a base64 encoded part as their last path segment,
+ /// which may end with a period. The default .NET Uri parser trims off
+ /// trailing periods, which breaks OpenID unless special precautions are taken.
+ /// </remarks>
+ [TestCase]
+ public void TrailingPeriodsNotTrimmed() {
+ TestAsFullAndPartialTrust(fullTrust => {
+ string claimedIdentifier = "https://me.yahoo.com/a/AsDf.#asdf";
+ Identifier id = claimedIdentifier;
+ Assert.AreEqual(claimedIdentifier, id.OriginalString);
+ Assert.AreEqual(claimedIdentifier, id.ToString());
+
+ UriIdentifier idUri = new UriIdentifier(claimedIdentifier);
+ Assert.AreEqual(claimedIdentifier, idUri.OriginalString);
+ Assert.AreEqual(claimedIdentifier, idUri.ToString());
+ if (fullTrust) {
+ Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri);
+ }
+ Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString());
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString);
+ Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed.");
+
+ idUri = new UriIdentifier(new Uri(claimedIdentifier));
+ Assert.AreEqual(claimedIdentifier, idUri.OriginalString);
+ Assert.AreEqual(claimedIdentifier, idUri.ToString());
+ if (fullTrust) {
+ Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri);
+ }
+ Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString());
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString);
+ Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed.");
+
+ claimedIdentifier = "https://me.yahoo.com:443/a/AsDf.#asdf";
+ id = claimedIdentifier;
+ Assert.AreEqual(claimedIdentifier, id.OriginalString);
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.#asdf", id.ToString());
+ });
+ }
+
[TestCase]
public void HttpSchemePrepended() {
UriIdentifier id = new UriIdentifier("www.yahoo.com");
@@ -193,5 +267,45 @@ namespace DotNetOpenAuth.Test.OpenId {
Assert.AreEqual("http://www.yahoo.com/", secureId.ToString());
Assert.AreEqual(0, Discover(secureId).Count());
}
+
+ /// <summary>
+ /// Verifies that unicode hostnames are handled.
+ /// </summary>
+ [TestCase]
+ public void UnicodeHostSupport() {
+ var id = new UriIdentifier("http://server崎/村");
+ Assert.AreEqual("server崎", id.Uri.Host);
+ }
+
+ /// <summary>
+ /// Verifies SimpleUri behavior
+ /// </summary>
+ [TestCase]
+ public void SimpleUri() {
+ Assert.AreEqual("http://abc/D./e.?Qq#Ff", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.?Qq#Ff").ToString());
+ Assert.AreEqual("http://abc/D./e.?Qq", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.?Qq").ToString());
+ Assert.AreEqual("http://abc/D./e.#Ff", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.#Ff").ToString());
+ Assert.AreEqual("http://abc/", new UriIdentifier.SimpleUri("HTTP://ABC/").ToString());
+ Assert.AreEqual("http://abc/", new UriIdentifier.SimpleUri("HTTP://ABC").ToString());
+ Assert.AreEqual("http://abc/?q", new UriIdentifier.SimpleUri("HTTP://ABC?q").ToString());
+ Assert.AreEqual("http://abc/#f", new UriIdentifier.SimpleUri("HTTP://ABC#f").ToString());
+
+ Assert.AreEqual("http://abc/a//b", new UriIdentifier.SimpleUri("http://abc/a//b").ToString());
+ Assert.AreEqual("http://abc/a%2Fb/c", new UriIdentifier.SimpleUri("http://abc/a%2fb/c").ToString());
+ Assert.AreEqual("http://abc/A/c", new UriIdentifier.SimpleUri("http://abc/%41/c").ToString());
+ }
+
+ private static void TestAsFullAndPartialTrust(Action<bool> action) {
+ // Test a bunch of interesting URLs both with scheme substitution on and off.
+ Assert.IsTrue(UriIdentifier_Accessor.schemeSubstitution, "Expected scheme substitution to be working.");
+ action(true);
+
+ UriIdentifier_Accessor.schemeSubstitution = false;
+ try {
+ action(false);
+ } finally {
+ UriIdentifier_Accessor.schemeSubstitution = true;
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln
index dcff8d0..60dffe6 100644
--- a/src/DotNetOpenAuth.sln
+++ b/src/DotNetOpenAuth.sln
@@ -120,54 +120,6 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OpenIdRelyingPartyClassicAs
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthConsumerWpf", "..\samples\OAuthConsumerWpf\OAuthConsumerWpf.csproj", "{6EC36418-DBC5-4AD1-A402-413604AA7A08}"
- ProjectSection(ProjectDependencies) = postProject
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191} = {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}
- EndProjectSection
-EndProject
-Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OAuthConsumer", "..\samples\OAuthConsumer\", "{9ADBE36D-9960-48F6-82E9-B4AC559E9AC3}"
- ProjectSection(WebsiteProperties) = preProject
- TargetFrameworkMoniker = ".NETFramework,Version%3Dv3.5"
- ProjectReferences = "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}|DotNetOpenAuth.dll;{AA78D112-D889-414B-A7D4-467B34C7B663}|DotNetOpenAuth.ApplicationBlock.dll;"
- Debug.AspNetCompiler.VirtualPath = "/OAuthConsumer"
- Debug.AspNetCompiler.PhysicalPath = "..\samples\OAuthConsumer\"
- Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\OAuthConsumer\"
- Debug.AspNetCompiler.Updateable = "true"
- Debug.AspNetCompiler.ForceOverwrite = "true"
- Debug.AspNetCompiler.FixedNames = "false"
- Debug.AspNetCompiler.Debug = "True"
- Release.AspNetCompiler.VirtualPath = "/OAuthConsumer"
- Release.AspNetCompiler.PhysicalPath = "..\samples\OAuthConsumer\"
- Release.AspNetCompiler.TargetPath = "PrecompiledWeb\OAuthConsumer\"
- Release.AspNetCompiler.Updateable = "true"
- Release.AspNetCompiler.ForceOverwrite = "true"
- Release.AspNetCompiler.FixedNames = "false"
- Release.AspNetCompiler.Debug = "False"
- VWDPort = "59721"
- StartServerOnDebug = "false"
- EndProjectSection
-EndProject
-Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OAuthServiceProvider", "..\samples\OAuthServiceProvider\", "{7ADCCD5C-AC2B-4340-9410-FE3A31A48191}"
- ProjectSection(WebsiteProperties) = preProject
- TargetFrameworkMoniker = ".NETFramework,Version%3Dv3.5"
- ProjectReferences = "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}|DotNetOpenAuth.dll;"
- Debug.AspNetCompiler.VirtualPath = "/OAuthServiceProvider"
- Debug.AspNetCompiler.PhysicalPath = "..\samples\OAuthServiceProvider\"
- Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\OAuthServiceProvider\"
- Debug.AspNetCompiler.Updateable = "true"
- Debug.AspNetCompiler.ForceOverwrite = "true"
- Debug.AspNetCompiler.FixedNames = "false"
- Debug.AspNetCompiler.Debug = "True"
- Release.AspNetCompiler.VirtualPath = "/OAuthServiceProvider"
- Release.AspNetCompiler.PhysicalPath = "..\samples\OAuthServiceProvider\"
- Release.AspNetCompiler.TargetPath = "PrecompiledWeb\OAuthServiceProvider\"
- Release.AspNetCompiler.Updateable = "true"
- Release.AspNetCompiler.ForceOverwrite = "true"
- Release.AspNetCompiler.FixedNames = "false"
- Release.AspNetCompiler.Debug = "False"
- VWDPort = "65169"
- VWDDynamicPort = "false"
- StartServerOnDebug = "false"
- EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdOfflineProvider", "..\samples\OpenIdOfflineProvider\OpenIdOfflineProvider.csproj", "{5C65603B-235F-47E6-B536-06385C60DE7F}"
EndProject
@@ -188,6 +140,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdWebRingSsoProvider",
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "OpenIdRelyingPartyWebFormsVB", "..\samples\OpenIdRelyingPartyWebFormsVB\OpenIdRelyingPartyWebFormsVB.vbproj", "{F289B925-4307-4BEC-B411-885CE70E3379}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthConsumer", "..\samples\OAuthConsumer\OAuthConsumer.csproj", "{9529606E-AF76-4387-BFB7-3D10A5B399AA}"
+ ProjectSection(ProjectDependencies) = postProject
+ {E135F455-0669-49F8-9207-07FCA8C8FC79} = {E135F455-0669-49F8-9207-07FCA8C8FC79}
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthServiceProvider", "..\samples\OAuthServiceProvider\OAuthServiceProvider.csproj", "{E135F455-0669-49F8-9207-07FCA8C8FC79}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeAnalysis|Any CPU = CodeAnalysis|Any CPU
@@ -261,18 +220,6 @@ Global
{6EC36418-DBC5-4AD1-A402-413604AA7A08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EC36418-DBC5-4AD1-A402-413604AA7A08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EC36418-DBC5-4AD1-A402-413604AA7A08}.Release|Any CPU.Build.0 = Release|Any CPU
- {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU
- {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU
- {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3}.Release|Any CPU.ActiveCfg = Debug|Any CPU
- {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3}.Release|Any CPU.Build.0 = Debug|Any CPU
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.Release|Any CPU.ActiveCfg = Debug|Any CPU
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.Release|Any CPU.Build.0 = Debug|Any CPU
{5C65603B-235F-47E6-B536-06385C60DE7F}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
{5C65603B-235F-47E6-B536-06385C60DE7F}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
{5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -324,6 +271,18 @@ Global
{F289B925-4307-4BEC-B411-885CE70E3379}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F289B925-4307-4BEC-B411-885CE70E3379}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F289B925-4307-4BEC-B411-885CE70E3379}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9529606E-AF76-4387-BFB7-3D10A5B399AA}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
+ {9529606E-AF76-4387-BFB7-3D10A5B399AA}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
+ {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E135F455-0669-49F8-9207-07FCA8C8FC79}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
+ {E135F455-0669-49F8-9207-07FCA8C8FC79}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
+ {E135F455-0669-49F8-9207-07FCA8C8FC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E135F455-0669-49F8-9207-07FCA8C8FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E135F455-0669-49F8-9207-07FCA8C8FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E135F455-0669-49F8-9207-07FCA8C8FC79}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -343,8 +302,8 @@ Global
{0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1}
{F289B925-4307-4BEC-B411-885CE70E3379} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1}
{6EC36418-DBC5-4AD1-A402-413604AA7A08} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
- {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
- {7ADCCD5C-AC2B-4340-9410-FE3A31A48191} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
+ {9529606E-AF76-4387-BFB7-3D10A5B399AA} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
+ {E135F455-0669-49F8-9207-07FCA8C8FC79} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
{6EB90284-BD15-461C-BBF2-131CF55F7C8B} = {8A5CEDB9-7F8A-4BE2-A1B9-97130F453277}
{5C65603B-235F-47E6-B536-06385C60DE7F} = {E9ED920D-1F83-48C0-9A4B-09CCE505FE6D}
{A78F8FC6-7B03-4230-BE41-761E400D6810} = {B9EB8729-4B54-4453-B089-FE6761BA3057}
diff --git a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs b/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
index 37f9c78..980d90f 100644
--- a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
+++ b/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
@@ -144,6 +144,7 @@ using System.Reflection;
/// The conversion cannot be performed.
/// </exception>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
+ Contract.Assume(destinationType != null, "Missing contract.");
if (destinationType.IsInstanceOfType(value)) {
return value;
}
diff --git a/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs b/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
index 6ba9c4b..61c0fd8 100644
--- a/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
+++ b/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
@@ -45,7 +45,7 @@ namespace DotNetOpenAuth.ComponentModel {
return null;
}
- MemberInfo identifierParse = typeof(Identifier).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
+ MemberInfo identifierParse = typeof(Identifier).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
return CreateInstanceDescriptor(identifierParse, new object[] { value.ToString() });
}
diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
index a9c5965..3164ec5 100644
--- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
+++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
@@ -319,7 +319,32 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
- <xs:attribute name="allowDualPurposeIdentifiers" type="xs:boolean" />
+ <xs:attribute name="allowDualPurposeIdentifiers" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation>
+ Controls whether identifiers that are both OP Identifiers and Claimed Identifiers
+ should ever be recognized as claimed identifiers.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowApproximateIdentifierDiscovery" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation>
+ Controls whether certain Claimed Identifiers that exploit
+ features that .NET does not have the ability to send exact HTTP requests for will
+ still be allowed by using an approximate HTTP request.
+ Only impacts hosts running under partial trust.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation>
+ Controls whether the relying party should take special care
+ to protect users against replay attacks when interoperating with OpenID 1.1 Providers.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="behaviors">
diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
index 0d8b768..1bf2ebc 100644
--- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
+++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
@@ -71,6 +71,16 @@ namespace DotNetOpenAuth.Configuration {
private const string AllowDualPurposeIdentifiersConfigName = "allowDualPurposeIdentifiers";
/// <summary>
+ /// Gets the name of the @allowApproximateIdentifierDiscovery attribute.
+ /// </summary>
+ private const string AllowApproximateIdentifierDiscoveryConfigName = "allowApproximateIdentifierDiscovery";
+
+ /// <summary>
+ /// Gets the name of the @protectDownlevelReplayAttacks attribute.
+ /// </summary>
+ private const string ProtectDownlevelReplayAttacksConfigName = "protectDownlevelReplayAttacks";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="OpenIdRelyingPartySecuritySettingsElement"/> class.
/// </summary>
public OpenIdRelyingPartySecuritySettingsElement() {
@@ -201,6 +211,30 @@ namespace DotNetOpenAuth.Configuration {
}
/// <summary>
+ /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit
+ /// features that .NET does not have the ability to send exact HTTP requests for will
+ /// still be allowed by using an approximate HTTP request.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>true</c>.
+ /// </value>
+ [ConfigurationProperty(AllowApproximateIdentifierDiscoveryConfigName, DefaultValue = true)]
+ public bool AllowApproximateIdentifierDiscovery {
+ get { return (bool)this[AllowApproximateIdentifierDiscoveryConfigName]; }
+ set { this[AllowApproximateIdentifierDiscoveryConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the Relying Party should take special care
+ /// to protect users against replay attacks when interoperating with OpenID 1.1 Providers.
+ /// </summary>
+ [ConfigurationProperty(ProtectDownlevelReplayAttacksConfigName, DefaultValue = RelyingPartySecuritySettings.ProtectDownlevelReplayAttacksDefault)]
+ public bool ProtectDownlevelReplayAttacks {
+ get { return (bool)this[ProtectDownlevelReplayAttacksConfigName]; }
+ set { this[ProtectDownlevelReplayAttacksConfigName] = value; }
+ }
+
+ /// <summary>
/// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file.
/// </summary>
/// <returns>The newly created security settings object.</returns>
@@ -219,6 +253,8 @@ namespace DotNetOpenAuth.Configuration {
settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers;
settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions;
settings.AllowDualPurposeIdentifiers = this.AllowDualPurposeIdentifiers;
+ settings.AllowApproximateIdentifierDiscovery = this.AllowApproximateIdentifierDiscovery;
+ settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks;
return settings;
}
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 292da82..3edb13d 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -163,7 +163,7 @@ http://opensource.org/licenses/ms-pl.html
<CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs>
<CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions>
<CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly>
- <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ <CodeAnalysisRuleSet>Migrated rules for DotNetOpenAuth.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
@@ -303,6 +303,8 @@ http://opensource.org/licenses/ms-pl.html
<Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" />
<Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" />
<Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" />
+ <Compile Include="Mvc\OpenIdHelper.cs" />
+ <Compile Include="Mvc\OpenIdAjaxOptions.cs" />
<Compile Include="Messaging\StandardMessageFactory.cs" />
<Compile Include="OAuthWrap\AuthorizationState.cs" />
<Compile Include="OAuthWrap\IClientTokenManager.cs" />
@@ -552,7 +554,9 @@ http://opensource.org/licenses/ms-pl.html
<Compile Include="OpenId\RelyingParty\AssociationPreference.cs" />
<Compile Include="OpenId\RelyingParty\AuthenticationRequest.cs" />
<Compile Include="OpenId\RelyingParty\AuthenticationRequestMode.cs" />
+ <Compile Include="OpenId\RelyingParty\DuplicateRequestedHostsComparer.cs" />
<Compile Include="OpenId\RelyingParty\IProviderEndpoint.cs" />
+ <Compile Include="OpenId\RelyingParty\OpenIdAjaxRelyingParty.cs" />
<Compile Include="OpenId\RelyingParty\SelectorButtonContract.cs" />
<Compile Include="OpenId\RelyingParty\SelectorProviderButton.cs" />
<Compile Include="OpenId\RelyingParty\SelectorOpenIdButton.cs" />
@@ -688,7 +692,7 @@ http://opensource.org/licenses/ms-pl.html
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
- <EmbeddedResource Include="OpenId\RelyingParty\openid_login.gif" />
+ <EmbeddedResource Include="OpenId\RelyingParty\openid_login.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="OpenId\RelyingParty\login_failure.png" />
@@ -775,19 +779,19 @@ http://opensource.org/licenses/ms-pl.html
</ItemGroup>
<ItemGroup>
<SignDependsOn Include="BuildUnifiedProduct" />
- <DelaySignedAssemblies Include="$(ILMergeOutputAssembly);&#xD;&#xA; $(OutputPath)$(ProductName).Contracts.dll;&#xD;&#xA; " />
- </ItemGroup>
- <ItemGroup>
+ <DelaySignedAssemblies Include="$(ILMergeOutputAssembly);
+ $(OutputPath)CodeContracts\$(ProductName).Contracts.dll;
+ " />
<Folder Include="OAuthWrap\Messages\Device\" />
</ItemGroup>
- <PropertyGroup>
- <!-- Don't sign the non-unified version of the assembly. -->
- <SuppressTargetPathDelaySignedAssembly>true</SuppressTargetPathDelaySignedAssembly>
- </PropertyGroup>
+ <PropertyGroup>
+ <!-- Don't sign the non-unified version of the assembly. -->
+ <SuppressTargetPathDelaySignedAssembly>true</SuppressTargetPathDelaySignedAssembly>
+ </PropertyGroup>
<Target Name="BuildUnifiedProduct" DependsOnTargets="Build" Inputs="@(ILMergeInputAssemblies)" Outputs="$(ILMergeOutputAssembly)">
<MakeDir Directories="$(ILMergeOutputAssemblyDirectory)" />
<ILMerge ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" InputAssemblies="@(ILMergeInputAssemblies)" OutputFile="$(ILMergeOutputAssembly)" KeyFile="$(PublicKeyFile)" DelaySign="true" />
</Target>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
-</Project> \ No newline at end of file
+</Project>
diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs
index e436846..9b1bcfa 100644
--- a/src/DotNetOpenAuth/GlobalSuppressions.cs
+++ b/src/DotNetOpenAuth/GlobalSuppressions.cs
@@ -57,3 +57,6 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.Provider.IProviderBehavior.OnIncomingRequest(DotNetOpenAuth.OpenId.Provider.IRequest)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.Provider.IProviderBehavior.ApplySecuritySettings(DotNetOpenAuth.OpenId.Provider.ProviderSecuritySettings)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.Mvc")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Mvc", Scope = "namespace", Target = "DotNetOpenAuth.Mvc")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1903:UseOnlyApiFromTargetedFramework", MessageId = "System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
index 86c1118..ae45229 100644
--- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
+++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
@@ -268,6 +268,7 @@ namespace DotNetOpenAuth.InfoCard {
[Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)]
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "We construct a Uri to validate the format of the string.")]
[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "That overload is NOT the same.")]
+ [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "This can take ~/ paths.")]
public string PrivacyUrl {
get {
return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault;
@@ -570,24 +571,28 @@ namespace DotNetOpenAuth.InfoCard {
Panel supportedPanel = new Panel();
- if (!this.DesignMode) {
- // At the user agent, assume InfoCard is not supported until
- // the JavaScript discovers otherwise and reveals this panel.
- supportedPanel.Style[HtmlTextWriterStyle.Display] = "none";
- }
+ try {
+ if (!this.DesignMode) {
+ // At the user agent, assume InfoCard is not supported until
+ // the JavaScript discovers otherwise and reveals this panel.
+ supportedPanel.Style[HtmlTextWriterStyle.Display] = "none";
+ }
- supportedPanel.Controls.Add(this.CreateInfoCardImage());
+ supportedPanel.Controls.Add(this.CreateInfoCardImage());
- // trigger the selector at page load?
- if (this.AutoPopup && !this.Page.IsPostBack) {
- this.Page.ClientScript.RegisterStartupScript(
- typeof(InfoCardSelector),
- "selector_load_trigger",
- this.GetInfoCardSelectorActivationScript(true),
- true);
+ // trigger the selector at page load?
+ if (this.AutoPopup && !this.Page.IsPostBack) {
+ this.Page.ClientScript.RegisterStartupScript(
+ typeof(InfoCardSelector),
+ "selector_load_trigger",
+ this.GetInfoCardSelectorActivationScript(true),
+ true);
+ }
+ return supportedPanel;
+ } catch {
+ supportedPanel.Dispose();
+ throw;
}
-
- return supportedPanel;
}
/// <summary>
@@ -624,10 +629,15 @@ namespace DotNetOpenAuth.InfoCard {
Contract.Ensures(Contract.Result<Panel>() != null);
Panel unsupportedPanel = new Panel();
- if (this.UnsupportedTemplate != null) {
- this.UnsupportedTemplate.InstantiateIn(unsupportedPanel);
+ try {
+ if (this.UnsupportedTemplate != null) {
+ this.UnsupportedTemplate.InstantiateIn(unsupportedPanel);
+ }
+ return unsupportedPanel;
+ } catch {
+ unsupportedPanel.Dispose();
+ throw;
}
- return unsupportedPanel;
}
/// <summary>
@@ -692,13 +702,18 @@ namespace DotNetOpenAuth.InfoCard {
private Image CreateInfoCardImage() {
// add clickable image
Image image = new Image();
- image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize));
- image.AlternateText = InfoCardStrings.SelectorClickPrompt;
- image.ToolTip = this.ToolTip;
- image.Style[HtmlTextWriterStyle.Cursor] = "hand";
-
- image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false);
- return image;
+ try {
+ image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize));
+ image.AlternateText = InfoCardStrings.SelectorClickPrompt;
+ image.ToolTip = this.ToolTip;
+ image.Style[HtmlTextWriterStyle.Cursor] = "hand";
+
+ image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false);
+ return image;
+ } catch {
+ image.Dispose();
+ throw;
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
index 124f9f8..2ac2b7e 100644
--- a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
+++ b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
@@ -74,7 +74,13 @@ namespace DotNetOpenAuth.InfoCard {
public void AddDecryptingToken(X509Certificate2 certificate) {
Contract.Requires<ArgumentNullException>(certificate != null);
Contract.Requires<ArgumentException>(certificate.HasPrivateKey);
- this.AddDecryptingToken(new X509SecurityToken(certificate));
+ var cert = new X509SecurityToken(certificate);
+ try {
+ this.AddDecryptingToken(cert);
+ } catch {
+ cert.Dispose();
+ throw;
+ }
}
#if CONTRACTS_FULL
diff --git a/src/DotNetOpenAuth/InfoCard/Token/Token.cs b/src/DotNetOpenAuth/InfoCard/Token/Token.cs
index 7fa9a95..89fa3a3 100644
--- a/src/DotNetOpenAuth/InfoCard/Token/Token.cs
+++ b/src/DotNetOpenAuth/InfoCard/Token/Token.cs
@@ -49,16 +49,18 @@ namespace DotNetOpenAuth.InfoCard {
byte[] decryptedBytes;
string decryptedString;
- using (XmlReader tokenReader = XmlReader.Create(new StringReader(tokenXml))) {
- Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null
- if (IsEncrypted(tokenReader)) {
- Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml);
- decryptedBytes = decryptor.DecryptToken(tokenReader);
- decryptedString = Encoding.UTF8.GetString(decryptedBytes);
- Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here
- } else {
- decryptedBytes = Encoding.UTF8.GetBytes(tokenXml);
- decryptedString = tokenXml;
+ using (StringReader xmlReader = new StringReader(tokenXml)) {
+ using (XmlReader tokenReader = XmlReader.Create(xmlReader)) {
+ Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null
+ if (IsEncrypted(tokenReader)) {
+ Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml);
+ decryptedBytes = decryptor.DecryptToken(tokenReader);
+ decryptedString = Encoding.UTF8.GetString(decryptedBytes);
+ Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here
+ } else {
+ decryptedBytes = Encoding.UTF8.GetBytes(tokenXml);
+ decryptedString = tokenXml;
+ }
}
}
diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
index 48b7794..4ac871a 100644
--- a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
+++ b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
@@ -226,7 +226,9 @@ namespace DotNetOpenAuth.InfoCard {
int charMapLength = charMap.Length;
byte[] raw = Convert.FromBase64String(ppid);
- raw = SHA1.Create().ComputeHash(raw);
+ using (HashAlgorithm hasher = SHA1.Create()) {
+ raw = hasher.ComputeHash(raw);
+ }
StringBuilder callSign = new StringBuilder();
diff --git a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
index dd34d90..c9bc1d3 100644
--- a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
+++ b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
@@ -90,11 +90,16 @@ namespace DotNetOpenAuth.Messaging {
public override StreamReader GetResponseReader() {
this.ResponseStream.Seek(0, SeekOrigin.Begin);
string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
- if (string.IsNullOrEmpty(contentEncoding)) {
- return new StreamReader(this.ResponseStream);
- } else {
- return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding));
+ Encoding encoding = null;
+ if (!string.IsNullOrEmpty(contentEncoding)) {
+ try {
+ encoding = Encoding.GetEncoding(contentEncoding);
+ } catch (ArgumentException ex) {
+ Logger.Messaging.ErrorFormat("Encoding.GetEncoding(\"{0}\") threw ArgumentException: {1}", contentEncoding, ex);
+ }
}
+
+ return encoding != null ? new StreamReader(this.ResponseStream, encoding) : new StreamReader(this.ResponseStream);
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs
index df17dcb..03a8e6c 100644
--- a/src/DotNetOpenAuth/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth/Messaging/Channel.cs
@@ -696,27 +696,28 @@ namespace DotNetOpenAuth.Messaging {
WebHeaderCollection headers = new WebHeaderCollection();
headers.Add(HttpResponseHeader.ContentType, "text/html");
- StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture);
- StringBuilder hiddenFields = new StringBuilder();
- foreach (var field in fields) {
- hiddenFields.AppendFormat(
- "\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n",
- HttpUtility.HtmlEncode(field.Key),
- HttpUtility.HtmlEncode(field.Value));
+ using (StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture)) {
+ StringBuilder hiddenFields = new StringBuilder();
+ foreach (var field in fields) {
+ hiddenFields.AppendFormat(
+ "\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n",
+ HttpUtility.HtmlEncode(field.Key),
+ HttpUtility.HtmlEncode(field.Value));
+ }
+ bodyWriter.WriteLine(
+ IndirectMessageFormPostFormat,
+ HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri),
+ hiddenFields);
+ bodyWriter.Flush();
+ OutgoingWebResponse response = new OutgoingWebResponse {
+ Status = HttpStatusCode.OK,
+ Headers = headers,
+ Body = bodyWriter.ToString(),
+ OriginalMessage = message
+ };
+
+ return response;
}
- bodyWriter.WriteLine(
- IndirectMessageFormPostFormat,
- HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri),
- hiddenFields);
- bodyWriter.Flush();
- OutgoingWebResponse response = new OutgoingWebResponse {
- Status = HttpStatusCode.OK,
- Headers = headers,
- Body = bodyWriter.ToString(),
- OriginalMessage = message
- };
-
- return response;
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
index ea3bf6b..1be62f5 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30128.0
+// Runtime Version:4.0.30319.1
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -394,6 +394,15 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Looks up a localized string similar to An HttpContext.Current.Session object is required..
+ /// </summary>
+ internal static string SessionRequired {
+ get {
+ return ResourceManager.GetString("SessionRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Message signature was incorrect..
/// </summary>
internal static string SignatureInvalid {
diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
index 8b50767..cb80442 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
+++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
@@ -300,7 +300,10 @@
<data name="BinaryDataRequiresMultipart" xml:space="preserve">
<value>Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false.</value>
</data>
+ <data name="SessionRequired" xml:space="preserve">
+ <value>An HttpContext.Current.Session object is required.</value>
+ </data>
<data name="StandardMessageFactoryUnsupportedMessageType" xml:space="preserve">
<value>This message factory does not support message type(s): {0}</value>
</data>
-</root> \ No newline at end of file
+</root>
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index a52f51b..231637a 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -89,7 +89,7 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult.
/// </summary>
- /// <param name="response">The response to send to the uesr agent.</param>
+ /// <param name="response">The response to send to the user agent.</param>
/// <returns>The <see cref="ActionResult"/> instance to be returned by the Controller's action method.</returns>
public static ActionResult AsActionResult(this OutgoingWebResponse response) {
Contract.Requires<ArgumentNullException>(response != null);
@@ -176,6 +176,23 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Flattens the specified sequence of sequences.
+ /// </summary>
+ /// <typeparam name="T">The type of element contained in the sequence.</typeparam>
+ /// <param name="sequence">The sequence of sequences to flatten.</param>
+ /// <returns>A sequence of the contained items.</returns>
+ [Obsolete("Use Enumerable.SelectMany instead.")]
+ public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> sequence) {
+ ErrorUtilities.VerifyArgumentNotNull(sequence, "sequence");
+
+ foreach (IEnumerable<T> subsequence in sequence) {
+ foreach (T item in subsequence) {
+ yield return item;
+ }
+ }
+ }
+
+ /// <summary>
/// Sends a multipart HTTP POST request (useful for posting files) but doesn't call GetResponse on it.
/// </summary>
/// <param name="request">The HTTP request.</param>
diff --git a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs
index cf22bb2..cc655cf 100644
--- a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs
+++ b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs
@@ -121,7 +121,7 @@ namespace DotNetOpenAuth.Messaging {
/// Automatically sends the appropriate response to the user agent
/// and ends execution on the current page or handler.
/// </summary>
- /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
+ /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
/// <remarks>
/// Requires a current HttpContext.
/// </remarks>
@@ -137,7 +137,7 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <param name="context">The context of the HTTP request whose response should be set.
/// Typically this is <see cref="HttpContext.Current"/>.</param>
- /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
+ /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
public virtual void Send(HttpContext context) {
Contract.Requires<ArgumentNullException>(context != null);
@@ -183,7 +183,10 @@ namespace DotNetOpenAuth.Messaging {
/// would transmit the message that normally would be transmitted via a user agent redirect.
/// </summary>
/// <param name="channel">The channel to use for encoding.</param>
- /// <returns>The URL that would transmit the original message.</returns>
+ /// <returns>
+ /// The URL that would transmit the original message. This URL may exceed the normal 2K limit,
+ /// and should therefore be broken up manually and POSTed as form fields when it exceeds this length.
+ /// </returns>
/// <remarks>
/// This is useful for desktop applications that will spawn a user agent to transmit the message
/// rather than cause a redirect.
diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
index 08e2411..ae54c3a 100644
--- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
+++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
@@ -73,10 +73,10 @@ namespace DotNetOpenAuth.Messaging.Reflection {
Contract.Assume(str != null);
return bool.Parse(str);
};
- Func<string, Identifier> safeIdentfier = str => {
+ Func<string, Identifier> safeIdentifier = str => {
Contract.Assume(str != null);
ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected);
- return Identifier.Parse(str);
+ return Identifier.Parse(str, true);
};
Func<byte[], string> safeFromByteArray = bytes => {
Contract.Assume(bytes != null);
@@ -94,7 +94,7 @@ namespace DotNetOpenAuth.Messaging.Reflection {
Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc));
Map<byte[]>(safeFromByteArray, safeToByteArray);
Map<Realm>(realm => realm.ToString(), safeRealm);
- Map<Identifier>(id => id.ToString(), safeIdentfier);
+ Map<Identifier>(id => id.SerializedString, safeIdentifier);
Map<bool>(value => value.ToString().ToLowerInvariant(), safeBool);
Map<CultureInfo>(c => c.Name, str => new CultureInfo(str));
Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), str => str.Split(',').Select(s => new CultureInfo(s)).ToArray());
diff --git a/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset b/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset
index cee6f53..db238b6 100644
--- a/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset
+++ b/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset
@@ -5,5 +5,6 @@
<Rule Id="CA1054" Action="None" />
<Rule Id="CA1055" Action="None" />
<Rule Id="CA1056" Action="None" />
+ <Rule Id="CA2104" Action="None" />
</Rules>
</RuleSet> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs b/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs
new file mode 100644
index 0000000..9956966
--- /dev/null
+++ b/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdAjaxOptions.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Mvc {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// A set of customizations available for the scripts sent to the browser in AJAX OpenID scenarios.
+ /// </summary>
+ public class OpenIdAjaxOptions {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdAjaxOptions"/> class.
+ /// </summary>
+ public OpenIdAjaxOptions() {
+ this.AssertionHiddenFieldId = "openid_openidAuthData";
+ this.ReturnUrlHiddenFieldId = "ReturnUrl";
+ }
+
+ /// <summary>
+ /// Gets or sets the ID of the hidden field that should carry the positive assertion
+ /// until it is posted to the RP.
+ /// </summary>
+ public string AssertionHiddenFieldId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ID of the hidden field that should be set with the parent window/frame's URL
+ /// prior to posting the form with the positive assertion. Useful for jQuery popup dialogs.
+ /// </summary>
+ public string ReturnUrlHiddenFieldId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the index of the form in the document.forms array on the browser that should
+ /// be submitted when the user is ready to send the positive assertion to the RP.
+ /// </summary>
+ public int FormIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preloaded discovery results.
+ /// </summary>
+ public string PreloadedDiscoveryResults { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to print diagnostic trace messages in the browser.
+ /// </summary>
+ public bool ShowDiagnosticTrace { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to show all the "hidden" iframes that facilitate
+ /// asynchronous authentication of the user for diagnostic purposes.
+ /// </summary>
+ public bool ShowDiagnosticIFrame { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs b/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs
new file mode 100644
index 0000000..f276019
--- /dev/null
+++ b/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs
@@ -0,0 +1,432 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdHelper.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Mvc {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Text;
+ using System.Web;
+ using System.Web.Mvc;
+ using System.Web.Routing;
+ using System.Web.UI;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Methods that generate HTML or Javascript for hosting AJAX OpenID "controls" on
+ /// ASP.NET MVC web sites.
+ /// </summary>
+ public static class OpenIdHelper {
+ /// <summary>
+ /// Emits a series of stylesheet import tags to support the AJAX OpenID Selector.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <returns>HTML that should be sent directly to the browser.</returns>
+ public static string OpenIdSelectorStyles(this HtmlHelper html, Page page) {
+ Contract.Requires<ArgumentNullException>(html != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Ensures(Contract.Result<string>() != null);
+
+ StringWriter result = new StringWriter();
+ result.WriteStylesheetLink(page, OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName);
+ result.WriteStylesheetLink(page, OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedStylesheetResourceName);
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <returns>HTML that should be sent directly to the browser.</returns>
+ public static string OpenIdSelectorScripts(this HtmlHelper html, Page page) {
+ return OpenIdSelectorScripts(html, page, null, null);
+ }
+
+ /// <summary>
+ /// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="selectorOptions">An optional instance of an <see cref="OpenIdSelector"/> control, whose properties have been customized to express how this MVC control should be rendered.</param>
+ /// <param name="additionalOptions">An optional set of additional script customizations.</param>
+ /// <returns>
+ /// HTML that should be sent directly to the browser.
+ /// </returns>
+ public static string OpenIdSelectorScripts(this HtmlHelper html, Page page, OpenIdSelector selectorOptions, OpenIdAjaxOptions additionalOptions) {
+ Contract.Requires<ArgumentNullException>(html != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Ensures(Contract.Result<string>() != null);
+
+ if (selectorOptions == null) {
+ selectorOptions = new OpenId.RelyingParty.OpenIdSelector();
+ }
+
+ if (additionalOptions == null) {
+ additionalOptions = new OpenIdAjaxOptions();
+ }
+
+ StringWriter result = new StringWriter();
+
+ if (additionalOptions.ShowDiagnosticIFrame || additionalOptions.ShowDiagnosticTrace) {
+ string scriptFormat = @"window.openid_visible_iframe = {0}; // causes the hidden iframe to show up
+window.openid_trace = {1}; // causes lots of messages";
+ result.WriteScriptBlock(string.Format(
+ CultureInfo.InvariantCulture,
+ scriptFormat,
+ additionalOptions.ShowDiagnosticIFrame ? "true" : "false",
+ additionalOptions.ShowDiagnosticTrace ? "true" : "false"));
+ }
+ var scriptResources = new[] {
+ OpenIdRelyingPartyControlBase.EmbeddedJavascriptResource,
+ OpenIdRelyingPartyAjaxControlBase.EmbeddedAjaxJavascriptResource,
+ OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName,
+ };
+ result.WriteScriptTags(page, scriptResources);
+
+ if (selectorOptions.DownloadYahooUILibrary) {
+ result.WriteScriptTags(new[] { "https://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js" });
+ }
+
+ var blockBuilder = new StringWriter();
+ if (selectorOptions.DownloadYahooUILibrary) {
+ blockBuilder.WriteLine(@" try {
+ if (YAHOO) {
+ var loader = new YAHOO.util.YUILoader({
+ require: ['button', 'menu'],
+ loadOptional: false,
+ combine: true
+ });
+
+ loader.insert();
+ }
+ } catch (e) { }");
+ }
+
+ blockBuilder.WriteLine("window.aspnetapppath = '{0}';", VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath));
+
+ // Positive assertions can last no longer than this library is willing to consider them valid,
+ // and when they come with OP private associations they last no longer than the OP is willing
+ // to consider them valid. We assume the OP will hold them valid for at least five minutes.
+ double assertionLifetimeInMilliseconds = Math.Min(TimeSpan.FromMinutes(5).TotalMilliseconds, Math.Min(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime.TotalMilliseconds, DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime.TotalMilliseconds));
+ blockBuilder.WriteLine(
+ "{0} = {1};",
+ OpenIdRelyingPartyAjaxControlBase.MaxPositiveAssertionLifetimeJsName,
+ assertionLifetimeInMilliseconds.ToString(CultureInfo.InvariantCulture));
+
+ if (additionalOptions.PreloadedDiscoveryResults != null) {
+ blockBuilder.WriteLine(additionalOptions.PreloadedDiscoveryResults);
+ }
+
+ string discoverUrl = VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath) + html.RouteCollection["OpenIdDiscover"].GetVirtualPath(html.ViewContext.RequestContext, new RouteValueDictionary(new { identifier = "xxx" })).VirtualPath;
+ string blockFormat = @" {0} = function (argument, resultFunction, errorCallback) {{
+ jQuery.ajax({{
+ async: true,
+ dataType: 'text',
+ error: function (request, status, error) {{ errorCallback(status, argument); }},
+ success: function (result) {{ resultFunction(result, argument); }},
+ url: '{1}'.replace('xxx', encodeURIComponent(argument))
+ }});
+ }};";
+ blockBuilder.WriteLine(blockFormat, OpenIdRelyingPartyAjaxControlBase.CallbackJSFunctionAsync, discoverUrl);
+
+ blockFormat = @" window.postLoginAssertion = function (positiveAssertion) {{
+ $('#{0}')[0].setAttribute('value', positiveAssertion);
+ if ($('#{1}')[0] && !$('#{1}')[0].value) {{ // popups have no ReturnUrl predefined, but full page LogOn does.
+ $('#{1}')[0].setAttribute('value', window.parent.location.href);
+ }}
+ document.forms[{2}].submit();
+ }};";
+ blockBuilder.WriteLine(
+ blockFormat,
+ additionalOptions.AssertionHiddenFieldId,
+ additionalOptions.ReturnUrlHiddenFieldId,
+ additionalOptions.FormIndex);
+
+ blockFormat = @" $(function () {{
+ var box = document.getElementsByName('openid_identifier')[0];
+ initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5},
+ null, // js function to invoke on receiving a positive assertion
+ {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17},
+ false, // auto postback
+ null); // PostBackEventReference (unused in MVC)
+ }});";
+ blockBuilder.WriteLine(
+ blockFormat,
+ MessagingUtilities.GetSafeJavascriptValue(page.ClientScript.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenIdTextBox.EmbeddedLogoResourceName)),
+ MessagingUtilities.GetSafeJavascriptValue(page.ClientScript.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName)),
+ MessagingUtilities.GetSafeJavascriptValue(page.ClientScript.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)),
+ MessagingUtilities.GetSafeJavascriptValue(page.ClientScript.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginFailureResourceName)),
+ selectorOptions.Throttle,
+ selectorOptions.Timeout.TotalMilliseconds,
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnText),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnToolTip),
+ selectorOptions.TextBox.ShowLogOnPostBackButton ? "true" : "false",
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnPostBackToolTip),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryText),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryToolTip),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.BusyToolTip),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.IdentifierRequiredMessage),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnInProgressMessage),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationSucceededToolTip),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticatedAsToolTip),
+ MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationFailedToolTip));
+
+ result.WriteScriptBlock(blockBuilder.ToString());
+ result.WriteScriptTags(page, OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName);
+
+ Reporting.RecordFeatureUse("MVC " + typeof(OpenIdSelector).Name);
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Emits the HTML to render an OpenID Provider button as a part of the overall OpenID Selector UI.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="providerIdentifier">The OP Identifier.</param>
+ /// <param name="imageUrl">The URL of the image to display on the button.</param>
+ /// <returns>
+ /// HTML that should be sent directly to the browser.
+ /// </returns>
+ public static string OpenIdSelectorOPButton(this HtmlHelper html, Page page, Identifier providerIdentifier, string imageUrl) {
+ Contract.Requires<ArgumentNullException>(html != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Requires<ArgumentNullException>(providerIdentifier != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl));
+ Contract.Ensures(Contract.Result<string>() != null);
+
+ return OpenIdSelectorButton(html, page, providerIdentifier, "OPButton", imageUrl);
+ }
+
+ /// <summary>
+ /// Emits the HTML to render a generic OpenID button as a part of the overall OpenID Selector UI,
+ /// allowing the user to enter their own OpenID.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="imageUrl">The URL of the image to display on the button.</param>
+ /// <returns>
+ /// HTML that should be sent directly to the browser.
+ /// </returns>
+ public static string OpenIdSelectorOpenIdButton(this HtmlHelper html, Page page, string imageUrl) {
+ Contract.Requires<ArgumentNullException>(html != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl));
+ Contract.Ensures(Contract.Result<string>() != null);
+
+ return OpenIdSelectorButton(html, page, "OpenIDButton", "OpenIDButton", imageUrl);
+ }
+
+ /// <summary>
+ /// Emits the HTML to render the entire OpenID Selector UI.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="buttons">The buttons to include on the selector.</param>
+ /// <returns>
+ /// HTML that should be sent directly to the browser.
+ /// </returns>
+ public static string OpenIdSelector(this HtmlHelper html, Page page, params SelectorButton[] buttons) {
+ Contract.Requires<ArgumentNullException>(html != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Requires<ArgumentNullException>(buttons != null);
+ Contract.Ensures(Contract.Result<string>() != null);
+
+ var writer = new StringWriter();
+ var h = new HtmlTextWriter(writer);
+
+ h.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders");
+ h.RenderBeginTag(HtmlTextWriterTag.Ul);
+
+ foreach (SelectorButton button in buttons) {
+ var op = button as SelectorProviderButton;
+ if (op != null) {
+ h.Write(OpenIdSelectorOPButton(html, page, op.OPIdentifier, op.Image));
+ continue;
+ }
+
+ var openid = button as SelectorOpenIdButton;
+ if (openid != null) {
+ h.Write(OpenIdSelectorOpenIdButton(html, page, openid.Image));
+ continue;
+ }
+
+ ErrorUtilities.VerifySupported(false, "The {0} button is not yet supported for MVC.", button.GetType().Name);
+ }
+
+ h.RenderEndTag(); // ul
+
+ if (buttons.OfType<SelectorOpenIdButton>().Any()) {
+ h.Write(OpenIdAjaxTextBox(html));
+ }
+
+ return writer.ToString();
+ }
+
+ /// <summary>
+ /// Emits the HTML to render the <see cref="OpenIdAjaxTextBox"/> control as a part of the overall
+ /// OpenID Selector UI.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <returns>
+ /// HTML that should be sent directly to the browser.
+ /// </returns>
+ public static string OpenIdAjaxTextBox(this HtmlHelper html) {
+ return @"<div style='display: none' id='OpenIDForm'>
+ <span class='OpenIdAjaxTextBox' style='display: inline-block; position: relative; font-size: 16px'>
+ <input name='openid_identifier' id='openid_identifier' size='40' style='padding-left: 18px; border-style: solid; border-width: 1px; border-color: lightgray' />
+ </span>
+ </div>";
+ }
+
+ /// <summary>
+ /// Emits the HTML to render a button as a part of the overall OpenID Selector UI.
+ /// </summary>
+ /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="id">The value to assign to the HTML id attribute.</param>
+ /// <param name="cssClass">The value to assign to the HTML class attribute.</param>
+ /// <param name="imageUrl">The URL of the image to draw on the button.</param>
+ /// <returns>
+ /// HTML that should be sent directly to the browser.
+ /// </returns>
+ private static string OpenIdSelectorButton(this HtmlHelper html, Page page, string id, string cssClass, string imageUrl) {
+ Contract.Requires<ArgumentNullException>(html != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Requires<ArgumentNullException>(id != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl));
+ Contract.Ensures(Contract.Result<string>() != null);
+
+ var writer = new StringWriter();
+ var h = new HtmlTextWriter(writer);
+
+ h.AddAttribute(HtmlTextWriterAttribute.Id, id);
+ if (!string.IsNullOrEmpty(cssClass)) {
+ h.AddAttribute(HtmlTextWriterAttribute.Class, cssClass);
+ }
+ h.RenderBeginTag(HtmlTextWriterTag.Li);
+
+ h.AddAttribute(HtmlTextWriterAttribute.Href, "#");
+ h.RenderBeginTag(HtmlTextWriterTag.A);
+
+ h.RenderBeginTag(HtmlTextWriterTag.Div);
+ h.RenderBeginTag(HtmlTextWriterTag.Div);
+
+ h.AddAttribute(HtmlTextWriterAttribute.Src, imageUrl);
+ h.RenderBeginTag(HtmlTextWriterTag.Img);
+ h.RenderEndTag();
+
+ h.AddAttribute(HtmlTextWriterAttribute.Src, page.ClientScript.GetWebResourceUrl(typeof(OpenIdSelector), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName));
+ h.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess");
+ h.AddAttribute(HtmlTextWriterAttribute.Title, "Authenticated as {0}");
+ h.RenderBeginTag(HtmlTextWriterTag.Img);
+ h.RenderEndTag();
+
+ h.RenderEndTag(); // div
+
+ h.AddAttribute(HtmlTextWriterAttribute.Class, "ui-widget-overlay");
+ h.RenderBeginTag(HtmlTextWriterTag.Div);
+ h.RenderEndTag(); // div
+
+ h.RenderEndTag(); // div
+ h.RenderEndTag(); // a
+ h.RenderEndTag(); // li
+
+ return writer.ToString();
+ }
+
+ /// <summary>
+ /// Emits &lt;script&gt; tags that import a given set of scripts given their URLs.
+ /// </summary>
+ /// <param name="writer">The writer to emit the tags to.</param>
+ /// <param name="scriptUrls">The locations of the scripts to import.</param>
+ private static void WriteScriptTags(this TextWriter writer, IEnumerable<string> scriptUrls) {
+ Contract.Requires<ArgumentNullException>(writer != null);
+ Contract.Requires<ArgumentNullException>(scriptUrls != null);
+
+ foreach (string script in scriptUrls) {
+ writer.WriteLine("<script type='text/javascript' src='{0}'></script>", script);
+ }
+ }
+
+ /// <summary>
+ /// Writes out script tags that import a script from resources embedded in this assembly.
+ /// </summary>
+ /// <param name="writer">The writer to emit the tags to.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="resourceName">Name of the resource.</param>
+ private static void WriteScriptTags(this TextWriter writer, Page page, string resourceName) {
+ Contract.Requires<ArgumentNullException>(writer != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(resourceName));
+
+ WriteScriptTags(writer, page, new[] { resourceName });
+ }
+
+ /// <summary>
+ /// Writes out script tags that import scripts from resources embedded in this assembly.
+ /// </summary>
+ /// <param name="writer">The writer to emit the tags to.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="resourceNames">The resource names.</param>
+ private static void WriteScriptTags(this TextWriter writer, Page page, IEnumerable<string> resourceNames) {
+ Contract.Requires<ArgumentNullException>(writer != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Requires<ArgumentNullException>(resourceNames != null);
+
+ writer.WriteScriptTags(resourceNames.Select(r => page.ClientScript.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), r)));
+ }
+
+ /// <summary>
+ /// Writes a given script block, surrounding it with &lt;script&gt; and CDATA tags.
+ /// </summary>
+ /// <param name="writer">The writer to emit the tags to.</param>
+ /// <param name="script">The script to inline on the page.</param>
+ private static void WriteScriptBlock(this TextWriter writer, string script) {
+ Contract.Requires<ArgumentNullException>(writer != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(script));
+
+ writer.WriteLine("<script type='text/javascript' language='javascript'><!--");
+ writer.WriteLine("//<![CDATA[");
+ writer.WriteLine(script);
+ writer.WriteLine("//]]>--></script>");
+ }
+
+ /// <summary>
+ /// Writes a given CSS link.
+ /// </summary>
+ /// <param name="writer">The writer to emit the tags to.</param>
+ /// <param name="page">The page being rendered.</param>
+ /// <param name="resourceName">Name of the resource containing the CSS content.</param>
+ private static void WriteStylesheetLink(this TextWriter writer, Page page, string resourceName) {
+ Contract.Requires<ArgumentNullException>(writer != null);
+ Contract.Requires<ArgumentNullException>(page != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(resourceName));
+
+ WriteStylesheetLink(writer, page.ClientScript.GetWebResourceUrl(typeof(OpenIdRelyingPartyAjaxControlBase), resourceName));
+ }
+
+ /// <summary>
+ /// Writes a given CSS link.
+ /// </summary>
+ /// <param name="writer">The writer to emit the tags to.</param>
+ /// <param name="stylesheet">The stylesheet to link in.</param>
+ private static void WriteStylesheetLink(this TextWriter writer, string stylesheet) {
+ Contract.Requires<ArgumentNullException>(writer != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(stylesheet));
+
+ writer.WriteLine("<link rel='stylesheet' type='text/css' href='{0}' />", stylesheet);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
index ed41183..dc59b56 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
@@ -146,7 +146,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
ContentType contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]);
if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) {
foreach (string key in request.Form) {
- fields.Add(key, request.Form[key]);
+ if (key != null) {
+ fields.Add(key, request.Form[key]);
+ } else {
+ Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.Form[key]);
+ }
}
}
}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
index b45da66..cf09036 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
@@ -258,6 +258,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>.
/// </returns>
protected virtual bool IsSignatureValid(ITamperResistantOAuthMessage message) {
+ Contract.Requires<ArgumentNullException>(message != null);
+
string signature = this.GetSignature(message);
return message.Signature == signature;
}
diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs
index 62e91ec..3c7e89f 100644
--- a/src/DotNetOpenAuth/OpenId/Association.cs
+++ b/src/DotNetOpenAuth/OpenId/Association.cs
@@ -238,24 +238,28 @@ namespace DotNetOpenAuth.OpenId {
/// </returns>
public override int GetHashCode() {
HMACSHA1 hmac = new HMACSHA1(this.SecretKey);
- CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write);
+ try {
+ CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write);
- byte[] hbytes = ASCIIEncoding.ASCII.GetBytes(this.Handle);
+ byte[] hbytes = ASCIIEncoding.ASCII.GetBytes(this.Handle);
- cs.Write(hbytes, 0, hbytes.Length);
- cs.Close();
+ cs.Write(hbytes, 0, hbytes.Length);
+ cs.Close();
- byte[] hash = hmac.Hash;
- hmac.Clear();
+ byte[] hash = hmac.Hash;
+ hmac.Clear();
- long val = 0;
- for (int i = 0; i < hash.Length; i++) {
- val = val ^ (long)hash[i];
- }
+ long val = 0;
+ for (int i = 0; i < hash.Length; i++) {
+ val = val ^ (long)hash[i];
+ }
- val = val ^ this.Expires.ToFileTimeUtc();
+ val = val ^ this.Expires.ToFileTimeUtc();
- return (int)val;
+ return (int)val;
+ } finally {
+ ((IDisposable)hmac).Dispose();
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs b/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs
index 13633b4..7a20fd5 100644
--- a/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs
+++ b/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs
@@ -20,12 +20,22 @@ namespace DotNetOpenAuth.OpenId {
/// </remarks>
internal class AssociationMemoryStore<TKey> : IAssociationStore<TKey> {
/// <summary>
+ /// How many association store requests should occur between each spring cleaning.
+ /// </summary>
+ private const int PeriodicCleaningFrequency = 10;
+
+ /// <summary>
/// For Relying Parties, this maps OP Endpoints to a set of associations with that endpoint.
/// For Providers, this keeps smart and dumb associations in two distinct pools.
/// </summary>
private Dictionary<TKey, Associations> serverAssocsTable = new Dictionary<TKey, Associations>();
/// <summary>
+ /// A counter to track how close we are to an expired association cleaning run.
+ /// </summary>
+ private int periodicCleaning;
+
+ /// <summary>
/// Stores a given association for later recall.
/// </summary>
/// <param name="distinguishingFactor">The distinguishing factor, either an OP Endpoint or smart/dumb mode.</param>
@@ -38,6 +48,13 @@ namespace DotNetOpenAuth.OpenId {
Associations server_assocs = this.serverAssocsTable[distinguishingFactor];
server_assocs.Set(association);
+
+ unchecked {
+ this.periodicCleaning++;
+ }
+ if (this.periodicCleaning % PeriodicCleaningFrequency == 0) {
+ this.ClearExpiredAssociations();
+ }
}
}
@@ -88,17 +105,6 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
- /// Clears all expired associations from the store.
- /// </summary>
- public void ClearExpiredAssociations() {
- lock (this) {
- foreach (Associations assocs in this.serverAssocsTable.Values) {
- assocs.ClearExpired();
- }
- }
- }
-
- /// <summary>
/// Gets the server associations for a given OP Endpoint or dumb/smart mode.
/// </summary>
/// <param name="distinguishingFactor">The distinguishing factor, either an OP Endpoint (for relying parties) or smart/dumb (for providers).</param>
@@ -112,5 +118,16 @@ namespace DotNetOpenAuth.OpenId {
return this.serverAssocsTable[distinguishingFactor];
}
}
+
+ /// <summary>
+ /// Clears all expired associations from the store.
+ /// </summary>
+ private void ClearExpiredAssociations() {
+ lock (this) {
+ foreach (Associations assocs in this.serverAssocsTable.Values) {
+ assocs.ClearExpired();
+ }
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs
index 580bdfd..01b74a1 100644
--- a/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs
+++ b/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs
@@ -65,7 +65,10 @@ namespace DotNetOpenAuth.OpenId.Behaviors {
/// without malfunctioning.
/// </remarks>
void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) {
- request.SpreadSregToAX(AXFormats);
+ // Don't create AX extensions for OpenID 1.x messages, since AX requires OpenID 2.0.
+ if (request.Provider.Version.Major >= 2) {
+ request.SpreadSregToAX(AXFormats);
+ }
}
/// <summary>
@@ -112,12 +115,7 @@ namespace DotNetOpenAuth.OpenId.Behaviors {
bool IProviderBehavior.OnIncomingRequest(IRequest request) {
var extensionRequest = request as Provider.HostProcessedRequest;
if (extensionRequest != null) {
- if (extensionRequest.GetExtension<ClaimsRequest>() == null) {
- ClaimsRequest sreg = extensionRequest.UnifyExtensionsAsSreg();
- if (sreg != null) {
- ((IProtocolMessageWithExtensions)extensionRequest.RequestMessage).Extensions.Add(sreg);
- }
- }
+ extensionRequest.UnifyExtensionsAsSreg();
}
return false;
diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs
index 43d6c03..370192a 100644
--- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs
+++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs
@@ -209,7 +209,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements {
/// or if unsolicited assertions should be rejected at the RP; otherwise <c>false</c>.
/// </returns>
private bool UseRequestNonce(IMessage message) {
- return message != null && (message.Version.Major < 2 || this.securitySettings.RejectUnsolicitedAssertions);
+ return message != null && (this.securitySettings.RejectUnsolicitedAssertions ||
+ (message.Version.Major < 2 && this.securitySettings.ProtectDownlevelReplayAttacks));
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs
index 9e7ccd2..1b58c2f 100644
--- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs
@@ -130,7 +130,8 @@ namespace DotNetOpenAuth.OpenId.Extensions {
/// <summary>
/// Looks for Simple Registration and Attribute Exchange (all known formats)
- /// request extensions and returns them as a Simple Registration extension.
+ /// request extensions and returns them as a Simple Registration extension,
+ /// and adds the new extension to the original request message if it was absent.
/// </summary>
/// <param name="request">The authentication request.</param>
/// <returns>
@@ -140,7 +141,7 @@ namespace DotNetOpenAuth.OpenId.Extensions {
internal static ClaimsRequest UnifyExtensionsAsSreg(this Provider.IHostProcessedRequest request) {
Contract.Requires<ArgumentNullException>(request != null);
- var req = (Provider.AuthenticationRequest)request;
+ var req = (Provider.HostProcessedRequest)request;
var sreg = req.GetExtension<ClaimsRequest>();
if (sreg != null) {
return sreg;
@@ -148,7 +149,7 @@ namespace DotNetOpenAuth.OpenId.Extensions {
var ax = req.GetExtension<FetchRequest>();
if (ax != null) {
- sreg = new ClaimsRequest();
+ sreg = new ClaimsRequest(SimpleRegistration.Constants.sreg_ns);
sreg.Synthesized = true;
((IProtocolMessageWithExtensions)req.RequestMessage).Extensions.Add(sreg);
sreg.BirthDate = GetDemandLevelFor(ax, WellKnownAttributes.BirthDate.WholeBirthDate);
@@ -176,9 +177,9 @@ namespace DotNetOpenAuth.OpenId.Extensions {
/// </remarks>
internal static void ConvertSregToMatchRequest(this Provider.IHostProcessedRequest request) {
var req = (Provider.HostProcessedRequest)request;
- var response = (IProtocolMessageWithExtensions)req.Response;
+ var response = req.Response as IProtocolMessageWithExtensions; // negative responses don't support extensions.
var sregRequest = request.GetExtension<ClaimsRequest>();
- if (sregRequest != null) {
+ if (sregRequest != null && response != null) {
if (sregRequest.Synthesized) {
var axRequest = request.GetExtension<FetchRequest>();
ErrorUtilities.VerifyInternal(axRequest != null, "How do we have a synthesized Sreg request without an AX request?");
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs
index 76a1c9b..f178647 100644
--- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs
@@ -61,6 +61,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI {
/// </summary>
public UIRequest() {
this.LanguagePreference = new[] { CultureInfo.CurrentUICulture };
+ this.Mode = UIModes.Popup;
}
/// <summary>
@@ -75,12 +76,11 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI {
public CultureInfo[] LanguagePreference { get; set; }
/// <summary>
- /// Gets the style of UI that the RP is hosting the OP's authentication page in.
+ /// Gets or sets the style of UI that the RP is hosting the OP's authentication page in.
/// </summary>
/// <value>Some value from the <see cref="UIModes"/> class. Defaults to <see cref="UIModes.Popup"/>.</value>
- [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Design is to allow this later to be changable when more than one value exists.")]
[MessagePart("mode", AllowEmpty = false, IsRequired = true)]
- public string Mode { get { return UIModes.Popup; } }
+ public string Mode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Relying Party has an icon
diff --git a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
index e96f362..ba9852e 100644
--- a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
+++ b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
@@ -110,29 +110,29 @@ namespace DotNetOpenAuth.OpenId {
var results = new List<IdentifierDiscoveryResult>();
string signingHost;
- var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost);
-
- if (response != null) {
- try {
- var document = new XrdsDocument(XmlReader.Create(response.ResponseStream));
- ValidateXmlDSig(document, uriIdentifier, response, signingHost);
- var xrds = GetXrdElements(document, uriIdentifier.Uri.Host);
-
- // Look for claimed identifier template URIs for an additional XRDS document.
- results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler));
-
- // If we couldn't find any claimed identifiers, look for OP identifiers.
- // Normally this would be the opposite (OP Identifiers take precedence over
- // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers
- // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers,
- // which would break positive assertion checks).
- if (results.Count == 0) {
- results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier));
+ using (var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost)) {
+ if (response != null) {
+ try {
+ var document = new XrdsDocument(XmlReader.Create(response.ResponseStream));
+ ValidateXmlDSig(document, uriIdentifier, response, signingHost);
+ var xrds = GetXrdElements(document, uriIdentifier.Uri.Host);
+
+ // Look for claimed identifier template URIs for an additional XRDS document.
+ results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler));
+
+ // If we couldn't find any claimed identifiers, look for OP identifiers.
+ // Normally this would be the opposite (OP Identifiers take precedence over
+ // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers
+ // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers,
+ // which would break positive assertion checks).
+ if (results.Count == 0) {
+ results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier));
+ }
+
+ abortDiscoveryChain = true;
+ } catch (XmlException ex) {
+ Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex);
}
-
- abortDiscoveryChain = true;
- } catch (XmlException ex) {
- Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex);
}
}
@@ -188,10 +188,11 @@ namespace DotNetOpenAuth.OpenId {
Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri)));
string nextAuthority = nextAuthorityNode != null ? nextAuthorityNode.Value.Trim() : identifier.Uri.Host;
try {
- var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation);
- XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream));
- ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority);
- results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier));
+ using (var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation)) {
+ XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream));
+ ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority);
+ results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier));
+ }
} catch (ProtocolException ex) {
Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex);
} catch (XmlException ex) {
@@ -217,9 +218,9 @@ namespace DotNetOpenAuth.OpenId {
Contract.Requires<ArgumentNullException>(response != null);
var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver);
- ErrorUtilities.VerifyProtocol(signatureNode != null, "Missing Signature element.");
+ ErrorUtilities.VerifyProtocol(signatureNode != null, OpenIdStrings.MissingElement, "Signature");
var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver);
- ErrorUtilities.VerifyProtocol(signedInfoNode != null, "Missing SignedInfo element.");
+ ErrorUtilities.VerifyProtocol(signedInfoNode != null, OpenIdStrings.MissingElement, "SignedInfo");
ErrorUtilities.VerifyProtocol(
signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null,
"Unrecognized or missing canonicalization method.");
@@ -227,16 +228,19 @@ namespace DotNetOpenAuth.OpenId {
signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null,
"Unrecognized or missing signature method.");
var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver);
- ErrorUtilities.VerifyProtocol(certNodes.Count > 0, "Missing X509Certificate element.");
+ ErrorUtilities.VerifyProtocol(certNodes.Count > 0, OpenIdStrings.MissingElement, "X509Certificate");
var certs = certNodes.Cast<XPathNavigator>().Select(n => new X509Certificate2(Convert.FromBase64String(n.Value.Trim()))).ToList();
// Verify that we trust the signer of the certificates.
// Start by trying to validate just the certificate used to sign the XRDS document,
// since we can do that with partial trust.
+ Logger.OpenId.Debug("Verifying that we trust the certificate used to sign the discovery document.");
if (!certs[0].Verify()) {
// We couldn't verify just the signing certificate, so try to verify the whole certificate chain.
try {
+ Logger.OpenId.Debug("Verifying the whole certificate chain.");
VerifyCertChain(certs);
+ Logger.OpenId.Debug("Certificate chain verified.");
} catch (SecurityException) {
Logger.Yadis.Warn("Signing certificate verification failed and we have insufficient code access security permissions to perform certificate chain validation.");
ErrorUtilities.ThrowProtocol(OpenIdStrings.X509CertificateNotTrusted);
@@ -303,7 +307,7 @@ namespace DotNetOpenAuth.OpenId {
request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy;
request.Accept = ContentTypes.Xrds;
var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None;
- var response = requestHandler.GetResponse(request, options);
+ var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan);
if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) {
Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType);
}
@@ -342,23 +346,24 @@ namespace DotNetOpenAuth.OpenId {
private Uri GetXrdsLocation(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) {
Contract.Requires<ArgumentNullException>(identifier != null);
Contract.Requires<ArgumentNullException>(requestHandler != null);
- var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost);
- if (hostMetaResponse == null) {
- return null;
- }
+ using (var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost)) {
+ if (hostMetaResponse == null) {
+ return null;
+ }
- using (var sr = hostMetaResponse.GetResponseReader()) {
- string line = sr.ReadLine();
- Match m = HostMetaLink.Match(line);
- if (m.Success) {
- Uri location = new Uri(m.Groups["location"].Value);
- Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri);
- return location;
+ using (var sr = hostMetaResponse.GetResponseReader()) {
+ string line = sr.ReadLine();
+ Match m = HostMetaLink.Match(line);
+ if (m.Success) {
+ Uri location = new Uri(m.Groups["location"].Value);
+ Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri);
+ return location;
+ }
}
- }
- Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri);
- return null;
+ Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri);
+ return null;
+ }
}
/// <summary>
@@ -381,13 +386,19 @@ namespace DotNetOpenAuth.OpenId {
if (identifier.IsDiscoverySecureEndToEnd) {
options |= DirectWebRequestOptions.RequireSsl;
}
- var response = requestHandler.GetResponse(request, options);
- if (response.Status == HttpStatusCode.OK) {
- Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation);
- signingHost = hostMetaProxy.GetSigningHost(identifier);
- return response;
- } else {
- Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation);
+ var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan);
+ try {
+ if (response.Status == HttpStatusCode.OK) {
+ Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation);
+ signingHost = hostMetaProxy.GetSigningHost(identifier);
+ return response;
+ } else {
+ Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation);
+ response.Dispose();
+ }
+ } catch {
+ response.Dispose();
+ throw;
}
}
diff --git a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs
index 71d8652..fb4487c 100644
--- a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs
+++ b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs
@@ -36,6 +36,12 @@ namespace DotNetOpenAuth.OpenId {
/// <see cref="System.Uri"/> for consumers (to distinguish associations across servers) or
/// <see cref="AssociationRelyingPartyType"/> for providers (to distinguish dumb and smart client associations).
/// </typeparam>
+ /// <remarks>
+ /// Expired associations should be periodically cleared out of an association store.
+ /// This should be done frequently enough to avoid a memory leak, but sparingly enough
+ /// to not be a performance drain. Because this balance can vary by host, it is the
+ /// responsibility of the host to initiate this cleaning.
+ /// </remarks>
////[ContractClass(typeof(IAssociationStoreContract<>))]
public interface IAssociationStore<TKey> {
/// <summary>
@@ -84,17 +90,6 @@ namespace DotNetOpenAuth.OpenId {
/// before this call.
/// </remarks>
bool RemoveAssociation(TKey distinguishingFactor, string handle);
-
- /// <summary>
- /// Clears all expired associations from the store.
- /// </summary>
- /// <remarks>
- /// If another algorithm is in place to periodically clear out expired associations,
- /// this method call may be ignored.
- /// This should be done frequently enough to avoid a memory leak, but sparingly enough
- /// to not be a performance drain.
- /// </remarks>
- void ClearExpiredAssociations();
}
// For some odd reason, having this next class causes our test project to fail to build with this error:
diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs
index 2ab5360..36ec784 100644
--- a/src/DotNetOpenAuth/OpenId/Identifier.cs
+++ b/src/DotNetOpenAuth/OpenId/Identifier.cs
@@ -39,6 +39,18 @@ namespace DotNetOpenAuth.OpenId {
public string OriginalString { get; private set; }
/// <summary>
+ /// Gets the Identifier in the form in which it should be serialized.
+ /// </summary>
+ /// <value>
+ /// For Identifiers that were originally deserialized, this is the exact same
+ /// string that was deserialized. For Identifiers instantiated in some other way, this is
+ /// the normalized form of the string used to instantiate the identifier.
+ /// </value>
+ internal virtual string SerializedString {
+ get { return this.IsDeserializedInstance ? this.OriginalString : this.ToString(); }
+ }
+
+ /// <summary>
/// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal
/// based solely on their string reprsentations.
/// </summary>
@@ -59,6 +71,18 @@ namespace DotNetOpenAuth.OpenId {
protected internal bool IsDiscoverySecureEndToEnd { get; private set; }
/// <summary>
+ /// Gets a value indicating whether this instance was initialized from
+ /// deserializing a message.
+ /// </summary>
+ /// <remarks>
+ /// This is interesting because when an Identifier comes from the network,
+ /// we can't normalize it and then expect signatures to still verify.
+ /// But if the Identifier is initialized locally, we can and should normalize it
+ /// before serializing it.
+ /// </remarks>
+ protected bool IsDeserializedInstance { get; private set; }
+
+ /// <summary>
/// Converts the string representation of an Identifier to its strong type.
/// </summary>
/// <param name="identifier">The identifier.</param>
@@ -118,11 +142,32 @@ namespace DotNetOpenAuth.OpenId {
Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(identifier));
Contract.Ensures(Contract.Result<Identifier>() != null);
+ return Parse(identifier, false);
+ }
+
+ /// <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>
+ /// <param name="serializeExactValue">if set to <c>true</c> this Identifier will serialize exactly as given rather than in its normalized form.</param>
+ /// <returns>
+ /// An <see cref="Identifier"/> instance for the given value.
+ /// </returns>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
+ public static Identifier Parse(string identifier, bool serializeExactValue) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(identifier));
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+
+ Identifier id;
if (XriIdentifier.IsValidXri(identifier)) {
- return new XriIdentifier(identifier);
+ id = new XriIdentifier(identifier);
} else {
- return new UriIdentifier(identifier);
+ id = new UriIdentifier(identifier);
}
+
+ id.IsDeserializedInstance = serializeExactValue;
+ return id;
}
/// <summary>
@@ -212,6 +257,19 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Reparses the specified identifier in order to be assured that the concrete type that
+ /// implements the identifier is one of the well-known ones.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <returns>Either <see cref="XriIdentifier"/> or <see cref="UriIdentifier"/>.</returns>
+ internal static Identifier Reparse(Identifier identifier) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+
+ return Parse(identifier, identifier.IsDeserializedInstance);
+ }
+
+ /// <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.
diff --git a/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs b/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs
index 3190920..c851f24 100644
--- a/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs
+++ b/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs
@@ -89,7 +89,7 @@ namespace DotNetOpenAuth.OpenId {
// not a derived type that will override expected behavior.
// Elsewhere in this class, we count on the fact that this property
// is either UriIdentifier or XriIdentifier. MockIdentifier messes it up.
- this.claimedIdentifier = value != null ? Identifier.Parse(value) : null;
+ this.claimedIdentifier = value != null ? Identifier.Reparse(value) : null;
}
}
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index 29315bb..43283ac 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30104.0
+// Runtime Version:4.0.30319.1
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -196,6 +196,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to This OpenID exploits features that this relying party cannot reliably verify. Please try logging in with a human-readable OpenID or from a different OpenID Provider..
+ /// </summary>
+ internal static string ClaimedIdentifierDefiesDotNetNormalization {
+ get {
+ return ResourceManager.GetString("ClaimedIdentifierDefiesDotNetNormalization", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The ClaimedIdentifier property must be set first..
/// </summary>
internal static string ClaimedIdentifierMustBeSetFirst {
@@ -416,6 +425,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to Missing {0} element..
+ /// </summary>
+ internal static string MissingElement {
+ get {
+ return ResourceManager.GetString("MissingElement", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to No recognized association type matches the requested length of {0}..
/// </summary>
internal static string NoAssociationTypeFoundByLength {
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index ae68fe6..fab03a9 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -349,4 +349,10 @@ Discovered endpoint info:
<data name="X509CertificateNotTrusted" xml:space="preserve">
<value>The X.509 certificate used to sign this document is not trusted.</value>
</data>
+ <data name="ClaimedIdentifierDefiesDotNetNormalization" xml:space="preserve">
+ <value>This OpenID exploits features that this relying party cannot reliably verify. Please try logging in with a human-readable OpenID or from a different OpenID Provider.</value>
+ </data>
+ <data name="MissingElement" xml:space="preserve">
+ <value>Missing {0} element.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs
index 445978e..e792a81 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs
@@ -98,11 +98,15 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// </remarks>
public static IAuthenticationRequest PendingAuthenticationRequest {
get {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
Contract.Ensures(Contract.Result<IAuthenticationRequest>() == null || PendingRequest != null);
return HttpContext.Current.Session[PendingRequestKey] as IAuthenticationRequest;
}
set {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
HttpContext.Current.Session[PendingRequestKey] = value;
}
}
@@ -118,11 +122,15 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// </remarks>
public static IAnonymousRequest PendingAnonymousRequest {
get {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
Contract.Ensures(Contract.Result<IAnonymousRequest>() == null || PendingRequest != null);
return HttpContext.Current.Session[PendingRequestKey] as IAnonymousRequest;
}
set {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
HttpContext.Current.Session[PendingRequestKey] = value;
}
}
@@ -137,8 +145,17 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// before responding to the relying party's request.
/// </remarks>
public static IHostProcessedRequest PendingRequest {
- get { return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest; }
- set { HttpContext.Current.Session[PendingRequestKey] = value; }
+ get {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
+ return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest;
+ }
+
+ set {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
+ HttpContext.Current.Session[PendingRequestKey] = value;
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs
index f33a655..4fa2d64 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs
@@ -100,19 +100,6 @@ namespace DotNetOpenAuth.OpenId.Provider {
return this.associationStore.RemoveAssociation(distinguishingFactor, handle);
}
- /// <summary>
- /// Clears all expired associations from the store.
- /// </summary>
- /// <remarks>
- /// If another algorithm is in place to periodically clear out expired associations,
- /// this method call may be ignored.
- /// This should be done frequently enough to avoid a memory leak, but sparingly enough
- /// to not be a performance drain.
- /// </remarks>
- public void ClearExpiredAssociations() {
- this.associationStore.ClearExpiredAssociations();
- }
-
#endregion
#region INonceStore Members
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs
index 7d30a3f..ac70387 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs
@@ -10,6 +10,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
+ using System.Security;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId.ChannelElements;
@@ -148,10 +149,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
return null;
}
- var associateRequest = AssociateRequest.Create(this.securitySettings, provider);
-
- const int RenegotiateRetries = 1;
- return this.CreateNewAssociation(provider, associateRequest, RenegotiateRetries);
+ try {
+ var associateRequest = AssociateRequest.Create(this.securitySettings, provider);
+
+ const int RenegotiateRetries = 1;
+ return this.CreateNewAssociation(provider, associateRequest, RenegotiateRetries);
+ } catch (VerificationException ex) {
+ // See Trac ticket #163. In partial trust host environments, the
+ // Diffie-Hellman implementation we're using for HTTP OP endpoints
+ // sometimes causes the CLR to throw:
+ // "VerificationException: Operation could destabilize the runtime."
+ // Just give up and use dumb mode in this case.
+ Logger.OpenId.ErrorFormat("VerificationException occurred while trying to create an association with {0}. {1}", provider.Uri, ex);
+ return null;
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs
index e90c1d3..77d8ae1 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs
@@ -4,6 +4,8 @@
// </copyright>
//-----------------------------------------------------------------------
+using System.Threading;
+
namespace DotNetOpenAuth.OpenId.RelyingParty {
using System;
using System.Collections.Generic;
@@ -297,6 +299,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// <remarks>
/// This method requires an ASP.NET HttpContext.
/// </remarks>
+ /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
public void RedirectToProvider() {
this.RedirectingResponse.Send();
}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs
new file mode 100644
index 0000000..94eb5ba
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------
+// <copyright file="DuplicateRequestedHostsComparer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// An authentication request comparer that judges equality solely on the OP endpoint hostname.
+ /// </summary>
+ internal class DuplicateRequestedHostsComparer : IEqualityComparer<IAuthenticationRequest> {
+ /// <summary>
+ /// The singleton instance of this comparer.
+ /// </summary>
+ private static IEqualityComparer<IAuthenticationRequest> instance = new DuplicateRequestedHostsComparer();
+
+ /// <summary>
+ /// Prevents a default instance of the <see cref="DuplicateRequestedHostsComparer"/> class from being created.
+ /// </summary>
+ private DuplicateRequestedHostsComparer() {
+ }
+
+ /// <summary>
+ /// Gets the singleton instance of this comparer.
+ /// </summary>
+ internal static IEqualityComparer<IAuthenticationRequest> Instance {
+ get { return instance; }
+ }
+
+ #region IEqualityComparer<IAuthenticationRequest> Members
+
+ /// <summary>
+ /// Determines whether the specified objects are equal.
+ /// </summary>
+ /// <param name="x">The first object to compare.</param>
+ /// <param name="y">The second object to compare.</param>
+ /// <returns>
+ /// true if the specified objects are equal; otherwise, false.
+ /// </returns>
+ public bool Equals(IAuthenticationRequest x, IAuthenticationRequest y) {
+ if (x == null && y == null) {
+ return true;
+ }
+
+ if (x == null || y == null) {
+ return false;
+ }
+
+ // We'll distinguish based on the host name only, which
+ // admittedly is only a heuristic, but if we remove one that really wasn't a duplicate, well,
+ // this multiple OP attempt thing was just a convenience feature anyway.
+ return string.Equals(x.Provider.Uri.Host, y.Provider.Uri.Host, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Returns a hash code for the specified object.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.</param>
+ /// <returns>A hash code for the specified object.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
+ /// </exception>
+ public int GetHashCode(IAuthenticationRequest obj) {
+ return obj.Provider.Uri.Host.GetHashCode();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs
new file mode 100644
index 0000000..ae9fbdc
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs
@@ -0,0 +1,238 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdAjaxRelyingParty.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Mime;
+ using System.Text;
+ using System.Web;
+ using System.Web.Script.Serialization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Extensions.UI;
+
+ /// <summary>
+ /// Provides the programmatic facilities to act as an AJAX-enabled OpenID relying party.
+ /// </summary>
+ public class OpenIdAjaxRelyingParty : OpenIdRelyingParty {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class.
+ /// </summary>
+ public OpenIdAjaxRelyingParty() {
+ Reporting.RecordFeatureUse(this);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class.
+ /// </summary>
+ /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param>
+ public OpenIdAjaxRelyingParty(IRelyingPartyApplicationStore applicationStore)
+ : base(applicationStore) {
+ Reporting.RecordFeatureUse(this);
+ }
+
+ /// <summary>
+ /// Generates AJAX-ready authentication requests that can satisfy the requirements of some OpenID Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param>
+ /// <param name="realm">The shorest URL that describes this relying party web site's address.
+ /// For example, if your login page is found at https://www.example.com/login.aspx,
+ /// your realm would typically be https://www.example.com/.</param>
+ /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication
+ /// responses from the OpenID Provider.</param>
+ /// <returns>
+ /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
+ /// Never null, but may be empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>Any individual generated request can satisfy the authentication.
+ /// The generated requests are sorted in preferred order.
+ /// Each request is generated as it is enumerated to. Associations are created only as
+ /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
+ /// <para>No exception is thrown if no OpenID endpoints were discovered.
+ /// An empty enumerable is returned instead.</para>
+ /// </remarks>
+ public override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
+ var requests = base.CreateRequests(userSuppliedIdentifier, realm, returnToUrl);
+
+ // Alter the requests so that have AJAX characteristics.
+ // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example).
+ // Since we're gathering OPs to try one after the other, just take the first choice of each OP
+ // and don't try it multiple times.
+ requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance);
+
+ // Configure each generated request.
+ int reqIndex = 0;
+ foreach (var req in requests) {
+ // Inform ourselves in return_to that we're in a popup.
+ req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.UIPopupCallbackKey, "1");
+
+ if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) {
+ // Inform the OP that we'll be using a popup window consistent with the UI extension.
+ req.AddExtension(new UIRequest());
+
+ // Provide a hint for the client javascript about whether the OP supports the UI extension.
+ // This is so the window can be made the correct size for the extension.
+ // If the OP doesn't advertise support for the extension, the javascript will use
+ // a bigger popup window.
+ req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.PopupUISupportedJSHint, "1");
+ }
+
+ req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture));
+
+ // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter
+ if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) {
+ req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString);
+ }
+
+ // Our javascript needs to let the user know which endpoint responded. So we force it here.
+ // This gives us the info even for 1.0 OPs and 2.0 setup_required responses.
+ req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.OPEndpointParameterName, req.Provider.Uri.AbsoluteUri);
+ req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty);
+
+ // Inform ourselves in return_to that we're in a popup or iframe.
+ req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.UIPopupCallbackKey, "1");
+
+ // We append a # at the end so that if the OP happens to support it,
+ // the OpenID response "query string" is appended after the hash rather than before, resulting in the
+ // browser being super-speedy in closing the popup window since it doesn't try to pull a newer version
+ // of the static resource down from the server merely because of a changed URL.
+ // http://www.nabble.com/Re:-Defining-how-OpenID-should-behave-with-fragments-in-the-return_to-url-p22694227.html
+ ////TODO:
+
+ yield return req;
+ }
+ }
+
+ /// <summary>
+ /// Serializes discovery results on some <i>single</i> identifier on behalf of Javascript running on the browser.
+ /// </summary>
+ /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param>
+ /// <returns>
+ /// The JSON result to return to the user agent.
+ /// </returns>
+ /// <remarks>
+ /// We prepare a JSON object with this interface:
+ /// <code>
+ /// class jsonResponse {
+ /// string claimedIdentifier;
+ /// Array requests; // never null
+ /// string error; // null if no error
+ /// }
+ /// </code>
+ /// Each element in the requests array looks like this:
+ /// <code>
+ /// class jsonAuthRequest {
+ /// string endpoint; // URL to the OP endpoint
+ /// string immediate; // URL to initiate an immediate request
+ /// string setup; // URL to initiate a setup request.
+ /// }
+ /// </code>
+ /// </remarks>
+ public OutgoingWebResponse AsAjaxDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) {
+ Contract.Requires<ArgumentNullException>(requests != null);
+
+ var serializer = new JavaScriptSerializer();
+ return new OutgoingWebResponse {
+ Body = serializer.Serialize(this.AsJsonDiscoveryResult(requests)),
+ };
+ }
+
+ /// <summary>
+ /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries
+ /// an AJAX-aware OpenID control.
+ /// </summary>
+ /// <param name="requests">The discovery results to serialize as a JSON response.</param>
+ /// <returns>
+ /// The JSON result to return to the user agent.
+ /// </returns>
+ public string AsAjaxPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) {
+ Contract.Requires<ArgumentNullException>(requests != null);
+
+ var serializer = new JavaScriptSerializer();
+ string json = serializer.Serialize(this.AsJsonPreloadedDiscoveryResult(requests));
+
+ string script = "window.dnoa_internal.loadPreloadedDiscoveryResults(" + json + ");";
+ return script;
+ }
+
+ /// <summary>
+ /// Converts a sequence of authentication requests to a JSON object for seeding an AJAX-enabled login page.
+ /// </summary>
+ /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param>
+ /// <returns>A JSON object, not yet serialized.</returns>
+ internal object AsJsonDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) {
+ Contract.Requires<ArgumentNullException>(requests != null);
+
+ requests = requests.CacheGeneratedResults();
+
+ if (requests.Any()) {
+ return new {
+ claimedIdentifier = (string)requests.First().ClaimedIdentifier,
+ requests = requests.Select(req => new {
+ endpoint = req.Provider.Uri.AbsoluteUri,
+ immediate = this.GetRedirectUrl(req, true),
+ setup = this.GetRedirectUrl(req, false),
+ }).ToArray()
+ };
+ } else {
+ return new {
+ requests = new object[0],
+ error = OpenIdStrings.OpenIdEndpointNotFound,
+ };
+ }
+ }
+
+ /// <summary>
+ /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries
+ /// an AJAX-aware OpenID control.
+ /// </summary>
+ /// <param name="requests">The discovery results to serialize as a JSON response.</param>
+ /// <returns>
+ /// A JSON object, not yet serialized to a string.
+ /// </returns>
+ private object AsJsonPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) {
+ Contract.Requires<ArgumentNullException>(requests != null);
+
+ // We prepare a JSON object with this interface:
+ // Array discoveryWrappers;
+ // Where each element in the above array has this interface:
+ // class discoveryWrapper {
+ // string userSuppliedIdentifier;
+ // jsonResponse discoveryResult; // contains result of call to SerializeDiscoveryAsJson(Identifier)
+ // }
+ var json = (from request in requests
+ group request by request.DiscoveryResult.UserSuppliedIdentifier into requestsByIdentifier
+ select new {
+ userSuppliedIdentifier = (string)requestsByIdentifier.Key,
+ discoveryResult = this.AsJsonDiscoveryResult(requestsByIdentifier),
+ }).ToArray();
+
+ return json;
+ }
+
+ /// <summary>
+ /// Gets the full URL that carries an OpenID message, even if it exceeds the normal maximum size of a URL,
+ /// for purposes of sending to an AJAX component running in the browser.
+ /// </summary>
+ /// <param name="request">The authentication request.</param>
+ /// <param name="immediate"><c>true</c>to create a checkid_immediate request;
+ /// <c>false</c> to create a checkid_setup request.</param>
+ /// <returns>The absolute URL that carries the entire OpenID message.</returns>
+ private Uri GetRedirectUrl(IAuthenticationRequest request, bool immediate) {
+ Contract.Requires<ArgumentNullException>(request != null);
+
+ request.Mode = immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup;
+ return request.RedirectingResponse.GetDirectUriRequest(this.Channel);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs
index 097d065..d80bf6a 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs
@@ -64,6 +64,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
internal const bool DownloadYahooUILibraryDefault = true;
+ /// <summary>
+ /// The default value for the <see cref="Throttle"/> property.
+ /// </summary>
+ internal const int ThrottleDefault = 3;
+
#region Property viewstate keys
/// <summary>
@@ -221,11 +226,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
private const string AuthenticationFailedToolTipDefault = "Authentication failed.";
/// <summary>
- /// The default value for the <see cref="Throttle"/> property.
- /// </summary>
- private const int ThrottleDefault = 3;
-
- /// <summary>
/// The default value for the <see cref="LogOnText"/> property.
/// </summary>
private const string LogOnTextDefault = "LOG IN";
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs
index c13c61c..4aa78a5 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs
@@ -702,79 +702,104 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
// top row, left cell
cell = new TableCell();
- this.label = new HtmlGenericControl("label");
- this.label.InnerText = LabelTextDefault;
- cell.Controls.Add(this.label);
- row1.Cells.Add(cell);
+ try {
+ this.label = new HtmlGenericControl("label");
+ this.label.InnerText = LabelTextDefault;
+ cell.Controls.Add(this.label);
+ row1.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// top row, middle cell
cell = new TableCell();
- cell.Controls.Add(new InPlaceControl(this));
- row1.Cells.Add(cell);
+ try {
+ cell.Controls.Add(new InPlaceControl(this));
+ row1.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// top row, right cell
cell = new TableCell();
- this.loginButton = new Button();
- this.loginButton.ID = "loginButton";
- this.loginButton.Text = ButtonTextDefault;
- this.loginButton.ToolTip = ButtonToolTipDefault;
- this.loginButton.Click += this.LoginButton_Click;
- this.loginButton.ValidationGroup = ValidationGroupDefault;
+ try {
+ this.loginButton = new Button();
+ this.loginButton.ID = "loginButton";
+ this.loginButton.Text = ButtonTextDefault;
+ this.loginButton.ToolTip = ButtonToolTipDefault;
+ this.loginButton.Click += this.LoginButton_Click;
+ this.loginButton.ValidationGroup = ValidationGroupDefault;
#if !Mono
- this.panel.DefaultButton = this.loginButton.ID;
+ this.panel.DefaultButton = this.loginButton.ID;
#endif
- cell.Controls.Add(this.loginButton);
- row1.Cells.Add(cell);
+ cell.Controls.Add(this.loginButton);
+ row1.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// middle row, left cell
row2.Cells.Add(new TableCell());
// middle row, middle cell
cell = new TableCell();
- cell.Style[HtmlTextWriterStyle.Color] = "gray";
- cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
- this.requiredValidator = new RequiredFieldValidator();
- this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix;
- this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix;
- this.requiredValidator.Display = ValidatorDisplay.Dynamic;
- this.requiredValidator.ValidationGroup = ValidationGroupDefault;
- cell.Controls.Add(this.requiredValidator);
- this.identifierFormatValidator = new CustomValidator();
- this.identifierFormatValidator.ErrorMessage = UriFormatTextDefault + RequiredTextSuffix;
- this.identifierFormatValidator.Text = UriFormatTextDefault + RequiredTextSuffix;
- this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate;
- this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault;
- this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic;
- this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault;
- cell.Controls.Add(this.identifierFormatValidator);
- this.errorLabel = new Label();
- this.errorLabel.EnableViewState = false;
- this.errorLabel.ForeColor = System.Drawing.Color.Red;
- this.errorLabel.Style[HtmlTextWriterStyle.Display] = "block"; // puts it on its own line
- this.errorLabel.Visible = false;
- cell.Controls.Add(this.errorLabel);
- this.examplePrefixLabel = new Label();
- this.examplePrefixLabel.Text = ExamplePrefixDefault;
- cell.Controls.Add(this.examplePrefixLabel);
- cell.Controls.Add(new LiteralControl(" "));
- this.exampleUrlLabel = new Label();
- this.exampleUrlLabel.Font.Bold = true;
- this.exampleUrlLabel.Text = ExampleUrlDefault;
- cell.Controls.Add(this.exampleUrlLabel);
- row2.Cells.Add(cell);
+ try {
+ cell.Style[HtmlTextWriterStyle.Color] = "gray";
+ cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
+ this.requiredValidator = new RequiredFieldValidator();
+ this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix;
+ this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix;
+ this.requiredValidator.Display = ValidatorDisplay.Dynamic;
+ this.requiredValidator.ValidationGroup = ValidationGroupDefault;
+ cell.Controls.Add(this.requiredValidator);
+ this.identifierFormatValidator = new CustomValidator();
+ this.identifierFormatValidator.ErrorMessage = UriFormatTextDefault + RequiredTextSuffix;
+ this.identifierFormatValidator.Text = UriFormatTextDefault + RequiredTextSuffix;
+ this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate;
+ this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault;
+ this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic;
+ this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault;
+ cell.Controls.Add(this.identifierFormatValidator);
+ this.errorLabel = new Label();
+ this.errorLabel.EnableViewState = false;
+ this.errorLabel.ForeColor = System.Drawing.Color.Red;
+ this.errorLabel.Style[HtmlTextWriterStyle.Display] = "block"; // puts it on its own line
+ this.errorLabel.Visible = false;
+ cell.Controls.Add(this.errorLabel);
+ this.examplePrefixLabel = new Label();
+ this.examplePrefixLabel.Text = ExamplePrefixDefault;
+ cell.Controls.Add(this.examplePrefixLabel);
+ cell.Controls.Add(new LiteralControl(" "));
+ this.exampleUrlLabel = new Label();
+ this.exampleUrlLabel.Font.Bold = true;
+ this.exampleUrlLabel.Text = ExampleUrlDefault;
+ cell.Controls.Add(this.exampleUrlLabel);
+ row2.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// middle row, right cell
cell = new TableCell();
- cell.Style[HtmlTextWriterStyle.Color] = "gray";
- cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
- cell.Style[HtmlTextWriterStyle.TextAlign] = "center";
- this.registerLink = new HyperLink();
- this.registerLink.Text = RegisterTextDefault;
- this.registerLink.ToolTip = RegisterToolTipDefault;
- this.registerLink.NavigateUrl = RegisterUrlDefault;
- this.registerLink.Visible = RegisterVisibleDefault;
- cell.Controls.Add(this.registerLink);
- row2.Cells.Add(cell);
+ try {
+ cell.Style[HtmlTextWriterStyle.Color] = "gray";
+ cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
+ cell.Style[HtmlTextWriterStyle.TextAlign] = "center";
+ this.registerLink = new HyperLink();
+ this.registerLink.Text = RegisterTextDefault;
+ this.registerLink.ToolTip = RegisterToolTipDefault;
+ this.registerLink.NavigateUrl = RegisterUrlDefault;
+ this.registerLink.Visible = RegisterVisibleDefault;
+ cell.Controls.Add(this.registerLink);
+ row2.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// bottom row, left cell
cell = new TableCell();
@@ -782,17 +807,27 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
// bottom row, middle cell
cell = new TableCell();
- this.rememberMeCheckBox = new CheckBox();
- this.rememberMeCheckBox.Text = RememberMeTextDefault;
- this.rememberMeCheckBox.Checked = this.UsePersistentCookie != LogOnPersistence.Session;
- this.rememberMeCheckBox.Visible = RememberMeVisibleDefault;
- this.rememberMeCheckBox.CheckedChanged += this.RememberMeCheckBox_CheckedChanged;
- cell.Controls.Add(this.rememberMeCheckBox);
- row3.Cells.Add(cell);
+ try {
+ this.rememberMeCheckBox = new CheckBox();
+ this.rememberMeCheckBox.Text = RememberMeTextDefault;
+ this.rememberMeCheckBox.Checked = this.UsePersistentCookie != LogOnPersistence.Session;
+ this.rememberMeCheckBox.Visible = RememberMeVisibleDefault;
+ this.rememberMeCheckBox.CheckedChanged += this.RememberMeCheckBox_CheckedChanged;
+ cell.Controls.Add(this.rememberMeCheckBox);
+ row3.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// bottom row, right cell
cell = new TableCell();
- row3.Cells.Add(cell);
+ try {
+ row3.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// this sets all the controls' tab indexes
this.TabIndex = TabIndexDefault;
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs
index dbf9530..8684bd1 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs
@@ -762,13 +762,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
IRelyingPartyApplicationStore store = this.Stateless ? null :
(this.CustomApplicationStore ?? DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore));
var rp = new OpenIdRelyingParty(store);
-
- // Only set RequireSsl to true, as we don't want to override
- // a .config setting of true with false.
- if (this.RequireSsl) {
- rp.SecuritySettings.RequireSsl = true;
+ try {
+ // Only set RequireSsl to true, as we don't want to override
+ // a .config setting of true with false.
+ if (this.RequireSsl) {
+ rp.SecuritySettings.RequireSsl = true;
+ }
+ return rp;
+ } catch {
+ rp.Dispose();
+ throw;
}
- return rp;
}
}
}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs
index a0a0fb9..a416f3a 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs
@@ -12,7 +12,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
+ using System.Globalization;
using System.Linq;
+ using System.Net;
+ using System.Net.Mime;
+ using System.Text;
using System.Web;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
@@ -33,10 +37,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
public delegate bool EndpointSelector(IProviderEndpoint endpoint);
/// <summary>
- /// Provides the programmatic facilities to act as an OpenId consumer.
+ /// Provides the programmatic facilities to act as an OpenID relying party.
/// </summary>
[ContractVerification(true)]
- public sealed class OpenIdRelyingParty : IDisposable {
+ public class OpenIdRelyingParty : IDisposable {
/// <summary>
/// The name of the key to use in the HttpApplication cache to store the
/// instance of <see cref="StandardRelyingPartyApplicationStore"/> to use.
@@ -54,6 +58,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
private readonly IList<IIdentifierDiscoveryService> discoveryServices = new List<IIdentifierDiscoveryService>(2);
/// <summary>
+ /// Backing field for the <see cref="NonVerifyingRelyingParty"/> property.
+ /// </summary>
+ private OpenIdRelyingParty nonVerifyingRelyingParty;
+
+ /// <summary>
+ /// The lock to obtain when initializing the <see cref="nonVerifyingRelyingParty"/> member.
+ /// </summary>
+ private object nonVerifyingRelyingPartyInitLock = new object();
+
+ /// <summary>
+ /// A dictionary of extension response types and the javascript member
+ /// name to map them to on the user agent.
+ /// </summary>
+ private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>();
+
+ /// <summary>
/// Backing field for the <see cref="SecuritySettings"/> property.
/// </summary>
private RelyingPartySecuritySettings securitySettings;
@@ -78,7 +98,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// <summary>
/// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class.
/// </summary>
- /// <param name="applicationStore">The application store. If null, the relying party will always operate in "dumb mode".</param>
+ /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param>
public OpenIdRelyingParty(IRelyingPartyApplicationStore applicationStore)
: this(applicationStore, applicationStore) {
}
@@ -108,11 +128,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
// Without a nonce store, we must rely on the Provider to protect against
// replay attacks. But only 2.0+ Providers can be expected to provide
// replay protection.
- if (nonceStore == null) {
- if (this.SecuritySettings.MinimumRequiredOpenIdVersion < ProtocolVersion.V20) {
- Logger.OpenId.Warn("Raising minimum OpenID version requirement for Providers to 2.0 to protect this stateless RP from replay attacks.");
- this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
- }
+ if (nonceStore == null &&
+ this.SecuritySettings.ProtectDownlevelReplayAttacks &&
+ this.SecuritySettings.MinimumRequiredOpenIdVersion < ProtocolVersion.V20) {
+ Logger.OpenId.Warn("Raising minimum OpenID version requirement for Providers to 2.0 to protect this stateless RP from replay attacks.");
+ this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
}
this.channel = new OpenIdChannel(associationStore, nonceStore, this.SecuritySettings);
@@ -270,6 +290,24 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
internal AssociationManager AssociationManager { get; private set; }
/// <summary>
+ /// Gets the <see cref="OpenIdRelyingParty"/> instance used to process authentication responses
+ /// without verifying the assertion or consuming nonces.
+ /// </summary>
+ protected OpenIdRelyingParty NonVerifyingRelyingParty {
+ get {
+ if (this.nonVerifyingRelyingParty == null) {
+ lock (this.nonVerifyingRelyingPartyInitLock) {
+ if (this.nonVerifyingRelyingParty == null) {
+ this.nonVerifyingRelyingParty = OpenIdRelyingParty.CreateNonVerifying();
+ }
+ }
+ }
+
+ return this.nonVerifyingRelyingParty;
+ }
+ }
+
+ /// <summary>
/// Creates an authentication request to verify that a user controls
/// some given Identifier.
/// </summary>
@@ -389,13 +427,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// <para>No exception is thrown if no OpenID endpoints were discovered.
/// An empty enumerable is returned instead.</para>
/// </remarks>
- public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
+ public virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null);
Contract.Requires<ArgumentNullException>(realm != null);
Contract.Requires<ArgumentNullException>(returnToUrl != null);
Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null);
- return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>();
+ return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>().CacheGeneratedResults();
}
/// <summary>
@@ -487,6 +525,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
/// </remarks>
public IAuthenticationResponse GetResponse() {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
return this.GetResponse(this.Channel.GetRequestFromContext());
}
@@ -533,6 +572,52 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
}
+ /// <summary>
+ /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication.
+ /// </summary>
+ /// <returns>The HTTP response to send to this HTTP request.</returns>
+ /// <remarks>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ public OutgoingWebResponse ProcessResponseFromPopup() {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+ Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
+
+ return this.ProcessResponseFromPopup(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param>
+ /// <returns>The HTTP response to send to this HTTP request.</returns>
+ public OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request) {
+ Contract.Requires<ArgumentNullException>(request != null);
+ Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
+
+ return this.ProcessResponseFromPopup(request, null);
+ }
+
+ /// <summary>
+ /// Allows an OpenID extension to read data out of an unverified positive authentication assertion
+ /// and send it down to the client browser so that Javascript running on the page can perform
+ /// some preprocessing on the extension data.
+ /// </summary>
+ /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam>
+ /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param>
+ /// <remarks>
+ /// This method should be called before <see cref="ProcessResponseFromPopup()"/>.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")]
+ public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName));
+ ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName);
+ foreach (var ext in this.clientScriptExtensions.Keys) {
+ ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName);
+ }
+ this.clientScriptExtensions.Add(typeof(T), propertyName);
+ }
+
#region IDisposable Members
/// <summary>
@@ -580,6 +665,66 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
+ /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param>
+ /// <param name="callback">The callback fired after the response status has been determined but before the Javascript response is formulated.</param>
+ /// <returns>
+ /// The HTTP response to send to this HTTP request.
+ /// </returns>
+ internal OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request, Action<AuthenticationStatus> callback) {
+ Contract.Requires<ArgumentNullException>(request != null);
+ Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
+
+ string extensionsJson = null;
+ var authResponse = this.NonVerifyingRelyingParty.GetResponse();
+ ErrorUtilities.VerifyProtocol(authResponse != null, "OpenID popup window or iframe did not recognize an OpenID response in the request.");
+
+ // Give the caller a chance to notify the hosting page and fill up the clientScriptExtensions collection.
+ if (callback != null) {
+ callback(authResponse.Status);
+ }
+
+ Logger.OpenId.DebugFormat("Popup or iframe callback from OP: {0}", request.Url);
+ Logger.Controls.DebugFormat(
+ "An authentication response was found in a popup window or iframe using a non-verifying RP with status: {0}",
+ authResponse.Status);
+ if (authResponse.Status == AuthenticationStatus.Authenticated) {
+ var extensionsDictionary = new Dictionary<string, string>();
+ foreach (var pair in this.clientScriptExtensions) {
+ IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key);
+ if (extension == null) {
+ continue;
+ }
+ var positiveResponse = (PositiveAuthenticationResponse)authResponse;
+ string js = extension.InitializeJavaScriptData(positiveResponse.Response);
+ if (!string.IsNullOrEmpty(js)) {
+ extensionsDictionary[pair.Value] = js;
+ }
+ }
+
+ extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true);
+ }
+
+ string payload = "document.URL";
+ if (request.HttpMethod == "POST") {
+ // Promote all form variables to the query string, but since it won't be passed
+ // to any server (this is a javascript window-to-window transfer) the length of
+ // it can be arbitrarily long, whereas it was POSTed here probably because it
+ // was too long for HTTP transit.
+ UriBuilder payloadUri = new UriBuilder(request.Url);
+ payloadUri.AppendQueryArgs(request.Form.ToDictionary());
+ payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri);
+ }
+
+ if (!string.IsNullOrEmpty(extensionsJson)) {
+ payload += ", " + extensionsJson;
+ }
+
+ return InvokeParentPageScript("dnoa_internal.processAuthorizationResult(" + payload + ")");
+ }
+
+ /// <summary>
/// Performs discovery on the specified identifier.
/// </summary>
/// <param name="identifier">The identifier to discover services for.</param>
@@ -619,8 +764,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- private void Dispose(bool disposing) {
+ protected virtual void Dispose(bool disposing) {
if (disposing) {
+ if (this.nonVerifyingRelyingParty != null) {
+ this.nonVerifyingRelyingParty.Dispose();
+ this.nonVerifyingRelyingParty = null;
+ }
+
// Tear off the instance member as a local variable for thread safety.
IDisposable disposableChannel = this.channel as IDisposable;
if (disposableChannel != null) {
@@ -630,6 +780,42 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
+ /// Invokes a method on a parent frame or window and closes the calling popup window if applicable.
+ /// </summary>
+ /// <param name="methodCall">The method to call on the parent window, including
+ /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param>
+ /// <returns>The entire HTTP response to send to the popup window or iframe to perform the invocation.</returns>
+ private static OutgoingWebResponse InvokeParentPageScript(string methodCall) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(methodCall));
+
+ Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall);
+ StringBuilder builder = new StringBuilder();
+ builder.AppendLine("<html><body><script type='text/javascript' language='javascript'><!--");
+ builder.AppendLine("//<![CDATA[");
+ builder.Append(@" var inPopup = !window.frameElement;
+ var objSrc = inPopup ? window.opener : window.frameElement;
+");
+
+ // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable,
+ // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already
+ // whether to call window.self.close() after the call.
+ string htmlFormat = @" if (inPopup) {{
+ objSrc.{0};
+ window.self.close();
+ }} else {{
+ objSrc.{0};
+ }}";
+ builder.AppendFormat(CultureInfo.InvariantCulture, htmlFormat, methodCall);
+ builder.AppendLine("//]]>--></script>");
+ builder.AppendLine("</body></html>");
+
+ var response = new OutgoingWebResponse();
+ response.Body = builder.ToString();
+ response.Headers.Add(HttpResponseHeader.ContentType, new ContentType("text/html").ToString());
+ return response;
+ }
+
+ /// <summary>
/// Called by derived classes when behaviors are added or removed.
/// </summary>
/// <param name="sender">The collection being modified.</param>
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
index 12d676b..f22645f 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
@@ -16,6 +16,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
using System.Linq;
using System.Text;
using System.Web;
+ using System.Web.Script.Serialization;
using System.Web.UI;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
@@ -31,30 +32,30 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
internal const string EmbeddedAjaxJavascriptResource = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdRelyingPartyAjaxControlBase.js";
/// <summary>
- /// The name of the javascript function that will initiate a synchronous callback.
+ /// The "dnoa.op_endpoint" string.
/// </summary>
- protected const string CallbackJSFunction = "window.dnoa_internal.callback";
+ internal const string OPEndpointParameterName = OpenIdUtilities.CustomParameterPrefix + "op_endpoint";
/// <summary>
- /// The name of the javascript function that will initiate an asynchronous callback.
+ /// The "dnoa.claimed_id" string.
/// </summary>
- protected const string CallbackJSFunctionAsync = "window.dnoa_internal.callbackAsync";
+ internal const string ClaimedIdParameterName = OpenIdUtilities.CustomParameterPrefix + "claimed_id";
/// <summary>
/// The name of the javascript field that stores the maximum time a positive assertion is
/// good for before it must be refreshed.
/// </summary>
- private const string MaxPositiveAssertionLifetimeJsName = "window.dnoa_internal.maxPositiveAssertionLifetime";
+ internal const string MaxPositiveAssertionLifetimeJsName = "window.dnoa_internal.maxPositiveAssertionLifetime";
/// <summary>
- /// The "dnoa.op_endpoint" string.
+ /// The name of the javascript function that will initiate an asynchronous callback.
/// </summary>
- private const string OPEndpointParameterName = OpenIdUtilities.CustomParameterPrefix + "op_endpoint";
+ protected internal const string CallbackJSFunctionAsync = "window.dnoa_internal.callbackAsync";
/// <summary>
- /// The "dnoa.claimed_id" string.
+ /// The name of the javascript function that will initiate a synchronous callback.
/// </summary>
- private const string ClaimedIdParameterName = OpenIdUtilities.CustomParameterPrefix + "claimed_id";
+ protected const string CallbackJSFunction = "window.dnoa_internal.callback";
#region Property viewstate keys
@@ -86,11 +87,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.None;
/// <summary>
- /// Backing field for the <see cref="RelyingPartyNonVerifying"/> property.
- /// </summary>
- private static OpenIdRelyingParty relyingPartyNonVerifying;
-
- /// <summary>
/// The authentication response that just came in.
/// </summary>
private IAuthenticationResponse authenticationResponse;
@@ -102,12 +98,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
private string discoveryResult;
/// <summary>
- /// A dictionary of extension response types and the javascript member
- /// name to map them to on the user agent.
- /// </summary>
- private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>();
-
- /// <summary>
/// Initializes a new instance of the <see cref="OpenIdRelyingPartyAjaxControlBase"/> class.
/// </summary>
protected OpenIdRelyingPartyAjaxControlBase() {
@@ -152,6 +142,31 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
+ /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use.
+ /// </summary>
+ /// <value>
+ /// The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file.
+ /// </value>
+ /// <remarks>
+ /// A performance optimization would be to store off the
+ /// instance as a static member in your web site and set it
+ /// to this property in your <see cref="Control.Load">Page.Load</see>
+ /// event since instantiating these instances can be expensive on
+ /// heavily trafficked web pages.
+ /// </remarks>
+ public override OpenIdRelyingParty RelyingParty {
+ get {
+ return base.RelyingParty;
+ }
+
+ set {
+ // Make sure we get an AJAX-ready instance.
+ ErrorUtilities.VerifyArgument(value is OpenIdAjaxRelyingParty, OpenIdStrings.TypeMustImplementX, typeof(OpenIdAjaxRelyingParty).Name);
+ base.RelyingParty = value;
+ }
+ }
+
+ /// <summary>
/// Gets the completed authentication response.
/// </summary>
public IAuthenticationResponse AuthenticationResponse {
@@ -193,22 +208,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
- /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field).
+ /// Gets the relying party as its AJAX type.
/// </summary>
- /// <value>Usually a concatenation of the control's name and <c>"_openidAuthData"</c>.</value>
- protected abstract string OpenIdAuthDataFormKey { get; }
+ protected OpenIdAjaxRelyingParty AjaxRelyingParty {
+ get { return (OpenIdAjaxRelyingParty)this.RelyingParty; }
+ }
/// <summary>
- /// Gets the relying party to use when verification of incoming messages is NOT wanted.
+ /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field).
/// </summary>
- private static OpenIdRelyingParty RelyingPartyNonVerifying {
- get {
- if (relyingPartyNonVerifying == null) {
- relyingPartyNonVerifying = OpenIdRelyingParty.CreateNonVerifying();
- }
- return relyingPartyNonVerifying;
- }
- }
+ /// <value>Usually a concatenation of the control's name and <c>"_openidAuthData"</c>.</value>
+ protected abstract string OpenIdAuthDataFormKey { get; }
/// <summary>
/// Gets or sets a value indicating whether an authentication in the page's view state
@@ -232,11 +242,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")]
public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse {
Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName));
- ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName);
- foreach (var ext in this.clientScriptExtensions.Keys) {
- ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName);
- }
- this.clientScriptExtensions.Add(typeof(T), propertyName);
+ this.RelyingParty.RegisterClientScriptExtension<T>(propertyName);
}
#region ICallbackEventHandler Members
@@ -263,27 +269,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
#endregion
/// <summary>
- /// Creates the authentication requests for a given user-supplied Identifier.
- /// </summary>
- /// <param name="identifier">The identifier to create a request for.</param>
- /// <returns>
- /// A sequence of authentication requests, any one of which may be
- /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.
- /// </returns>
- protected internal override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) {
- // If this control is actually a member of another OpenID RP control,
- // delegate creation of requests to the parent control.
- var parentOwner = this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().FirstOrDefault();
- if (parentOwner != null) {
- return parentOwner.CreateRequests(identifier);
- } else {
- // We delegate all our logic to another method, since invoking base. methods
- // within an iterator method results in unverifiable code.
- return this.CreateRequestsCore(base.CreateRequests(identifier));
- }
- }
-
- /// <summary>
/// Returns the results of a callback event that targets a control.
/// </summary>
/// <returns>The result of the callback.</returns>
@@ -305,7 +290,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
Logger.OpenId.InfoFormat("AJAX discovery on {0} requested.", userSuppliedIdentifier);
this.Identifier = userSuppliedIdentifier;
- this.discoveryResult = this.SerializeDiscoveryAsJson(this.Identifier);
+
+ var serializer = new JavaScriptSerializer();
+ IEnumerable<IAuthenticationRequest> requests = this.CreateRequests(this.Identifier);
+ this.discoveryResult = serializer.Serialize(this.AjaxRelyingParty.AsJsonDiscoveryResult(requests));
+ }
+
+ /// <summary>
+ /// Creates the relying party instance used to generate authentication requests.
+ /// </summary>
+ /// <param name="store">The store to pass to the relying party constructor.</param>
+ /// <returns>The instantiated relying party.</returns>
+ protected override OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store) {
+ return new OpenIdAjaxRelyingParty(store);
}
/// <summary>
@@ -323,8 +320,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <param name="identifiers">The identifiers to perform discovery on.</param>
protected void PreloadDiscovery(IEnumerable<Identifier> identifiers) {
- string discoveryResults = this.SerializeDiscoveryAsJson(identifiers);
- string script = "window.dnoa_internal.loadPreloadedDiscoveryResults(" + discoveryResults + ");";
+ string script = this.AjaxRelyingParty.AsAjaxPreloadedDiscoveryResult(
+ identifiers.SelectMany(id => this.CreateRequests(id)));
this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), this.ClientID, script, true);
}
@@ -402,6 +399,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract.");
base.Render(writer);
// Emit a hidden field to let the javascript on the user agent know if an
@@ -420,172 +418,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// Notifies the user agent via an AJAX response of a completed authentication attempt.
/// </summary>
protected override void ScriptClosingPopupOrIFrame() {
- Logger.OpenId.DebugFormat("AJAX (iframe) callback from OP: {0}", this.Page.Request.Url);
- string extensionsJson = null;
-
- var authResponse = RelyingPartyNonVerifying.GetResponse();
- Logger.Controls.DebugFormat(
- "The {0} control checked for an authentication response from a popup window or iframe using a non-verifying RP and found: {1}",
- this.ID,
- authResponse.Status);
- if (authResponse.Status == AuthenticationStatus.Authenticated) {
- this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection.
- var extensionsDictionary = new Dictionary<string, string>();
- foreach (var pair in this.clientScriptExtensions) {
- IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key);
- if (extension == null) {
- continue;
- }
- var positiveResponse = (PositiveAuthenticationResponse)authResponse;
- string js = extension.InitializeJavaScriptData(positiveResponse.Response);
- if (!string.IsNullOrEmpty(js)) {
- extensionsDictionary[pair.Value] = js;
- }
+ Action<AuthenticationStatus> callback = status => {
+ if (status == AuthenticationStatus.Authenticated) {
+ this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection.
}
+ };
- extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true);
- }
-
- string payload = "document.URL";
- if (Page.Request.HttpMethod == "POST") {
- // Promote all form variables to the query string, but since it won't be passed
- // to any server (this is a javascript window-to-window transfer) the length of
- // it can be arbitrarily long, whereas it was POSTed here probably because it
- // was too long for HTTP transit.
- UriBuilder payloadUri = new UriBuilder(Page.Request.Url);
- payloadUri.AppendQueryArgs(Page.Request.Form.ToDictionary());
- payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri);
- }
-
- if (!string.IsNullOrEmpty(extensionsJson)) {
- payload += ", " + extensionsJson;
- }
-
- this.CallbackUserAgentMethod("dnoa_internal.processAuthorizationResult(" + payload + ")");
- }
-
- /// <summary>
- /// Serializes the discovery of multiple identifiers as a JSON object.
- /// </summary>
- /// <param name="identifiers">The identifiers to perform discovery on and create requests for.</param>
- /// <returns>The serialized JSON object.</returns>
- private string SerializeDiscoveryAsJson(IEnumerable<Identifier> identifiers) {
- ErrorUtilities.VerifyArgumentNotNull(identifiers, "identifiers");
-
- // We prepare a JSON object with this interface:
- // Array discoveryWrappers;
- // Where each element in the above array has this interface:
- // class discoveryWrapper {
- // string userSuppliedIdentifier;
- // jsonResponse discoveryResult; // contains result of call to SerializeDiscoveryAsJson(Identifier)
- // }
- StringBuilder discoveryResultBuilder = new StringBuilder();
- discoveryResultBuilder.Append("[");
- foreach (var identifier in identifiers) { // TODO: parallelize discovery on these identifiers
- discoveryResultBuilder.Append("{");
- discoveryResultBuilder.AppendFormat("userSuppliedIdentifier: {0},", MessagingUtilities.GetSafeJavascriptValue(identifier));
- discoveryResultBuilder.AppendFormat("discoveryResult: {0}", this.SerializeDiscoveryAsJson(identifier));
- discoveryResultBuilder.Append("},");
- }
-
- discoveryResultBuilder.Length -= 1; // trim last comma
- discoveryResultBuilder.Append("]");
- return discoveryResultBuilder.ToString();
- }
-
- /// <summary>
- /// Serializes the results of discovery and the created auth requests as a JSON object
- /// for the user agent to initiate.
- /// </summary>
- /// <param name="identifier">The identifier to perform discovery on.</param>
- /// <returns>The JSON string.</returns>
- private string SerializeDiscoveryAsJson(Identifier identifier) {
- ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier");
-
- // We prepare a JSON object with this interface:
- // class jsonResponse {
- // string claimedIdentifier;
- // Array requests; // never null
- // string error; // null if no error
- // }
- // Each element in the requests array looks like this:
- // class jsonAuthRequest {
- // string endpoint; // URL to the OP endpoint
- // string immediate; // URL to initiate an immediate request
- // string setup; // URL to initiate a setup request.
- // }
- StringBuilder discoveryResultBuilder = new StringBuilder();
- discoveryResultBuilder.Append("{");
- try {
- IEnumerable<IAuthenticationRequest> requests = this.CreateRequests(identifier).CacheGeneratedResults();
- if (requests.Any()) {
- discoveryResultBuilder.AppendFormat("claimedIdentifier: {0},", MessagingUtilities.GetSafeJavascriptValue(requests.First().ClaimedIdentifier));
- discoveryResultBuilder.Append("requests: [");
- foreach (IAuthenticationRequest request in requests) {
- discoveryResultBuilder.Append("{");
- discoveryResultBuilder.AppendFormat("endpoint: {0},", MessagingUtilities.GetSafeJavascriptValue(request.Provider.Uri.AbsoluteUri));
- request.Mode = AuthenticationRequestMode.Immediate;
- OutgoingWebResponse response = request.RedirectingResponse;
- discoveryResultBuilder.AppendFormat("immediate: {0},", MessagingUtilities.GetSafeJavascriptValue(response.GetDirectUriRequest(this.RelyingParty.Channel).AbsoluteUri));
- request.Mode = AuthenticationRequestMode.Setup;
- response = request.RedirectingResponse;
- discoveryResultBuilder.AppendFormat("setup: {0}", MessagingUtilities.GetSafeJavascriptValue(response.GetDirectUriRequest(this.RelyingParty.Channel).AbsoluteUri));
- discoveryResultBuilder.Append("},");
- }
- discoveryResultBuilder.Length -= 1; // trim off last comma
- discoveryResultBuilder.Append("]");
- } else {
- discoveryResultBuilder.Append("requests: [],");
- discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(OpenIdStrings.OpenIdEndpointNotFound));
- }
- } catch (ProtocolException ex) {
- discoveryResultBuilder.Append("requests: [],");
- discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(ex.Message));
- }
-
- discoveryResultBuilder.Append("}");
- return discoveryResultBuilder.ToString();
- }
-
- /// <summary>
- /// Creates the authentication requests for a given user-supplied Identifier.
- /// </summary>
- /// <param name="requests">The authentication requests to prepare.</param>
- /// <returns>
- /// A sequence of authentication requests, any one of which may be
- /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.
- /// </returns>
- private IEnumerable<IAuthenticationRequest> CreateRequestsCore(IEnumerable<IAuthenticationRequest> requests) {
- ErrorUtilities.VerifyArgumentNotNull(requests, "requests"); // NO CODE CONTRACTS! (yield return used here)
-
- // Configure each generated request.
- int reqIndex = 0;
- foreach (var req in requests) {
- req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture));
-
- // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter
- if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) {
- Identifier userSuppliedIdentifier = ((AuthenticationRequest)req).DiscoveryResult.UserSuppliedIdentifier;
- req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString);
- }
-
- // Our javascript needs to let the user know which endpoint responded. So we force it here.
- // This gives us the info even for 1.0 OPs and 2.0 setup_required responses.
- req.SetUntrustedCallbackArgument(OPEndpointParameterName, req.Provider.Uri.AbsoluteUri);
- req.SetUntrustedCallbackArgument(ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty);
-
- // Inform ourselves in return_to that we're in a popup or iframe.
- req.SetUntrustedCallbackArgument(UIPopupCallbackKey, "1");
-
- // We append a # at the end so that if the OP happens to support it,
- // the OpenID response "query string" is appended after the hash rather than before, resulting in the
- // browser being super-speedy in closing the popup window since it doesn't try to pull a newer version
- // of the static resource down from the server merely because of a changed URL.
- // http://www.nabble.com/Re:-Defining-how-OpenID-should-behave-with-fragments-in-the-return_to-url-p22694227.html
- ////TODO:
+ OutgoingWebResponse response = this.RelyingParty.ProcessResponseFromPopup(
+ this.RelyingParty.Channel.GetRequestFromContext(),
+ callback);
- yield return req;
- }
+ response.Send();
}
/// <summary>
@@ -615,33 +458,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
- /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox,
- /// and closes the calling popup window if applicable.
- /// </summary>
- /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including
- /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param>
- private void CallbackUserAgentMethod(string methodCall) {
- Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall);
- Page.Response.Write(@"<html><body><script language='javascript'>
- var inPopup = !window.frameElement;
- var objSrc = inPopup ? window.opener : window.frameElement;
-");
-
- // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable,
- // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already
- // whether to call window.self.close() after the call.
- string htmlFormat = @" if (inPopup) {{
- objSrc.{0};
- window.self.close();
- }} else {{
- objSrc.{0};
- }}
-</script></body></html>";
- Page.Response.Write(string.Format(CultureInfo.InvariantCulture, htmlFormat, methodCall));
- Page.Response.End();
- }
-
- /// <summary>
/// Sets the window.aspnetapppath variable on the user agent so that cookies can be set with the proper path.
/// </summary>
private void SetWebAppPathOnUserAgent() {
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js
index 6faad56..4de5188 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js
@@ -701,7 +701,7 @@ window.dnoa_internal.PositiveAssertion = function(uri) {
};
window.dnoa_internal.clone = function(obj) {
- if (obj === null || typeof (obj) != 'object') {
+ if (obj === null || typeof (obj) != 'object' || !isNaN(obj)) { // !isNaN catches Date objects
return obj;
}
@@ -710,6 +710,10 @@ window.dnoa_internal.clone = function(obj) {
temp[key] = window.dnoa_internal.clone(obj[key]);
}
+ // Copy over some built-in methods that were not included in the above loop,
+ // but nevertheless may have been overridden.
+ temp.toString = window.dnoa_internal.clone(obj.toString);
+
return temp;
};
@@ -732,7 +736,7 @@ window.dnoa_internal.clearExpiredPositiveAssertions = function() {
var discoveryResult = window.dnoa_internal.discoveryResults[identifier];
if (typeof (discoveryResult) != 'object') { continue; } // skip functions
for (var i = 0; i < discoveryResult.length; i++) {
- if (discoveryResult[i].result === window.dnoa_internal.authSuccess) {
+ if (discoveryResult[i] && discoveryResult[i].result === window.dnoa_internal.authSuccess) {
if (new Date() - discoveryResult[i].successReceived > window.dnoa_internal.maxPositiveAssertionLifetime) {
// This positive assertion is too old, and may eventually be rejected by DNOA during verification.
// Let's clear out the positive assertion so it can be renewed.
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
index a4a60d1..5090ecd 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
@@ -92,6 +92,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
internal const string ReturnToReceivingControlId = OpenIdUtilities.CustomParameterPrefix + "receiver";
+ #region Protected internal callback parameter names
+
+ /// <summary>
+ /// The callback parameter to use for recognizing when the callback is in a popup window or hidden iframe.
+ /// </summary>
+ protected internal const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup";
+
+ /// <summary>
+ /// The parameter name to include in the formulated auth request so that javascript can know whether
+ /// the OP advertises support for the UI extension.
+ /// </summary>
+ protected internal const string PopupUISupportedJSHint = OpenIdUtilities.CustomParameterPrefix + "popupUISupported";
+
+ #endregion
+
#region Property category constants
/// <summary>
@@ -111,18 +126,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
#endregion
- #region Callback parameter names
-
- /// <summary>
- /// The callback parameter to use for recognizing when the callback is in a popup window or hidden iframe.
- /// </summary>
- protected const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup";
-
- /// <summary>
- /// The parameter name to include in the formulated auth request so that javascript can know whether
- /// the OP advertises support for the UI extension.
- /// </summary>
- protected const string PopupUISupportedJSHint = OpenIdUtilities.CustomParameterPrefix + "popupUISupported";
+ #region Private callback parameter names
/// <summary>
/// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property.
@@ -293,10 +297,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// heavily trafficked web pages.
/// </remarks>
[Browsable(false)]
- public OpenIdRelyingParty RelyingParty {
+ public virtual OpenIdRelyingParty RelyingParty {
get {
if (this.relyingParty == null) {
this.relyingParty = this.CreateRelyingParty();
+ this.ConfigureRelyingParty(this.relyingParty);
this.relyingPartyOwned = true;
}
return this.relyingParty;
@@ -353,6 +358,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
set {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(value));
+
if (Page != null && !DesignMode) {
// Validate new value by trying to construct a Realm object based on it.
new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure.
@@ -556,8 +563,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
protected internal virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) {
Contract.Requires<ArgumentNullException>(identifier != null);
- // Delegate to a private method to keep 'yield return' and Code Contract separate.
- return this.CreateRequestsCore(identifier);
+ // If this control is actually a member of another OpenID RP control,
+ // delegate creation of requests to the parent control.
+ var parentOwner = this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().FirstOrDefault();
+ if (parentOwner != null) {
+ return parentOwner.CreateRequests(identifier);
+ } else {
+ // Delegate to a private method to keep 'yield return' and Code Contract separate.
+ return this.CreateRequestsCore(identifier);
+ }
}
/// <summary>
@@ -634,6 +648,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
+ /// Notifies the user agent via an AJAX response of a completed authentication attempt.
+ /// </summary>
+ protected virtual void ScriptClosingPopupOrIFrame() {
+ this.RelyingParty.ProcessResponseFromPopup();
+ }
+
+ /// <summary>
/// Called when the <see cref="Identifier"/> property is changed.
/// </summary>
protected virtual void OnIdentifierChanged() {
@@ -769,29 +790,32 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// Creates the relying party instance used to generate authentication requests.
/// </summary>
/// <returns>The instantiated relying party.</returns>
- protected virtual OpenIdRelyingParty CreateRelyingParty() {
- return this.CreateRelyingParty(true);
+ protected OpenIdRelyingParty CreateRelyingParty() {
+ IRelyingPartyApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore);
+ return this.CreateRelyingParty(store);
}
/// <summary>
/// Creates the relying party instance used to generate authentication requests.
/// </summary>
- /// <param name="verifySignature">
- /// A value indicating whether message protections should be applied to the processed messages.
- /// Use <c>false</c> to postpone verification to a later time without invalidating nonces.
- /// </param>
+ /// <param name="store">The store to pass to the relying party constructor.</param>
/// <returns>The instantiated relying party.</returns>
- protected virtual OpenIdRelyingParty CreateRelyingParty(bool verifySignature) {
- IRelyingPartyApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore);
- var rp = verifySignature ? new OpenIdRelyingParty(store) : OpenIdRelyingParty.CreateNonVerifying();
+ protected virtual OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store) {
+ return new OpenIdRelyingParty(store);
+ }
+
+ /// <summary>
+ /// Configures the relying party.
+ /// </summary>
+ /// <param name="relyingParty">The relying party.</param>
+ protected virtual void ConfigureRelyingParty(OpenIdRelyingParty relyingParty) {
+ Contract.Requires<ArgumentNullException>(relyingParty != null);
// Only set RequireSsl to true, as we don't want to override
// a .config setting of true with false.
if (this.RequireSsl) {
- rp.SecuritySettings.RequireSsl = true;
+ relyingParty.SecuritySettings.RequireSsl = true;
}
-
- return rp;
}
/// <summary>
@@ -841,21 +865,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
- /// Wires the popup window to close itself and pass the authentication result to the parent window.
- /// </summary>
- protected virtual void ScriptClosingPopupOrIFrame() {
- StringBuilder startupScript = new StringBuilder();
- startupScript.AppendLine("window.opener.dnoa_internal.processAuthorizationResult(document.URL);");
- startupScript.AppendLine("window.close();");
-
- this.Page.ClientScript.RegisterStartupScript(typeof(OpenIdRelyingPartyControlBase), "loginPopupClose", startupScript.ToString(), true);
-
- // TODO: alternately we should probably take over rendering this page here to avoid
- // a lot of unnecessary work on the server and possible momentary display of the
- // page in the popup window.
- }
-
- /// <summary>
/// Creates the identifier-persisting cookie, either for saving or deleting.
/// </summary>
/// <param name="response">The positive authentication response; or <c>null</c> to clear the cookie.</param>
@@ -939,7 +948,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) {
// Inform the OP that we'll be using a popup window consistent with the UI extension.
- req.AddExtension(new UIRequest());
+ // But beware that the extension MAY have already been added if we're using
+ // the OpenIdAjaxRelyingParty class.
+ if (!((AuthenticationRequest)req).Extensions.OfType<UIRequest>().Any()) {
+ req.AddExtension(new UIRequest());
+ }
// Provide a hint for the client javascript about whether the OP supports the UI extension.
// This is so the window can be made the correct size for the extension.
@@ -1029,67 +1042,5 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
return false;
}
-
- /// <summary>
- /// An authentication request comparer that judges equality solely on the OP endpoint hostname.
- /// </summary>
- private class DuplicateRequestedHostsComparer : IEqualityComparer<IAuthenticationRequest> {
- /// <summary>
- /// The singleton instance of this comparer.
- /// </summary>
- private static IEqualityComparer<IAuthenticationRequest> instance = new DuplicateRequestedHostsComparer();
-
- /// <summary>
- /// Prevents a default instance of the <see cref="DuplicateRequestedHostsComparer"/> class from being created.
- /// </summary>
- private DuplicateRequestedHostsComparer() {
- }
-
- /// <summary>
- /// Gets the singleton instance of this comparer.
- /// </summary>
- internal static IEqualityComparer<IAuthenticationRequest> Instance {
- get { return instance; }
- }
-
- #region IEqualityComparer<IAuthenticationRequest> Members
-
- /// <summary>
- /// Determines whether the specified objects are equal.
- /// </summary>
- /// <param name="x">The first object to compare.</param>
- /// <param name="y">The second object to compare.</param>
- /// <returns>
- /// true if the specified objects are equal; otherwise, false.
- /// </returns>
- public bool Equals(IAuthenticationRequest x, IAuthenticationRequest y) {
- if (x == null && y == null) {
- return true;
- }
-
- if (x == null || y == null) {
- return false;
- }
-
- // We'll distinguish based on the host name only, which
- // admittedly is only a heuristic, but if we remove one that really wasn't a duplicate, well,
- // this multiple OP attempt thing was just a convenience feature anyway.
- return string.Equals(x.Provider.Uri.Host, y.Provider.Uri.Host, StringComparison.OrdinalIgnoreCase);
- }
-
- /// <summary>
- /// Returns a hash code for the specified object.
- /// </summary>
- /// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.</param>
- /// <returns>A hash code for the specified object.</returns>
- /// <exception cref="T:System.ArgumentNullException">
- /// The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
- /// </exception>
- public int GetHashCode(IAuthenticationRequest obj) {
- return obj.Provider.Uri.Host.GetHashCode();
- }
-
- #endregion
- }
}
}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs
index e93383d..b7a54eb 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs
@@ -81,11 +81,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
private HiddenField positiveAssertionField;
/// <summary>
- /// A field to store the value to set on the <see cref="textBox"/> control after it's created.
- /// </summary>
- private bool downloadYuiLibrary = OpenIdAjaxTextBox.DownloadYahooUILibraryDefault;
-
- /// <summary>
/// Initializes a new instance of the <see cref="OpenIdSelector"/> class.
/// </summary>
public OpenIdSelector() {
@@ -102,6 +97,50 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
public event EventHandler<TokenProcessingErrorEventArgs> TokenProcessingError;
/// <summary>
+ /// Gets the text box where applicable.
+ /// </summary>
+ public OpenIdAjaxTextBox TextBox {
+ get {
+ this.EnsureChildControlsAreCreatedSafe();
+ return this.textBox;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with.
+ /// </summary>
+ [Browsable(true), DefaultValue(OpenIdAjaxTextBox.ThrottleDefault), Category(BehaviorCategory)]
+ [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")]
+ public int Throttle {
+ get {
+ this.EnsureChildControlsAreCreatedSafe();
+ return this.textBox.Throttle;
+ }
+
+ set {
+ this.EnsureChildControlsAreCreatedSafe();
+ this.textBox.Throttle = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.
+ /// </summary>
+ [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:08"), Category(BehaviorCategory)]
+ [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")]
+ public TimeSpan Timeout {
+ get {
+ this.EnsureChildControlsAreCreatedSafe();
+ return this.textBox.Timeout;
+ }
+
+ set {
+ this.EnsureChildControlsAreCreatedSafe();
+ this.textBox.Timeout = value;
+ }
+ }
+
+ /// <summary>
/// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds.
/// </summary>
[Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)]
@@ -126,19 +165,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
[Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")]
public bool DownloadYahooUILibrary {
get {
- return this.textBox != null ? this.textBox.DownloadYahooUILibrary : this.downloadYuiLibrary;
+ this.EnsureChildControlsAreCreatedSafe();
+ return this.textBox.DownloadYahooUILibrary;
}
set {
- // We don't just call EnsureChildControls() and then set the property on
- // this.textBox itself because (apparently) setting this property in the ASPX
- // page and thus calling this EnsureID() via EnsureChildControls() this early
- // results in no ID.
- if (this.textBox != null) {
- this.textBox.DownloadYahooUILibrary = value;
- } else {
- this.downloadYuiLibrary = value;
- }
+ this.EnsureChildControlsAreCreatedSafe();
+ this.textBox.DownloadYahooUILibrary = value;
}
}
@@ -207,10 +240,37 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
/// </summary>
protected override void CreateChildControls() {
+ this.EnsureChildControlsAreCreatedSafe();
+
base.CreateChildControls();
+
+ // Now do the ID specific work.
this.EnsureID();
ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(this.UniqueID), "Control.EnsureID() failed to give us a unique ID. Try setting an ID on the OpenIdSelector control. But please also file this bug with the project owners.");
+ this.Controls.Add(this.textBox);
+
+ this.positiveAssertionField.ID = this.ID + AuthDataFormKeySuffix;
+ this.Controls.Add(this.positiveAssertionField);
+ }
+
+ /// <summary>
+ /// Ensures that the child controls have been built, but doesn't set control
+ /// properties that require executing <see cref="Control.EnsureID"/> in order to avoid
+ /// certain initialization order problems.
+ /// </summary>
+ /// <remarks>
+ /// We don't just call EnsureChildControls() and then set the property on
+ /// this.textBox itself because (apparently) setting this property in the ASPX
+ /// page and thus calling this EnsureID() via EnsureChildControls() this early
+ /// results in no ID.
+ /// </remarks>
+ protected virtual void EnsureChildControlsAreCreatedSafe() {
+ // If we've already created the child controls, this method is a no-op.
+ if (this.textBox != null) {
+ return;
+ }
+
var selectorButton = this.Buttons.OfType<SelectorInfoCardButton>().FirstOrDefault();
if (selectorButton != null) {
var selector = selectorButton.InfoCardSelector;
@@ -225,12 +285,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
this.textBox.ID = "openid_identifier";
this.textBox.HookFormSubmit = false;
this.textBox.ShowLogOnPostBackButton = true;
- this.textBox.DownloadYahooUILibrary = this.downloadYuiLibrary;
- this.Controls.Add(this.textBox);
this.positiveAssertionField = new HiddenField();
- this.positiveAssertionField.ID = this.ID + AuthDataFormKeySuffix;
- this.Controls.Add(this.positiveAssertionField);
}
/// <summary>
@@ -254,11 +310,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
this.EnsureValidButtons();
var css = new HtmlLink();
- css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName);
- css.Attributes["rel"] = "stylesheet";
- css.Attributes["type"] = "text/css";
- ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer);
- this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override
+ try {
+ css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName);
+ css.Attributes["rel"] = "stylesheet";
+ css.Attributes["type"] = "text/css";
+ ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer);
+ this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override
+ } catch {
+ css.Dispose();
+ throw;
+ }
// Import the .js file where most of the code is.
this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdSelector), EmbeddedScriptResourceName);
@@ -288,6 +349,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract");
writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders");
writer.RenderBeginTag(HtmlTextWriterTag.Ul);
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs
index 4d635fb..335b435 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdTextBox.EmbeddedLogoResourceName, "image/gif")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdTextBox.EmbeddedLogoResourceName, "image/png")]
#pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use
@@ -45,7 +45,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// The name of the manifest stream containing the
/// OpenID logo that is placed inside the text box.
/// </summary>
- internal const string EmbeddedLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.openid_login.gif";
+ internal const string EmbeddedLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.openid_login.png";
/// <summary>
/// Default value for <see cref="TabIndex"/> property.
@@ -584,6 +584,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract.");
+
if (this.ShowLogo) {
string logoUrl = Page.ClientScript.GetWebResourceUrl(
typeof(OpenIdTextBox), EmbeddedLogoResourceName);
@@ -625,6 +627,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// true if the server control's state changes as a result of the postback; otherwise, false.
/// </returns>
protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
+ Contract.Assume(postCollection != null, "Missing contract");
+
// If the control was temporarily hidden, it won't be in the Form data,
// and we'll just implicitly keep the last Text setting.
if (postCollection[this.Name] != null) {
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
index b6a1b76..3e2298c 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
@@ -146,6 +146,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
}
+ // Check whether this particular identifier presents a problem with HTTP discovery
+ // due to limitations in the .NET Uri class.
+ UriIdentifier claimedIdUri = claimedId as UriIdentifier;
+ if (claimedIdUri != null && claimedIdUri.ProblematicNormalization) {
+ ErrorUtilities.VerifyProtocol(relyingParty.SecuritySettings.AllowApproximateIdentifierDiscovery, OpenIdStrings.ClaimedIdentifierDefiesDotNetNormalization);
+ Logger.OpenId.WarnFormat("Positive assertion for claimed identifier {0} cannot be precisely verified under partial trust hosting due to .NET limitation. An approximate verification will be attempted.", claimedId);
+ }
+
// While it LOOKS like we're performing discovery over HTTP again
// Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable
// which means that the .NET runtime is caching our discoveries for us. This turns out
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
index 071a488..a7686c5 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
@@ -16,11 +16,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
public sealed class RelyingPartySecuritySettings : SecuritySettings {
/// <summary>
+ /// The default value for the <see cref="ProtectDownlevelReplayAttacks"/> property.
+ /// </summary>
+ internal const bool ProtectDownlevelReplayAttacksDefault = true;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="RelyingPartySecuritySettings"/> class.
/// </summary>
internal RelyingPartySecuritySettings()
: base(false) {
this.PrivateSecretMaximumAge = TimeSpan.FromDays(7);
+ this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault;
+ this.AllowApproximateIdentifierDiscovery = true;
}
/// <summary>
@@ -126,6 +133,31 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
public bool AllowDualPurposeIdentifiers { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit
+ /// features that .NET does not have the ability to send exact HTTP requests for will
+ /// still be allowed by using an approximate HTTP request.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>true</c>.
+ /// </value>
+ public bool AllowApproximateIdentifierDiscovery { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether special measures are taken to
+ /// protect users from replay attacks when those users' identities are hosted
+ /// by OpenID 1.x Providers.
+ /// </summary>
+ /// <value>The default value is <c>true</c>.</value>
+ /// <remarks>
+ /// <para>Nonces for protection against replay attacks were not mandated
+ /// by OpenID 1.x, which leaves users open to replay attacks.</para>
+ /// <para>This feature works by adding a signed nonce to the authentication request.
+ /// This might increase the request size beyond what some OpenID 1.1 Providers
+ /// (such as Blogger) are capable of handling.</para>
+ /// </remarks>
+ internal bool ProtectDownlevelReplayAttacks { get; set; }
+
+ /// <summary>
/// Filters out any disallowed endpoints.
/// </summary>
/// <param name="endpoints">The endpoints discovered on an Identifier.</param>
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs
index 15b6ca7..ac4dcbf 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs
@@ -5,6 +5,7 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Drawing.Design;
@@ -24,6 +25,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
+ /// Initializes a new instance of the <see cref="SelectorOpenIdButton"/> class.
+ /// </summary>
+ /// <param name="imageUrl">The image to display on the button.</param>
+ public SelectorOpenIdButton(string imageUrl)
+ : this() {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl));
+
+ this.Image = imageUrl;
+ }
+
+ /// <summary>
/// Gets or sets the path to the image to display on the button's surface.
/// </summary>
/// <value>The virtual path to the image.</value>
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs
index 3a05287..2195e73 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs
@@ -5,6 +5,7 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Drawing.Design;
@@ -25,6 +26,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
+ /// Initializes a new instance of the <see cref="SelectorProviderButton"/> class.
+ /// </summary>
+ /// <param name="providerIdentifier">The OP Identifier.</param>
+ /// <param name="imageUrl">The image to display on the button.</param>
+ public SelectorProviderButton(Identifier providerIdentifier, string imageUrl)
+ : this() {
+ Contract.Requires<ArgumentNullException>(providerIdentifier != null);
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl));
+
+ this.OPIdentifier = providerIdentifier;
+ this.Image = imageUrl;
+ }
+
+ /// <summary>
/// Gets or sets the path to the image to display on the button's surface.
/// </summary>
/// <value>The virtual path to the image.</value>
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs
index f0a1574..fdb6931 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs
@@ -84,19 +84,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
return this.associationStore.RemoveAssociation(distinguishingFactor, handle);
}
- /// <summary>
- /// Clears all expired associations from the store.
- /// </summary>
- /// <remarks>
- /// If another algorithm is in place to periodically clear out expired associations,
- /// this method call may be ignored.
- /// This should be done frequently enough to avoid a memory leak, but sparingly enough
- /// to not be a performance drain.
- /// </remarks>
- public void ClearExpiredAssociations() {
- this.associationStore.ClearExpiredAssociations();
- }
-
#endregion
#region INonceStore Members
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.gif b/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.gif
deleted file mode 100644
index cde836c..0000000
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.gif
+++ /dev/null
Binary files differ
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.png b/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.png
new file mode 100644
index 0000000..caebd58
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.png
Binary files differ
diff --git a/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs
index f017d9f..7d17fd9 100644
--- a/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs
+++ b/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs
@@ -67,6 +67,7 @@ namespace DotNetOpenAuth.OpenId {
// Failing YADIS discovery of an XRDS document, we try HTML discovery.
if (endpoints.Count == 0) {
+ yadisResult.TryRevertToHtmlResponse();
var htmlEndpoints = new List<IdentifierDiscoveryResult>(DiscoverFromHtml(yadisResult.NormalizedUri, uriIdentifier, yadisResult.ResponseText));
if (htmlEndpoints.Any()) {
Logger.Yadis.DebugFormat("Total services discovered in HTML: {0}", htmlEndpoints.Count);
diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
index 0b7a0e3..278ae37 100644
--- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
+++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
@@ -10,6 +10,9 @@ namespace DotNetOpenAuth.OpenId {
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
+ using System.Reflection;
+ using System.Security;
+ using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;
using System.Xml;
@@ -30,6 +33,68 @@ namespace DotNetOpenAuth.OpenId {
private static readonly string[] allowedSchemes = { "http", "https" };
/// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// A value indicating whether scheme substitution is being used to workaround
+ /// .NET path compression that invalidates some OpenIDs that have trailing periods
+ /// in one of their path segments.
+ /// </summary>
+ private static bool schemeSubstitution;
+
+ /// <summary>
+ /// Initializes static members of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ /// <remarks>
+ /// This method attempts to workaround the .NET Uri class parsing bug described here:
+ /// https://connect.microsoft.com/VisualStudio/feedback/details/386695/system-uri-incorrectly-strips-trailing-dots?wa=wsignin1.0#tabs
+ /// since some identifiers (like some of the pseudonymous identifiers from Yahoo) include path segments
+ /// that end with periods, which the Uri class will typically trim off.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Some things just can't be done in a field initializer.")]
+ static UriIdentifier() {
+ // Our first attempt to handle trailing periods in path segments is to leverage
+ // full trust if it's available to rewrite the rules.
+ // In fact this is the ONLY way in .NET 3.5 (and arguably in .NET 4.0) to send
+ // outbound HTTP requests with trailing periods, so it's the only way to perform
+ // discovery on such an identifier.
+ try {
+ UriParser.Register(roundTrippingHttpParser, "dnoarthttp", 80);
+ UriParser.Register(roundTrippingHttpsParser, "dnoarthttps", 443);
+ UriParser.Register(publishableHttpParser, "dnoahttp", 80);
+ UriParser.Register(publishableHttpsParser, "dnoahttps", 443);
+ roundTrippingHttpParser.Initialize(false);
+ roundTrippingHttpsParser.Initialize(false);
+ publishableHttpParser.Initialize(true);
+ publishableHttpsParser.Initialize(true);
+ schemeSubstitution = true;
+ Logger.OpenId.Debug(".NET Uri class path compression overridden.");
+ Reporting.RecordFeatureUse("FullTrust");
+ } catch (SecurityException) {
+ // We must be running in partial trust. Nothing more we can do.
+ Logger.OpenId.Warn("Unable to coerce .NET to stop compressing URI paths due to partial trust limitations. Some URL identifiers may be unable to complete login.");
+ Reporting.RecordFeatureUse("PartialTrust");
+ }
+ }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="UriIdentifier"/> class.
/// </summary>
/// <param name="uri">The value this identifier will represent.</param>
@@ -62,7 +127,8 @@ namespace DotNetOpenAuth.OpenId {
/// 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) {
+ internal UriIdentifier(Uri uri)
+ : this(uri, false) {
}
/// <summary>
@@ -73,7 +139,13 @@ namespace DotNetOpenAuth.OpenId {
internal UriIdentifier(Uri uri, bool requireSslDiscovery)
: base(uri != null ? uri.OriginalString : null, requireSslDiscovery) {
Contract.Requires<ArgumentNullException>(uri != null);
- if (!TryCanonicalize(new UriBuilder(uri), out uri)) {
+
+ string uriAsString = uri.OriginalString;
+ if (schemeSubstitution) {
+ uriAsString = NormalSchemeToSpecialRoundTrippingScheme(uriAsString);
+ }
+
+ if (!TryCanonicalize(uriAsString, out uri)) {
throw new UriFormatException();
}
if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) {
@@ -96,6 +168,26 @@ namespace DotNetOpenAuth.OpenId {
internal bool SchemeImplicitlyPrepended { get; private set; }
/// <summary>
+ /// Gets a value indicating whether this Identifier has characters or patterns that
+ /// the <see cref="Uri"/> class normalizes away and invalidating the Identifier.
+ /// </summary>
+ internal bool ProblematicNormalization {
+ get {
+ if (schemeSubstitution) {
+ // With full trust, we have no problematic URIs
+ return false;
+ }
+
+ var simpleUri = new SimpleUri(this.OriginalString);
+ if (simpleUri.Path.EndsWith(".", StringComparison.Ordinal) || simpleUri.Path.Contains("./")) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ /// <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>
@@ -137,7 +229,12 @@ namespace DotNetOpenAuth.OpenId {
if (other == null) {
return false;
}
- return this.Uri == other.Uri;
+
+ if (this.ProblematicNormalization || other.ProblematicNormalization) {
+ return new SimpleUri(this.OriginalString).Equals(new SimpleUri(other.OriginalString));
+ } else {
+ return this.Uri == other.Uri;
+ }
}
/// <summary>
@@ -157,16 +254,13 @@ namespace DotNetOpenAuth.OpenId {
/// 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) {
- Uri normalizedUri;
- bool result = TryCanonicalize(uri, out normalizedUri);
- canonicalUri = normalizedUri.AbsoluteUri;
- return result;
+ if (this.ProblematicNormalization) {
+ return new SimpleUri(this.OriginalString).ToString();
+ } else {
+ return this.Uri.AbsoluteUri;
+ }
}
-#endif
+
/// <summary>
/// Determines whether a URI is a valid OpenID Identifier (of any kind).
/// </summary>
@@ -222,9 +316,7 @@ namespace DotNetOpenAuth.OpenId {
}
// Strip the fragment.
- UriBuilder builder = new UriBuilder(Uri);
- builder.Fragment = null;
- return builder.Uri;
+ return new UriIdentifier(this.OriginalString.Substring(0, this.OriginalString.IndexOf('#')));
}
/// <summary>
@@ -335,8 +427,12 @@ namespace DotNetOpenAuth.OpenId {
schemePrepended = true;
}
+ if (schemeSubstitution) {
+ uri = NormalSchemeToSpecialRoundTrippingScheme(uri);
+ }
+
// Use a UriBuilder because it helps to normalize the URL as well.
- return TryCanonicalize(new UriBuilder(uri), out canonicalUri);
+ return TryCanonicalize(uri, out canonicalUri);
} catch (UriFormatException) {
// We try not to land here with checks in the try block, but just in case.
return false;
@@ -344,9 +440,9 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
- /// Removes the fragment from a URL and sets the host to lowercase.
+ /// Fixes up the scheme if appropriate.
/// </summary>
- /// <param name="uriBuilder">The URI builder with the value to canonicalize.</param>
+ /// <param name="uri">The URI 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>
@@ -356,12 +452,48 @@ namespace DotNetOpenAuth.OpenId {
/// 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;
+ private static bool TryCanonicalize(string uri, out Uri canonicalUri) {
+ Contract.Requires<ArgumentNullException>(uri != null);
+
+ if (schemeSubstitution) {
+ UriBuilder uriBuilder = new UriBuilder(uri);
+
+ // Swap out our round-trippable scheme for the publishable (hidden) scheme.
+ uriBuilder.Scheme = uriBuilder.Scheme == roundTrippingHttpParser.RegisteredScheme ? publishableHttpParser.RegisteredScheme : publishableHttpsParser.RegisteredScheme;
+ canonicalUri = uriBuilder.Uri;
+ } else {
+ canonicalUri = new Uri(uri);
+ }
+
return true;
}
+ /// <summary>
+ /// Gets the special non-compressing scheme or URL for a standard scheme or URL.
+ /// </summary>
+ /// <param name="normal">The ordinary URL or scheme name.</param>
+ /// <returns>The non-compressing equivalent scheme or URL for the given value.</returns>
+ private static string NormalSchemeToSpecialRoundTrippingScheme(string normal) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(normal));
+ Contract.Requires<InternalErrorException>(schemeSubstitution);
+ Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
+
+ int delimiterIndex = normal.IndexOf(Uri.SchemeDelimiter);
+ string normalScheme = delimiterIndex < 0 ? normal : normal.Substring(0, delimiterIndex);
+ string nonCompressingScheme;
+ if (string.Equals(normalScheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpParser.RegisteredScheme;
+ } else if (string.Equals(normalScheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpsParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpsParser.RegisteredScheme;
+ } else {
+ throw new NotSupportedException();
+ }
+
+ return delimiterIndex < 0 ? nonCompressingScheme : nonCompressingScheme + normal.Substring(delimiterIndex);
+ }
+
#if CONTRACTS_FULL
/// <summary>
/// Verifies conditions that should be true for any valid state of this object.
@@ -374,5 +506,178 @@ namespace DotNetOpenAuth.OpenId {
Contract.Invariant(this.Uri.AbsoluteUri != null);
}
#endif
+
+ /// <summary>
+ /// A simple URI class that doesn't suffer from the parsing problems of the <see cref="Uri"/> class.
+ /// </summary>
+ internal class SimpleUri {
+ /// <summary>
+ /// URI characters that separate the URI Path from subsequent elements.
+ /// </summary>
+ private static readonly char[] PathEndingCharacters = new char[] { '?', '#' };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SimpleUri"/> class.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ internal SimpleUri(string value) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(value));
+
+ // Leverage the Uri class's parsing where we can.
+ Uri uri = new Uri(value);
+ this.Scheme = uri.Scheme;
+ this.Authority = uri.Authority;
+ this.Query = uri.Query;
+ this.Fragment = uri.Fragment;
+
+ // Get the Path out ourselves, since the default Uri parser compresses it too much for OpenID.
+ int schemeLength = value.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal);
+ Contract.Assume(schemeLength > 0);
+ int hostStart = schemeLength + Uri.SchemeDelimiter.Length;
+ int hostFinish = value.IndexOf('/', hostStart);
+ if (hostFinish < 0) {
+ this.Path = "/";
+ } else {
+ int pathFinish = value.IndexOfAny(PathEndingCharacters, hostFinish);
+ Contract.Assume(pathFinish >= hostFinish || pathFinish < 0);
+ if (pathFinish < 0) {
+ this.Path = value.Substring(hostFinish);
+ } else {
+ this.Path = value.Substring(hostFinish, pathFinish - hostFinish);
+ }
+ }
+
+ this.Path = NormalizePathEscaping(this.Path);
+ }
+
+ /// <summary>
+ /// Gets the scheme.
+ /// </summary>
+ /// <value>The scheme.</value>
+ public string Scheme { get; private set; }
+
+ /// <summary>
+ /// Gets the authority.
+ /// </summary>
+ /// <value>The authority.</value>
+ public string Authority { get; private set; }
+
+ /// <summary>
+ /// Gets the path of the URI.
+ /// </summary>
+ /// <value>The path from the URI.</value>
+ public string Path { get; private set; }
+
+ /// <summary>
+ /// Gets the query.
+ /// </summary>
+ /// <value>The query.</value>
+ public string Query { get; private set; }
+
+ /// <summary>
+ /// Gets the fragment.
+ /// </summary>
+ /// <value>The fragment.</value>
+ public string Fragment { get; private set; }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String"/> that represents this instance.
+ /// </returns>
+ public override string ToString() {
+ return this.Scheme + Uri.SchemeDelimiter + this.Authority + this.Path + this.Query + this.Fragment;
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
+ /// </summary>
+ /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
+ /// <returns>
+ /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ SimpleUri other = obj as SimpleUri;
+ if (other == null) {
+ return false;
+ }
+
+ // Note that this equality check is intentionally leaving off the Fragment part
+ // to match Uri behavior, and is intentionally being case sensitive and insensitive
+ // for different parts.
+ return string.Equals(this.Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Authority, other.Authority, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Path, other.Path, StringComparison.Ordinal) &&
+ string.Equals(this.Query, other.Query, StringComparison.Ordinal);
+ }
+
+ /// <summary>
+ /// Normalizes the characters that are escaped in the given URI path.
+ /// </summary>
+ /// <param name="path">The path to normalize.</param>
+ /// <returns>The given path, with exactly those characters escaped which should be.</returns>
+ private static string NormalizePathEscaping(string path) {
+ Contract.Requires<ArgumentNullException>(path != null);
+
+ string[] segments = path.Split('/');
+ for (int i = 0; i < segments.Length; i++) {
+ segments[i] = Uri.EscapeDataString(Uri.UnescapeDataString(segments[i]));
+ }
+
+ return string.Join("/", segments);
+ }
+ }
+
+ /// <summary>
+ /// A URI parser that does not compress paths, such as trimming trailing periods from path segments.
+ /// </summary>
+ private class NonPathCompressingUriParser : GenericUriParser {
+ /// <summary>
+ /// The field that stores the scheme that this parser is registered under.
+ /// </summary>
+ private static FieldInfo schemeField;
+
+ /// <summary>
+ /// The standard "http" or "https" scheme that this parser is subverting.
+ /// </summary>
+ private string standardScheme;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NonPathCompressingUriParser"/> class.
+ /// </summary>
+ /// <param name="standardScheme">The standard scheme that this parser will be subverting.</param>
+ public NonPathCompressingUriParser(string standardScheme)
+ : base(GenericUriParserOptions.DontCompressPath | GenericUriParserOptions.IriParsing | GenericUriParserOptions.Idn) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(standardScheme));
+ this.standardScheme = standardScheme;
+ }
+
+ /// <summary>
+ /// Gets the scheme this parser is registered under.
+ /// </summary>
+ /// <value>The registered scheme.</value>
+ internal string RegisteredScheme { get; private set; }
+
+ /// <summary>
+ /// Initializes this parser with the actual scheme it should appear to be.
+ /// </summary>
+ /// <param name="hideNonStandardScheme">if set to <c>true</c> Uris using this scheme will look like they're using the original standard scheme.</param>
+ [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Schemes are traditionally displayed in lowercase.")]
+ internal void Initialize(bool hideNonStandardScheme) {
+ if (schemeField == null) {
+ schemeField = typeof(UriParser).GetField("m_Scheme", BindingFlags.NonPublic | BindingFlags.Instance);
+ }
+
+ this.RegisteredScheme = (string)schemeField.GetValue(this);
+
+ if (hideNonStandardScheme) {
+ schemeField.SetValue(this, this.standardScheme.ToLowerInvariant());
+ }
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth/Properties/AssemblyInfo.cs
index ce08780..a63c71e 100644
--- a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs
+++ b/src/DotNetOpenAuth/Properties/AssemblyInfo.cs
@@ -4,18 +4,6 @@
// </copyright>
//-----------------------------------------------------------------------
-// Uncomment this line to build a partially trusted assembly.
-// This has some security bonuses in that if there was a way to
-// hijack this assembly to do something it is not designed to do,
-// it will fail before doing much damage.
-// But a partially trusted assembly's events, handled by the hosting
-// web site, will also be under the partial trust restriction.
-// Also note that http://support.microsoft.com/kb/839300 states a
-// strong-name signed assembly must use AllowPartiallyTrustedCallers
-// to be called from a web page, but defining PARTIAL_TRUST below also
-// accomplishes this.
-////#define PARTIAL_TRUST
-
// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build.
using System;
@@ -69,38 +57,3 @@ using System.Web.UI;
#else
[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")]
#endif
-
-#if !CLR4
-
-// Specify what permissions are required and optional for the assembly.
-// In order for CAS to remove unnecessary privileges from this assembly (which is desirable
-// for security), we need at least one RequestMinimum and at least one RequestOptional.
-// These permissions were determined using PermCalc.exe
-
-// We need to be allowed to execute code. Besides, it gives a good baseline RequestMinimum permission.
-[assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution = true)]
-
-// Allows the consumer to call out to the web server. This is unnecessary in provider-only scenarios.
-// Note: we don't use a single demand for https?://.* because the regex pattern must exactly
-// match the one used by hosting providers. Listing them individually seems to be more common.
-[assembly: WebPermission(SecurityAction.RequestMinimum, ConnectPattern = @"http://.*")]
-[assembly: WebPermission(SecurityAction.RequestMinimum, ConnectPattern = @"https://.*")]
-#if PARTIAL_TRUST
-// Allows hosting this assembly in an ASP.NET setting. Not all applications
-// will host this using ASP.NET, so this is optional. Besides, we need at least
-// one optional permission to activate CAS permission shrinking.
-[assembly: AspNetHostingPermission(SecurityAction.RequestOptional, Level = AspNetHostingPermissionLevel.Medium)]
-
-// Allows this assembly to store reporting data.
-[assembly: IsolatedStorageFilePermission(SecurityAction.RequestOptional, UsageAllowed = IsolatedStorageContainment.AssemblyIsolationByUser)]
-
-// The following are only required for diagnostic logging (Trace.Write, Debug.Assert, etc.).
-#if TRACE || DEBUG
-[assembly: KeyContainerPermission(SecurityAction.RequestOptional, Unrestricted = true)]
-[assembly: ReflectionPermission(SecurityAction.RequestOptional, MemberAccess = true)]
-[assembly: RegistryPermission(SecurityAction.RequestOptional, Unrestricted = true)]
-[assembly: SecurityPermission(SecurityAction.RequestOptional, ControlEvidence = true, UnmanagedCode = true, ControlThread = true)]
-[assembly: FileIOPermission(SecurityAction.RequestOptional, AllFiles = FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read)]
-#endif // TRACE || DEBUG
-#endif // PARTIAL_TRUST
-#endif // CLR4
diff --git a/src/DotNetOpenAuth/Reporting.cs b/src/DotNetOpenAuth/Reporting.cs
index c4421c4..612845f 100644
--- a/src/DotNetOpenAuth/Reporting.cs
+++ b/src/DotNetOpenAuth/Reporting.cs
@@ -151,6 +151,7 @@ namespace DotNetOpenAuth {
/// </summary>
/// <param name="eventName">Name of the event.</param>
/// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "PersistentCounter instances are stored in a table for later use.")]
internal static void RecordEventOccurrence(string eventName, string category) {
Contract.Requires(!String.IsNullOrEmpty(eventName));
@@ -318,6 +319,7 @@ namespace DotNetOpenAuth {
/// <summary>
/// Initializes Reporting if it has not been initialized yet.
/// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method must never throw.")]
private static void Initialize() {
lock (initializationSync) {
if (!broken && !initialized) {
@@ -355,44 +357,49 @@ namespace DotNetOpenAuth {
/// <returns>A stream that contains the report.</returns>
private static Stream GetReport() {
var stream = new MemoryStream();
- var writer = new StreamWriter(stream, Encoding.UTF8);
- writer.WriteLine(reportOriginIdentity.ToString("B"));
- writer.WriteLine(Util.LibraryVersion);
- writer.WriteLine(".NET Framework {0}", Environment.Version);
-
- foreach (var observation in observations) {
- observation.Flush();
- writer.WriteLine("====================================");
- writer.WriteLine(observation.FileName);
- try {
- using (var fileStream = new IsolatedStorageFileStream(observation.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
- writer.Flush();
- fileStream.CopyTo(writer.BaseStream);
+ try {
+ var writer = new StreamWriter(stream, Encoding.UTF8);
+ writer.WriteLine(reportOriginIdentity.ToString("B"));
+ writer.WriteLine(Util.LibraryVersion);
+ writer.WriteLine(".NET Framework {0}", Environment.Version);
+
+ foreach (var observation in observations) {
+ observation.Flush();
+ writer.WriteLine("====================================");
+ writer.WriteLine(observation.FileName);
+ try {
+ using (var fileStream = new IsolatedStorageFileStream(observation.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
+ writer.Flush();
+ fileStream.CopyTo(writer.BaseStream);
+ }
+ } catch (FileNotFoundException) {
+ writer.WriteLine("(missing)");
}
- } catch (FileNotFoundException) {
- writer.WriteLine("(missing)");
}
- }
- // Not all event counters may have even loaded in this app instance.
- // We flush the ones in memory, and then read all of them off disk.
- foreach (var counter in events.Values) {
- counter.Flush();
- }
+ // Not all event counters may have even loaded in this app instance.
+ // We flush the ones in memory, and then read all of them off disk.
+ foreach (var counter in events.Values) {
+ counter.Flush();
+ }
- foreach (string eventFile in file.GetFileNames("event-*.txt")) {
- writer.WriteLine("====================================");
- writer.WriteLine(eventFile);
- using (var fileStream = new IsolatedStorageFileStream(eventFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
- writer.Flush();
- fileStream.CopyTo(writer.BaseStream);
+ foreach (string eventFile in file.GetFileNames("event-*.txt")) {
+ writer.WriteLine("====================================");
+ writer.WriteLine(eventFile);
+ using (var fileStream = new IsolatedStorageFileStream(eventFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
+ writer.Flush();
+ fileStream.CopyTo(writer.BaseStream);
+ }
}
- }
- // Make sure the stream is positioned at the beginning.
- writer.Flush();
- stream.Position = 0;
- return stream;
+ // Make sure the stream is positioned at the beginning.
+ writer.Flush();
+ stream.Position = 0;
+ return stream;
+ } catch {
+ stream.Dispose();
+ throw;
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Util.cs b/src/DotNetOpenAuth/Util.cs
index 9f8b30c..8a18ef8 100644
--- a/src/DotNetOpenAuth/Util.cs
+++ b/src/DotNetOpenAuth/Util.cs
@@ -126,7 +126,7 @@ namespace DotNetOpenAuth {
sb.Append("\t");
sb.Append(objString);
- if (!objString.EndsWith(Environment.NewLine)) {
+ if (!objString.EndsWith(Environment.NewLine, StringComparison.Ordinal)) {
sb.AppendLine();
}
sb.AppendLine("}, {");
diff --git a/src/DotNetOpenAuth/XrdsPublisher.cs b/src/DotNetOpenAuth/XrdsPublisher.cs
index e7c04d8..83d82ff 100644
--- a/src/DotNetOpenAuth/XrdsPublisher.cs
+++ b/src/DotNetOpenAuth/XrdsPublisher.cs
@@ -9,6 +9,7 @@ namespace DotNetOpenAuth {
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
using System.Drawing.Design;
using System.Text;
using System.Web;
@@ -209,6 +210,7 @@ namespace DotNetOpenAuth {
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")]
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract.");
if (this.Enabled && this.Visible && !string.IsNullOrEmpty(this.XrdsUrl)) {
Uri xrdsAddress = new Uri(MessagingUtilities.GetRequestUrlFromContext(), Page.Response.ApplyAppPathModifier(this.XrdsUrl));
if ((this.XrdsAdvertisement & XrdsUrlLocations.HttpHeader) != 0) {
diff --git a/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs b/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs
index 01dae40..06c6fc7 100644
--- a/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs
+++ b/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs
@@ -17,6 +17,12 @@ namespace DotNetOpenAuth.Yadis {
/// </summary>
internal class DiscoveryResult {
/// <summary>
+ /// The original web response, backed up here if the final web response is the preferred response to use
+ /// in case it turns out to not work out.
+ /// </summary>
+ private CachedDirectWebResponse htmlFallback;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="DiscoveryResult"/> class.
/// </summary>
/// <param name="requestUri">The user-supplied identifier.</param>
@@ -25,10 +31,8 @@ namespace DotNetOpenAuth.Yadis {
public DiscoveryResult(Uri requestUri, CachedDirectWebResponse initialResponse, CachedDirectWebResponse finalResponse) {
this.RequestUri = requestUri;
this.NormalizedUri = initialResponse.FinalUri;
- if (finalResponse == null) {
- this.ContentType = initialResponse.ContentType;
- this.ResponseText = initialResponse.GetResponseString();
- this.IsXrds = this.ContentType != null && this.ContentType.MediaType == ContentTypes.Xrds;
+ if (finalResponse == null || finalResponse.Status != System.Net.HttpStatusCode.OK) {
+ this.ApplyHtmlResponse(initialResponse);
} else {
this.ContentType = finalResponse.ContentType;
this.ResponseText = finalResponse.GetResponseString();
@@ -36,6 +40,9 @@ namespace DotNetOpenAuth.Yadis {
if (initialResponse != finalResponse) {
this.YadisLocation = finalResponse.RequestUri;
}
+
+ // Back up the initial HTML response in case the XRDS is not useful.
+ this.htmlFallback = initialResponse;
}
}
@@ -77,13 +84,23 @@ namespace DotNetOpenAuth.Yadis {
public bool IsXrds { get; private set; }
/// <summary>
- /// Gets a value indicating whether discovery resulted in an
- /// XRDS document at a referred location.
+ /// Reverts to the HTML response after the XRDS response didn't work out.
+ /// </summary>
+ internal void TryRevertToHtmlResponse() {
+ if (this.htmlFallback != null) {
+ this.ApplyHtmlResponse(this.htmlFallback);
+ this.htmlFallback = null;
+ }
+ }
+
+ /// <summary>
+ /// Applies the HTML response to the object.
/// </summary>
- /// <value><c>true</c> if the response to the userSuppliedIdentifier
- /// pointed to a different URL for the XRDS document.</value>
- public bool UsedYadisLocation {
- get { return this.YadisLocation != null; }
+ /// <param name="initialResponse">The initial response.</param>
+ private void ApplyHtmlResponse(CachedDirectWebResponse initialResponse) {
+ this.ContentType = initialResponse.ContentType;
+ this.ResponseText = initialResponse.GetResponseString();
+ this.IsXrds = this.ContentType != null && this.ContentType.MediaType == ContentTypes.Xrds;
}
}
}
diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs
index f1c8be3..8b8c20f 100644
--- a/src/DotNetOpenAuth/Yadis/Yadis.cs
+++ b/src/DotNetOpenAuth/Yadis/Yadis.cs
@@ -39,7 +39,7 @@ namespace DotNetOpenAuth.Yadis {
/// The maximum number of bytes to read from an HTTP response
/// in searching for a link to a YADIS document.
/// </summary>
- private const int MaximumResultToScan = 1024 * 1024;
+ internal const int MaximumResultToScan = 1024 * 1024;
/// <summary>
/// Performs YADIS discovery on some identifier.
@@ -96,7 +96,6 @@ namespace DotNetOpenAuth.Yadis {
response2 = Request(requestHandler, url, requireSsl, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan);
if (response2.Status != HttpStatusCode.OK) {
Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response2.Status, response2.Status, uri);
- return null;
}
} else {
Logger.Yadis.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url);