diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-09-27 07:40:58 -0700 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-09-27 07:40:58 -0700 |
commit | faa8a59cf77470e092ce9c2fe99f9ed7432567b3 (patch) | |
tree | 272e2d32d34c3b83829df0452241a68435a29d59 | |
parent | d5e2339b50047e961a661f03d882dd555c45fd74 (diff) | |
download | DotNetOpenAuth-faa8a59cf77470e092ce9c2fe99f9ed7432567b3.zip DotNetOpenAuth-faa8a59cf77470e092ce9c2fe99f9ed7432567b3.tar.gz DotNetOpenAuth-faa8a59cf77470e092ce9c2fe99f9ed7432567b3.tar.bz2 |
Added final protected resource request to Appendix scenario test.
-rw-r--r-- | src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs | 20 | ||||
-rw-r--r-- | src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs | 25 | ||||
-rw-r--r-- | src/DotNetOAuth.Test/Scenarios/Coordinator.cs | 25 | ||||
-rw-r--r-- | src/DotNetOAuth/ChannelElements/OAuthChannel.cs | 2 | ||||
-rw-r--r-- | src/DotNetOAuth/Consumer.cs | 32 | ||||
-rw-r--r-- | src/DotNetOAuth/Messaging/HttpRequestInfo.cs | 11 | ||||
-rw-r--r-- | src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs | 18 | ||||
-rw-r--r-- | src/DotNetOAuth/Messaging/MessagingStrings.resx | 6 | ||||
-rw-r--r-- | src/DotNetOAuth/Messaging/MessagingUtilities.cs | 31 | ||||
-rw-r--r-- | src/DotNetOAuth/Messaging/Response.cs | 70 |
10 files changed, 204 insertions, 36 deletions
diff --git a/src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs b/src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs index 25e3e38..61f7968 100644 --- a/src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs +++ b/src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs @@ -6,6 +6,7 @@ namespace DotNetOAuth.Test {
using System;
+ using System.IO;
using System.Linq;
using System.Net;
using DotNetOAuth.ChannelElements;
@@ -36,10 +37,12 @@ namespace DotNetOAuth.Test { consumer.Channel = channel;
consumer.RequestUserAuthorization(new Uri("http://printer.example.com/request_token_ready"));
string accessToken = consumer.ProcessUserAuthorization();
- WebRequest photoRequest = consumer.CreateAuthorizedRequest(accessPhotoEndpoint, accessToken);
- Assert.IsNotNull(photoRequest);
- Assert.IsFalse(string.IsNullOrEmpty(photoRequest.Headers[HttpRequestHeader.Authorization]));
- TestContext.WriteLine("OAuth Authorization: {0}", photoRequest.Headers[HttpRequestHeader.Authorization]);
+ var photoRequest = consumer.CreateAuthorizedRequestInternal(accessPhotoEndpoint, accessToken);
+ Response protectedPhoto = 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);
},
channel => {
tokenManager.AddConsumer(consumer.ConsumerKey, consumer.ConsumerSecret);
@@ -51,9 +54,16 @@ namespace DotNetOAuth.Test { sp.SendAuthorizationResponse(authRequest);
var accessRequest = sp.ReadAccessTokenRequest();
sp.SendAccessToken(accessRequest);
+ string accessToken = sp.GetAccessTokenInRequest();
+ channel.SendDirectRawResponse(new Response {
+ ResponseStream = new MemoryStream(new byte[] { 0x33, 0x66 }),
+ Headers = new WebHeaderCollection {
+ { HttpResponseHeader.ContentType, "image/jpeg" },
+ },
+ });
});
coordinator.SigningElement = (SigningBindingElementBase)sp.Channel.BindingElements.Single(el => el is SigningBindingElementBase);
- coordinator.Start();
+ coordinator.Run();
}
}
}
diff --git a/src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs b/src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs index 49bec4c..300c252 100644 --- a/src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs +++ b/src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs @@ -9,6 +9,7 @@ namespace DotNetOAuth.Test.Scenarios { using System.Reflection;
using System.Threading;
using DotNetOAuth.ChannelElements;
+ using DotNetOAuth.Messages;
using DotNetOAuth.Messaging;
using DotNetOAuth.Messaging.Bindings;
using DotNetOAuth.Messaging.Reflection;
@@ -20,6 +21,7 @@ namespace DotNetOAuth.Test.Scenarios { 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.
@@ -40,6 +42,20 @@ namespace DotNetOAuth.Test.Scenarios { /// </summary>
internal CoordinatingOAuthChannel RemoteChannel { get; set; }
+ internal Response RequestProtectedResource(AccessProtectedResourcesMessage request) {
+ TestBase.TestLogger.InfoFormat("Sending protected resource request: {0}", request);
+ PrepareMessageForSending(request);
+ // Drop the outgoing message in the other channel's in-slot and let them know it's there.
+ this.RemoteChannel.incomingMessage = request;
+ this.RemoteChannel.incomingMessageSignal.Set();
+ return this.AwaitIncomingRawResponse();
+ }
+
+ internal void SendDirectRawResponse(Response response) {
+ this.RemoteChannel.incomingRawResponse = response;
+ this.RemoteChannel.incomingMessageSignal.Set();
+ }
+
protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
TestBase.TestLogger.InfoFormat("Sending request: {0}", request);
// Drop the outgoing message in the other channel's in-slot and let them know it's there.
@@ -66,7 +82,7 @@ namespace DotNetOAuth.Test.Scenarios { }
protected override IProtocolMessage ReadFromRequestInternal(HttpRequestInfo request) {
- return request.Message;
+ return request.Message ?? base.ReadFromRequestInternal(request);
}
private IProtocolMessage AwaitIncomingMessage() {
@@ -76,6 +92,13 @@ namespace DotNetOAuth.Test.Scenarios { return response;
}
+ private Response AwaitIncomingRawResponse() {
+ this.incomingMessageSignal.WaitOne();
+ Response response = this.incomingRawResponse;
+ this.incomingRawResponse = null;
+ return response;
+ }
+
private T CloneSerializedParts<T>(T message) where T : class, IProtocolMessage {
if (message == null) {
throw new ArgumentNullException("message");
diff --git a/src/DotNetOAuth.Test/Scenarios/Coordinator.cs b/src/DotNetOAuth.Test/Scenarios/Coordinator.cs index c3eaad6..77449c6 100644 --- a/src/DotNetOAuth.Test/Scenarios/Coordinator.cs +++ b/src/DotNetOAuth.Test/Scenarios/Coordinator.cs @@ -32,7 +32,7 @@ namespace DotNetOAuth.Test.Scenarios { this.serviceProviderAction = serviceProviderAction;
}
- internal delegate void Actor(OAuthChannel channel);
+ internal delegate void Actor(CoordinatingOAuthChannel channel);
/// <summary>
/// Gets or sets the signing element the Consumer channel should use.
@@ -45,7 +45,7 @@ namespace DotNetOAuth.Test.Scenarios { /// <summary>
/// Starts the simulation.
/// </summary>
- internal void Start() {
+ internal void Run() {
if (this.SigningElement == null) {
throw new InvalidOperationException("SigningElement must be set first.");
}
@@ -59,7 +59,9 @@ namespace DotNetOAuth.Test.Scenarios { Thread consumerThread = null, serviceProviderThread = null;
Exception failingException = null;
- Action<Actor, OAuthChannel> safeWrapper = (actor, channel) => {
+ // 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<Actor, CoordinatingOAuthChannel> safeWrapper = (actor, channel) => {
try {
actor(channel);
} catch (Exception ex) {
@@ -75,13 +77,22 @@ namespace DotNetOAuth.Test.Scenarios { }
};
+ // 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, consumerChannel); });
serviceProviderThread = new Thread(() => { safeWrapper(serviceProviderAction, serviceProviderChannel); });
- consumerThread.Start();
- serviceProviderThread.Start();
- consumerThread.Join();
- serviceProviderThread.Join();
+ 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/DotNetOAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOAuth/ChannelElements/OAuthChannel.cs index 2d0d1b4..3840d14 100644 --- a/src/DotNetOAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOAuth/ChannelElements/OAuthChannel.cs @@ -180,7 +180,7 @@ namespace DotNetOAuth.ChannelElements { HttpWebRequest httpRequest = this.InitializeRequestInternal(request);
Response response = this.webRequestHandler.GetResponse(httpRequest);
- if (response.Body == null) {
+ if (response.ResponseStream == null) {
return null;
}
var responseFields = HttpUtility.ParseQueryString(response.Body).ToDictionary();
diff --git a/src/DotNetOAuth/Consumer.cs b/src/DotNetOAuth/Consumer.cs index c0fe705..08320c3 100644 --- a/src/DotNetOAuth/Consumer.cs +++ b/src/DotNetOAuth/Consumer.cs @@ -130,20 +130,7 @@ namespace DotNetOAuth { /// <param name="accessToken">The access token that permits access to the protected resource.</param>
/// <returns>The initialized WebRequest object.</returns>
public WebRequest CreateAuthorizedRequest(ServiceProviderEndpoint endpoint, string accessToken) {
- if (endpoint == null) {
- throw new ArgumentNullException("endpoint");
- }
- if (String.IsNullOrEmpty(accessToken)) {
- throw new ArgumentNullException("accessToken");
- }
-
- AccessProtectedResourcesMessage message = new AccessProtectedResourcesMessage(endpoint) {
- AccessToken = accessToken,
- TokenSecret = this.TokenManager.GetTokenSecret(accessToken),
- ConsumerKey = this.ConsumerKey,
- ConsumerSecret = this.ConsumerSecret,
- };
-
+ IDirectedProtocolMessage message = this.CreateAuthorizedRequestInternal(endpoint, accessToken);
WebRequest wr = this.Channel.InitializeRequest(message);
return wr;
}
@@ -157,6 +144,19 @@ namespace DotNetOAuth { /// <returns>The initialized WebRequest object.</returns>
/// <exception cref="WebException">Thrown if the request fails for any reason after it is sent to the Service Provider.</exception>
public Response SendAuthorizedRequest(ServiceProviderEndpoint endpoint, string accessToken) {
+ IDirectedProtocolMessage message = this.CreateAuthorizedRequestInternal(endpoint, accessToken);
+ HttpWebRequest wr = this.Channel.InitializeRequest(message);
+ return this.WebRequestHandler.GetResponse(wr);
+ }
+
+ /// <summary>
+ /// Creates a web request prepared with OAuth authorization
+ /// that may be further tailored by adding parameters by the caller.
+ /// </summary>
+ /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param>
+ /// <param name="accessToken">The access token that permits access to the protected resource.</param>
+ /// <returns>The initialized WebRequest object.</returns>
+ internal AccessProtectedResourcesMessage CreateAuthorizedRequestInternal(ServiceProviderEndpoint endpoint, string accessToken) {
if (endpoint == null) {
throw new ArgumentNullException("endpoint");
}
@@ -171,9 +171,7 @@ namespace DotNetOAuth { ConsumerSecret = this.ConsumerSecret,
};
- HttpWebRequest wr = this.Channel.InitializeRequest(message);
- Response response = new Response((HttpWebResponse)wr.GetResponse());
- return response;
+ return message;
}
}
}
diff --git a/src/DotNetOAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs index bce9ccb..d9d0c72 100644 --- a/src/DotNetOAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs @@ -58,6 +58,17 @@ namespace DotNetOAuth.Messaging { /// <summary>
/// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
/// </summary>
+ /// <param name="request">The HttpWebRequest (that was never used) to copy from.</param>
+ internal HttpRequestInfo(WebRequest request) {
+ this.HttpMethod = request.Method;
+ this.Url = request.RequestUri;
+ this.Headers = GetHeaderCollection(request.Headers);
+ this.InputStream = null;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
+ /// </summary>
/// <param name="message">The message being passed in through a mock transport.</param>
internal HttpRequestInfo(IProtocolMessage message) {
this.Message = message;
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs index 0416255..9bac8f7 100644 --- a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs +++ b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs @@ -277,6 +277,24 @@ namespace DotNetOAuth.Messaging { }
/// <summary>
+ /// Looks up a localized string similar to The stream's CanRead property returned false..
+ /// </summary>
+ internal static string StreamUnreadable {
+ get {
+ return ResourceManager.GetString("StreamUnreadable", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The stream's CanWrite property returned false..
+ /// </summary>
+ internal static string StreamUnwritable {
+ get {
+ return ResourceManager.GetString("StreamUnwritable", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Expected at most 1 binding element offering the {0} protection, but found {1}..
/// </summary>
internal static string TooManyBindingsOfferingSameProtection {
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.resx b/src/DotNetOAuth/Messaging/MessagingStrings.resx index f549b9c..91688fb 100644 --- a/src/DotNetOAuth/Messaging/MessagingStrings.resx +++ b/src/DotNetOAuth/Messaging/MessagingStrings.resx @@ -189,6 +189,12 @@ <data name="SigningNotSupported" xml:space="preserve">
<value>This channel does not support signing messages. To support signing messages, a derived Channel type must override the Sign and IsSignatureValid methods.</value>
</data>
+ <data name="StreamUnreadable" xml:space="preserve">
+ <value>The stream's CanRead property returned false.</value>
+ </data>
+ <data name="StreamUnwritable" xml:space="preserve">
+ <value>The stream's CanWrite property returned false.</value>
+ </data>
<data name="TooManyBindingsOfferingSameProtection" xml:space="preserve">
<value>Expected at most 1 binding element offering the {0} protection, but found {1}.</value>
</data>
diff --git a/src/DotNetOAuth/Messaging/MessagingUtilities.cs b/src/DotNetOAuth/Messaging/MessagingUtilities.cs index 7c95696..88aea0c 100644 --- a/src/DotNetOAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOAuth/Messaging/MessagingUtilities.cs @@ -8,6 +8,7 @@ namespace DotNetOAuth.Messaging { using System;
using System.Collections.Generic;
using System.Collections.Specialized;
+ using System.IO;
using System.Linq;
using System.Net;
using System.Text;
@@ -46,6 +47,36 @@ namespace DotNetOAuth.Messaging { }
/// <summary>
+ /// Copies the contents of one stream to another.
+ /// </summary>
+ /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param>
+ /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param>
+ /// <remarks>
+ /// Copying begins at the streams' current positions.
+ /// The positions are NOT reset after copying is complete.
+ /// </remarks>
+ internal static void CopyTo(this Stream copyFrom, Stream copyTo) {
+ if (copyFrom == null) {
+ throw new ArgumentNullException("copyFrom");
+ }
+ if (copyTo == null) {
+ throw new ArgumentNullException("copyTo");
+ }
+ if (!copyFrom.CanRead) {
+ throw new ArgumentException(MessagingStrings.StreamUnreadable, "copyFrom");
+ }
+ if (!copyTo.CanWrite) {
+ throw new ArgumentException(MessagingStrings.StreamUnwritable, "copyTo");
+ }
+
+ byte[] buffer = new byte[1024];
+ int readBytes;
+ while ((readBytes = copyFrom.Read(buffer, 0, 1024)) > 0) {
+ copyTo.Write(buffer, 0, readBytes);
+ }
+ }
+
+ /// <summary>
/// Concatenates a list of name-value pairs as key=value&key=value,
/// taking care to properly encode each key and value for URL
/// transmission. No ? is prefixed to the string.
diff --git a/src/DotNetOAuth/Messaging/Response.cs b/src/DotNetOAuth/Messaging/Response.cs index 8a7c2e1..c02acc2 100644 --- a/src/DotNetOAuth/Messaging/Response.cs +++ b/src/DotNetOAuth/Messaging/Response.cs @@ -8,6 +8,7 @@ namespace DotNetOAuth.Messaging { using System;
using System.IO;
using System.Net;
+ using System.Text;
using System.Web;
/// <summary>
@@ -41,8 +42,10 @@ namespace DotNetOAuth.Messaging { internal Response(HttpWebResponse response) {
this.Status = response.StatusCode;
this.Headers = response.Headers;
- using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
- this.Body = reader.ReadToEnd();
+ this.ResponseStream = new MemoryStream();
+ using (Stream responseStream = response.GetResponseStream()) {
+ responseStream.CopyTo(this.ResponseStream);
+ this.ResponseStream.Seek(0, SeekOrigin.Begin);
}
}
@@ -59,7 +62,21 @@ namespace DotNetOAuth.Messaging { /// <summary>
/// Gets the body of the HTTP response.
/// </summary>
- public string Body { get; internal set; }
+ public Stream ResponseStream { get; internal set; }
+
+ /// <summary>
+ /// Gets a value indicating whether the response stream is incomplete due
+ /// to a length limitation imposed by the HttpWebRequest or calling method.
+ /// </summary>
+ public bool IsResponseTruncated { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets the body of the response as a string.
+ /// </summary>
+ public string Body {
+ get { return this.ResponseStream != null ? this.GetResponseReader().ReadToEnd() : null; }
+ set { this.SetResponse(value); }
+ }
/// <summary>
/// Gets the HTTP status code to use in the HTTP response.
@@ -73,6 +90,20 @@ namespace DotNetOAuth.Messaging { internal IProtocolMessage OriginalMessage { get; set; }
/// <summary>
+ /// Creates a text reader for the response stream.
+ /// </summary>
+ /// <returns>The text reader, initialized for the proper encoding.</returns>
+ public StreamReader GetResponseReader() {
+ this.ResponseStream.Seek(0, SeekOrigin.Begin);
+ string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
+ if (string.IsNullOrEmpty(contentEncoding)) {
+ return new StreamReader(this.ResponseStream);
+ } else {
+ return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding));
+ }
+ }
+
+ /// <summary>
/// Automatically sends the appropriate response to the user agent.
/// Requires a current HttpContext.
/// </summary>
@@ -84,10 +115,39 @@ namespace DotNetOAuth.Messaging { HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = (int)this.Status;
MessagingUtilities.ApplyHeadersToResponse(this.Headers, HttpContext.Current.Response);
- if (this.Body != null) {
- HttpContext.Current.Response.Output.Write(this.Body);
+ if (this.ResponseStream != null) {
+ try {
+ this.ResponseStream.CopyTo(HttpContext.Current.Response.OutputStream);
+ } catch (HttpException ex) {
+ if (ex.ErrorCode == -2147467259 && HttpContext.Current.Response.Output != null) {
+ // Test scenarios can generate this, since the stream is being spoofed:
+ // System.Web.HttpException: OutputStream is not available when a custom TextWriter is used.
+ HttpContext.Current.Response.Output.Write(this.Body);
+ } else {
+ throw;
+ }
+ }
}
HttpContext.Current.Response.End();
}
+
+ /// <summary>
+ /// Sets the response to some string, encoded as UTF-8.
+ /// </summary>
+ /// <param name="body">The string to set the response to.</param>
+ internal void SetResponse(string body) {
+ if (body == null) {
+ this.ResponseStream = null;
+ return;
+ }
+
+ Encoding encoding = Encoding.UTF8;
+ this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName;
+ this.ResponseStream = new MemoryStream();
+ StreamWriter writer = new StreamWriter(this.ResponseStream, encoding);
+ writer.Write(body);
+ writer.Flush();
+ this.ResponseStream.Seek(0, SeekOrigin.Begin);
+ }
}
}
|