diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-09-18 22:54:10 -0700 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-09-18 22:54:10 -0700 |
commit | 4b6f60f351b8520e3824ee80dc0066f1c69c29b4 (patch) | |
tree | b541a53615096c49d2f4988e68622a10b50ff412 /src | |
parent | 11615a19bad90741c7a755eaf741b8683992c9ff (diff) | |
download | DotNetOpenAuth-4b6f60f351b8520e3824ee80dc0066f1c69c29b4.zip DotNetOpenAuth-4b6f60f351b8520e3824ee80dc0066f1c69c29b4.tar.gz DotNetOpenAuth-4b6f60f351b8520e3824ee80dc0066f1c69c29b4.tar.bz2 |
All tests passing, with more work done.
Diffstat (limited to 'src')
15 files changed, 148 insertions, 12 deletions
diff --git a/src/DotNetOAuth.Test/Messaging/ChannelTests.cs b/src/DotNetOAuth.Test/Messaging/ChannelTests.cs index 5ffd1d6..e9c39ee 100644 --- a/src/DotNetOAuth.Test/Messaging/ChannelTests.cs +++ b/src/DotNetOAuth.Test/Messaging/ChannelTests.cs @@ -14,6 +14,7 @@ namespace DotNetOAuth.Test.Messaging { using DotNetOAuth.Messaging.Bindings;
using DotNetOAuth.Test.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using System.Xml;
[TestClass]
public class ChannelTests : MessagingTestBase {
@@ -198,7 +199,11 @@ namespace DotNetOAuth.Test.Messaging { [TestMethod]
public void ReadFromRequestWithContext() {
// TODO: make this a request with a message in it.
- HttpRequest request = new HttpRequest("somefile", "http://someurl", "age=15");
+ var fields = new Dictionary<string, string>() {
+ { "age", "15" },
+ { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) },
+ };
+ HttpRequest request = new HttpRequest("somefile", "http://someurl", MessagingUtilities.CreateQueryString(fields));
HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
IProtocolMessage message = this.Channel.ReadFromRequest();
Assert.IsNotNull(message);
@@ -278,12 +283,15 @@ namespace DotNetOAuth.Test.Messaging { transformB,
transformA);
- Assert.AreEqual(5, channel.BindingElements.Count);
+ Assert.AreEqual(6, channel.BindingElements.Count);
Assert.AreSame(transformB, channel.BindingElements[0]);
Assert.AreSame(transformA, channel.BindingElements[1]);
Assert.AreSame(replay, channel.BindingElements[2]);
Assert.AreSame(expire, channel.BindingElements[3]);
Assert.AreSame(sign, channel.BindingElements[4]);
+
+ // This last one is added by the channel.
+ Assert.IsInstanceOfType(channel.BindingElements[5], typeof(EnsureCompleteMessageBindingElement));
}
[TestMethod, ExpectedException(typeof(UnprotectedMessageException))]
@@ -298,5 +306,14 @@ namespace DotNetOAuth.Test.Messaging { this.Channel = CreateChannel(MessageProtection.None, MessageProtection.TamperProtection);
this.ParameterizedReceiveProtectedTest(DateTime.Now, false);
}
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void IncomingMessageMissingRequiredParameters() {
+ var fields = new Dictionary<string, string> {
+ { "age", "15" },
+ // missing required Timestamp parameter.
+ };
+ this.Channel.ReadFromRequest(CreateHttpRequestInfo("GET", fields));
+ }
}
}
diff --git a/src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs b/src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs index 25dd8e3..f034981 100644 --- a/src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs +++ b/src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs @@ -9,6 +9,7 @@ namespace DotNetOAuth.Test.Messaging { using System.Collections.Generic;
using DotNetOAuth.Messaging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using System.Xml;
/// <summary>
/// Tests for the <see cref="MessageSerializer"/> class.
@@ -112,6 +113,7 @@ namespace DotNetOAuth.Test.Messaging { Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
fields["age"] = "15";
fields["Name"] = "Andrew";
+ fields["Timestamp"] = XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc);
// Add some field that is not recognized by the class. This simulates a querystring with
// more parameters than are actually interesting to the protocol message.
fields["someExtraField"] = "asdf";
diff --git a/src/DotNetOAuth.Test/Messaging/MessagingTestBase.cs b/src/DotNetOAuth.Test/Messaging/MessagingTestBase.cs index a21b000..e75087f 100644 --- a/src/DotNetOAuth.Test/Messaging/MessagingTestBase.cs +++ b/src/DotNetOAuth.Test/Messaging/MessagingTestBase.cs @@ -90,6 +90,7 @@ namespace DotNetOAuth.Test { { "age", "15" },
{ "Name", "Andrew" },
{ "Location", "http://hostb/pathB" },
+ { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) },
};
IProtocolMessage requestMessage = this.Channel.ReadFromRequest(CreateHttpRequestInfo(method, fields));
Assert.IsNotNull(requestMessage);
@@ -107,6 +108,7 @@ namespace DotNetOAuth.Test { { "Location", "http://hostb/pathB" },
{ "Signature", invalidSignature ? "badsig" : MockSigningBindingElement.MessageSignature },
{ "Nonce", "someNonce" },
+ { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) },
};
if (utcCreatedDate.HasValue) {
utcCreatedDate = DateTime.Parse(utcCreatedDate.Value.ToUniversalTime().ToString()); // round off the milliseconds so comparisons work later
diff --git a/src/DotNetOAuth.Test/Messaging/Reflection/MessagePartTests.cs b/src/DotNetOAuth.Test/Messaging/Reflection/MessagePartTests.cs index 1d7e1b8..bfc8192 100644 --- a/src/DotNetOAuth.Test/Messaging/Reflection/MessagePartTests.cs +++ b/src/DotNetOAuth.Test/Messaging/Reflection/MessagePartTests.cs @@ -14,16 +14,23 @@ namespace DotNetOAuth.Test.Messaging.Reflection { /// Optional structs like int must be nullable for Optional to make sense.
/// </summary>
[MessagePart(IsRequired = false)]
- internal int optionalInt;
+ internal int optionalInt = 0;
+ }
+ class MessageWithNonNullableRequiredStruct {
+ /// <summary>
+ /// This should work because a required field will always have a value so it
+ /// need not be nullable.
+ /// </summary>
+ [MessagePart(IsRequired = true)]
+ internal int optionalInt = 0;
}
class MessageWithNullableOptionalStruct {
/// <summary>
/// Optional structs like int must be nullable for Optional to make sense.
/// </summary>
[MessagePart(IsRequired = false)]
- internal int? optionalInt;
+ internal int? optionalInt = 0;
}
-
[TestMethod, ExpectedException(typeof(ArgumentException))]
public void OptionalNonNullableStruct() {
@@ -31,6 +38,11 @@ namespace DotNetOAuth.Test.Messaging.Reflection { }
[TestMethod]
+ public void RequiredNonNullableStruct() {
+ ParameterizedMessageTypeTest(typeof(MessageWithNonNullableRequiredStruct));
+ }
+
+ [TestMethod]
public void OptionalNullableStruct() {
ParameterizedMessageTypeTest(typeof(MessageWithNullableOptionalStruct));
}
diff --git a/src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs b/src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs index 8066adf..a52b66a 100644 --- a/src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs +++ b/src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs @@ -26,12 +26,18 @@ namespace DotNetOAuth.Test.Mocks { [MessagePart(Name = "age", IsRequired = true)]
public int Age { get; set; }
+
[MessagePart]
public string Name { get; set; }
+
[MessagePart]
public string EmptyMember { get; set; }
+
[MessagePart]
public Uri Location { get; set; }
+
+ [MessagePart(IsRequired = true)]
+ public DateTime Timestamp { get; set; }
#region IDirectedProtocolMessage Members
diff --git a/src/DotNetOAuth.Test/OAuthChannelTests.cs b/src/DotNetOAuth.Test/OAuthChannelTests.cs index 8011496..8b7b24b 100644 --- a/src/DotNetOAuth.Test/OAuthChannelTests.cs +++ b/src/DotNetOAuth.Test/OAuthChannelTests.cs @@ -15,6 +15,7 @@ namespace DotNetOAuth.Test { using DotNetOAuth.Messaging;
using DotNetOAuth.Test.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using System.Xml;
[TestClass]
public class OAuthChannelTests : TestBase {
@@ -85,6 +86,7 @@ namespace DotNetOAuth.Test { { "age", "15" },
{ "Name", "Andrew" },
{ "Location", "http://hostb/pathB" },
+ { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) },
};
MemoryStream ms = new MemoryStream();
@@ -208,6 +210,7 @@ namespace DotNetOAuth.Test { Name = "Andrew",
Location = new Uri("http://hostb/pathB"),
Recipient = new Uri("http://localtest"),
+ Timestamp = DateTime.UtcNow,
};
Response rawResponse = null;
@@ -220,11 +223,13 @@ namespace DotNetOAuth.Test { Assert.AreEqual(request.Age, incomingMessage.Age);
Assert.AreEqual(request.Name, incomingMessage.Name);
Assert.AreEqual(request.Location, incomingMessage.Location);
+ Assert.AreEqual(request.Timestamp, incomingMessage.Timestamp);
var responseFields = new Dictionary<string, string> {
{ "age", request.Age.ToString() },
{ "Name", request.Name },
{ "Location", request.Location.AbsoluteUri },
+ { "Timestamp", XmlConvert.ToString(request.Timestamp, XmlDateTimeSerializationMode.Utc) },
};
rawResponse = new Response {
Body = MessagingUtilities.CreateQueryString(responseFields),
@@ -247,6 +252,7 @@ namespace DotNetOAuth.Test { { "age", "15" },
{ "Name", "Andrew" },
{ "Location", "http://hostb/pathB" },
+ { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) },
};
IProtocolMessage requestMessage = this.channel.ReadFromRequest(CreateHttpRequestInfo(scheme, fields));
Assert.IsNotNull(requestMessage);
diff --git a/src/DotNetOAuth/DotNetOAuth.csproj b/src/DotNetOAuth/DotNetOAuth.csproj index f5882ef..a1fac72 100644 --- a/src/DotNetOAuth/DotNetOAuth.csproj +++ b/src/DotNetOAuth/DotNetOAuth.csproj @@ -68,6 +68,7 @@ <ItemGroup>
<Compile Include="Consumer.cs" />
<Compile Include="IWebRequestHandler.cs" />
+ <Compile Include="Messaging\Bindings\EnsureCompleteMessageBindingElement.cs" />
<Compile Include="Messaging\Reflection\MessagePartAttribute.cs" />
<Compile Include="Messaging\MessageProtection.cs" />
<Compile Include="Messaging\IChannelBindingElement.cs" />
diff --git a/src/DotNetOAuth/Messaging/Bindings/EnsureCompleteMessageBindingElement.cs b/src/DotNetOAuth/Messaging/Bindings/EnsureCompleteMessageBindingElement.cs new file mode 100644 index 0000000..dc00c12 --- /dev/null +++ b/src/DotNetOAuth/Messaging/Bindings/EnsureCompleteMessageBindingElement.cs @@ -0,0 +1,33 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using DotNetOAuth.Messaging.Reflection;
+
+namespace DotNetOAuth.Messaging.Bindings {
+ internal class EnsureCompleteMessageBindingElement : IChannelBindingElement {
+ #region IChannelBindingElement Members
+
+ public MessageProtection Protection {
+ get { return MessageProtection.None; }
+ }
+
+ public bool PrepareMessageForSending(IProtocolMessage message) {
+ // Before we're sending the message, make sure all the required parts are present.
+ MessageDictionary dictionary = new MessageDictionary(message);
+ MessageDescription.Get(message.GetType()).EnsureRequiredMessagePartsArePresent(dictionary.Keys);
+ return true;
+ }
+
+ public bool PrepareMessageForReceiving(IProtocolMessage message) {
+ // Once the message is deserialized, it is too late to use the MessageDictionary
+ // to see if all the message parts were included in the original serialized message
+ // because non-nullable value types will already have default values by now.
+ // The code for verifying complete incoming messages is included in
+ // MessageSerializer.Deserialize.
+ return false;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Channel.cs b/src/DotNetOAuth/Messaging/Channel.cs index fd7e676..be3cdf2 100644 --- a/src/DotNetOAuth/Messaging/Channel.cs +++ b/src/DotNetOAuth/Messaging/Channel.cs @@ -16,6 +16,7 @@ namespace DotNetOAuth.Messaging { using System.Text;
using System.Web;
using DotNetOAuth.Messaging.Reflection;
+ using DotNetOAuth.Messaging.Bindings;
/// <summary>
/// Manages sending direct messages to a remote party and receiving responses.
@@ -83,6 +84,9 @@ namespace DotNetOAuth.Messaging { this.messageTypeProvider = messageTypeProvider;
this.bindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements));
+
+ // Add a complete message check as a last outgoing step.
+ this.bindingElements.Add(new EnsureCompleteMessageBindingElement());
}
/// <summary>
@@ -532,8 +536,7 @@ namespace DotNetOAuth.Messaging { private void EnsureValidMessageParts(IProtocolMessage message) {
Debug.Assert(message != null, "message == null");
- // TODO: call MessagePart.IsValidValue()
- MessageDescription description = new MessageDescription(message.GetType());
+ MessageDescription description = MessageDescription.Get(message.GetType());
List<MessagePart> invalidParts = description.Mapping.Values.Where(part => !part.IsValidValue(message)).ToList();
if (invalidParts.Count > 0) {
throw new ProtocolException(string.Format(
diff --git a/src/DotNetOAuth/Messaging/MessageSerializer.cs b/src/DotNetOAuth/Messaging/MessageSerializer.cs index 4a06076..6edb0b6 100644 --- a/src/DotNetOAuth/Messaging/MessageSerializer.cs +++ b/src/DotNetOAuth/Messaging/MessageSerializer.cs @@ -13,6 +13,7 @@ namespace DotNetOAuth.Messaging { using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Linq;
+ using DotNetOAuth.Messaging.Reflection;
/// <summary>
/// Serializes/deserializes OAuth messages for/from transit.
@@ -68,7 +69,9 @@ namespace DotNetOAuth.Messaging { throw new ArgumentNullException("message");
}
- return new Reflection.MessageDictionary(message);
+ var result = new Reflection.MessageDictionary(message);
+
+ return result;
}
/// <summary>
@@ -81,6 +84,9 @@ namespace DotNetOAuth.Messaging { throw new ArgumentNullException("fields");
}
+ // Before we deserialize the message, make sure all the required parts are present.
+ MessageDescription.Get(this.messageType).EnsureRequiredMessagePartsArePresent(fields.Keys);
+
IProtocolMessage result ;
try {
result = (IProtocolMessage)Activator.CreateInstance(this.messageType, true);
@@ -88,7 +94,7 @@ namespace DotNetOAuth.Messaging { throw new ProtocolException("Failed to instantiate type " + this.messageType.FullName, ex);
}
foreach (var pair in fields) {
- IDictionary<string, string> dictionary = new Reflection.MessageDictionary(result);
+ IDictionary<string, string> dictionary = new MessageDictionary(result);
dictionary.Add(pair);
}
result.EnsureValidMessage();
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs index b8183c8..9386f53 100644 --- a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs +++ b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs @@ -196,6 +196,15 @@ namespace DotNetOAuth.Messaging { }
/// <summary>
+ /// Looks up a localized string similar to The following required parameters were missing from the {0} message: {1}.
+ /// </summary>
+ internal static string RequiredParametersMissing {
+ get {
+ return ResourceManager.GetString("RequiredParametersMissing", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The binding element offering the {0} protection requires other protection that is not provided..
/// </summary>
internal static string RequiredProtectionMissing {
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.resx b/src/DotNetOAuth/Messaging/MessagingStrings.resx index b6b98d1..bdbd212 100644 --- a/src/DotNetOAuth/Messaging/MessagingStrings.resx +++ b/src/DotNetOAuth/Messaging/MessagingStrings.resx @@ -162,6 +162,9 @@ <data name="ReplayProtectionNotSupported" xml:space="preserve">
<value>This channel does not support replay protection.</value>
</data>
+ <data name="RequiredParametersMissing" xml:space="preserve">
+ <value>The following required parameters were missing from the {0} message: {1}</value>
+ </data>
<data name="RequiredProtectionMissing" xml:space="preserve">
<value>The binding element offering the {0} protection requires other protection that is not provided.</value>
</data>
diff --git a/src/DotNetOAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOAuth/Messaging/Reflection/MessageDescription.cs index ef73a31..6b898bb 100644 --- a/src/DotNetOAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOAuth/Messaging/Reflection/MessageDescription.cs @@ -9,12 +9,14 @@ namespace DotNetOAuth.Messaging.Reflection { using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+ using System.Globalization;
internal class MessageDescription {
+ private static Dictionary<Type, MessageDescription> reflectedMessageTypes = new Dictionary<Type,MessageDescription>();
private Type messageType;
private Dictionary<string, MessagePart> mapping;
- internal MessageDescription(Type messageType) {
+ private MessageDescription(Type messageType) {
if (messageType == null) {
throw new ArgumentNullException("messageType");
}
@@ -27,6 +29,23 @@ namespace DotNetOAuth.Messaging.Reflection { this.ReflectMessageType();
}
+ internal static MessageDescription Get(Type messageType) {
+ if (messageType == null) {
+ throw new ArgumentNullException("messageType");
+ }
+
+ MessageDescription result;
+ if (!reflectedMessageTypes.TryGetValue(messageType, out result)) {
+ lock (reflectedMessageTypes) {
+ if (!reflectedMessageTypes.TryGetValue(messageType, out result)) {
+ reflectedMessageTypes[messageType] = result = new MessageDescription(messageType);
+ }
+ }
+ }
+
+ return result;
+ }
+
internal Type MessageType {
get { return this.messageType; }
}
@@ -52,5 +71,22 @@ namespace DotNetOAuth.Messaging.Reflection { currentType = currentType.BaseType;
} while (currentType != null);
}
+
+ /// <summary>
+ /// Verifies that a given set of keys include all the required parameters
+ /// for this message type or throws an exception.
+ /// </summary>
+ /// <exception cref="ProtocolException">Thrown when required parts of a message are not in <paramref name="keys"/></exception>
+ internal void EnsureRequiredMessagePartsArePresent(IEnumerable<string> keys) {
+ var missingKeys = (from part in Mapping.Values
+ where part.IsRequired && !keys.Contains(part.Name)
+ select part.Name).ToArray();
+ if (missingKeys.Length > 0) {
+ throw new ProtocolException(string.Format(CultureInfo.CurrentCulture,
+ MessagingStrings.RequiredParametersMissing,
+ this.messageType.FullName,
+ string.Join(", ", missingKeys)));
+ }
+ }
}
}
diff --git a/src/DotNetOAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOAuth/Messaging/Reflection/MessageDictionary.cs index 9490894..196af54 100644 --- a/src/DotNetOAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOAuth/Messaging/Reflection/MessageDictionary.cs @@ -26,7 +26,7 @@ namespace DotNetOAuth.Messaging.Reflection { }
this.message = message;
- this.description = new MessageDescription(message.GetType());
+ this.description = MessageDescription.Get(message.GetType());
}
#region IDictionary<string,string> Members
diff --git a/src/DotNetOAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOAuth/Messaging/Reflection/MessagePart.cs index b3c66d0..4d2ebe6 100644 --- a/src/DotNetOAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOAuth/Messaging/Reflection/MessagePart.cs @@ -27,7 +27,7 @@ namespace DotNetOAuth.Messaging.Reflection { static MessagePart() {
Map<Uri>(uri => uri.AbsoluteUri, str => new Uri(str));
- Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => DateTime.Parse(str));
+ Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc));
}
internal MessagePart(MemberInfo member, MessagePartAttribute attribute) {
|