//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOAuth.ChannelElements {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using DotNetOAuth.Messaging;
using DotNetOAuth.Messaging.Bindings;
using DotNetOAuth.Messaging.Reflection;
///
/// An OAuth-specific implementation of the class.
///
internal class OAuthChannel : Channel {
///
/// The object that will transmit instances
/// and return their responses.
///
private IWebRequestHandler webRequestHandler;
///
/// Initializes a new instance of the class.
///
/// The binding element to use for signing.
/// The web application store to use for nonces.
/// The token manager instance to use.
internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager)
: this(signingBindingElement, store, new OAuthMessageTypeProvider(tokenManager), new StandardWebRequestHandler()) {
}
///
/// Initializes a new instance of the class.
///
/// The binding element to use for signing.
/// The web application store to use for nonces.
///
/// An injected message type provider instance.
/// Except for mock testing, this should always be .
///
///
/// An instance to a that will be used when submitting HTTP
/// requests and waiting for responses.
///
///
/// This overload for testing purposes only.
///
internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, IMessageTypeProvider messageTypeProvider, IWebRequestHandler webRequestHandler)
: base(messageTypeProvider, new OAuthHttpMethodBindingElement(), signingBindingElement, new StandardExpirationBindingElement(), new StandardReplayProtectionBindingElement(store)) {
if (webRequestHandler == null) {
throw new ArgumentNullException("webRequestHandler");
}
this.webRequestHandler = webRequestHandler;
}
///
/// Gets or sets the Consumer web application path.
///
internal Uri Realm { get; set; }
///
/// Encodes the names and values that are part of the message per OAuth 1.0 section 5.1.
///
/// The message with data to encode.
/// A dictionary of name-value pairs with their strings encoded.
internal static IDictionary GetEncodedParameters(IProtocolMessage message) {
var encodedDictionary = new Dictionary();
EncodeParameters(new MessageDictionary(message), encodedDictionary);
return encodedDictionary;
}
///
/// Encodes the names and values in a dictionary per OAuth 1.0 section 5.1.
///
/// The dictionary with names and values to encode.
/// The dictionary to add the encoded pairs to.
internal static void EncodeParameters(IDictionary source, IDictionary destination) {
if (source == null) {
throw new ArgumentNullException("source");
}
if (destination == null) {
throw new ArgumentNullException("destination");
}
foreach (var pair in source) {
var key = Uri.EscapeDataString(pair.Key);
var value = Uri.EscapeDataString(pair.Value);
destination.Add(key, value);
}
}
///
/// Initializes a web request for sending by attaching a message to it.
/// Use this method to prepare a protected resource request that you do NOT
/// expect an OAuth message response to.
///
/// The message to attach.
/// The initialized web request.
internal HttpWebRequest InitializeRequest(IDirectedProtocolMessage request) {
if (request == null) {
throw new ArgumentNullException("request");
}
PrepareMessageForSending(request);
return this.InitializeRequestInternal(request);
}
///
/// Searches an incoming HTTP request for data that could be used to assemble
/// a protocol request message.
///
/// The HTTP request to search.
/// A dictionary of data in the request. Should never be null, but may be empty.
protected override IProtocolMessage ReadFromRequestInternal(HttpRequestInfo request) {
if (request == null) {
throw new ArgumentNullException("request");
}
// First search the Authorization header. Use it exclusively if it's present.
string authorization = request.Headers[HttpRequestHeader.Authorization];
if (authorization != null) {
string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter?
string oauthPrefix = Protocol.Default.AuthorizationHeaderScheme + " ";
// The Authorization header may have multiple uses, and OAuth may be just one of them.
// Go through each one looking for an OAuth one.
foreach (string auth in authorizationSections) {
string trimmedAuth = auth.Trim();
if (trimmedAuth.StartsWith(oauthPrefix, StringComparison.Ordinal)) {
// We found an Authorization: OAuth header.
// Parse it according to the rules in section 5.4.1 of the V1.0 spec.
var fields = new Dictionary();
foreach (string stringPair in trimmedAuth.Substring(oauthPrefix.Length).Split(',')) {
string[] keyValueStringPair = stringPair.Trim().Split('=');
string key = Uri.UnescapeDataString(keyValueStringPair[0]);
string value = Uri.UnescapeDataString(keyValueStringPair[1].Trim('"'));
fields.Add(key, value);
}
return this.Receive(fields, request.GetRecipient());
}
}
}
// We didn't find an OAuth authorization header. Revert to other payload methods.
IProtocolMessage message = base.ReadFromRequestInternal(request);
// Add receiving HTTP transport information required for signature generation.
var signedMessage = message as ITamperResistantOAuthMessage;
if (signedMessage != null) {
signedMessage.Recipient = request.Url;
signedMessage.HttpMethod = request.HttpMethod;
}
return message;
}
///
/// Gets the protocol message that may be in the given HTTP response stream.
///
/// The response that is anticipated to contain an OAuth message.
/// The deserialized message, if one is found. Null otherwise.
protected override IProtocolMessage ReadFromResponseInternal(Stream responseStream) {
if (responseStream == null) {
throw new ArgumentNullException("responseStream");
}
using (StreamReader reader = new StreamReader(responseStream)) {
string response = reader.ReadToEnd();
var fields = HttpUtility.ParseQueryString(response).ToDictionary();
return Receive(fields, null);
}
}
///
/// Sends a direct message to a remote party and waits for the response.
///
/// The message to send.
/// The remote party's response.
protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
HttpWebRequest httpRequest = this.InitializeRequestInternal(request);
Response response = this.webRequestHandler.GetResponse(httpRequest);
if (response.ResponseStream == null) {
return null;
}
var responseFields = HttpUtility.ParseQueryString(response.Body).ToDictionary();
Type messageType = this.MessageTypeProvider.GetResponseMessageType(request, responseFields);
if (messageType == null) {
return null;
}
var responseSerialize = MessageSerializer.Get(messageType);
var responseMessage = responseSerialize.Deserialize(responseFields, null);
return responseMessage;
}
///
/// Queues a message for sending in the response stream where the fields
/// are sent in the response stream in querystring style.
///
/// The message to send as a response.
/// The pending user agent redirect based message to be sent as an HttpResponse.
///
/// This method implements spec V1.0 section 5.3.
///
protected override Response SendDirectMessageResponse(IProtocolMessage response) {
MessageSerializer serializer = MessageSerializer.Get(response.GetType());
var fields = serializer.Serialize(response);
string responseBody = MessagingUtilities.CreateQueryString(fields);
Response encodedResponse = new Response {
Body = responseBody,
OriginalMessage = response,
Status = HttpStatusCode.OK,
Headers = new System.Net.WebHeaderCollection(),
};
return encodedResponse;
}
///
/// Prepares to send a request to the Service Provider as the query string in a GET request.
///
/// The message to be transmitted to the ServiceProvider.
/// The web request ready to send.
///
/// This method implements OAuth 1.0 section 5.2, item #3.
///
private static HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) {
var serializer = MessageSerializer.Get(requestMessage.GetType());
var fields = serializer.Serialize(requestMessage);
UriBuilder builder = new UriBuilder(requestMessage.Recipient);
MessagingUtilities.AppendQueryArgs(builder, fields);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
return httpRequest;
}
///
/// Initializes a web request by attaching a message to it.
///
/// The message to attach.
/// The initialized web request.
private HttpWebRequest InitializeRequestInternal(IDirectedProtocolMessage request) {
if (request == null) {
throw new ArgumentNullException("request");
}
if (request.Recipient == null) {
throw new ArgumentException(MessagingStrings.DirectedMessageMissingRecipient, "request");
}
IOAuthDirectedMessage oauthRequest = request as IOAuthDirectedMessage;
if (oauthRequest == null) {
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
MessagingStrings.UnexpectedType,
typeof(IOAuthDirectedMessage),
request.GetType()));
}
HttpWebRequest httpRequest;
HttpDeliveryMethod transmissionMethod = oauthRequest.HttpMethods;
if ((transmissionMethod & HttpDeliveryMethod.AuthorizationHeaderRequest) != 0) {
httpRequest = this.InitializeRequestAsAuthHeader(request);
} else if ((transmissionMethod & HttpDeliveryMethod.PostRequest) != 0) {
httpRequest = this.InitializeRequestAsPost(request);
} else if ((transmissionMethod & HttpDeliveryMethod.GetRequest) != 0) {
httpRequest = InitializeRequestAsGet(request);
} else {
throw new NotSupportedException();
}
return httpRequest;
}
///
/// Prepares to send a request to the Service Provider via the Authorization header.
///
/// The message to be transmitted to the ServiceProvider.
/// The web request ready to send.
///
/// This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).
///
private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) {
var protocol = Protocol.Lookup(requestMessage.ProtocolVersion);
var dictionary = new MessageDictionary(requestMessage);
// copy so as to not modify original
var fields = new Dictionary();
foreach (string key in dictionary.DeclaredKeys) {
fields.Add(key, dictionary[key]);
}
if (this.Realm != null) {
fields.Add("realm", this.Realm.AbsoluteUri);
}
UriBuilder builder = new UriBuilder(requestMessage.Recipient);
MessagingUtilities.AppendQueryArgs(builder, requestMessage.ExtraData);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
StringBuilder authorization = new StringBuilder();
authorization.Append(protocol.AuthorizationHeaderScheme);
authorization.Append(" ");
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
httpRequest.Headers.Add(HttpRequestHeader.Authorization, authorization.ToString());
return httpRequest;
}
///
/// Prepares to send a request to the Service Provider as the payload of a POST request.
///
/// The message to be transmitted to the ServiceProvider.
/// The web request ready to send.
///
/// This method implements OAuth 1.0 section 5.2, item #2.
///
private HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) {
var serializer = MessageSerializer.Get(requestMessage.GetType());
var fields = serializer.Serialize(requestMessage);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
httpRequest.Method = "POST";
httpRequest.ContentType = "application/x-www-form-urlencoded";
string requestBody = MessagingUtilities.CreateQueryString(fields);
httpRequest.ContentLength = requestBody.Length;
using (TextWriter writer = this.webRequestHandler.GetRequestStream(httpRequest)) {
writer.Write(requestBody);
}
return httpRequest;
}
}
}