diff options
Diffstat (limited to 'src')
43 files changed, 1005 insertions, 284 deletions
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/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/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 a4f932e..3164ec5 100644 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd +++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd @@ -319,7 +319,24 @@ </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> diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs index 1e3df8f..1bf2ebc 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs @@ -71,6 +71,11 @@ 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"; @@ -206,6 +211,20 @@ 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> @@ -234,6 +253,7 @@ 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 3dee328..1c018eb 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"> @@ -736,7 +736,7 @@ http://opensource.org/licenses/ms-pl.html <ItemGroup> <SignDependsOn Include="BuildUnifiedProduct" /> <DelaySignedAssemblies Include="$(ILMergeOutputAssembly); - $(OutputPath)$(ProductName).Contracts.dll; + $(OutputPath)CodeContracts\$(ProductName).Contracts.dll; " /> </ItemGroup> <PropertyGroup> 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 7198c78..055ce68 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -695,27 +695,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 0bbac42..6f8c4f9 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.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. @@ -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 34385d4..bdf4f68 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx @@ -300,4 +300,7 @@ <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> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index 7367c01..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); @@ -181,7 +181,10 @@ namespace DotNetOpenAuth.Messaging { /// <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; diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index e9613ab..3524f41 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/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/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/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/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/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs index 37ba8c1..f22645f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -321,7 +321,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="identifiers">The identifiers to perform discovery on.</param> protected void PreloadDiscovery(IEnumerable<Identifier> identifiers) { string script = this.AjaxRelyingParty.AsAjaxPreloadedDiscoveryResult( - identifiers.Select(id => this.CreateRequests(id)).Flatten()); + identifiers.SelectMany(id => this.CreateRequests(id))); this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), this.ClientID, script, true); } @@ -399,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 diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs index 838b749..5090ecd 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -358,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. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs index 5b85e7c..b7a54eb 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs @@ -310,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); @@ -344,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 08e7aac..335b435 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs @@ -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 e2bf2a1..a7686c5 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs @@ -27,6 +27,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { : base(false) { this.PrivateSecretMaximumAge = TimeSpan.FromDays(7); this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault; + this.AllowApproximateIdentifierDiscovery = true; } /// <summary> @@ -132,6 +133,16 @@ 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. 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/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/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs index 7882797..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. |