diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-11-20 07:44:47 -0800 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-11-20 07:44:47 -0800 |
commit | 847882bc2a9e130aad0d39b7b7b1bf73e899c11e (patch) | |
tree | f9d37616ec063bc5653fb8caa6117c2401291a22 /src | |
parent | 1a882368e2e99e9360cfd1f4f23f5acb70306436 (diff) | |
download | DotNetOpenAuth-847882bc2a9e130aad0d39b7b7b1bf73e899c11e.zip DotNetOpenAuth-847882bc2a9e130aad0d39b7b7b1bf73e899c11e.tar.gz DotNetOpenAuth-847882bc2a9e130aad0d39b7b7b1bf73e899c11e.tar.bz2 |
Added MessagePart versioning capability.
A single message type can now serialize differently based on the protocol version a particular instance is representing.
This fix allows for the associate messages to pass their tests.
Diffstat (limited to 'src')
10 files changed, 125 insertions, 30 deletions
diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs index da5275b..47e86f7 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs @@ -12,13 +12,18 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { [TestClass] public class MessageDescriptionTests : MessagingTestBase { [TestMethod, ExpectedException(typeof(ArgumentNullException))] - public void GetNull() { - MessageDescription.Get(null); + public void GetNullType() { + MessageDescription.Get(null, new Version(1, 0)); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void GetNullVersion() { + MessageDescription.Get(typeof(Mocks.TestMessage), null); } [TestMethod, ExpectedException(typeof(ArgumentException))] public void GetNonMessageType() { - MessageDescription.Get(typeof(string)); + MessageDescription.Get(typeof(string), new Version(1, 0)); } } } diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 0f020cc..2db6b93 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -645,7 +645,7 @@ namespace DotNetOpenAuth.Messaging { Debug.Assert(message != null, "message == null"); MessageDictionary dictionary = new MessageDictionary(message); - MessageDescription description = MessageDescription.Get(message.GetType()); + MessageDescription description = MessageDescription.Get(message.GetType(), message.ProtocolVersion); description.EnsureMessagePartsPassBasicValidation(dictionary); } diff --git a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs index 248cb01..51a888e 100644 --- a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs +++ b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs @@ -12,7 +12,7 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Applied to fields and properties that form a key/value in a protocol message. /// </summary> - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = true)] public sealed class MessagePartAttribute : Attribute { /// <summary> /// The overridden name to use as the serialized name for the property. @@ -24,6 +24,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> public MessagePartAttribute() { this.AllowEmpty = true; + this.MinVersionValue = new Version(0, 0); } /// <summary> @@ -65,5 +66,22 @@ namespace DotNetOpenAuth.Messaging { /// Gets or sets a custom encoder to use to translate the applied member to and from a string. /// </summary> public Type Encoder { get; set; } + + /// <summary> + /// Gets or sets the minimum version of the protocol this attribute applies to + /// and overrides any attributes with lower values for this property. + /// </summary> + /// <value>Defaults to 0.0.</value> + public string MinVersion { + get { return this.MinVersionValue.ToString(); } + set { this.MinVersionValue = new Version(value); } + } + + /// <summary> + /// Gets or sets the minimum version of the protocol this attribute applies to + /// and overrides any attributes with lower values for this property. + /// </summary> + /// <value>Defaults to 0.0.</value> + internal Version MinVersionValue { get; set; } } } diff --git a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs index 1db0c57..cceab3d 100644 --- a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs +++ b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs @@ -84,7 +84,7 @@ namespace DotNetOpenAuth.Messaging { ErrorUtilities.VerifyArgumentNotNull(message, "message"); // Before we deserialize the message, make sure all the required parts are present. - MessageDescription.Get(this.messageType).EnsureMessagePartsPassBasicValidation(fields); + MessageDescription.Get(this.messageType, message.ProtocolVersion).EnsureMessagePartsPassBasicValidation(fields); try { foreach (var pair in fields) { diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs index 31b6c41..ae52679 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs @@ -20,12 +20,12 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// A dictionary of reflected message types and the generated reflection information. /// </summary> - private static Dictionary<Type, MessageDescription> reflectedMessageTypes = new Dictionary<Type, MessageDescription>(); + private static Dictionary<MessageTypeAndVersion, MessageDescription> reflectedMessageTypes = new Dictionary<MessageTypeAndVersion, MessageDescription>(); /// <summary> /// The type of message this instance was generated from. /// </summary> - private Type messageType; + private MessageTypeAndVersion messageTypeAndVersion; /// <summary> /// A mapping between the serialized key names and their @@ -36,19 +36,19 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// Initializes a new instance of the <see cref="MessageDescription"/> class. /// </summary> - /// <param name="messageType">The type of message to reflect over.</param> - private MessageDescription(Type messageType) { - Debug.Assert(messageType != null, "messageType == null"); + /// <param name="messageTypeAndVersion">The type and protocol version of the message to reflect over.</param> + private MessageDescription(MessageTypeAndVersion messageTypeAndVersion) { + ErrorUtilities.VerifyArgumentNotNull(messageTypeAndVersion, "messageTypeAndVersion"); - if (!typeof(IProtocolMessage).IsAssignableFrom(messageType)) { + if (!typeof(IProtocolMessage).IsAssignableFrom(messageTypeAndVersion.Type)) { throw new ArgumentException(string.Format( CultureInfo.CurrentCulture, MessagingStrings.UnexpectedType, typeof(IProtocolMessage), - messageType)); + messageTypeAndVersion.Type)); } - this.messageType = messageType; + this.messageTypeAndVersion = messageTypeAndVersion; this.ReflectMessageType(); } @@ -65,17 +65,19 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// given message type. /// </summary> /// <param name="messageType">A type that implements <see cref="IProtocolMessage"/>.</param> + /// <param name="messageVersion">The protocol version of the message.</param> /// <returns>A <see cref="MessageDescription"/> instance.</returns> - internal static MessageDescription Get(Type messageType) { - if (messageType == null) { - throw new ArgumentNullException("messageType"); - } + internal static MessageDescription Get(Type messageType, Version messageVersion) { + ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType"); + ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion"); + + MessageTypeAndVersion key = new MessageTypeAndVersion(messageType, messageVersion); MessageDescription result; - if (!reflectedMessageTypes.TryGetValue(messageType, out result)) { + if (!reflectedMessageTypes.TryGetValue(key, out result)) { lock (reflectedMessageTypes) { - if (!reflectedMessageTypes.TryGetValue(messageType, out result)) { - reflectedMessageTypes[messageType] = result = new MessageDescription(messageType); + if (!reflectedMessageTypes.TryGetValue(key, out result)) { + reflectedMessageTypes[key] = result = new MessageDescription(key); } } } @@ -90,11 +92,15 @@ namespace DotNetOpenAuth.Messaging.Reflection { internal void ReflectMessageType() { this.mapping = new Dictionary<string, MessagePart>(); - Type currentType = this.messageType; + Type currentType = this.messageTypeAndVersion.Type; do { foreach (MemberInfo member in currentType.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { if (member is PropertyInfo || member is FieldInfo) { - MessagePartAttribute partAttribute = member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>().FirstOrDefault(); + MessagePartAttribute partAttribute = + (from a in member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>() + orderby a.MinVersionValue descending + where a.MinVersionValue <= this.messageTypeAndVersion.Version + select a).FirstOrDefault(); if (partAttribute != null) { MessagePart part = new MessagePart(member, partAttribute); this.mapping.Add(part.Name, part); @@ -129,7 +135,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { string.Format( CultureInfo.CurrentCulture, MessagingStrings.RequiredParametersMissing, - this.messageType.FullName, + this.messageTypeAndVersion.Type.FullName, string.Join(", ", missingKeys))); } } @@ -148,9 +154,73 @@ namespace DotNetOpenAuth.Messaging.Reflection { string.Format( CultureInfo.CurrentCulture, MessagingStrings.RequiredNonEmptyParameterWasEmpty, - this.messageType.FullName, + this.messageTypeAndVersion.Type.FullName, string.Join(", ", emptyValuedKeys))); } } + + /// <summary> + /// A struct used as the key to bundle message type and version. + /// </summary> + private struct MessageTypeAndVersion { + /// <summary> + /// Backing store for the <see cref="Type"/> property. + /// </summary> + private readonly Type type; + + /// <summary> + /// Backing store for the <see cref="Version"/> property. + /// </summary> + private readonly Version version; + + /// <summary> + /// Initializes a new instance of the <see cref="MessageTypeAndVersion"/> struct. + /// </summary> + /// <param name="messageType">Type of the message.</param> + /// <param name="messageVersion">The message version.</param> + internal MessageTypeAndVersion(Type messageType, Version messageVersion) { + ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType"); + ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion"); + + this.type = messageType; + this.version = messageVersion; + } + + /// <summary> + /// Gets the message type. + /// </summary> + internal Type Type { get { return this.type; } } + + /// <summary> + /// Gets the message version. + /// </summary> + internal Version Version { get { return this.version; } } + + /// <summary> + /// Indicates whether this instance and a specified object are equal. + /// </summary> + /// <param name="obj">Another object to compare to.</param> + /// <returns> + /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false. + /// </returns> + public override bool Equals(object obj) { + if (obj is MessageTypeAndVersion) { + MessageTypeAndVersion other = (MessageTypeAndVersion)obj; + return this.type == other.type && this.version == other.version; + } else { + return false; + } + } + + /// <summary> + /// Returns the hash code for this instance. + /// </summary> + /// <returns> + /// A 32-bit signed integer that is the hash code for this instance. + /// </returns> + public override int GetHashCode() { + return this.type.GetHashCode(); + } + } } } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs index 45399aa..fa4ac83 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs @@ -36,7 +36,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { } this.message = message; - this.description = MessageDescription.Get(message.GetType()); + this.description = MessageDescription.Get(message.GetType(), message.ProtocolVersion); } #region ICollection<KeyValuePair<string,string>> Properties diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs index 4a5129c..d23de38 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs @@ -136,7 +136,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { private static string GetSignedParameterOrder(ITamperResistantOpenIdMessage signedMessage) { ErrorUtilities.VerifyArgumentNotNull(signedMessage, "signedMessage"); - MessageDescription description = MessageDescription.Get(signedMessage.GetType()); + MessageDescription description = MessageDescription.Get(signedMessage.GetType(), signedMessage.ProtocolVersion); var signedParts = from part in description.Mapping.Values where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 select part.Name; diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs index 020f2dc..621054f 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs @@ -39,7 +39,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value> /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks> - [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = false)] + [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = true)] + [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] internal string SessionType { get; set; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs index 043a98c..6486fce 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs @@ -54,7 +54,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value> /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks> - [MessagePart("session_type", IsRequired = true, AllowEmpty = false)] + [MessagePart("session_type", IsRequired = true, AllowEmpty = true)] + [MessagePart("session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] internal string SessionType { get; set; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs index e4ae233..bc00ac0 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs @@ -24,7 +24,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// This particular value MUST be present for the request to be a valid OpenID Authentication 2.0 request. Future versions of the specification may define different values in order to allow message recipients to properly interpret the request. /// </remarks> [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection.")] - [MessagePart("openid.ns", IsRequired = true, AllowEmpty = false)] + [MessagePart("openid.ns", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] #pragma warning disable 0414 // read by reflection private readonly string OpenIdNamespace = Protocol.OpenId2Namespace; #pragma warning restore 0414 |