diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-11-03 17:22:00 -0800 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-11-04 08:12:52 -0800 |
commit | 462e19abd9034c11a12cad30e9899740f2bef8ff (patch) | |
tree | e08667f1d69249f8daa6c348a919bd0fd5434415 /src/DotNetOpenAuth.Test | |
parent | 6a79be0eca3929d8fb4e797799dac8d6f7875475 (diff) | |
download | DotNetOpenAuth-462e19abd9034c11a12cad30e9899740f2bef8ff.zip DotNetOpenAuth-462e19abd9034c11a12cad30e9899740f2bef8ff.tar.gz DotNetOpenAuth-462e19abd9034c11a12cad30e9899740f2bef8ff.tar.bz2 |
Changed namepace and project names in preparation for merge with DotNetOpenId.
Diffstat (limited to 'src/DotNetOpenAuth.Test')
45 files changed, 3361 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj new file mode 100644 index 0000000..ae8525c --- /dev/null +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -0,0 +1,117 @@ +<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>9.0.30729</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth.Test</RootNamespace> + <AssemblyName>DotNetOpenAuth.Test</AssemblyName> + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>..\..\bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>..\..\bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Sign)' == 'true' "> + <SignAssembly>true</SignAssembly> + <AssemblyOriginatorKeyFile>..\official-build-key.pfx</AssemblyOriginatorKeyFile> + </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="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> + <Reference Include="System" /> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data" /> + <Reference Include="System.Data.DataSetExtensions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Runtime.Serialization"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Xml.Linq"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Messaging\CollectionAssert.cs" /> + <Compile Include="Messaging\MessageSerializerTests.cs" /> + <Compile Include="Messaging\Reflection\MessageDescriptionTests.cs" /> + <Compile Include="Messaging\Reflection\MessageDictionaryTests.cs" /> + <Compile Include="Messaging\MessagingTestBase.cs" /> + <Compile Include="Messaging\MessagingUtilitiesTests.cs" /> + <Compile Include="Messaging\ChannelTests.cs" /> + <Compile Include="Messaging\HttpRequestInfoTests.cs" /> + <Compile Include="Messaging\ProtocolExceptionTests.cs" /> + <Compile Include="Messaging\Bindings\StandardExpirationBindingElementTests.cs" /> + <Compile Include="Messaging\Reflection\MessagePartTests.cs" /> + <Compile Include="Messaging\Reflection\ValueMappingTests.cs" /> + <Compile Include="Mocks\InMemoryTokenManager.cs" /> + <Compile Include="Mocks\MockTransformationBindingElement.cs" /> + <Compile Include="Mocks\MockReplayProtectionBindingElement.cs" /> + <Compile Include="Mocks\TestBaseMessage.cs" /> + <Compile Include="Mocks\TestDerivedMessage.cs" /> + <Compile Include="Mocks\TestReplayProtectedMessage.cs" /> + <Compile Include="Mocks\TestDirectedMessage.cs" /> + <Compile Include="Mocks\TestBadChannel.cs" /> + <Compile Include="Mocks\TestExpiringMessage.cs" /> + <Compile Include="Mocks\TestSignedDirectedMessage.cs" /> + <Compile Include="Mocks\MockSigningBindingElement.cs" /> + <Compile Include="Mocks\TestWebRequestHandler.cs" /> + <Compile Include="Mocks\TestChannel.cs" /> + <Compile Include="Mocks\TestMessage.cs" /> + <Compile Include="Mocks\TestMessageTypeProvider.cs" /> + <Compile Include="OAuth\ChannelElements\HmacSha1SigningBindingElementTests.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthChannelTests.cs" /> + <Compile Include="OAuth\ChannelElements\PlaintextSigningBindingElementTest.cs" /> + <Compile Include="OAuth\ChannelElements\SigningBindingElementBaseTests.cs" /> + <Compile Include="OAuth\ConsumerDescription.cs" /> + <Compile Include="OAuth\ProtocolTests.cs" /> + <Compile Include="OAuth\ServiceProviderDescriptionTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Messaging\ResponseTests.cs" /> + <Compile Include="Scenarios\AppendixScenarios.cs" /> + <Compile Include="Scenarios\CoordinatingOAuthChannel.cs" /> + <Compile Include="Scenarios\Coordinator.cs" /> + <Compile Include="TestBase.cs" /> + <Compile Include="UriUtilTests.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth\DotNetOpenAuth.csproj"> + <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> + <Name>DotNetOpenAuth</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Logging.config" /> + </ItemGroup> + <ItemGroup> + <Shadow Include="Test References\DotNetOpenAuth.accessor" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Test/Logging.config b/src/DotNetOpenAuth.Test/Logging.config new file mode 100644 index 0000000..f67c031 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Logging.config @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8" ?> +<log4net> + <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender"> + <file value="Testing.log" /> + <appendToFile value="true" /> + <rollingStyle value="Size" /> + <maxSizeRollBackups value="10" /> + <maximumFileSize value="1024KB" /> + <staticLogFileName value="true" /> + <layout type="log4net.Layout.PatternLayout"> + <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> + </layout> + </appender> + <appender name="TraceAppender" type="log4net.Appender.TraceAppender"> + <immediateFlush value="true" /> + <layout type="log4net.Layout.PatternLayout"> + <conversionPattern value="%-5level - %message%newline" /> + </layout> + </appender> + <!-- Setup the root category, add the appenders and set the default level --> + <root> + <level value="Info" /> + <!--<appender-ref ref="RollingFileAppender" />--> + <appender-ref ref="TraceAppender" /> + </root> + <!-- Specify the level for some specific categories --> + <logger name="DotNetOpenAuth"> + <level value="Debug" /> + </logger> + <logger name="DotNetOpenAuth.Test"> + <level value="Debug" /> + </logger> +</log4net> diff --git a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs new file mode 100644 index 0000000..69f95f9 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardExpirationBindingElementTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging.Bindings { + using System; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class StandardExpirationBindingElementTests : MessagingTestBase { + [TestMethod] + public void SendSetsTimestamp() { + TestExpiringMessage message = new TestExpiringMessage(MessageTransport.Indirect); + message.Recipient = new Uri("http://localtest"); + ((IExpiringProtocolMessage)message).UtcCreationDate = DateTime.Parse("1/1/1990"); + + Channel channel = CreateChannel(MessageProtections.Expiration); + channel.Send(message); + Assert.IsTrue(DateTime.UtcNow - ((IExpiringProtocolMessage)message).UtcCreationDate < TimeSpan.FromSeconds(3), "The timestamp on the message was not set on send."); + } + + [TestMethod] + public void VerifyGoodTimestampIsAccepted() { + this.Channel = CreateChannel(MessageProtections.Expiration); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false); + } + + [TestMethod, ExpectedException(typeof(ExpiredMessageException))] + public void VerifyBadTimestampIsRejected() { + this.Channel = CreateChannel(MessageProtections.Expiration); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow - StandardExpirationBindingElement.DefaultMaximumMessageAge - TimeSpan.FromSeconds(1), false); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs b/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs new file mode 100644 index 0000000..4127b5a --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs @@ -0,0 +1,290 @@ +//----------------------------------------------------------------------- +// <copyright file="ChannelTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ChannelTests : MessagingTestBase { + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNull() { + // This bad channel is deliberately constructed to pass null to + // its protected base class' constructor. + new TestBadChannel(true); + } + + [TestMethod] + public void ReadFromRequestQueryString() { + this.ParameterizedReceiveTest("GET"); + } + + [TestMethod] + public void ReadFromRequestForm() { + this.ParameterizedReceiveTest("POST"); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void SendNull() { + this.Channel.Send(null); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void SendIndirectedUndirectedMessage() { + IProtocolMessage message = new TestMessage(MessageTransport.Indirect); + this.Channel.Send(message); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void SendDirectedNoRecipientMessage() { + IProtocolMessage message = new TestDirectedMessage(MessageTransport.Indirect); + this.Channel.Send(message); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void SendInvalidMessageTransport() { + IProtocolMessage message = new TestDirectedMessage((MessageTransport)100); + this.Channel.Send(message); + } + + [TestMethod] + public void SendIndirectMessage301Get() { + TestDirectedMessage message = new TestDirectedMessage(MessageTransport.Indirect); + GetStandardTestMessage(FieldFill.CompleteBeforeBindings, message); + message.Recipient = new Uri("http://provider/path"); + var expected = GetStandardTestFields(FieldFill.CompleteBeforeBindings); + + Response response = this.Channel.Send(message); + Assert.AreEqual(HttpStatusCode.Redirect, response.Status); + StringAssert.StartsWith(response.Headers[HttpResponseHeader.Location], "http://provider/path"); + foreach (var pair in expected) { + string key = HttpUtility.UrlEncode(pair.Key); + string value = HttpUtility.UrlEncode(pair.Value); + string substring = string.Format("{0}={1}", key, value); + StringAssert.Contains(response.Headers[HttpResponseHeader.Location], substring); + } + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void SendIndirectMessage301GetNullMessage() { + TestBadChannel badChannel = new TestBadChannel(false); + badChannel.Create301RedirectResponse(null, new Dictionary<string, string>()); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void SendIndirectMessage301GetEmptyRecipient() { + TestBadChannel badChannel = new TestBadChannel(false); + var message = new TestDirectedMessage(MessageTransport.Indirect); + badChannel.Create301RedirectResponse(message, new Dictionary<string, string>()); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void SendIndirectMessage301GetNullFields() { + TestBadChannel badChannel = new TestBadChannel(false); + var message = new TestDirectedMessage(MessageTransport.Indirect); + message.Recipient = new Uri("http://someserver"); + badChannel.Create301RedirectResponse(message, null); + } + + [TestMethod] + public void SendIndirectMessageFormPost() { + // We craft a very large message to force fallback to form POST. + // We'll also stick some HTML reserved characters in the string value + // to test proper character escaping. + var message = new TestDirectedMessage(MessageTransport.Indirect) { + Age = 15, + Name = "c<b" + new string('a', 10 * 1024), + Location = new Uri("http://host/path"), + Recipient = new Uri("http://provider/path"), + }; + Response response = this.Channel.Send(message); + Assert.AreEqual(HttpStatusCode.OK, response.Status, "A form redirect should be an HTTP successful response."); + Assert.IsNull(response.Headers[HttpResponseHeader.Location], "There should not be a redirection header in the response."); + string body = response.Body; + StringAssert.Contains(body, "<form "); + StringAssert.Contains(body, "action=\"http://provider/path\""); + StringAssert.Contains(body, "method=\"post\""); + StringAssert.Contains(body, "<input type=\"hidden\" name=\"age\" value=\"15\" />"); + StringAssert.Contains(body, "<input type=\"hidden\" name=\"Location\" value=\"http://host/path\" />"); + StringAssert.Contains(body, "<input type=\"hidden\" name=\"Name\" value=\"" + HttpUtility.HtmlEncode(message.Name) + "\" />"); + StringAssert.Contains(body, ".submit()", "There should be some javascript to automate form submission."); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void SendIndirectMessageFormPostNullMessage() { + TestBadChannel badChannel = new TestBadChannel(false); + badChannel.CreateFormPostResponse(null, new Dictionary<string, string>()); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void SendIndirectMessageFormPostEmptyRecipient() { + TestBadChannel badChannel = new TestBadChannel(false); + var message = new TestDirectedMessage(MessageTransport.Indirect); + badChannel.CreateFormPostResponse(message, new Dictionary<string, string>()); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void SendIndirectMessageFormPostNullFields() { + TestBadChannel badChannel = new TestBadChannel(false); + var message = new TestDirectedMessage(MessageTransport.Indirect); + message.Recipient = new Uri("http://someserver"); + badChannel.CreateFormPostResponse(message, null); + } + + /// <summary> + /// Tests that a direct message is sent when the appropriate message type is provided. + /// </summary> + /// <remarks> + /// Since this is a mock channel that doesn't actually formulate a direct message response, + /// we just check that the right method was called. + /// </remarks> + [TestMethod, ExpectedException(typeof(NotImplementedException), "SendDirectMessageResponse")] + public void SendDirectMessageResponse() { + IProtocolMessage message = new TestMessage { + Age = 15, + Name = "Andrew", + Location = new Uri("http://host/path"), + }; + this.Channel.Send(message); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void SendIndirectMessageNull() { + TestBadChannel badChannel = new TestBadChannel(false); + badChannel.SendIndirectMessage(null); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void ReceiveNull() { + TestBadChannel badChannel = new TestBadChannel(false); + badChannel.Receive(null, null); + } + + [TestMethod] + public void ReceiveUnrecognizedMessage() { + TestBadChannel badChannel = new TestBadChannel(false); + Assert.IsNull(badChannel.Receive(new Dictionary<string, string>(), null)); + } + + [TestMethod] + public void ReadFromRequestWithContext() { + var fields = GetStandardTestFields(FieldFill.AllRequired); + TestMessage expectedMessage = GetStandardTestMessage(FieldFill.AllRequired); + 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); + Assert.IsInstanceOfType(message, typeof(TestMessage)); + Assert.AreEqual(expectedMessage.Age, ((TestMessage)message).Age); + } + + [TestMethod, ExpectedException(typeof(InvalidOperationException))] + public void ReadFromRequestNoContext() { + TestBadChannel badChannel = new TestBadChannel(false); + badChannel.ReadFromRequest(); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void ReadFromRequestNull() { + TestBadChannel badChannel = new TestBadChannel(false); + badChannel.ReadFromRequest(null); + } + + [TestMethod] + public void SendReplayProtectedMessageSetsNonce() { + TestReplayProtectedMessage message = new TestReplayProtectedMessage(MessageTransport.Indirect); + message.Recipient = new Uri("http://localtest"); + + this.Channel = CreateChannel(MessageProtections.ReplayProtection); + this.Channel.Send(message); + Assert.IsNotNull(((IReplayProtectedProtocolMessage)message).Nonce); + } + + [TestMethod, ExpectedException(typeof(InvalidSignatureException))] + public void ReceivedInvalidSignature() { + this.Channel = CreateChannel(MessageProtections.TamperProtection); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, true); + } + + [TestMethod] + public void ReceivedReplayProtectedMessageJustOnce() { + this.Channel = CreateChannel(MessageProtections.ReplayProtection); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false); + } + + [TestMethod, ExpectedException(typeof(ReplayedMessageException))] + public void ReceivedReplayProtectedMessageTwice() { + this.Channel = CreateChannel(MessageProtections.ReplayProtection); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false); + } + + [TestMethod, ExpectedException(typeof(ProtocolException))] + public void MessageExpirationWithoutTamperResistance() { + new TestChannel( + new TestMessageTypeProvider(), + new StandardExpirationBindingElement()); + } + + [TestMethod, ExpectedException(typeof(ProtocolException))] + public void TooManyBindingElementsProvidingSameProtection() { + new TestChannel( + new TestMessageTypeProvider(), + new MockSigningBindingElement(), + new MockSigningBindingElement()); + } + + [TestMethod] + public void BindingElementsOrdering() { + IChannelBindingElement transformA = new MockTransformationBindingElement("a"); + IChannelBindingElement transformB = new MockTransformationBindingElement("b"); + IChannelBindingElement sign = new MockSigningBindingElement(); + IChannelBindingElement replay = new MockReplayProtectionBindingElement(); + IChannelBindingElement expire = new StandardExpirationBindingElement(); + + Channel channel = new TestChannel( + new TestMessageTypeProvider(), + sign, + replay, + expire, + transformB, + transformA); + + Assert.AreEqual(5, 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]); + } + + [TestMethod, ExpectedException(typeof(UnprotectedMessageException))] + public void InsufficientlyProtectedMessageSent() { + var message = new TestSignedDirectedMessage(MessageTransport.Direct); + message.Recipient = new Uri("http://localtest"); + this.Channel.Send(message); + } + + [TestMethod, ExpectedException(typeof(UnprotectedMessageException))] + public void InsufficientlyProtectedMessageReceived() { + this.Channel = CreateChannel(MessageProtections.None, MessageProtections.TamperProtection); + this.ParameterizedReceiveProtectedTest(DateTime.Now, false); + } + + [TestMethod, ExpectedException(typeof(ProtocolException))] + public void IncomingMessageMissingRequiredParameters() { + var fields = GetStandardTestFields(FieldFill.IdentifiableButNotAllRequired); + this.Channel.ReadFromRequest(CreateHttpRequestInfo("GET", fields)); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs new file mode 100644 index 0000000..d06ef62 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------- +// <copyright file="CollectionAssert.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System.Collections; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + internal class CollectionAssert<T> { + internal static void AreEquivalent(ICollection<T> expected, ICollection<T> actual) { + ICollection expectedNonGeneric = new List<T>(expected); + ICollection actualNonGeneric = new List<T>(actual); + CollectionAssert.AreEquivalent(expectedNonGeneric, actualNonGeneric); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs new file mode 100644 index 0000000..5333f97 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="HttpRequestInfoTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System.Web; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class HttpRequestInfoTests : TestBase { + [TestMethod] + public void CtorRequest() { + HttpRequest request = new HttpRequest("file", "http://someserver?a=b", "a=b"); + ////request.Headers["headername"] = "headervalue"; // PlatformNotSupportedException prevents us mocking this up + HttpRequestInfo info = new HttpRequestInfo(request); + Assert.AreEqual(request.Headers["headername"], info.Headers["headername"]); + Assert.AreEqual(request.Url.Query, info.Query); + Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); + Assert.AreEqual(request.Url, info.Url); + Assert.AreEqual(request.HttpMethod, info.HttpMethod); + } + + /// <summary> + /// Checks that a property dependent on another null property + /// doesn't generate a NullReferenceException. + /// </summary> + [TestMethod] + public void QueryBeforeSettingUrl() { + HttpRequestInfo info = new HttpRequestInfo(); + Assert.IsNull(info.Query); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs new file mode 100644 index 0000000..62b6393 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageSerializerTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System; + using System.Collections.Generic; + using System.Xml; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// <summary> + /// Tests for the <see cref="MessageSerializer"/> class. + /// </summary> + [TestClass()] + public class MessageSerializerTests : MessagingTestBase { + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void SerializeNull() { + var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage)); + serializer.Serialize(null); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void GetInvalidMessageType() { + MessageSerializer.Get(typeof(string)); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void GetNullType() { + MessageSerializer.Get(null); + } + + [TestMethod()] + public void SerializeTest() { + var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage)); + var message = GetStandardTestMessage(FieldFill.CompleteBeforeBindings); + var expected = GetStandardTestFields(FieldFill.CompleteBeforeBindings); + IDictionary<string, string> actual = serializer.Serialize(message); + Assert.AreEqual(4, actual.Count); + + // Test case sensitivity of generated dictionary + Assert.IsFalse(actual.ContainsKey("Age")); + Assert.IsTrue(actual.ContainsKey("age")); + + // Test contents of dictionary + Assert.AreEqual(expected["age"], actual["age"]); + Assert.AreEqual(expected["Name"], actual["Name"]); + Assert.AreEqual(expected["Location"], actual["Location"]); + Assert.AreEqual(expected["Timestamp"], actual["Timestamp"]); + Assert.IsFalse(actual.ContainsKey("EmptyMember")); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void DeserializeNull() { + var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage)); + serializer.Deserialize(null, null); + } + + [TestMethod] + public void DeserializeSimple() { + var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage)); + Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal); + fields["Name"] = "Andrew"; + fields["age"] = "15"; + fields["Timestamp"] = "1990-01-01T00:00:00"; + var actual = (Mocks.TestMessage)serializer.Deserialize(fields, null); + Assert.AreEqual(15, actual.Age); + Assert.AreEqual("Andrew", actual.Name); + Assert.AreEqual(DateTime.Parse("1/1/1990"), actual.Timestamp); + Assert.IsNull(actual.EmptyMember); + } + + /// <summary> + /// This tests deserialization of a message that is comprised of [MessagePart]'s + /// that are defined in multiple places in the inheritance tree. + /// </summary> + /// <remarks> + /// The element sorting rules are first inheritance order, then alphabetical order. + /// This test validates correct behavior on both. + /// </remarks> + [TestMethod] + public void DeserializeVerifyElementOrdering() { + var serializer = MessageSerializer.Get(typeof(Mocks.TestDerivedMessage)); + Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal); + // We deliberately do this OUT of order, + // since DataContractSerializer demands elements to be in + // 1) inheritance then 2) alphabetical order. + // Proper xml element order would be: Name, age, Second..., TheFirst... + fields["TheFirstDerivedElement"] = "first"; + fields["age"] = "15"; + fields["Name"] = "Andrew"; + fields["SecondDerivedElement"] = "second"; + fields["explicit"] = "explicitValue"; + fields["private"] = "privateValue"; + var actual = (Mocks.TestDerivedMessage)serializer.Deserialize(fields, null); + Assert.AreEqual(15, actual.Age); + Assert.AreEqual("Andrew", actual.Name); + Assert.AreEqual("first", actual.TheFirstDerivedElement); + Assert.AreEqual("second", actual.SecondDerivedElement); + Assert.AreEqual("explicitValue", ((Mocks.IBaseMessageExplicitMembers)actual).ExplicitProperty); + Assert.AreEqual("privateValue", actual.PrivatePropertyAccessor); + } + + [TestMethod] + public void DeserializeWithExtraFields() { + var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage)); + 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"; + var actual = (Mocks.TestMessage)serializer.Deserialize(fields, null); + Assert.AreEqual(15, actual.Age); + Assert.AreEqual("Andrew", actual.Name); + Assert.IsNull(actual.EmptyMember); + } + + [TestMethod, ExpectedException(typeof(ProtocolException))] + public void DeserializeInvalidMessage() { + var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage)); + var fields = GetStandardTestFields(FieldFill.AllRequired); + fields["age"] = "-1"; // Set an disallowed value. + serializer.Deserialize(fields, null); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs new file mode 100644 index 0000000..4413c67 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs @@ -0,0 +1,188 @@ +//----------------------------------------------------------------------- +// <copyright file="MessagingTestBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Xml; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// <summary> + /// The base class that all messaging test classes inherit from. + /// </summary> + public class MessagingTestBase : TestBase { + internal enum FieldFill { + /// <summary> + /// An empty dictionary is returned. + /// </summary> + None, + + /// <summary> + /// Only enough fields for the <see cref="TestMessageTypeProvider"/> + /// to identify the message are included. + /// </summary> + IdentifiableButNotAllRequired, + + /// <summary> + /// All fields marked as required are included. + /// </summary> + AllRequired, + + /// <summary> + /// All user-fillable fields in the message, leaving out those whose + /// values are to be set by channel binding elements. + /// </summary> + CompleteBeforeBindings, + } + + internal Channel Channel { get; set; } + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + + this.Channel = new TestChannel(); + } + + internal static HttpRequestInfo CreateHttpRequestInfo(string method, IDictionary<string, string> fields) { + string query = MessagingUtilities.CreateQueryString(fields); + UriBuilder requestUri = new UriBuilder("http://localhost/path"); + WebHeaderCollection headers = new WebHeaderCollection(); + MemoryStream ms = new MemoryStream(); + if (method == "POST") { + headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + StreamWriter sw = new StreamWriter(ms); + sw.Write(query); + sw.Flush(); + ms.Position = 0; + } else if (method == "GET") { + requestUri.Query = query; + } else { + throw new ArgumentOutOfRangeException("method", method, "Expected POST or GET"); + } + HttpRequestInfo request = new HttpRequestInfo { + HttpMethod = method, + Url = requestUri.Uri, + Headers = headers, + InputStream = ms, + }; + + return request; + } + + internal static Channel CreateChannel(MessageProtections capabilityAndRecognition) { + return CreateChannel(capabilityAndRecognition, capabilityAndRecognition); + } + + internal static Channel CreateChannel(MessageProtections capability, MessageProtections recognition) { + var bindingElements = new List<IChannelBindingElement>(); + if (capability >= MessageProtections.TamperProtection) { + bindingElements.Add(new MockSigningBindingElement()); + } + if (capability >= MessageProtections.Expiration) { + bindingElements.Add(new StandardExpirationBindingElement()); + } + if (capability >= MessageProtections.ReplayProtection) { + bindingElements.Add(new MockReplayProtectionBindingElement()); + } + + bool signing = false, expiration = false, replay = false; + if (recognition >= MessageProtections.TamperProtection) { + signing = true; + } + if (recognition >= MessageProtections.Expiration) { + expiration = true; + } + if (recognition >= MessageProtections.ReplayProtection) { + replay = true; + } + + var typeProvider = new TestMessageTypeProvider(signing, expiration, replay); + return new TestChannel(typeProvider, bindingElements.ToArray()); + } + + internal static IDictionary<string, string> GetStandardTestFields(FieldFill fill) { + TestMessage expectedMessage = GetStandardTestMessage(fill); + + var fields = new Dictionary<string, string>(); + if (fill >= FieldFill.IdentifiableButNotAllRequired) { + fields.Add("age", expectedMessage.Age.ToString()); + } + if (fill >= FieldFill.AllRequired) { + fields.Add("Timestamp", XmlConvert.ToString(expectedMessage.Timestamp, XmlDateTimeSerializationMode.Utc)); + } + if (fill >= FieldFill.CompleteBeforeBindings) { + fields.Add("Name", expectedMessage.Name); + fields.Add("Location", expectedMessage.Location.AbsoluteUri); + } + + return fields; + } + + internal static TestMessage GetStandardTestMessage(FieldFill fill) { + TestMessage message = new TestMessage(); + GetStandardTestMessage(fill, message); + return message; + } + + internal static void GetStandardTestMessage(FieldFill fill, TestMessage message) { + if (message == null) { + throw new ArgumentNullException("message"); + } + + if (fill >= FieldFill.IdentifiableButNotAllRequired) { + message.Age = 15; + } + if (fill >= FieldFill.AllRequired) { + message.Timestamp = DateTime.SpecifyKind(DateTime.Parse("9/19/2008 8 AM"), DateTimeKind.Utc); + } + if (fill >= FieldFill.CompleteBeforeBindings) { + message.Name = "Andrew"; + message.Location = new Uri("http://localtest/path"); + } + } + + internal void ParameterizedReceiveTest(string method) { + var fields = GetStandardTestFields(FieldFill.CompleteBeforeBindings); + TestMessage expectedMessage = GetStandardTestMessage(FieldFill.CompleteBeforeBindings); + + IProtocolMessage requestMessage = this.Channel.ReadFromRequest(CreateHttpRequestInfo(method, fields)); + Assert.IsNotNull(requestMessage); + Assert.IsInstanceOfType(requestMessage, typeof(TestMessage)); + TestMessage actualMessage = (TestMessage)requestMessage; + Assert.AreEqual(expectedMessage.Age, actualMessage.Age); + Assert.AreEqual(expectedMessage.Name, actualMessage.Name); + Assert.AreEqual(expectedMessage.Location, actualMessage.Location); + } + + internal void ParameterizedReceiveProtectedTest(DateTime? utcCreatedDate, bool invalidSignature) { + TestMessage expectedMessage = GetStandardTestMessage(FieldFill.CompleteBeforeBindings); + var fields = GetStandardTestFields(FieldFill.CompleteBeforeBindings); + fields.Add("Signature", invalidSignature ? "badsig" : MockSigningBindingElement.MessageSignature); + fields.Add("Nonce", "someNonce"); + if (utcCreatedDate.HasValue) { + utcCreatedDate = DateTime.Parse(utcCreatedDate.Value.ToUniversalTime().ToString()); // round off the milliseconds so comparisons work later + fields.Add("created_on", XmlConvert.ToString(utcCreatedDate.Value, XmlDateTimeSerializationMode.Utc)); + } + IProtocolMessage requestMessage = this.Channel.ReadFromRequest(CreateHttpRequestInfo("GET", fields)); + Assert.IsNotNull(requestMessage); + Assert.IsInstanceOfType(requestMessage, typeof(TestSignedDirectedMessage)); + TestSignedDirectedMessage actualMessage = (TestSignedDirectedMessage)requestMessage; + Assert.AreEqual(expectedMessage.Age, actualMessage.Age); + Assert.AreEqual(expectedMessage.Name, actualMessage.Name); + Assert.AreEqual(expectedMessage.Location, actualMessage.Location); + if (utcCreatedDate.HasValue) { + IExpiringProtocolMessage expiringMessage = (IExpiringProtocolMessage)requestMessage; + Assert.AreEqual(utcCreatedDate.Value, expiringMessage.UtcCreationDate); + } + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs new file mode 100644 index 0000000..fc60ef6 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------- +// <copyright file="MessagingUtilitiesTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.IO; + using System.Net; + using System.Web; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MessagingUtilitiesTests : TestBase { + [TestMethod] + public void CreateQueryString() { + var args = new Dictionary<string, string>(); + args.Add("a", "b"); + args.Add("c/d", "e/f"); + Assert.AreEqual("a=b&c%2fd=e%2ff", MessagingUtilities.CreateQueryString(args)); + } + + [TestMethod] + public void CreateQueryStringEmptyCollection() { + Assert.AreEqual(0, MessagingUtilities.CreateQueryString(new Dictionary<string, string>()).Length); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CreateQueryStringNullDictionary() { + MessagingUtilities.CreateQueryString(null); + } + + [TestMethod] + public void AppendQueryArgs() { + UriBuilder uri = new UriBuilder("http://baseline.org/page"); + var args = new Dictionary<string, string>(); + args.Add("a", "b"); + args.Add("c/d", "e/f"); + MessagingUtilities.AppendQueryArgs(uri, args); + Assert.AreEqual("http://baseline.org/page?a=b&c%2fd=e%2ff", uri.Uri.AbsoluteUri); + args.Clear(); + args.Add("g", "h"); + MessagingUtilities.AppendQueryArgs(uri, args); + Assert.AreEqual("http://baseline.org/page?a=b&c%2fd=e%2ff&g=h", uri.Uri.AbsoluteUri); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void AppendQueryArgsNullUriBuilder() { + MessagingUtilities.AppendQueryArgs(null, new Dictionary<string, string>()); + } + + [TestMethod] + public void AppendQueryArgsNullDictionary() { + MessagingUtilities.AppendQueryArgs(new UriBuilder(), null); + } + + [TestMethod] + public void ToDictionary() { + NameValueCollection nvc = new NameValueCollection(); + nvc["a"] = "b"; + nvc["c"] = "d"; + Dictionary<string, string> actual = MessagingUtilities.ToDictionary(nvc); + Assert.AreEqual(nvc.Count, actual.Count); + Assert.AreEqual(nvc["a"], actual["a"]); + Assert.AreEqual(nvc["c"], actual["c"]); + } + + [TestMethod] + public void ToDictionaryNull() { + Assert.IsNull(MessagingUtilities.ToDictionary(null)); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void ApplyHeadersToResponseNullResponse() { + MessagingUtilities.ApplyHeadersToResponse(new WebHeaderCollection(), null); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void ApplyHeadersToResponseNullHeaders() { + MessagingUtilities.ApplyHeadersToResponse(null, new HttpResponse(new StringWriter())); + } + + [TestMethod] + public void ApplyHeadersToResponse() { + var headers = new WebHeaderCollection(); + headers[HttpResponseHeader.ContentType] = "application/binary"; + + var response = new HttpResponse(new StringWriter()); + MessagingUtilities.ApplyHeadersToResponse(headers, response); + + Assert.AreEqual(headers[HttpResponseHeader.ContentType], response.ContentType); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs b/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs new file mode 100644 index 0000000..a2e3eaa --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------- +// <copyright file="ProtocolExceptionTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ProtocolExceptionTests : TestBase { + [TestMethod] + public void CtorDefault() { + ProtocolException ex = new ProtocolException(); + } + + [TestMethod] + public void CtorWithTextMessage() { + ProtocolException ex = new ProtocolException("message"); + Assert.AreEqual("message", ex.Message); + } + + [TestMethod] + public void CtorWithTextMessageAndInnerException() { + Exception innerException = new Exception(); + ProtocolException ex = new ProtocolException("message", innerException); + Assert.AreEqual("message", ex.Message); + Assert.AreSame(innerException, ex.InnerException); + } + + [TestMethod] + public void CtorWithProtocolMessage() { + IProtocolMessage request = new Mocks.TestMessage(); + Uri receiver = new Uri("http://receiver"); + ProtocolException ex = new ProtocolException("some error occurred", request, receiver); + IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex; + Assert.AreEqual("some error occurred", ex.Message); + Assert.AreSame(receiver, msg.Recipient); + Assert.AreEqual(request.ProtocolVersion, msg.ProtocolVersion); + Assert.AreEqual(request.Transport, msg.Transport); + msg.EnsureValidMessage(); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorWithNullProtocolMessage() { + new ProtocolException("message", null, new Uri("http://receiver")); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorWithNullReceiver() { + new ProtocolException("message", new Mocks.TestDirectedMessage(MessageTransport.Indirect), null); + } + + /// <summary> + /// Tests that exceptions being sent as direct responses do not need an explicit receiver. + /// </summary> + [TestMethod] + public void CtorUndirectedMessageWithNullReceiver() { + IProtocolMessage request = new Mocks.TestDirectedMessage(MessageTransport.Direct); + ProtocolException ex = new ProtocolException("message", request, null); + IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex; + Assert.IsNull(msg.Recipient); + Assert.AreEqual(request.ProtocolVersion, msg.ProtocolVersion); + Assert.AreEqual(request.Transport, msg.Transport); + } + + [TestMethod, ExpectedException(typeof(InvalidOperationException))] + public void ProtocolVersionWithoutMessage() { + ProtocolException ex = new ProtocolException(); + IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex; + var temp = msg.ProtocolVersion; + } + + [TestMethod, ExpectedException(typeof(InvalidOperationException))] + public void TransportWithoutMessage() { + ProtocolException ex = new ProtocolException(); + IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex; + var temp = msg.Transport; + } + + [TestMethod, ExpectedException(typeof(InvalidOperationException))] + public void RecipientWithoutMessage() { + ProtocolException ex = new ProtocolException(); + IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex; + var temp = msg.Recipient; + } + + [TestMethod, ExpectedException(typeof(InvalidOperationException))] + public void EnsureValidMessageWithoutMessage() { + ProtocolException ex = new ProtocolException(); + IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex; + msg.EnsureValidMessage(); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs new file mode 100644 index 0000000..da5275b --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageDescriptionTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging.Reflection { + using System; + using DotNetOpenAuth.Messaging.Reflection; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MessageDescriptionTests : MessagingTestBase { + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void GetNull() { + MessageDescription.Get(null); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void GetNonMessageType() { + MessageDescription.Get(typeof(string)); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs new file mode 100644 index 0000000..039743e --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs @@ -0,0 +1,347 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageDictionaryTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging.Reflection { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Xml; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MessageDictionaryTests : MessagingTestBase { + private Mocks.TestMessage message; + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + + this.message = new Mocks.TestMessage(); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNull() { + new MessageDictionary(null); + } + + /// <summary> + /// A test for System.Collections.Generic.IDictionary<System.String,System.String>.Values + /// </summary> + [TestMethod] + public void Values() { + IDictionary<string, string> target = new MessageDictionary(this.message); + Collection<string> expected = new Collection<string> { + this.message.Age.ToString(), + XmlConvert.ToString(DateTime.SpecifyKind(this.message.Timestamp, DateTimeKind.Utc), XmlDateTimeSerializationMode.Utc), + }; + CollectionAssert<string>.AreEquivalent(expected, target.Values); + + this.message.Age = 15; + this.message.Location = new Uri("http://localtest"); + this.message.Name = "Andrew"; + target["extra"] = "a"; + expected = new Collection<string> { + this.message.Age.ToString(), + this.message.Location.AbsoluteUri, + this.message.Name, + XmlConvert.ToString(DateTime.SpecifyKind(this.message.Timestamp, DateTimeKind.Utc), XmlDateTimeSerializationMode.Utc), + "a", + }; + CollectionAssert<string>.AreEquivalent(expected, target.Values); + } + + /// <summary> + /// A test for System.Collections.Generic.IDictionary<System.String,System.String>.Keys + /// </summary> + [TestMethod] + public void Keys() { + // We expect that non-nullable value type fields will automatically have keys + // in the dictionary for them. + IDictionary<string, string> target = new MessageDictionary(this.message); + Collection<string> expected = new Collection<string> { + "age", + "Timestamp", + }; + CollectionAssert<string>.AreEquivalent(expected, target.Keys); + + this.message.Name = "Andrew"; + expected.Add("Name"); + target["extraField"] = string.Empty; + expected.Add("extraField"); + CollectionAssert<string>.AreEquivalent(expected, target.Keys); + } + + /// <summary> + /// A test for System.Collections.Generic.IDictionary<System.String,System.String>.Item + /// </summary> + [TestMethod] + public void Item() { + IDictionary<string, string> target = new MessageDictionary(this.message); + + // Test setting of declared message properties. + this.message.Age = 15; + Assert.AreEqual("15", target["age"]); + target["age"] = "13"; + Assert.AreEqual(13, this.message.Age); + + // Test setting extra fields + target["extra"] = "fun"; + Assert.AreEqual("fun", target["extra"]); + Assert.AreEqual("fun", ((IProtocolMessage)this.message).ExtraData["extra"]); + + // Test clearing extra fields + target["extra"] = null; + Assert.IsFalse(target.ContainsKey("extra")); + } + + /// <summary> + /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.IsReadOnly + /// </summary> + [TestMethod] + public void IsReadOnly() { + ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message); + Assert.IsFalse(target.IsReadOnly); + } + + /// <summary> + /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.Count + /// </summary> + [TestMethod] + public void Count() { + ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message); + IDictionary<string, string> targetDictionary = (IDictionary<string, string>)target; + Assert.AreEqual(targetDictionary.Keys.Count, target.Count); + targetDictionary["extraField"] = "hi"; + Assert.AreEqual(targetDictionary.Keys.Count, target.Count); + } + + /// <summary> + /// A test for System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String,System.String<<.GetEnumerator + /// </summary> + [TestMethod] + public void GetEnumerator() { + IEnumerable<KeyValuePair<string, string>> target = new MessageDictionary(this.message); + IDictionary<string, string> targetDictionary = (IDictionary<string, string>)target; + var keys = targetDictionary.Keys.GetEnumerator(); + var values = targetDictionary.Values.GetEnumerator(); + IEnumerator<KeyValuePair<string, string>> actual = target.GetEnumerator(); + + bool keysLast = true, valuesLast = true, actualLast = true; + while (true) { + keysLast = keys.MoveNext(); + valuesLast = values.MoveNext(); + actualLast = actual.MoveNext(); + if (!keysLast || !valuesLast || !actualLast) { + break; + } + + Assert.AreEqual(keys.Current, actual.Current.Key); + Assert.AreEqual(values.Current, actual.Current.Value); + } + Assert.IsTrue(keysLast == valuesLast && keysLast == actualLast); + } + + [TestMethod] + public void GetEnumeratorUntyped() { + IEnumerable target = new MessageDictionary(this.message); + IDictionary<string, string> targetDictionary = (IDictionary<string, string>)target; + var keys = targetDictionary.Keys.GetEnumerator(); + var values = targetDictionary.Values.GetEnumerator(); + IEnumerator actual = target.GetEnumerator(); + + bool keysLast = true, valuesLast = true, actualLast = true; + while (true) { + keysLast = keys.MoveNext(); + valuesLast = values.MoveNext(); + actualLast = actual.MoveNext(); + if (!keysLast || !valuesLast || !actualLast) { + break; + } + + KeyValuePair<string, string> current = (KeyValuePair<string, string>)actual.Current; + Assert.AreEqual(keys.Current, current.Key); + Assert.AreEqual(values.Current, current.Value); + } + Assert.IsTrue(keysLast == valuesLast && keysLast == actualLast); + } + + /// <summary> + /// A test for System.Collections.Generic.IDictionary<System.String,System.String>.TryGetValue + /// </summary> + [TestMethod] + public void TryGetValue() { + IDictionary<string, string> target = new MessageDictionary(this.message); + this.message.Name = "andrew"; + string name; + Assert.IsTrue(target.TryGetValue("Name", out name)); + Assert.AreEqual(this.message.Name, name); + + Assert.IsFalse(target.TryGetValue("name", out name)); + Assert.IsNull(name); + + target["extra"] = "value"; + string extra; + Assert.IsTrue(target.TryGetValue("extra", out extra)); + Assert.AreEqual("value", extra); + } + + /// <summary> + /// A test for System.Collections.Generic.IDictionary<System.String,System.String>.Remove + /// </summary> + [TestMethod] + public void RemoveTest1() { + IDictionary<string, string> target = new MessageDictionary(this.message); + this.message.Name = "andrew"; + Assert.IsTrue(target.Remove("Name")); + Assert.IsNull(this.message.Name); + Assert.IsFalse(target.Remove("Name")); + + Assert.IsFalse(target.Remove("extra")); + target["extra"] = "value"; + Assert.IsTrue(target.Remove("extra")); + Assert.IsFalse(target.ContainsKey("extra")); + } + + /// <summary> + /// A test for System.Collections.Generic.IDictionary<System.String,System.String>.ContainsKey + /// </summary> + [TestMethod] + public void ContainsKey() { + IDictionary<string, string> target = new MessageDictionary(this.message); + Assert.IsTrue(target.ContainsKey("age"), "Value type declared element should have a key."); + Assert.IsFalse(target.ContainsKey("Name"), "Null declared element should NOT have a key."); + + Assert.IsFalse(target.ContainsKey("extra")); + target["extra"] = "value"; + Assert.IsTrue(target.ContainsKey("extra")); + } + + /// <summary> + /// A test for System.Collections.Generic.IDictionary<System.String,System.String>.Add + /// </summary> + [TestMethod] + public void AddByKeyAndValue() { + IDictionary<string, string> target = new MessageDictionary(this.message); + target.Add("extra", "value"); + Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("extra", "value"))); + target.Add("Name", "Andrew"); + Assert.AreEqual("Andrew", this.message.Name); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void AddNullValue() { + IDictionary<string, string> target = new MessageDictionary(this.message); + target.Add("extra", null); + } + + /// <summary> + /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.Add + /// </summary> + [TestMethod] + public void AddByKeyValuePair() { + IDictionary<string, string> target = new MessageDictionary(this.message); + target.Add(new KeyValuePair<string, string>("extra", "value")); + Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("extra", "value"))); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void AddExtraFieldThatAlreadyExists() { + IDictionary<string, string> target = new MessageDictionary(this.message); + target.Add("extra", "value"); + target.Add("extra", "value"); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void AddDeclaredValueThatAlreadyExists() { + IDictionary<string, string> target = new MessageDictionary(this.message); + target.Add("Name", "andrew"); + target.Add("Name", "andrew"); + } + + [TestMethod] + public void DefaultReferenceTypeDeclaredPropertyHasNoKey() { + IDictionary<string, string> target = new MessageDictionary(this.message); + Assert.IsFalse(target.ContainsKey("Name"), "A null value should result in no key."); + Assert.IsFalse(target.Keys.Contains("Name"), "A null value should result in no key."); + } + + [TestMethod] + public void RemoveStructDeclaredProperty() { + IDictionary<string, string> target = new MessageDictionary(this.message); + this.message.Age = 5; + Assert.IsTrue(target.ContainsKey("age")); + target.Remove("age"); + Assert.IsTrue(target.ContainsKey("age")); + Assert.AreEqual(0, this.message.Age); + } + + /// <summary> + /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.Remove + /// </summary> + [TestMethod] + public void RemoveByKeyValuePair() { + ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message); + this.message.Name = "Andrew"; + Assert.IsFalse(target.Remove(new KeyValuePair<string, string>("Name", "andrew"))); + Assert.AreEqual("Andrew", this.message.Name); + Assert.IsTrue(target.Remove(new KeyValuePair<string, string>("Name", "Andrew"))); + Assert.IsNull(this.message.Name); + } + + /// <summary> + /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.CopyTo + /// </summary> + [TestMethod] + public void CopyTo() { + ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message); + IDictionary<string, string> targetAsDictionary = ((IDictionary<string, string>)target); + KeyValuePair<string, string>[] array = new KeyValuePair<string, string>[target.Count + 1]; + int arrayIndex = 1; + target.CopyTo(array, arrayIndex); + Assert.AreEqual(new KeyValuePair<string, string>(), array[0]); + for (int i = 1; i < array.Length; i++) { + Assert.IsNotNull(array[i].Key); + Assert.AreEqual(targetAsDictionary[array[i].Key], array[i].Value); + } + } + + /// <summary> + /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.Contains + /// </summary> + [TestMethod] + public void ContainsKeyValuePair() { + ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message); + IDictionary<string, string> targetAsDictionary = ((IDictionary<string, string>)target); + Assert.IsFalse(target.Contains(new KeyValuePair<string, string>("age", "1"))); + Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("age", "0"))); + + targetAsDictionary["extra"] = "value"; + Assert.IsFalse(target.Contains(new KeyValuePair<string, string>("extra", "Value"))); + Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("extra", "value"))); + Assert.IsFalse(target.Contains(new KeyValuePair<string, string>("wayoff", "value"))); + } + + /// <summary> + /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.Clear + /// </summary> + [TestMethod] + public void Clear() { + ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message); + IDictionary<string, string> targetAsDictionary = ((IDictionary<string, string>)target); + this.message.Name = "Andrew"; + this.message.Age = 15; + targetAsDictionary["extra"] = "value"; + target.Clear(); + Assert.AreEqual(2, target.Count, "Clearing should remove all keys except for declared non-nullable structs."); + Assert.IsFalse(targetAsDictionary.ContainsKey("extra")); + Assert.IsNull(this.message.Name); + Assert.AreEqual(0, this.message.Age); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs new file mode 100644 index 0000000..c3c69c2 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------- +// <copyright file="MessagePartTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging.Reflection { + using System; + using System.Linq; + using System.Reflection; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MessagePartTests : MessagingTestBase { + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void OptionalNonNullableStruct() { + this.ParameterizedMessageTypeTest(typeof(MessageWithNonNullableOptionalStruct)); + } + + [TestMethod] + public void RequiredNonNullableStruct() { + this.ParameterizedMessageTypeTest(typeof(MessageWithNonNullableRequiredStruct)); + } + + [TestMethod] + public void OptionalNullableStruct() { + this.ParameterizedMessageTypeTest(typeof(MessageWithNullableOptionalStruct)); + } + + [TestMethod] + public void RequiredNullableStruct() { + this.ParameterizedMessageTypeTest(typeof(MessageWithNullableRequiredStruct)); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullMember() { + new MessagePart(null, new MessagePartAttribute()); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullAttribute() { + PropertyInfo field = typeof(MessageWithNullableOptionalStruct).GetProperty("OptionalInt", BindingFlags.NonPublic | BindingFlags.Instance); + new MessagePart(field, null); + } + + [TestMethod] + public void SetValue() { + var message = new MessageWithNonNullableRequiredStruct(); + MessagePart part = this.ParameterizedMessageTypeTest(message.GetType()); + part.SetValue(message, "5"); + Assert.AreEqual(5, message.OptionalInt); + } + + [TestMethod] + public void GetValue() { + var message = new MessageWithNonNullableRequiredStruct(); + message.OptionalInt = 8; + MessagePart part = this.ParameterizedMessageTypeTest(message.GetType()); + Assert.AreEqual("8", part.GetValue(message)); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void NonFieldOrPropertyMember() { + MemberInfo method = typeof(MessageWithNullableOptionalStruct).GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance); + new MessagePart(method, new MessagePartAttribute()); + } + + private MessagePart ParameterizedMessageTypeTest(Type messageType) { + PropertyInfo field = messageType.GetProperty("OptionalInt", BindingFlags.NonPublic | BindingFlags.Instance); + MessagePartAttribute attribute = field.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>().Single(); + return new MessagePart(field, attribute); + } + + private class MessageWithNonNullableOptionalStruct : TestMessage { + // Optional structs like int must be nullable for Optional to make sense. + [MessagePart(IsRequired = false)] + internal int OptionalInt { get; set; } + } + + private class MessageWithNonNullableRequiredStruct : TestMessage { + // This should work because a required field will always have a value so it + // need not be nullable. + [MessagePart(IsRequired = true)] + internal int OptionalInt { get; set; } + } + + private class MessageWithNullableOptionalStruct : TestMessage { + // Optional structs like int must be nullable for Optional to make sense. + [MessagePart(IsRequired = false)] + internal int? OptionalInt { get; set; } + } + + private class MessageWithNullableRequiredStruct : TestMessage { + [MessagePart(IsRequired = true)] + private int? OptionalInt { get; set; } + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs new file mode 100644 index 0000000..c4a79b5 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------- +// <copyright file="ValueMappingTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging.Reflection { + using System; + using DotNetOpenAuth.Messaging.Reflection; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ValueMappingTests { + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullToString() { + new ValueMapping(null, str => new object()); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullToObject() { + new ValueMapping(obj => obj.ToString(), null); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs b/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs new file mode 100644 index 0000000..972bb58 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// <copyright file="ResponseTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System; + using System.IO; + using System.Web; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ResponseTests : TestBase { + [TestMethod, ExpectedException(typeof(InvalidOperationException))] + public void SendWithoutAspNetContext() { + new Response().Send(); + } + + [TestMethod] + public void Send() { + StringWriter writer = new StringWriter(); + HttpRequest httpRequest = new HttpRequest("file", "http://server", string.Empty); + HttpResponse httpResponse = new HttpResponse(writer); + HttpContext context = new HttpContext(httpRequest, httpResponse); + HttpContext.Current = context; + + Response response = new Response(); + response.Status = System.Net.HttpStatusCode.OK; + response.Headers["someHeaderName"] = "someHeaderValue"; + response.Body = "some body"; + response.Send(); + string results = writer.ToString(); + // For some reason the only output in test is the body... the headers require a web host + Assert.AreEqual(response.Body, results); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs new file mode 100644 index 0000000..571bba7 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------- +// <copyright file="InMemoryTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + internal class InMemoryTokenManager : ITokenManager { + private Dictionary<string, string> consumersAndSecrets = new Dictionary<string, string>(); + private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); + + /// <summary> + /// Request tokens that have been issued, and whether they have been authorized yet. + /// </summary> + private Dictionary<string, bool> requestTokens = new Dictionary<string, bool>(); + + /// <summary> + /// Access tokens that have been issued and have not yet expired. + /// </summary> + private List<string> accessTokens = new List<string>(); + + #region ITokenManager Members + + public string GetConsumerSecret(string consumerKey) { + return this.consumersAndSecrets[consumerKey]; + } + + public string GetTokenSecret(string token) { + return this.tokensAndSecrets[token]; + } + + public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { + this.tokensAndSecrets[response.Token] = response.TokenSecret; + this.requestTokens.Add(response.Token, false); + } + + /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + public bool IsRequestTokenAuthorized(string requestToken) { + return this.requestTokens[requestToken]; + } + + public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + // The following line is commented out because consumers don't mark their own tokens + // as authorized... only the SPs do. And since we multi-purpose this test class for + // both SPs and Consumers, we won't do this extra check. + ////Debug.Assert(this.requestTokens[requestToken], "Unauthorized token should not be exchanged for access token."); + this.requestTokens.Remove(requestToken); + this.accessTokens.Add(accessToken); + this.tokensAndSecrets.Remove(requestToken); + this.tokensAndSecrets[accessToken] = accessTokenSecret; + } + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns>Request or Access token, or invalid if the token is not recognized.</returns> + public TokenType GetTokenType(string token) { + if (this.requestTokens.ContainsKey(token)) { + return TokenType.RequestToken; + } else if (this.accessTokens.Contains(token)) { + return TokenType.AccessToken; + } else { + return TokenType.InvalidToken; + } + } + + #endregion + + /// <summary> + /// Tells a Service Provider's token manager about a consumer and its secret + /// so that the SP can verify the Consumer's signed messages. + /// </summary> + /// <param name="consumerDescription">The consumer description.</param> + internal void AddConsumer(ConsumerDescription consumerDescription) { + this.consumersAndSecrets.Add(consumerDescription.ConsumerKey, consumerDescription.ConsumerSecret); + } + + /// <summary> + /// Marks an existing token as authorized. + /// </summary> + /// <param name="requestToken">The request token.</param> + internal void AuthorizeRequestToken(string requestToken) { + if (requestToken == null) { + throw new ArgumentNullException("requestToken"); + } + + this.requestTokens[requestToken] = true; + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs b/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs new file mode 100644 index 0000000..5e65a59 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="MockReplayProtectionBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + internal class MockReplayProtectionBindingElement : IChannelBindingElement { + private bool messageReceived; + + #region IChannelBindingElement Members + + MessageProtections IChannelBindingElement.Protection { + get { return MessageProtections.ReplayProtection; } + } + + bool IChannelBindingElement.PrepareMessageForSending(IProtocolMessage message) { + var replayMessage = message as IReplayProtectedProtocolMessage; + if (replayMessage != null) { + replayMessage.Nonce = "someNonce"; + return true; + } + + return false; + } + + bool IChannelBindingElement.PrepareMessageForReceiving(IProtocolMessage message) { + var replayMessage = message as IReplayProtectedProtocolMessage; + if (replayMessage != null) { + Assert.AreEqual("someNonce", replayMessage.Nonce, "The nonce didn't serialize correctly, or something"); + // this mock implementation passes the first time and fails subsequent times. + if (this.messageReceived) { + throw new ReplayedMessageException(message); + } + this.messageReceived = true; + return true; + } + + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/MockSigningBindingElement.cs b/src/DotNetOpenAuth.Test/Mocks/MockSigningBindingElement.cs new file mode 100644 index 0000000..eab9a39 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/MockSigningBindingElement.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="MockSigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + internal class MockSigningBindingElement : IChannelBindingElement { + internal const string MessageSignature = "mocksignature"; + + #region IChannelBindingElement Members + + MessageProtections IChannelBindingElement.Protection { + get { return MessageProtections.TamperProtection; } + } + + bool IChannelBindingElement.PrepareMessageForSending(IProtocolMessage message) { + ITamperResistantProtocolMessage signedMessage = message as ITamperResistantProtocolMessage; + if (signedMessage != null) { + signedMessage.Signature = MessageSignature; + return true; + } + + return false; + } + + bool IChannelBindingElement.PrepareMessageForReceiving(IProtocolMessage message) { + ITamperResistantProtocolMessage signedMessage = message as ITamperResistantProtocolMessage; + if (signedMessage != null) { + if (signedMessage.Signature != MessageSignature) { + throw new InvalidSignatureException(message); + } + return true; + } + + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/MockTransformationBindingElement.cs b/src/DotNetOpenAuth.Test/Mocks/MockTransformationBindingElement.cs new file mode 100644 index 0000000..7c5a240 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/MockTransformationBindingElement.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="MockTransformationBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + internal class MockTransformationBindingElement : IChannelBindingElement { + private string transform; + + internal MockTransformationBindingElement(string transform) { + if (transform == null) { + throw new ArgumentNullException("transform"); + } + + this.transform = transform; + } + + #region IChannelBindingElement Members + + MessageProtections IChannelBindingElement.Protection { + get { return MessageProtections.None; } + } + + bool IChannelBindingElement.PrepareMessageForSending(IProtocolMessage message) { + var testMessage = message as TestMessage; + if (testMessage != null) { + testMessage.Name = this.transform + testMessage.Name; + return true; + } + + return false; + } + + bool IChannelBindingElement.PrepareMessageForReceiving(IProtocolMessage message) { + var testMessage = message as TestMessage; + if (testMessage != null) { + StringAssert.StartsWith(testMessage.Name, this.transform); + testMessage.Name = testMessage.Name.Substring(this.transform.Length); + return true; + } + + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs new file mode 100644 index 0000000..8fbfaf9 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="TestBadChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A Channel derived type that passes null to the protected constructor. + /// </summary> + internal class TestBadChannel : Channel { + internal TestBadChannel(bool badConstructorParam) + : base(badConstructorParam ? null : new TestMessageTypeProvider()) { + } + + internal new void Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { + base.Create301RedirectResponse(message, fields); + } + + internal new void CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { + base.CreateFormPostResponse(message, fields); + } + + internal new void SendIndirectMessage(IDirectedProtocolMessage message) { + base.SendIndirectMessage(message); + } + + internal new IProtocolMessage Receive(Dictionary<string, string> fields, MessageReceivingEndpoint recipient) { + return base.Receive(fields, recipient); + } + + internal new IProtocolMessage ReadFromRequest(HttpRequestInfo request) { + return base.ReadFromRequest(request); + } + + protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) { + throw new NotImplementedException(); + } + + protected override IProtocolMessage ReadFromResponseInternal(System.IO.Stream responseStream) { + throw new NotImplementedException(); + } + + protected override Response SendDirectMessageResponse(IProtocolMessage response) { + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestBaseMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestBaseMessage.cs new file mode 100644 index 0000000..ef0693c --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestBaseMessage.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="TestBaseMessage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + + internal interface IBaseMessageExplicitMembers { + string ExplicitProperty { get; set; } + } + + internal class TestBaseMessage : IProtocolMessage, IBaseMessageExplicitMembers { + private Dictionary<string, string> extraData = new Dictionary<string, string>(); + + [MessagePart("age", IsRequired = true)] + public int Age { get; set; } + + [MessagePart] + public string Name { get; set; } + + [MessagePart("explicit")] + string IBaseMessageExplicitMembers.ExplicitProperty { get; set; } + + Version IProtocolMessage.ProtocolVersion { + get { return new Version(1, 0); } + } + + MessageProtections IProtocolMessage.RequiredProtection { + get { return MessageProtections.None; } + } + + MessageTransport IProtocolMessage.Transport { + get { return MessageTransport.Indirect; } + } + + IDictionary<string, string> IProtocolMessage.ExtraData { + get { return this.extraData; } + } + + internal string PrivatePropertyAccessor { + get { return this.PrivateProperty; } + set { this.PrivateProperty = value; } + } + + [MessagePart("private")] + private string PrivateProperty { get; set; } + + void IProtocolMessage.EnsureValidMessage() { } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs new file mode 100644 index 0000000..ebb5858 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="TestChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + internal class TestChannel : Channel { + internal TestChannel() + : this(new TestMessageTypeProvider()) { + } + + internal TestChannel(IMessageTypeProvider messageTypeProvider, params IChannelBindingElement[] bindingElements) + : base(messageTypeProvider, bindingElements) { + } + + protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) { + throw new NotImplementedException("Request"); + } + + protected override IProtocolMessage ReadFromResponseInternal(System.IO.Stream responseStream) { + throw new NotImplementedException("ReadFromResponse"); + } + + protected override Response SendDirectMessageResponse(IProtocolMessage response) { + throw new NotImplementedException("SendDirectMessageResponse"); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestDerivedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestDerivedMessage.cs new file mode 100644 index 0000000..de34329 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestDerivedMessage.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="TestDerivedMessage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System.Runtime.Serialization; + using DotNetOpenAuth.Messaging; + + internal class TestDerivedMessage : TestBaseMessage { + /// <summary> + /// Gets or sets the first value. + /// </summary> + /// <remarks> + /// This element should appear AFTER <see cref="SecondDerivedElement"/> + /// due to alphabetical ordering rules, but after all the elements in the + /// base class due to inheritance rules. + /// </remarks> + [MessagePart] + public string TheFirstDerivedElement { get; set; } + + /// <summary> + /// Gets or sets the second value. + /// </summary> + /// <remarks> + /// This element should appear BEFORE <see cref="TheFirstDerivedElement"/>, + /// but after all the elements in the base class. + /// </remarks> + [MessagePart] + public string SecondDerivedElement { get; set; } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestDirectedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestDirectedMessage.cs new file mode 100644 index 0000000..67d0eb0 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestDirectedMessage.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="TestDirectedMessage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.ChannelElements; + + internal class TestDirectedMessage : TestMessage, IOAuthDirectedMessage { + internal TestDirectedMessage() { + } + + internal TestDirectedMessage(MessageTransport transport) : base(transport) { + } + + #region IDirectedProtocolMessage Members + + public Uri Recipient { get; set; } + + #endregion + + #region IProtocolMessage Properties + + MessageProtections IProtocolMessage.RequiredProtection { + get { return this.RequiredProtection; } + } + + #endregion + + #region IOAuthDirectedMessage Members + + public HttpDeliveryMethods HttpMethods { get; internal set; } + + #endregion + + protected virtual MessageProtections RequiredProtection { + get { return MessageProtections.None; } + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestExpiringMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestExpiringMessage.cs new file mode 100644 index 0000000..0aae6ae --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestExpiringMessage.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="TestExpiringMessage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Diagnostics; + using System.Runtime.Serialization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + internal class TestExpiringMessage : TestSignedDirectedMessage, IExpiringProtocolMessage { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private DateTime utcCreationDate; + + internal TestExpiringMessage() { } + + internal TestExpiringMessage(MessageTransport transport) + : base(transport) { + } + + #region IExpiringProtocolMessage Members + + [MessagePart("created_on", IsRequired = true)] + DateTime IExpiringProtocolMessage.UtcCreationDate { + get { return this.utcCreationDate; } + set { this.utcCreationDate = value.ToUniversalTime(); } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestMessage.cs new file mode 100644 index 0000000..301e70d --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestMessage.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="TestMessage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + + internal class TestMessage : IProtocolMessage { + private MessageTransport transport; + private Dictionary<string, string> extraData = new Dictionary<string, string>(); + + internal TestMessage() + : this(MessageTransport.Direct) { + } + + internal TestMessage(MessageTransport transport) { + this.transport = transport; + } + + [MessagePart("age", IsRequired = true)] + public int Age { get; set; } + [MessagePart("Name")] + public string Name { get; set; } + [MessagePart] + public string EmptyMember { get; set; } + [MessagePart(null)] // null name tests that Location is still the name. + public Uri Location { get; set; } + [MessagePart(IsRequired = true)] + public DateTime Timestamp { get; set; } + + #region IProtocolMessage Members + + Version IProtocolMessage.ProtocolVersion { + get { return new Version(1, 0); } + } + + MessageProtections IProtocolMessage.RequiredProtection { + get { return MessageProtections.None; } + } + + MessageTransport IProtocolMessage.Transport { + get { return this.transport; } + } + + IDictionary<string, string> IProtocolMessage.ExtraData { + get { return this.extraData; } + } + + void IProtocolMessage.EnsureValidMessage() { + if (this.EmptyMember != null || this.Age < 0) { + throw new ProtocolException(); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestMessageTypeProvider.cs b/src/DotNetOpenAuth.Test/Mocks/TestMessageTypeProvider.cs new file mode 100644 index 0000000..c6bb0c7 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestMessageTypeProvider.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="TestMessageTypeProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + internal class TestMessageTypeProvider : IMessageTypeProvider { + private bool signedMessages; + private bool expiringMessages; + private bool replayMessages; + + internal TestMessageTypeProvider() + : this(false, false, false) { + } + + internal TestMessageTypeProvider(bool signed, bool expiring, bool replay) { + if ((!signed && expiring) || (!expiring && replay)) { + throw new ArgumentException("Invalid combination of protection."); + } + this.signedMessages = signed; + this.expiringMessages = expiring; + this.replayMessages = replay; + } + + #region IMessageTypeProvider Members + + public Type GetRequestMessageType(IDictionary<string, string> fields) { + if (fields.ContainsKey("age")) { + if (this.signedMessages) { + if (this.expiringMessages) { + if (this.replayMessages) { + return typeof(TestReplayProtectedMessage); + } + return typeof(TestExpiringMessage); + } + return typeof(TestSignedDirectedMessage); + } + return typeof(TestMessage); + } + return null; + } + + public Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) { + return this.GetRequestMessageType(fields); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestReplayProtectedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestReplayProtectedMessage.cs new file mode 100644 index 0000000..396db44 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestReplayProtectedMessage.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="TestReplayProtectedMessage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System.Runtime.Serialization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + internal class TestReplayProtectedMessage : TestExpiringMessage, IReplayProtectedProtocolMessage { + internal TestReplayProtectedMessage() { } + + internal TestReplayProtectedMessage(MessageTransport transport) + : base(transport) { + } + + #region IReplayProtectedProtocolMessage Members + + [MessagePart("Nonce")] + string IReplayProtectedProtocolMessage.Nonce { + get; + set; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestSignedDirectedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestSignedDirectedMessage.cs new file mode 100644 index 0000000..d665db8 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestSignedDirectedMessage.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="TestSignedDirectedMessage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + internal class TestSignedDirectedMessage : TestDirectedMessage, ITamperResistantProtocolMessage { + internal TestSignedDirectedMessage() { } + + internal TestSignedDirectedMessage(MessageTransport transport) + : base(transport) { + } + + #region ISignedProtocolMessage Members + + [MessagePart] + public string Signature { + get; + set; + } + + #endregion + + protected override MessageProtections RequiredProtection { + get { return MessageProtections.TamperProtection; } + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs new file mode 100644 index 0000000..d7092b4 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------- +// <copyright file="TestWebRequestHandler.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.IO; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.ChannelElements; + + internal class TestWebRequestHandler : IWebRequestHandler { + private StringBuilder postEntity; + + internal Func<HttpWebRequest, Response> Callback { get; set; } + + internal Stream RequestEntityStream { + get { + if (this.postEntity == null) { + return null; + } + return new MemoryStream(Encoding.UTF8.GetBytes(this.postEntity.ToString())); + } + } + + #region IWebRequestHandler Members + + public TextWriter GetRequestStream(HttpWebRequest request) { + this.postEntity = new StringBuilder(); + return new StringWriter(this.postEntity); + } + + public Response GetResponse(HttpWebRequest request) { + if (this.Callback == null) { + throw new InvalidOperationException("Set the Callback property first."); + } + + return this.Callback(request); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs new file mode 100644 index 0000000..ac2c0b1 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// <copyright file="HmacSha1SigningBindingElementTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.ChannelElements { + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class HmacSha1SigningBindingElementTests : MessagingTestBase { + [TestMethod] + public void SignatureTest() { + UnauthorizedTokenRequest message = SigningBindingElementBaseTests.CreateTestRequestTokenMessage(); + + HmacSha1SigningBindingElement_Accessor hmac = new HmacSha1SigningBindingElement_Accessor(); + Assert.AreEqual("kR0LhH8UqylaLfR/esXVVlP4sQI=", hmac.GetSignature(message)); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs new file mode 100644 index 0000000..c5d3b14 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs @@ -0,0 +1,293 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthChannelTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.ChannelElements { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.IO; + using System.Net; + using System.Text; + using System.Web; + using System.Xml; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class OAuthChannelTests : TestBase { + private OAuthChannel channel; + private TestWebRequestHandler webRequestHandler; + private SigningBindingElementBase signingElement; + private INonceStore nonceStore; + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + + this.webRequestHandler = new TestWebRequestHandler(); + this.signingElement = new RsaSha1SigningBindingElement(); + this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); + this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), new TestMessageTypeProvider(), this.webRequestHandler); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullHandler() { + new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, new InMemoryTokenManager(), new TestMessageTypeProvider(), null); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void CtorNullSigner() { + new OAuthChannel(null, this.nonceStore, new InMemoryTokenManager(), new TestMessageTypeProvider(), this.webRequestHandler); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullStore() { + new OAuthChannel(new RsaSha1SigningBindingElement(), null, new InMemoryTokenManager(), new TestMessageTypeProvider(), this.webRequestHandler); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void CtorNullTokenManager() { + new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, null, new TestMessageTypeProvider(), this.webRequestHandler); + } + + [TestMethod] + public void CtorSimpleConsumer() { + new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, new InMemoryTokenManager(), true); + } + + [TestMethod] + public void CtorSimpleServiceProvider() { + new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, new InMemoryTokenManager(), false); + } + + [TestMethod] + public void ReadFromRequestAuthorization() { + this.ParameterizedReceiveTest(HttpDeliveryMethods.AuthorizationHeaderRequest); + } + + [TestMethod] + public void ReadFromRequestForm() { + this.ParameterizedReceiveTest(HttpDeliveryMethods.PostRequest); + } + + [TestMethod] + public void ReadFromRequestQueryString() { + this.ParameterizedReceiveTest(HttpDeliveryMethods.GetRequest); + } + + [TestMethod] + public void SendDirectMessageResponse() { + IProtocolMessage message = new TestMessage { + Age = 15, + Name = "Andrew", + Location = new Uri("http://hostb/pathB"), + }; + + Response response = this.channel.Send(message); + Assert.AreSame(message, response.OriginalMessage); + Assert.AreEqual(HttpStatusCode.OK, response.Status); + Assert.AreEqual(0, response.Headers.Count); + + NameValueCollection body = HttpUtility.ParseQueryString(response.Body); + Assert.AreEqual("15", body["age"]); + Assert.AreEqual("Andrew", body["Name"]); + Assert.AreEqual("http://hostb/pathB", body["Location"]); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void ReadFromResponseNull() { + Channel_Accessor accessor = Channel_Accessor.AttachShadow(this.channel); + accessor.ReadFromResponse(null); + } + + [TestMethod] + public void ReadFromResponse() { + var fields = new Dictionary<string, string> { + { "age", "15" }, + { "Name", "Andrew" }, + { "Location", "http://hostb/pathB" }, + { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) }, + }; + + MemoryStream ms = new MemoryStream(); + StreamWriter writer = new StreamWriter(ms); + writer.Write(MessagingUtilities.CreateQueryString(fields)); + writer.Flush(); + ms.Seek(0, SeekOrigin.Begin); + Channel_Accessor channelAccessor = Channel_Accessor.AttachShadow(this.channel); + IProtocolMessage message = channelAccessor.ReadFromResponse(ms); + Assert.IsNotNull(message); + Assert.IsInstanceOfType(message, typeof(TestMessage)); + TestMessage testMessage = (TestMessage)message; + Assert.AreEqual(15, testMessage.Age); + Assert.AreEqual("Andrew", testMessage.Name); + Assert.AreEqual("http://hostb/pathB", testMessage.Location.AbsoluteUri); + Assert.IsNull(testMessage.EmptyMember); + } + + [TestMethod, ExpectedException(typeof(ArgumentNullException))] + public void RequestNull() { + this.channel.Request(null); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void RequestNullRecipient() { + IDirectedProtocolMessage message = new TestDirectedMessage(MessageTransport.Direct); + this.channel.Request(message); + } + + [TestMethod, ExpectedException(typeof(NotSupportedException))] + public void RequestBadPreferredScheme() { + TestDirectedMessage message = new TestDirectedMessage(MessageTransport.Direct); + message.Recipient = new Uri("http://localtest"); + message.HttpMethods = HttpDeliveryMethods.None; + this.channel.Request(message); + } + + [TestMethod] + public void RequestUsingAuthorizationHeader() { + this.ParameterizedRequestTest(HttpDeliveryMethods.AuthorizationHeaderRequest); + } + + [TestMethod] + public void RequestUsingGet() { + this.ParameterizedRequestTest(HttpDeliveryMethods.GetRequest); + } + + [TestMethod] + public void RequestUsingPost() { + this.ParameterizedRequestTest(HttpDeliveryMethods.PostRequest); + } + + private static string CreateAuthorizationHeader(IDictionary<string, string> fields) { + if (fields == null) { + throw new ArgumentNullException("fields"); + } + + StringBuilder authorization = new StringBuilder(); + authorization.Append("OAuth "); + foreach (var pair in fields) { + string key = Uri.EscapeDataString(pair.Key); + string value = Uri.EscapeDataString(pair.Value); + authorization.Append(key); + authorization.Append("=\""); + authorization.Append(value); + authorization.Append("\","); + } + authorization.Length--; // remove trailing comma + + return authorization.ToString(); + } + + private static HttpRequestInfo CreateHttpRequestInfo(HttpDeliveryMethods scheme, IDictionary<string, string> fields) { + string query = MessagingUtilities.CreateQueryString(fields); + UriBuilder requestUri = new UriBuilder("http://localhost/path"); + WebHeaderCollection headers = new WebHeaderCollection(); + MemoryStream ms = new MemoryStream(); + string method; + switch (scheme) { + case HttpDeliveryMethods.PostRequest: + method = "POST"; + headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + StreamWriter sw = new StreamWriter(ms); + sw.Write(query); + sw.Flush(); + ms.Position = 0; + break; + case HttpDeliveryMethods.GetRequest: + method = "GET"; + requestUri.Query = query; + break; + case HttpDeliveryMethods.AuthorizationHeaderRequest: + method = "GET"; + headers.Add(HttpRequestHeader.Authorization, CreateAuthorizationHeader(fields)); + break; + default: + throw new ArgumentOutOfRangeException("scheme", scheme, "Unexpected value"); + } + HttpRequestInfo request = new HttpRequestInfo { + HttpMethod = method, + Url = requestUri.Uri, + Headers = headers, + InputStream = ms, + }; + + return request; + } + + private static HttpRequestInfo ConvertToRequestInfo(HttpWebRequest request, Stream postEntity) { + HttpRequestInfo info = new HttpRequestInfo { + HttpMethod = request.Method, + Url = request.RequestUri, + Headers = request.Headers, + InputStream = postEntity, + }; + return info; + } + + private void ParameterizedRequestTest(HttpDeliveryMethods scheme) { + TestDirectedMessage request = new TestDirectedMessage(MessageTransport.Direct) { + Age = 15, + Name = "Andrew", + Location = new Uri("http://hostb/pathB"), + Recipient = new Uri("http://localtest"), + Timestamp = DateTime.UtcNow, + HttpMethods = scheme, + }; + + Response rawResponse = null; + this.webRequestHandler.Callback = (req) => { + Assert.IsNotNull(req); + HttpRequestInfo reqInfo = ConvertToRequestInfo(req, this.webRequestHandler.RequestEntityStream); + Assert.AreEqual(scheme == HttpDeliveryMethods.PostRequest ? "POST" : "GET", reqInfo.HttpMethod); + var incomingMessage = this.channel.ReadFromRequest(reqInfo) as TestMessage; + Assert.IsNotNull(incomingMessage); + 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), + }; + return rawResponse; + }; + + IProtocolMessage response = this.channel.Request(request); + Assert.IsNotNull(response); + Assert.IsInstanceOfType(response, typeof(TestMessage)); + TestMessage responseMessage = (TestMessage)response; + Assert.AreEqual(request.Age, responseMessage.Age); + Assert.AreEqual(request.Name, responseMessage.Name); + Assert.AreEqual(request.Location, responseMessage.Location); + } + + private void ParameterizedReceiveTest(HttpDeliveryMethods scheme) { + var fields = new Dictionary<string, string> { + { "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); + Assert.IsInstanceOfType(requestMessage, typeof(TestMessage)); + TestMessage testMessage = (TestMessage)requestMessage; + Assert.AreEqual(15, testMessage.Age); + Assert.AreEqual("Andrew", testMessage.Name); + Assert.AreEqual("http://hostb/pathB", testMessage.Location.AbsoluteUri); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs new file mode 100644 index 0000000..22b6243 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="PlaintextSigningBindingElementTest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.ChannelElements +{ + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class PlaintextSigningBindingElementTest { + [TestMethod] + public void HttpsSignatureGeneration() { + SigningBindingElementBase target = new PlaintextSigningBindingElement(); + MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + message.ConsumerSecret = "cs"; + message.TokenSecret = "ts"; + Assert.IsTrue(target.PrepareMessageForSending(message)); + Assert.AreEqual("PLAINTEXT", message.SignatureMethod); + Assert.AreEqual("cs&ts", message.Signature); + } + + [TestMethod] + public void HttpsSignatureVerification() { + MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); + ITamperProtectionChannelBindingElement target = new PlaintextSigningBindingElement(); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + message.ConsumerSecret = "cs"; + message.TokenSecret = "ts"; + message.SignatureMethod = "PLAINTEXT"; + message.Signature = "cs&ts"; + Assert.IsTrue(target.PrepareMessageForReceiving(message)); + } + + [TestMethod] + public void HttpsSignatureVerificationNotApplicable() { + SigningBindingElementBase target = new PlaintextSigningBindingElement(); + MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + message.ConsumerSecret = "cs"; + message.TokenSecret = "ts"; + message.SignatureMethod = "ANOTHERALGORITHM"; + message.Signature = "somethingelse"; + Assert.IsFalse(target.PrepareMessageForReceiving(message), "PLAINTEXT binding element should opt-out where it doesn't understand."); + } + + [TestMethod] + public void HttpSignatureGeneration() { + SigningBindingElementBase target = new PlaintextSigningBindingElement(); + MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + message.ConsumerSecret = "cs"; + message.TokenSecret = "ts"; + + // Since this is (non-encrypted) HTTP, so the plain text signer should not be used + Assert.IsFalse(target.PrepareMessageForSending(message)); + Assert.IsNull(message.SignatureMethod); + Assert.IsNull(message.Signature); + } + + [TestMethod] + public void HttpSignatureVerification() { + SigningBindingElementBase target = new PlaintextSigningBindingElement(); + MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + message.ConsumerSecret = "cs"; + message.TokenSecret = "ts"; + message.SignatureMethod = "PLAINTEXT"; + message.Signature = "cs%26ts"; + Assert.IsFalse(target.PrepareMessageForReceiving(message), "PLAINTEXT signature binding element should refuse to participate in non-encrypted messages."); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs new file mode 100644 index 0000000..8f09ef6 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// <copyright file="SigningBindingElementBaseTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.ChannelElements { + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SigningBindingElementBaseTests : MessagingTestBase { + [TestMethod] + public void BaseSignatureStringTest() { + UnauthorizedTokenRequest message = CreateTestRequestTokenMessage(); + + Assert.AreEqual( + "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F", + SigningBindingElementBase_Accessor.ConstructSignatureBaseString(message)); + } + + internal static UnauthorizedTokenRequest CreateTestRequestTokenMessage() { + MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest); + UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint); + message.ConsumerKey = "nerdbank.org"; + ((ITamperResistantOAuthMessage)message).ConsumerSecret = "nerdbanksecret"; + var signedMessage = (ITamperResistantOAuthMessage)message; + signedMessage.HttpMethod = "GET"; + signedMessage.SignatureMethod = "HMAC-SHA1"; + MessageDictionary dictionary = new MessageDictionary(message); + dictionary["oauth_timestamp"] = "1222665749"; + dictionary["oauth_nonce"] = "fe4045a3f0efdd1e019fa8f8ae3f5c38"; + dictionary["scope"] = "http://www.google.com/m8/feeds/"; + return message; + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ConsumerDescription.cs b/src/DotNetOpenAuth.Test/OAuth/ConsumerDescription.cs new file mode 100644 index 0000000..625f416 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ConsumerDescription.cs @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------- +// <copyright file="ConsumerDescription.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + /// <summary> + /// Information necessary to initialize a <see cref="Consumer"/>, + /// and to tell a <see cref="ServiceProvider"/> about it. + /// </summary> + /// <remarks> + /// Immutable. + /// </remarks> + internal class ConsumerDescription { + /// <summary> + /// Initializes a new instance of the <see cref="ConsumerDescription"/> class. + /// </summary> + /// <param name="key">The consumer key.</param> + /// <param name="secret">The consumer secret.</param> + internal ConsumerDescription(string key, string secret) { + this.ConsumerKey = key; + this.ConsumerSecret = secret; + } + + /// <summary> + /// Gets the consumer key. + /// </summary> + /// <value>The consumer key.</value> + internal string ConsumerKey { get; private set; } + + /// <summary> + /// Gets the consumer secret. + /// </summary> + /// <value>The consumer secret.</value> + internal string ConsumerSecret { get; private set; } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs new file mode 100644 index 0000000..6a2551a --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="ProtocolTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using DotNetOpenAuth.OAuth; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ProtocolTests { + [TestMethod] + public void Default() { + Assert.AreSame(Protocol.V10, Protocol.Default); + } + + [TestMethod] + public void DataContractNamespace() { + Assert.AreEqual("http://oauth.net/core/1.0/", Protocol.V10.DataContractNamespace); + Assert.AreEqual("http://oauth.net/core/1.0/", Protocol.DataContractNamespaceV10); + } + + [TestMethod] + public void AuthorizationHeaderScheme() { + Assert.AreEqual("OAuth", Protocol.V10.AuthorizationHeaderScheme); + } + + [TestMethod] + public void ParameterPrefix() { + Assert.AreEqual("oauth_", Protocol.V10.ParameterPrefix); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ServiceProviderDescriptionTests.cs b/src/DotNetOpenAuth.Test/OAuth/ServiceProviderDescriptionTests.cs new file mode 100644 index 0000000..760a9e9 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ServiceProviderDescriptionTests.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="ServiceProviderDescriptionTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using System; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// <summary> + /// Tests for the <see cref="ServiceProviderEndpoints"/> class. + /// </summary> + [TestClass] + public class ServiceProviderDescriptionTests : TestBase { + /// <summary> + /// A test for UserAuthorizationUri + /// </summary> + [TestMethod] + public void UserAuthorizationUriTest() { + ServiceProviderDescription target = new ServiceProviderDescription(); + MessageReceivingEndpoint expected = new MessageReceivingEndpoint("http://localhost/authorization", HttpDeliveryMethods.GetRequest); + MessageReceivingEndpoint actual; + target.UserAuthorizationEndpoint = expected; + actual = target.UserAuthorizationEndpoint; + Assert.AreEqual(expected, actual); + + target.UserAuthorizationEndpoint = null; + Assert.IsNull(target.UserAuthorizationEndpoint); + } + + /// <summary> + /// A test for RequestTokenUri + /// </summary> + [TestMethod] + public void RequestTokenUriTest() { + var target = new ServiceProviderDescription(); + MessageReceivingEndpoint expected = new MessageReceivingEndpoint("http://localhost/requesttoken", HttpDeliveryMethods.GetRequest); + MessageReceivingEndpoint actual; + target.RequestTokenEndpoint = expected; + actual = target.RequestTokenEndpoint; + Assert.AreEqual(expected, actual); + + target.RequestTokenEndpoint = null; + Assert.IsNull(target.RequestTokenEndpoint); + } + + /// <summary> + /// Verifies that oauth parameters are not allowed in <see cref="ServiceProvider.RequestTokenUri"/>, + /// per section OAuth 1.0 section 4.1. + /// </summary> + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void RequestTokenUriWithOAuthParametersTest() { + var target = new ServiceProviderDescription(); + target.RequestTokenEndpoint = new MessageReceivingEndpoint("http://localhost/requesttoken?oauth_token=something", HttpDeliveryMethods.GetRequest); + } + + /// <summary> + /// A test for AccessTokenUri + /// </summary> + [TestMethod] + public void AccessTokenUriTest() { + var target = new ServiceProviderDescription(); + MessageReceivingEndpoint expected = new MessageReceivingEndpoint("http://localhost/accesstoken", HttpDeliveryMethods.GetRequest); + MessageReceivingEndpoint actual; + target.AccessTokenEndpoint = expected; + actual = target.AccessTokenEndpoint; + Assert.AreEqual(expected, actual); + + target.AccessTokenEndpoint = null; + Assert.IsNull(target.AccessTokenEndpoint); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..88c17e6 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth.Test")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM componenets. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("aef0bb13-b79c-4854-a69a-de58b8feb5d1")] diff --git a/src/DotNetOpenAuth.Test/Scenarios/AppendixScenarios.cs b/src/DotNetOpenAuth.Test/Scenarios/AppendixScenarios.cs new file mode 100644 index 0000000..5fb7538 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Scenarios/AppendixScenarios.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="AppendixScenarios.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using System; + using System.IO; + using System.Net; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.Test.Mocks; + using DotNetOpenAuth.Test.Scenarios; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class AppendixScenarios : TestBase { + [TestMethod] + public void SpecAppendixAExample() { + ServiceProviderDescription serviceDescription = new ServiceProviderDescription() { + RequestTokenEndpoint = new MessageReceivingEndpoint("https://photos.example.net/request_token", HttpDeliveryMethods.PostRequest), + UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://photos.example.net/authorize", HttpDeliveryMethods.GetRequest), + AccessTokenEndpoint = new MessageReceivingEndpoint("https://photos.example.net/access_token", HttpDeliveryMethods.PostRequest), + TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { + new PlaintextSigningBindingElement(), + new HmacSha1SigningBindingElement(), + }, + }; + MessageReceivingEndpoint accessPhotoEndpoint = new MessageReceivingEndpoint("http://photos.example.net/photos?file=vacation.jpg&size=original", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest); + ConsumerDescription consumerDescription = new ConsumerDescription("dpf43f3p2l4k3l03", "kd94hf93k423kf44"); + + Coordinator coordinator = new Coordinator( + consumerDescription, + serviceDescription, + consumer => { + consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(new Uri("http://printer.example.com/request_token_ready"), null, null)); // .Send() dropped because this is just a simulation + string accessToken = consumer.ProcessUserAuthorization().AccessToken; + var photoRequest = consumer.CreateAuthorizingMessage(accessPhotoEndpoint, accessToken); + Response protectedPhoto = ((CoordinatingOAuthChannel)consumer.Channel).RequestProtectedResource(photoRequest); + Assert.IsNotNull(protectedPhoto); + Assert.AreEqual(HttpStatusCode.OK, protectedPhoto.Status); + Assert.AreEqual("image/jpeg", protectedPhoto.Headers[HttpResponseHeader.ContentType]); + Assert.AreNotEqual(0, protectedPhoto.ResponseStream.Length); + }, + sp => { + var requestTokenMessage = sp.ReadTokenRequest(); + sp.Channel.Send(sp.PrepareUnauthorizedTokenMessage(requestTokenMessage)); // .Send() dropped because this is just a simulation + var authRequest = sp.ReadAuthorizationRequest(); + ((InMemoryTokenManager)sp.TokenManager).AuthorizeRequestToken(authRequest.RequestToken); + sp.Channel.Send(sp.PrepareAuthorizationResponse(authRequest)); // .Send() dropped because this is just a simulation + var accessRequest = sp.ReadAccessTokenRequest(); + sp.Channel.Send(sp.PrepareAccessTokenMessage(accessRequest)); // .Send() dropped because this is just a simulation + string accessToken = sp.ReadProtectedResourceAuthorization().AccessToken; + ((CoordinatingOAuthChannel)sp.Channel).SendDirectRawResponse(new Response { + ResponseStream = new MemoryStream(new byte[] { 0x33, 0x66 }), + Headers = new WebHeaderCollection { + { HttpResponseHeader.ContentType, "image/jpeg" }, + }, + }); + }); + + coordinator.Run(); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Scenarios/CoordinatingOAuthChannel.cs b/src/DotNetOpenAuth.Test/Scenarios/CoordinatingOAuthChannel.cs new file mode 100644 index 0000000..9fae4a9 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Scenarios/CoordinatingOAuthChannel.cs @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------- +// <copyright file="CoordinatingOAuthChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Scenarios { + using System; + using System.Threading; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.Test.Mocks; + + /// <summary> + /// A special channel used in test simulations to pass messages directly between two parties. + /// </summary> + internal class CoordinatingOAuthChannel : OAuthChannel { + private EventWaitHandle incomingMessageSignal = new AutoResetEvent(false); + private IProtocolMessage incomingMessage; + private Response incomingRawResponse; + + /// <summary> + /// Initializes a new instance of the <see cref="CoordinatingOAuthChannel"/> class for Consumers. + /// </summary> + /// <param name="signingBindingElement"> + /// The signing element for the Consumer to use. Null for the Service Provider. + /// </param> + /// <param name="isConsumer">True if this channel is constructed for a Consumer.</param> + /// <param name="tokenManager">The token manager to use.</param> + internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, bool isConsumer, ITokenManager tokenManager) + : base( + signingBindingElement, + new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge), + tokenManager, + isConsumer ? (IMessageTypeProvider)new OAuthConsumerMessageTypeProvider() : new OAuthServiceProviderMessageTypeProvider(tokenManager), + new TestWebRequestHandler()) { + } + + /// <summary> + /// Gets or sets the coordinating channel used by the other party. + /// </summary> + internal CoordinatingOAuthChannel RemoteChannel { get; set; } + + internal Response RequestProtectedResource(AccessProtectedResourceRequest request) { + ((ITamperResistantOAuthMessage)request).HttpMethod = this.GetHttpMethod(((ITamperResistantOAuthMessage)request).HttpMethods); + this.PrepareMessageForSending(request); + HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); + TestBase.TestLogger.InfoFormat("Sending protected resource request: {0}", requestInfo.Message); + // Drop the outgoing message in the other channel's in-slot and let them know it's there. + this.RemoteChannel.incomingMessage = requestInfo.Message; + this.RemoteChannel.incomingMessageSignal.Set(); + return this.AwaitIncomingRawResponse(); + } + + internal void SendDirectRawResponse(Response response) { + this.RemoteChannel.incomingRawResponse = response; + this.RemoteChannel.incomingMessageSignal.Set(); + } + + protected internal override HttpRequestInfo GetRequestFromContext() { + return new HttpRequestInfo(this.AwaitIncomingMessage()); + } + + protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) { + HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); + // Drop the outgoing message in the other channel's in-slot and let them know it's there. + this.RemoteChannel.incomingMessage = requestInfo.Message; + this.RemoteChannel.incomingMessageSignal.Set(); + // Now wait for a response... + return this.AwaitIncomingMessage(); + } + + protected override Response SendDirectMessageResponse(IProtocolMessage response) { + this.RemoteChannel.incomingMessage = CloneSerializedParts(response, null); + this.RemoteChannel.incomingMessageSignal.Set(); + return null; + } + + protected override Response SendIndirectMessage(IDirectedProtocolMessage message) { + // In this mock transport, direct and indirect messages are the same. + return this.SendDirectMessageResponse(message); + } + + protected override IProtocolMessage ReadFromRequestInternal(HttpRequestInfo request) { + return request.Message; + } + + /// <summary> + /// Spoof HTTP request information for signing/verification purposes. + /// </summary> + /// <param name="message">The message to add a pretend HTTP method to.</param> + /// <returns>A spoofed HttpRequestInfo that wraps the new message.</returns> + private HttpRequestInfo SpoofHttpMethod(IDirectedProtocolMessage message) { + HttpRequestInfo requestInfo = new HttpRequestInfo(message); + + var signedMessage = message as ITamperResistantOAuthMessage; + if (signedMessage != null) { + string httpMethod = this.GetHttpMethod(signedMessage.HttpMethods); + requestInfo.HttpMethod = httpMethod; + requestInfo.Url = message.Recipient; + signedMessage.HttpMethod = httpMethod; + } + + requestInfo.Message = this.CloneSerializedParts(message, requestInfo); + + return requestInfo; + } + + private IProtocolMessage AwaitIncomingMessage() { + this.incomingMessageSignal.WaitOne(); + IProtocolMessage response = this.incomingMessage; + this.incomingMessage = null; + return response; + } + + private Response AwaitIncomingRawResponse() { + this.incomingMessageSignal.WaitOne(); + Response response = this.incomingRawResponse; + this.incomingRawResponse = null; + return response; + } + + private T CloneSerializedParts<T>(T message, HttpRequestInfo requestInfo) where T : class, IProtocolMessage { + if (message == null) { + throw new ArgumentNullException("message"); + } + + MessageReceivingEndpoint recipient = null; + IOAuthDirectedMessage directedMessage = message as IOAuthDirectedMessage; + if (directedMessage != null && directedMessage.Recipient != null) { + recipient = new MessageReceivingEndpoint(directedMessage.Recipient, directedMessage.HttpMethods); + } + + MessageSerializer serializer = MessageSerializer.Get(message.GetType()); + return (T)serializer.Deserialize(serializer.Serialize(message), recipient); + } + + private string GetHttpMethod(HttpDeliveryMethods methods) { + return (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; + } + } +} diff --git a/src/DotNetOpenAuth.Test/Scenarios/Coordinator.cs b/src/DotNetOpenAuth.Test/Scenarios/Coordinator.cs new file mode 100644 index 0000000..0479092 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Scenarios/Coordinator.cs @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------- +// <copyright file="Coordinator.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Scenarios { + using System; + using System.Threading; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// <summary> + /// Runs a Consumer and Service Provider simultaneously so they can interact in a full simulation. + /// </summary> + internal class Coordinator { + private ConsumerDescription consumerDescription; + private ServiceProviderDescription serviceDescription; + private Action<WebConsumer> consumerAction; + private Action<ServiceProvider> serviceProviderAction; + + /// <summary>Initializes a new instance of the <see cref="Coordinator"/> class.</summary> + /// <param name="consumerDescription">The description of the consumer.</param> + /// <param name="serviceDescription">The service description that will be used to construct the Consumer and ServiceProvider objects.</param> + /// <param name="consumerAction">The code path of the Consumer.</param> + /// <param name="serviceProviderAction">The code path of the Service Provider.</param> + internal Coordinator(ConsumerDescription consumerDescription, ServiceProviderDescription serviceDescription, Action<WebConsumer> consumerAction, Action<ServiceProvider> serviceProviderAction) { + if (consumerDescription == null) { + throw new ArgumentNullException("consumerDescription"); + } + if (serviceDescription == null) { + throw new ArgumentNullException("serviceDescription"); + } + if (consumerAction == null) { + throw new ArgumentNullException("consumerAction"); + } + if (serviceProviderAction == null) { + throw new ArgumentNullException("serviceProviderAction"); + } + + this.consumerDescription = consumerDescription; + this.serviceDescription = serviceDescription; + this.consumerAction = consumerAction; + this.serviceProviderAction = serviceProviderAction; + } + + /// <summary> + /// Starts the simulation. + /// </summary> + internal void Run() { + // Clone the template signing binding element. + var signingElement = this.serviceDescription.CreateTamperProtectionElement(); + var consumerSigningElement = signingElement.Clone(); + var spSigningElement = signingElement.Clone(); + + // Prepare token managers + InMemoryTokenManager consumerTokenManager = new InMemoryTokenManager(); + InMemoryTokenManager serviceTokenManager = new InMemoryTokenManager(); + consumerTokenManager.AddConsumer(this.consumerDescription); + serviceTokenManager.AddConsumer(this.consumerDescription); + + // Prepare channels that will pass messages directly back and forth. + CoordinatingOAuthChannel consumerChannel = new CoordinatingOAuthChannel(consumerSigningElement, true, consumerTokenManager); + CoordinatingOAuthChannel serviceProviderChannel = new CoordinatingOAuthChannel(spSigningElement, false, serviceTokenManager); + consumerChannel.RemoteChannel = serviceProviderChannel; + serviceProviderChannel.RemoteChannel = consumerChannel; + + // Prepare the Consumer and Service Provider objects + WebConsumer consumer = new WebConsumer(this.serviceDescription, consumerTokenManager) { + OAuthChannel = consumerChannel, + ConsumerKey = this.consumerDescription.ConsumerKey, + }; + ServiceProvider serviceProvider = new ServiceProvider(this.serviceDescription, serviceTokenManager) { + OAuthChannel = serviceProviderChannel, + }; + + Thread consumerThread = null, serviceProviderThread = null; + Exception failingException = null; + + // Each thread we create needs a surrounding exception catcher so that we can + // terminate the other thread and inform the test host that the test failed. + Action<Action> safeWrapper = (action) => { + try { + action(); + } catch (Exception ex) { + // We may be the second thread in an ThreadAbortException, so check the "flag" + if (failingException == null) { + failingException = ex; + if (Thread.CurrentThread == consumerThread) { + serviceProviderThread.Abort(); + } else { + consumerThread.Abort(); + } + } + } + }; + + // Run the threads, and wait for them to complete. + // If this main thread is aborted (test run aborted), go ahead and abort the other two threads. + consumerThread = new Thread(() => { safeWrapper(() => { consumerAction(consumer); }); }); + serviceProviderThread = new Thread(() => { safeWrapper(() => { serviceProviderAction(serviceProvider); }); }); + try { + consumerThread.Start(); + serviceProviderThread.Start(); + consumerThread.Join(); + serviceProviderThread.Join(); + } catch (ThreadAbortException) { + consumerThread.Abort(); + serviceProviderThread.Abort(); + throw; + } + + // Use the failing reason of a failing sub-thread as our reason, if anything failed. + if (failingException != null) { + throw new AssertFailedException("Coordinator thread threw unhandled exception: " + failingException, failingException); + } + } + } +} diff --git a/src/DotNetOpenAuth.Test/Settings.StyleCop b/src/DotNetOpenAuth.Test/Settings.StyleCop new file mode 100644 index 0000000..26389d0 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Settings.StyleCop @@ -0,0 +1,29 @@ +<StyleCopSettings Version="4.3"> + <Analyzers> + <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules"> + <Rules> + <Rule Name="ElementsMustBeDocumented"> + <RuleSettings> + <BooleanProperty Name="Enabled">False</BooleanProperty> + </RuleSettings> + </Rule> + <Rule Name="EnumerationItemsMustBeDocumented"> + <RuleSettings> + <BooleanProperty Name="Enabled">False</BooleanProperty> + </RuleSettings> + </Rule> + </Rules> + <AnalyzerSettings /> + </Analyzer> + <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.LayoutRules"> + <Rules> + <Rule Name="SingleLineCommentMustBePrecededByBlankLine"> + <RuleSettings> + <BooleanProperty Name="Enabled">False</BooleanProperty> + </RuleSettings> + </Rule> + </Rules> + <AnalyzerSettings /> + </Analyzer> + </Analyzers> +</StyleCopSettings>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Test/Test References/DotNetOpenAuth.accessor b/src/DotNetOpenAuth.Test/Test References/DotNetOpenAuth.accessor new file mode 100644 index 0000000..35adf51 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Test References/DotNetOpenAuth.accessor @@ -0,0 +1 @@ +DotNetOpenAuth.dll diff --git a/src/DotNetOpenAuth.Test/TestBase.cs b/src/DotNetOpenAuth.Test/TestBase.cs new file mode 100644 index 0000000..9ec0858 --- /dev/null +++ b/src/DotNetOpenAuth.Test/TestBase.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="TestBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using System.Reflection; + using DotNetOpenAuth.OAuth.Messages; + using log4net; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// <summary> + /// The base class that all test classes inherit from. + /// </summary> + public class TestBase { + /// <summary> + /// The logger that tests should use. + /// </summary> + internal static readonly ILog TestLogger = LogManager.GetLogger("DotNetOpenAuth.Test"); + + /// <summary> + /// Gets or sets the test context which provides + /// information about and functionality for the current test run. + /// </summary> + public TestContext TestContext { get; set; } + + /// <summary> + /// The TestInitialize method for the test cases. + /// </summary> + [TestInitialize] + public virtual void SetUp() { + log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("DotNetOpenAuth.Test.Logging.config")); + MessageBase.LowSecurityMode = true; + } + + /// <summary> + /// The TestCleanup method for the test cases. + /// </summary> + [TestCleanup] + public virtual void Cleanup() { + log4net.LogManager.Shutdown(); + } + } +} diff --git a/src/DotNetOpenAuth.Test/UriUtilTests.cs b/src/DotNetOpenAuth.Test/UriUtilTests.cs new file mode 100644 index 0000000..3bd0772 --- /dev/null +++ b/src/DotNetOpenAuth.Test/UriUtilTests.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------- +// <copyright file="UriUtilTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class UriUtilTests { + [TestMethod] + public void QueryStringContainsOAuthParametersNull() { + Assert.IsFalse(UriUtil.QueryStringContainsOAuthParameters(null)); + } + } +} |