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/Scenarios | |
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/Scenarios')
3 files changed, 331 insertions, 0 deletions
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); + } + } + } +} |