diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-08 22:38:46 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-08 22:38:46 -0700 |
commit | c604f25eaf5cd4fce2a73e00bfe8b27fe97b86d0 (patch) | |
tree | e37f874cfe3a0937059e4acf1090254ce0b3dba6 /src | |
parent | b3a35b9ae0823dda35bb07b404c9c09fec09402a (diff) | |
parent | 436da522dcab28df4e19b212b49270c5211ff92b (diff) | |
download | DotNetOpenAuth-c604f25eaf5cd4fce2a73e00bfe8b27fe97b86d0.zip DotNetOpenAuth-c604f25eaf5cd4fce2a73e00bfe8b27fe97b86d0.tar.gz DotNetOpenAuth-c604f25eaf5cd4fce2a73e00bfe8b27fe97b86d0.tar.bz2 |
Merge branch 'v3.4' into oauth2
Conflicts:
src/DotNetOpenAuth/Configuration/MessagingElement.cs
src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
Diffstat (limited to 'src')
25 files changed, 256 insertions, 67 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index fbb7779..26d870b 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -138,18 +138,10 @@ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> </PropertyGroup> <ItemGroup> - <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\lib\log4net.dll</HintPath> - </Reference> + <Reference Include="log4net" /> <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" /> - <Reference Include="Moq, Version=3.1.416.3, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\lib\Moq.dll</HintPath> - </Reference> - <Reference Include="nunit.framework"> - <HintPath>..\..\lib\nunit.framework.dll</HintPath> - </Reference> + <Reference Include="Moq" /> + <Reference Include="NUnit.Framework"/> <Reference Include="System" /> <Reference Include="System.configuration" /> <Reference Include="System.Core"> diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs index d556b11..60c8bc3 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs @@ -13,12 +13,12 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { public class ValueMappingTests { [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullToString() { - new ValueMapping(null, str => new object()); + new ValueMapping(null, null, str => new object()); } [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullToObject() { - new ValueMapping(obj => obj.ToString(), null); + new ValueMapping(obj => obj.ToString(), null, null); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/CheckAuthenticationRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckAuthenticationRequestTests.cs index cf6b814..d2d2cc4 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Messages/CheckAuthenticationRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckAuthenticationRequestTests.cs @@ -6,12 +6,37 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Messages; using NUnit.Framework; [TestFixture] public class CheckAuthenticationRequestTests : OpenIdTestBase { + /// <summary> + /// Verifies that the check_auth request is sent preserving EXACTLY (non-normalized) + /// what is in the positive assertion. + /// </summary> + /// <remarks> + /// This is very important because any normalization + /// (like changing https://host:443/ to https://host/) in the message will invalidate the signature + /// and cause the authentication to inappropriately fail. + /// Designed to verify fix to Trac #198. + /// </remarks> + [TestCase] + public void ExactPositiveAssertionPreservation() { + var rp = CreateRelyingParty(true); + + // Initialize the positive assertion response with some data that is NOT in normalized form. + var positiveAssertion = new PositiveAssertionResponse(Protocol.Default.Version, RPUri) + { + ClaimedIdentifier = "https://HOST:443/a", + ProviderEndpoint = new Uri("https://anotherHOST:443/b"), + }; + + var checkAuth = new CheckAuthenticationRequest(positiveAssertion, rp.Channel); + var actual = rp.Channel.MessageDescriptions.GetAccessor(checkAuth); + Assert.AreEqual("https://HOST:443/a", actual["openid.claimed_id"]); + Assert.AreEqual("https://anotherHOST:443/b", actual["openid.op_endpoint"]); + } } } diff --git a/src/DotNetOpenAuth/Configuration/MessagingElement.cs b/src/DotNetOpenAuth/Configuration/MessagingElement.cs index cdb4dc2..fae18ec 100644 --- a/src/DotNetOpenAuth/Configuration/MessagingElement.cs +++ b/src/DotNetOpenAuth/Configuration/MessagingElement.cs @@ -37,6 +37,11 @@ namespace DotNetOpenAuth.Configuration { private const string RelaxSslRequirementsConfigName = "relaxSslRequirements"; /// <summary> + /// The name of the attribute that controls whether messaging rules are strictly followed. + /// </summary> + private const string StrictConfigName = "strict"; + + /// <summary> /// Gets the actual maximum message lifetime that a program should allow. /// </summary> /// <value>The sum of the <see cref="MaximumMessageLifetime"/> and @@ -98,6 +103,24 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets a value indicating whether messaging rules are strictly + /// adhered to. + /// </summary> + /// <value><c>true</c> by default.</value> + /// <remarks> + /// Strict will require that remote parties adhere strictly to the specifications, + /// even when a loose interpretation would not compromise security. + /// <c>true</c> is a good default because it shakes out interoperability bugs in remote services + /// so they can be identified and corrected. But some web sites want things to Just Work + /// more than they want to file bugs against others, so <c>false</c> is the setting for them. + /// </remarks> + [ConfigurationProperty(StrictConfigName, DefaultValue = true)] + internal bool Strict { + get { return (bool)this[StrictConfigName]; } + set { this[StrictConfigName] = value; } + } + + /// <summary> /// Gets or sets the configuration for the <see cref="UntrustedWebRequestHandler"/> class. /// </summary> /// <value>The untrusted web request.</value> diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 650e01a..3626101 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -168,7 +168,6 @@ http://opensource.org/licenses/ms-pl.html <ItemGroup> <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\lib\log4net.dll</HintPath> </Reference> <Reference Include="PresentationFramework"> <RequiredTargetFramework>3.0</RequiredTargetFramework> @@ -198,7 +197,6 @@ http://opensource.org/licenses/ms-pl.html </Reference> <Reference Include="System.Web" /> <Reference Include="System.Web.Abstractions"> - <HintPath>..\..\lib\System.Web.Abstractions.dll</HintPath> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> <Reference Include="System.Web.Extensions"> @@ -209,7 +207,6 @@ http://opensource.org/licenses/ms-pl.html </Reference> <Reference Include="System.Web.Mobile" Condition=" '$(ClrVersion)' != '4' " /> <Reference Include="System.Web.Routing"> - <HintPath>..\..\lib\System.Web.Routing.dll</HintPath> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> <Reference Include="System.Xaml" Condition=" '$(ClrVersion)' == '4' " /> @@ -304,6 +301,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="Messaging\OutgoingWebResponseActionResult.cs" /> <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" /> + <Compile Include="Messaging\Reflection\IMessagePartOriginalEncoder.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" /> <Compile Include="Mvc\OpenIdHelper.cs" /> <Compile Include="Mvc\OpenIdAjaxOptions.cs" /> diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 28babee..76dab88 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -1065,7 +1065,7 @@ namespace DotNetOpenAuth.Messaging { Contract.Requires<ArgumentNullException>(message != null); if (Logger.Channel.IsInfoEnabled) { - var messageAccessor = this.MessageDescriptions.GetAccessor(message); + var messageAccessor = this.MessageDescriptions.GetAccessor(message, true); Logger.Channel.InfoFormat( "Processing incoming {0} ({1}) message:{2}{3}", message.GetType().Name, diff --git a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs index 3420de9..d47da4a 100644 --- a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs @@ -82,7 +82,7 @@ namespace DotNetOpenAuth.Messaging { /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing.</para> + /// value, if set, should be Closed before throwing.</para> /// </remarks> IncomingWebResponse GetResponse(HttpWebRequest request); @@ -98,7 +98,7 @@ namespace DotNetOpenAuth.Messaging { /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing.</para> + /// value, if set, should be Closed before throwing.</para> /// </remarks> IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options); } @@ -183,7 +183,7 @@ namespace DotNetOpenAuth.Messaging { /// Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing. + /// value, if set, should be Closed before throwing. /// </remarks> IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { Contract.Requires<ArgumentNullException>(request != null); @@ -206,7 +206,7 @@ namespace DotNetOpenAuth.Messaging { /// Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing. + /// value, if set, should be Closed before throwing. /// </remarks> IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { Contract.Requires<ArgumentNullException>(request != null); diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index cbd7f51..60fbdd8 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -1245,7 +1245,7 @@ namespace DotNetOpenAuth.Messaging { /// by using appropriate character escaping. /// </summary> /// <param name="value">The untrusted string value to be escaped to protected against XSS attacks. May be null.</param> - /// <returns>The escaped string.</returns> + /// <returns>The escaped string, surrounded by single-quotes.</returns> internal static string GetSafeJavascriptValue(string value) { if (value == null) { return "null"; diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartOriginalEncoder.cs b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartOriginalEncoder.cs new file mode 100644 index 0000000..9ad55c9 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartOriginalEncoder.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// <copyright file="IMessagePartOriginalEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + /// <summary> + /// An interface describing how various objects can be serialized and deserialized between their object and string forms. + /// </summary> + /// <remarks> + /// Implementations of this interface must include a default constructor and must be thread-safe. + /// </remarks> + public interface IMessagePartOriginalEncoder : IMessagePartEncoder { + /// <summary> + /// Encodes the specified value as the original value that was formerly decoded. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> + string EncodeAsOriginalString(object value); + } +} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs index 53a5cdd..7dbab80 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs @@ -82,7 +82,20 @@ namespace DotNetOpenAuth.Messaging.Reflection { internal MessageDictionary GetDictionary(IMessage message) { Contract.Requires<ArgumentNullException>(message != null); Contract.Ensures(Contract.Result<MessageDictionary>() != null); - return new MessageDictionary(message, this); + return this.GetDictionary(message, false); + } + + /// <summary> + /// Gets a dictionary that provides read/write access to a message. + /// </summary> + /// <param name="message">The message the dictionary should provide access to.</param> + /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> + /// <returns>The dictionary accessor to the message</returns> + [Pure] + internal MessageDictionary GetDictionary(IMessage message, bool getOriginalValues) { + Contract.Requires<ArgumentNullException>(message != null); + Contract.Ensures(Contract.Result<MessageDictionary>() != null); + return new MessageDictionary(message, this, getOriginalValues); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs index 8911960..754b9d0 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs @@ -106,7 +106,19 @@ namespace DotNetOpenAuth.Messaging.Reflection { [Pure] internal MessageDictionary GetAccessor(IMessage message) { Contract.Requires<ArgumentNullException>(message != null); - return this.Get(message).GetDictionary(message); + return this.GetAccessor(message, false); + } + + /// <summary> + /// Gets the dictionary that provides read/write access to a message. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> + /// <returns>The dictionary.</returns> + [Pure] + internal MessageDictionary GetAccessor(IMessage message, bool getOriginalValues) { + Contract.Requires<ArgumentNullException>(message != null); + return this.Get(message).GetDictionary(message, getOriginalValues); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs index f6eed24..2b60a9c 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs @@ -30,17 +30,24 @@ namespace DotNetOpenAuth.Messaging.Reflection { private readonly MessageDescription description; /// <summary> + /// Whether original string values should be retrieved instead of normalized ones. + /// </summary> + private readonly bool getOriginalValues; + + /// <summary> /// Initializes a new instance of the <see cref="MessageDictionary"/> class. /// </summary> /// <param name="message">The message instance whose values will be manipulated by this dictionary.</param> /// <param name="description">The message description.</param> + /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> [Pure] - internal MessageDictionary(IMessage message, MessageDescription description) { + internal MessageDictionary(IMessage message, MessageDescription description, bool getOriginalValues) { Contract.Requires<ArgumentNullException>(message != null); Contract.Requires<ArgumentNullException>(description != null); this.message = message; this.description = description; + this.getOriginalValues = getOriginalValues; } /// <summary> @@ -103,7 +110,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { List<string> keys = new List<string>(this.description.Mapping.Count); foreach (var pair in this.description.Mapping) { // Don't include keys with null values, but default values for structs is ok - if (pair.Value.GetValue(this.message) != null) { + if (pair.Value.GetValue(this.message, this.getOriginalValues) != null) { keys.Add(pair.Key); } } @@ -126,8 +133,8 @@ namespace DotNetOpenAuth.Messaging.Reflection { get { List<string> values = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count); foreach (MessagePart part in this.description.Mapping.Values) { - if (part.GetValue(this.message) != null) { - values.Add(part.GetValue(this.message)); + if (part.GetValue(this.message, this.getOriginalValues) != null) { + values.Add(part.GetValue(this.message, this.getOriginalValues)); } } @@ -167,7 +174,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { // Never throw KeyNotFoundException for declared properties. - return part.GetValue(this.message); + return part.GetValue(this.message, this.getOriginalValues); } else { return this.message.ExtraData[key]; } @@ -223,7 +230,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <returns>True if the parameter by the given name has a set value. False otherwise.</returns> public bool ContainsKey(string key) { return this.message.ExtraData.ContainsKey(key) || - (this.description.Mapping.ContainsKey(key) && this.description.Mapping[key].GetValue(this.message) != null); + (this.description.Mapping.ContainsKey(key) && this.description.Mapping[key].GetValue(this.message, this.getOriginalValues) != null); } /// <summary> @@ -237,7 +244,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { } else { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { - if (part.GetValue(this.message) != null) { + if (part.GetValue(this.message, this.getOriginalValues) != null) { part.SetValue(this.message, null); return true; } @@ -255,7 +262,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { public bool TryGetValue(string key, out string value) { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { - value = part.GetValue(this.message); + value = part.GetValue(this.message, this.getOriginalValues); return value != null; } return this.message.ExtraData.TryGetValue(key, out value); @@ -306,7 +313,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { public bool Contains(KeyValuePair<string, string> item) { MessagePart part; if (this.description.Mapping.TryGetValue(item.Key, out part)) { - return string.Equals(part.GetValue(this.message), item.Value, StringComparison.Ordinal); + return string.Equals(part.GetValue(this.message, this.getOriginalValues), item.Value, StringComparison.Ordinal); } else { return this.message.ExtraData.Contains(item); } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index 6aa20e5..c1e7aa2 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -15,6 +15,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Net.Security; using System.Reflection; using System.Xml; + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.OpenId; /// <summary> @@ -90,15 +91,15 @@ namespace DotNetOpenAuth.Messaging.Reflection { Contract.Assume(str != null); return new Realm(str); }; - Map<Uri>(uri => uri.AbsoluteUri, safeUri); - Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); - Map<TimeSpan>(ts => ts.ToString(), str => TimeSpan.Parse(str)); - Map<byte[]>(safeFromByteArray, safeToByteArray); - Map<Realm>(realm => realm.ToString(), safeRealm); - 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()); + Map<Uri>(uri => uri.AbsoluteUri, uri => uri.OriginalString, safeUri); + Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), null, str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); + Map<TimeSpan>(ts => ts.ToString(), null, str => TimeSpan.Parse(str)); + Map<byte[]>(safeFromByteArray, null, safeToByteArray); + Map<Realm>(realm => realm.ToString(), realm => realm.OriginalString, safeRealm); + Map<Identifier>(id => id.SerializedString, id => id.OriginalString, safeIdentifier); + Map<bool>(value => value.ToString().ToLowerInvariant(), null, safeBool); + Map<CultureInfo>(c => c.Name, null, str => new CultureInfo(str)); + Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), null, str => str.Split(',').Select(s => new CultureInfo(s)).ToArray()); } /// <summary> @@ -138,15 +139,18 @@ namespace DotNetOpenAuth.Messaging.Reflection { if (converters.TryGetValue(underlyingType, out underlyingMapping)) { this.converter = new ValueMapping( underlyingMapping.ValueToString, + null, str => str != null ? underlyingMapping.StringToValue(str) : null); } else { this.converter = new ValueMapping( obj => obj != null ? obj.ToString() : null, + null, str => str != null ? Convert.ChangeType(str, underlyingType, CultureInfo.InvariantCulture) : null); } } else { this.converter = new ValueMapping( obj => obj != null ? obj.ToString() : null, + null, str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); } } @@ -229,7 +233,8 @@ namespace DotNetOpenAuth.Messaging.Reflection { try { if (this.IsConstantValue) { string constantValue = this.GetValue(message); - if (!string.Equals(constantValue, value)) { + var caseSensitivity = DotNetOpenAuthSection.Configuration.Messaging.Strict ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + if (!string.Equals(constantValue, value, caseSensitivity)) { throw new ArgumentException(string.Format( CultureInfo.CurrentCulture, MessagingStrings.UnexpectedMessagePartValueForConstant, @@ -251,7 +256,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> - /// Gets the value of a member of a given message. + /// Gets the normalized form of a value of a member of a given message. /// Used in serialization. /// </summary> /// <param name="message">The message instance to read the value from.</param> @@ -259,7 +264,23 @@ namespace DotNetOpenAuth.Messaging.Reflection { internal string GetValue(IMessage message) { try { object value = this.GetValueAsObject(message); - return this.ToString(value); + return this.ToString(value, false); + } catch (FormatException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartWriteFailure, message.GetType(), this.Name); + } + } + + /// <summary> + /// Gets the value of a member of a given message. + /// Used in serialization. + /// </summary> + /// <param name="message">The message instance to read the value from.</param> + /// <param name="originalValue">A value indicating whether the original value should be retrieved (as opposed to a normalized form of it).</param> + /// <returns>The string representation of the member's value.</returns> + internal string GetValue(IMessage message, bool originalValue) { + try { + object value = this.GetValueAsObject(message); + return this.ToString(value, originalValue); } catch (FormatException ex) { throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartWriteFailure, message.GetType(), this.Name); } @@ -296,11 +317,20 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <typeparam name="T">The custom type to convert to and from strings.</typeparam> /// <param name="toString">The function to convert the custom type to a string.</param> + /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> /// <param name="toValue">The function to convert a string to the custom type.</param> - private static void Map<T>(Func<T, string> toString, Func<string, T> toValue) { + private static void Map<T>(Func<T, string> toString, Func<T, string> toOriginalString, Func<string, T> toValue) { + Contract.Requires<ArgumentNullException>(toString != null, "toString"); + Contract.Requires<ArgumentNullException>(toValue != null, "toValue"); + + if (toOriginalString == null) { + toOriginalString = toString; + } + Func<object, string> safeToString = obj => obj != null ? toString((T)obj) : null; + Func<object, string> safeToOriginalString = obj => obj != null ? toOriginalString((T)obj) : null; Func<string, object> safeToT = str => str != null ? toValue(str) : default(T); - converters.Add(typeof(T), new ValueMapping(safeToString, safeToT)); + converters.Add(typeof(T), new ValueMapping(safeToString, safeToOriginalString, safeToT)); } /// <summary> @@ -352,11 +382,12 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// Converts the member's value to its string representation. /// </summary> /// <param name="value">The value of the member.</param> + /// <param name="originalString">A value indicating whether a string matching the originally decoded string should be returned (as opposed to a normalized string).</param> /// <returns> /// The string representation of the member's value. /// </returns> - private string ToString(object value) { - return this.converter.ValueToString(value); + private string ToString(object value, bool originalString) { + return originalString ? this.converter.ValueToOriginalString(value) : this.converter.ValueToString(value); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs index 1c7631e..b0b8b47 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs @@ -19,6 +19,12 @@ namespace DotNetOpenAuth.Messaging.Reflection { internal readonly Func<object, string> ValueToString; /// <summary> + /// The mapping function that converts some custom type to the original string + /// (possibly non-normalized) that represents it. + /// </summary> + internal readonly Func<object, string> ValueToOriginalString; + + /// <summary> /// The mapping function that converts a string to some custom type. /// </summary> internal readonly Func<string, object> StringToValue; @@ -26,13 +32,15 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// Initializes a new instance of the <see cref="ValueMapping"/> struct. /// </summary> - /// <param name="toString">The mapping function that converts some custom type to a string.</param> - /// <param name="toValue">The mapping function that converts a string to some custom type.</param> - internal ValueMapping(Func<object, string> toString, Func<string, object> toValue) { + /// <param name="toString">The mapping function that converts some custom value to a string.</param> + /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> + /// <param name="toValue">The mapping function that converts a string to some custom value.</param> + internal ValueMapping(Func<object, string> toString, Func<object, string> toOriginalString, Func<string, object> toValue) { Contract.Requires<ArgumentNullException>(toString != null); Contract.Requires<ArgumentNullException>(toValue != null); this.ValueToString = toString; + this.ValueToOriginalString = toOriginalString ?? toString; this.StringToValue = toValue; } @@ -45,8 +53,15 @@ namespace DotNetOpenAuth.Messaging.Reflection { var nullEncoder = encoder as IMessagePartNullEncoder; string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null; + var originalStringEncoder = encoder as IMessagePartOriginalEncoder; + Func<object, string> originalStringEncode = encoder.Encode; + if (originalStringEncoder != null) { + originalStringEncode = originalStringEncoder.EncodeAsOriginalString; + } + this.ValueToString = obj => (obj != null) ? encoder.Encode(obj) : nullString; this.StringToValue = str => (str != null) ? encoder.Decode(str) : null; + this.ValueToOriginalString = obj => (obj != null) ? originalStringEncode(obj) : nullString; } } } diff --git a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs index 440f5f6..a5725cd 100644 --- a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs @@ -95,7 +95,7 @@ namespace DotNetOpenAuth.Messaging { /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing.</para> + /// value, if set, should be Closed before throwing.</para> /// </remarks> public IncomingWebResponse GetResponse(HttpWebRequest request) { return this.GetResponse(request, DirectWebRequestOptions.None); @@ -115,7 +115,7 @@ namespace DotNetOpenAuth.Messaging { /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing.</para> + /// value, if set, should be Closed before throwing.</para> /// </remarks> public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { // This request MAY have already been prepared by GetRequestStream, but diff --git a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs index 3ea1bf2..838b7e8 100644 --- a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs @@ -231,7 +231,7 @@ namespace DotNetOpenAuth.Messaging { /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing.</para> + /// value, if set, should be Closed before throwing.</para> /// </remarks> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { @@ -298,7 +298,7 @@ namespace DotNetOpenAuth.Messaging { /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a /// <see cref="ProtocolException"/> to abstract away the transport and provide /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, shoud be Closed before throwing.</para> + /// value, if set, should be Closed before throwing.</para> /// </remarks> IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { return this.GetResponse(request, DirectWebRequestOptions.None); diff --git a/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs b/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs index 9956966..4b88d04 100644 --- a/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs +++ b/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs @@ -7,8 +7,10 @@ namespace DotNetOpenAuth.Mvc { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Text; + using DotNetOpenAuth.Messaging; /// <summary> /// A set of customizations available for the scripts sent to the browser in AJAX OpenID scenarios. @@ -41,6 +43,14 @@ namespace DotNetOpenAuth.Mvc { public int FormIndex { get; set; } /// <summary> + /// Gets or sets the id of the form in the document.forms array on the browser that should + /// be submitted when the user is ready to send the positive assertion to the RP. A value + /// in this property takes precedence over any value in the <see cref="FormIndex"/> property. + /// </summary> + /// <value>The form id.</value> + public string FormId { get; set; } + + /// <summary> /// Gets or sets the preloaded discovery results. /// </summary> public string PreloadedDiscoveryResults { get; set; } @@ -55,5 +65,12 @@ namespace DotNetOpenAuth.Mvc { /// asynchronous authentication of the user for diagnostic purposes. /// </summary> public bool ShowDiagnosticIFrame { get; set; } + + /// <summary> + /// Gets the form key to use when accessing the relevant form. + /// </summary> + internal string FormKey { + get { return string.IsNullOrEmpty(this.FormId) ? this.FormIndex.ToString(CultureInfo.InvariantCulture) : MessagingUtilities.GetSafeJavascriptValue(this.FormId); } + } } } diff --git a/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs b/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs index f276019..193e445 100644 --- a/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs +++ b/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs @@ -151,7 +151,7 @@ window.openid_trace = {1}; // causes lots of messages"; blockFormat, additionalOptions.AssertionHiddenFieldId, additionalOptions.ReturnUrlHiddenFieldId, - additionalOptions.FormIndex); + additionalOptions.FormKey); blockFormat = @" $(function () {{ var box = document.getElementsByName('openid_identifier')[0]; diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs index f178647..55c2dc5 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs @@ -30,6 +30,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// whether to use a standard full window redirect or a popup) via the /// <see cref="IdentifierDiscoveryResult.IsExtensionSupported<T>()"/> method.</para> /// </remarks> + [Serializable] public sealed class UIRequest : IOpenIdMessageExtension, IMessageWithEvents { /// <summary> /// The factory method that may be used in deserialization of this message. diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs index 5306c54..db69d3d 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs @@ -45,7 +45,7 @@ namespace DotNetOpenAuth.OpenId.Messages { // Copy all message parts from the id_res message into this one, // except for the openid.mode parameter. - MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(message); + MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(message, true); MessageDictionary thisPayload = channel.MessageDescriptions.GetAccessor(this); foreach (var pair in checkPayload) { if (!string.Equals(pair.Key, this.Protocol.openid.mode)) { diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs index 61825e8..f1bb5ac 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs @@ -47,7 +47,7 @@ namespace DotNetOpenAuth.OpenId.Messages { // really doesn't exist. OpenID 2.0 section 11.4.2.2. IndirectSignedResponse signedResponse = new IndirectSignedResponse(request, provider.Channel); string invalidateHandle = ((ITamperResistantOpenIdMessage)signedResponse).InvalidateHandle; - if (invalidateHandle != null && provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, invalidateHandle) == null) { + if (!string.IsNullOrEmpty(invalidateHandle) && provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, invalidateHandle) == null) { this.InvalidateHandle = invalidateHandle; } } @@ -70,8 +70,10 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <para>This two-step process for invalidating associations is necessary /// to prevent an attacker from invalidating an association at will by /// adding "invalidate_handle" parameters to an authentication response.</para> + /// <para>For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger.</para> /// </remarks> - [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = false)] + [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")] + [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] internal string InvalidateHandle { get; set; } } } diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs index 2f02974..fff4cf6 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs @@ -207,7 +207,11 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Gets or sets the association handle that the Provider wants the Relying Party to not use any more. /// </summary> /// <value>If the Relying Party sent an invalid association handle with the request, it SHOULD be included here.</value> - [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = false)] + /// <remarks> + /// For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger. + /// </remarks> + [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")] + [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] string ITamperResistantOpenIdMessage.InvalidateHandle { get; set; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs index 4137c72..98e3598 100644 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ b/src/DotNetOpenAuth/OpenId/Realm.cs @@ -180,6 +180,14 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Gets the original string. + /// </summary> + /// <value>The original string.</value> + internal string OriginalString { + get { return this.uri.OriginalString; } + } + + /// <summary> /// Gets the realm URL. If the realm includes a wildcard, it is not included here. /// </summary> internal Uri NoWildcardUri { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js index c58e06e..297ea23 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js @@ -178,15 +178,19 @@ $(function() { ajaxbox.focus(); } }); + + $(ajaxbox.form).keydown(function(e) { + if (e.keyCode == $.ui.keyCode.ENTER) { + // we do NOT want to submit the form on ENTER. + e.preventDefault(); + } + }); } // Make popup window close on escape (the dialog style is already taken care of) $(document).keydown(function(e) { if (e.keyCode == $.ui.keyCode.ESCAPE) { window.close(); - } else if (e.keyCode == $.ui.keyCode.ENTER) { - // we do NOT want to submit the form on ENTER. - e.preventDefault(); } }); });
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index 278ae37..639ff57 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -616,6 +616,21 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Returns a hash code for this instance. + /// </summary> + /// <returns> + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// </returns> + public override int GetHashCode() { + int hashCode = 0; + hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Scheme); + hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Authority); + hashCode += StringComparer.Ordinal.GetHashCode(this.Path); + hashCode += StringComparer.Ordinal.GetHashCode(this.Query); + return hashCode; + } + + /// <summary> /// Normalizes the characters that are escaped in the given URI path. /// </summary> /// <param name="path">The path to normalize.</param> |