summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2013-02-18 22:08:07 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2013-02-18 22:08:07 -0800
commit32270c7413e7a2c37a02341a0894e2447f6d74f7 (patch)
tree05f6cf8566c7b4a2661a01d08967e91f05778837
parent549017cdf590ea4ce4d8ad55c013c33a506133a3 (diff)
downloadDotNetOpenAuth-32270c7413e7a2c37a02341a0894e2447f6d74f7.zip
DotNetOpenAuth-32270c7413e7a2c37a02341a0894e2447f6d74f7.tar.gz
DotNetOpenAuth-32270c7413e7a2c37a02341a0894e2447f6d74f7.tar.bz2
Matured the OAuth 1 consumer signing handler a bit.
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs3
-rw-r--r--samples/OAuthConsumer/SignInWithTwitter.aspx.cs2
-rw-r--r--samples/OAuthConsumer/Twitter.aspx.cs3
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs18
-rw-r--r--src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj3
-rw-r--r--src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs30
-rw-r--r--src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HmacSha1HttpMessageHandler.cs59
-rw-r--r--src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandler.cs83
-rw-r--r--src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandlerBase.cs355
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs10
10 files changed, 469 insertions, 97 deletions
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs
index 48e6b4b..1ae9f84 100644
--- a/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs
+++ b/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs
@@ -135,8 +135,7 @@ namespace DotNetOpenAuth.ApplicationBlock {
public static async Task<JArray> GetUpdatesAsync(
ConsumerBase twitter, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) {
- var authorizingHandler = new OAuth1HttpMessageHandler(twitter.Channel.HostFactories.CreateHttpMessageHandler(), twitter, accessToken);
- using (var httpClient = twitter.Channel.HostFactories.CreateHttpClient(authorizingHandler)) {
+ using (var httpClient = twitter.CreateHttpClient(accessToken)) {
using (var response = await httpClient.GetAsync(GetFriendTimelineStatusEndpoint.Location, cancellationToken)) {
response.EnsureSuccessStatusCode();
string jsonString = await response.Content.ReadAsStringAsync();
diff --git a/samples/OAuthConsumer/SignInWithTwitter.aspx.cs b/samples/OAuthConsumer/SignInWithTwitter.aspx.cs
index b9d19ef..f90d557 100644
--- a/samples/OAuthConsumer/SignInWithTwitter.aspx.cs
+++ b/samples/OAuthConsumer/SignInWithTwitter.aspx.cs
@@ -9,8 +9,8 @@
using System.Web.UI.WebControls;
using System.Xml.Linq;
using System.Xml.XPath;
- using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.ApplicationBlock;
+ using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth;
public partial class SignInWithTwitter : System.Web.UI.Page {
diff --git a/samples/OAuthConsumer/Twitter.aspx.cs b/samples/OAuthConsumer/Twitter.aspx.cs
index a89c3bf..6ff6993 100644
--- a/samples/OAuthConsumer/Twitter.aspx.cs
+++ b/samples/OAuthConsumer/Twitter.aspx.cs
@@ -10,9 +10,8 @@
using System.Xml.Linq;
using System.Xml.XPath;
using DotNetOpenAuth.ApplicationBlock;
- using DotNetOpenAuth.OAuth;
-
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
public partial class Twitter : System.Web.UI.Page {
private string AccessToken {
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
index a4aff73..619f252 100644
--- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
@@ -1611,6 +1611,19 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Enumerates all members of the collection as key=value pairs.
+ /// </summary>
+ internal static IEnumerable<KeyValuePair<string, string>> AsKeyValuePairs(this NameValueCollection nvc) {
+ Requires.NotNull(nvc, "nvc");
+
+ foreach (string key in nvc) {
+ foreach (string value in nvc.GetValues(key)) {
+ yield return new KeyValuePair<string, string>(key, value);
+ }
+ }
+ }
+
+ /// <summary>
/// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
/// </summary>
/// <param name="nvc">The NameValueCollection to convert. May be null.</param>
@@ -1881,6 +1894,11 @@ namespace DotNetOpenAuth.Messaging {
internal static string EscapeUriDataStringRfc3986(string value) {
Requires.NotNull(value, "value");
+ // fast path for empty values.
+ if (value.Length == 0) {
+ return value;
+ }
+
// Start with RFC 2396 escaping by calling the .NET method to do the work.
// This MAY sometimes exhibit RFC 3986 behavior (according to the documentation).
// If it does, the escaping we do that follows it will be a no-op since the
diff --git a/src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj b/src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj
index cda3b68..3d43a38 100644
--- a/src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj
+++ b/src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj
@@ -25,7 +25,8 @@
<Compile Include="OAuth\ChannelElements\RsaSha1ConsumerSigningBindingElement.cs" />
<Compile Include="OAuth\ConsumerBase.cs" />
<Compile Include="OAuth\DesktopConsumer.cs" />
- <Compile Include="OAuth\OAuth1HttpMessageHandler.cs" />
+ <Compile Include="OAuth\OAuth1HmacSha1HttpMessageHandler.cs" />
+ <Compile Include="OAuth\OAuth1HttpMessageHandlerBase.cs" />
<Compile Include="OAuth\WebConsumer.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>
diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs
index dcde81c..d72ad08 100644
--- a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs
+++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs
@@ -81,9 +81,33 @@ namespace DotNetOpenAuth.OAuth {
/// <summary>
/// Creates a message handler that signs outbound requests with a previously obtained authorization.
/// </summary>
- /// <returns>A message handler.</returns>
- public OAuth1HttpMessageHandler CreateMessageHandler() {
- return new OAuth1HttpMessageHandler(this);
+ /// <param name="accessToken">The access token to authorize outbound HTTP requests with.</param>
+ /// <param name="innerHandler">The inner handler that actually sends the HTTP message on the network.</param>
+ /// <returns>
+ /// A message handler.
+ /// </returns>
+ public OAuth1HttpMessageHandlerBase CreateMessageHandler(string accessToken = null, HttpMessageHandler innerHandler = null) {
+ return new OAuth1HmacSha1HttpMessageHandler() {
+ ConsumerKey = this.ConsumerKey,
+ ConsumerSecret = this.TokenManager.ConsumerSecret,
+ AccessToken = accessToken,
+ AccessTokenSecret = accessToken != null ? this.TokenManager.GetTokenSecret(accessToken) : null,
+ InnerHandler = innerHandler ?? this.Channel.HostFactories.CreateHttpMessageHandler(),
+ };
+ }
+
+ /// <summary>
+ /// Creates the HTTP client.
+ /// </summary>
+ /// <param name="accessToken">The access token to authorize outbound HTTP requests with.</param>
+ /// <param name="innerHandler">The inner handler that actually sends the HTTP message on the network.</param>
+ /// <returns>The HttpClient to use.</returns>
+ public HttpClient CreateHttpClient(string accessToken, HttpMessageHandler innerHandler = null) {
+ Requires.NotNullOrEmpty(accessToken, "accessToken");
+
+ var handler = this.CreateMessageHandler(accessToken, innerHandler);
+ var client = this.Channel.HostFactories.CreateHttpClient(handler);
+ return client;
}
/// <summary>
diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HmacSha1HttpMessageHandler.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HmacSha1HttpMessageHandler.cs
new file mode 100644
index 0000000..11de257
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HmacSha1HttpMessageHandler.cs
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth1HmacSha1HttpMessageHandler.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Net.Http;
+ using System.Security.Cryptography;
+ using System.Text;
+ using System.Threading.Tasks;
+
+ /// <summary>
+ /// A delegating HTTP handler that signs outgoing HTTP requests
+ /// with an HMAC-SHA1 signature.
+ /// </summary>
+ public class OAuth1HmacSha1HttpMessageHandler : OAuth1HttpMessageHandlerBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth1HmacSha1HttpMessageHandler"/> class.
+ /// </summary>
+ public OAuth1HmacSha1HttpMessageHandler() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth1HmacSha1HttpMessageHandler"/> class.
+ /// </summary>
+ /// <param name="innerHandler">The inner handler which is responsible for processing the HTTP response messages.</param>
+ public OAuth1HmacSha1HttpMessageHandler(HttpMessageHandler innerHandler)
+ : base(innerHandler) {
+ }
+
+ /// <summary>
+ /// Calculates the signature for the specified buffer.
+ /// </summary>
+ /// <param name="signedPayload">The payload to calculate the signature for.</param>
+ /// <returns>
+ /// The signature.
+ /// </returns>
+ protected override byte[] Sign(byte[] signedPayload) {
+ using (var algorithm = HMACSHA1.Create()) {
+ algorithm.Key = Encoding.ASCII.GetBytes(this.GetConsumerAndTokenSecretString());
+ return algorithm.ComputeHash(signedPayload);
+ }
+ }
+
+ /// <summary>
+ /// Gets the signature method to include in the oauth_signature_method parameter.
+ /// </summary>
+ /// <value>
+ /// The signature method.
+ /// </value>
+ protected override string SignatureMethod {
+ get { return "HMAC-SHA1"; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandler.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandler.cs
deleted file mode 100644
index a763d5e..0000000
--- a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandler.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="OAuth1HttpMessageHandler.cs" company="Andrew Arnott">
-// Copyright (c) Andrew Arnott. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.OAuth {
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Http;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
-
- using DotNetOpenAuth.Messaging;
-
- using Validation;
-
- /// <summary>
- /// A delegated HTTP handler that automatically signs outgoing requests.
- /// </summary>
- public class OAuth1HttpMessageHandler : DelegatingHandler {
- /// <summary>
- /// Initializes a new instance of the <see cref="OAuth1HttpMessageHandler" /> class.
- /// </summary>
- /// <param name="consumer">The consumer.</param>
- /// <param name="accessToken">The access token.</param>
- public OAuth1HttpMessageHandler(ConsumerBase consumer = null, string accessToken = null) {
- this.Consumer = consumer;
- this.AccessToken = accessToken;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="OAuth1HttpMessageHandler" /> class.
- /// </summary>
- /// <param name="innerHandler">The inner handler.</param>
- /// <param name="consumer">The consumer.</param>
- /// <param name="accessToken">The access token.</param>
- public OAuth1HttpMessageHandler(HttpMessageHandler innerHandler, ConsumerBase consumer = null, string accessToken = null)
- : base(innerHandler) {
- this.Consumer = consumer;
- this.AccessToken = accessToken;
- }
-
- /// <summary>
- /// Gets or sets the access token.
- /// </summary>
- /// <value>
- /// The access token.
- /// </value>
- public string AccessToken { get; set; }
-
- /// <summary>
- /// Gets or sets the consumer.
- /// </summary>
- /// <value>
- /// The consumer.
- /// </value>
- public ConsumerBase Consumer { get; set; }
-
- /// <summary>
- /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation.
- /// </summary>
- /// <param name="request">The HTTP request message to send to the server.</param>
- /// <param name="cancellationToken">A cancellation token to cancel operation.</param>
- /// <returns>
- /// Returns <see cref="T:System.Threading.Tasks.Task`1" />. The task object representing the asynchronous operation.
- /// </returns>
- protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
- Verify.Operation(this.Consumer != null, Strings.RequiredPropertyNotYetPreset, "Consumer");
- Verify.Operation(!string.IsNullOrEmpty(this.AccessToken), Strings.RequiredPropertyNotYetPreset, "AccessToken");
-
- var deliveryMethods = MessagingUtilities.GetHttpDeliveryMethod(request.Method.Method) | HttpDeliveryMethods.AuthorizationHeaderRequest;
- var signed = await
- this.Consumer.PrepareAuthorizedRequestAsync(
- new MessageReceivingEndpoint(request.RequestUri, deliveryMethods), this.AccessToken, cancellationToken);
- request.Headers.Authorization = signed.Headers.Authorization;
-
- return await base.SendAsync(request, cancellationToken);
- }
- }
-}
diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandlerBase.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandlerBase.cs
new file mode 100644
index 0000000..6029d47
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/OAuth1HttpMessageHandlerBase.cs
@@ -0,0 +1,355 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth1HttpMessageHandler.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net.Http;
+ using System.Net.Http.Headers;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using Validation;
+
+ /// <summary>
+ /// A base class for delegating <see cref="HttpMessageHandler" />s that sign
+ /// outgoing HTTP requests per the OAuth 1.0 "3.4 Signature" in RFC 5849.
+ /// </summary>
+ /// <remarks>
+ /// http://tools.ietf.org/html/rfc5849#section-3.4
+ /// </remarks>
+ public abstract class OAuth1HttpMessageHandlerBase : DelegatingHandler {
+ /// <summary>
+ /// These are the characters that may be chosen from when forming a random nonce.
+ /// </summary>
+ private const string AllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+ /// <summary>
+ /// The default nonce length.
+ /// </summary>
+ private const int defaultNonceLength = 8;
+
+ private const OAuthParametersLocation defaultParametersLocation = OAuthParametersLocation.AuthorizationHttpHeader;
+
+ /// <summary>
+ /// The reference date and time for calculating time stamps.
+ /// </summary>
+ private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ /// <summary>
+ /// An array containing simply the amperstand character.
+ /// </summary>
+ private static readonly char[] ParameterSeparatorAsArray = new char[] { '&' };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth1HttpMessageHandlerBase"/> class.
+ /// </summary>
+ protected OAuth1HttpMessageHandlerBase() {
+ this.NonceLength = defaultNonceLength;
+ this.Location = defaultParametersLocation;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth1HttpMessageHandlerBase"/> class.
+ /// </summary>
+ /// <param name="innerHandler">The inner handler which is responsible for processing the HTTP response messages.</param>
+ protected OAuth1HttpMessageHandlerBase(HttpMessageHandler innerHandler)
+ : base(innerHandler) {
+ this.NonceLength = defaultNonceLength;
+ this.Location = defaultParametersLocation;
+ }
+
+ /// <summary>
+ /// The locations that oauth parameters may be added to HTTP requests.
+ /// </summary>
+ public enum OAuthParametersLocation {
+ /// <summary>
+ /// The oauth parameters are added to the query string in the URL.
+ /// </summary>
+ QueryString,
+
+ /// <summary>
+ /// An HTTP Authorization header is added with the OAuth scheme.
+ /// </summary>
+ AuthorizationHttpHeader,
+ }
+
+ /// <summary>
+ /// Gets or sets the location to add OAuth parameters to outbound HTTP requests.
+ /// </summary>
+ public OAuthParametersLocation Location { get; set; }
+
+ /// <summary>
+ /// Gets or sets the consumer key.
+ /// </summary>
+ /// <value>
+ /// The consumer key.
+ /// </value>
+ public string ConsumerKey { get; set; }
+
+ /// <summary>
+ /// Gets or sets the consumer secret.
+ /// </summary>
+ /// <value>
+ /// The consumer secret.
+ /// </value>
+ public string ConsumerSecret { get; set; }
+
+ /// <summary>
+ /// Gets or sets the access token.
+ /// </summary>
+ /// <value>
+ /// The access token.
+ /// </value>
+ public string AccessToken { get; set; }
+
+ /// <summary>
+ /// Gets or sets the access token secret.
+ /// </summary>
+ /// <value>
+ /// The access token secret.
+ /// </value>
+ public string AccessTokenSecret { get; set; }
+
+ /// <summary>
+ /// Gets the length of the nonce.
+ /// </summary>
+ /// <value>
+ /// The length of the nonce.
+ /// </value>
+ public int NonceLength { get; set; }
+
+ /// <summary>
+ /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation.
+ /// </summary>
+ /// <param name="request">The HTTP request message to send to the server.</param>
+ /// <param name="cancellationToken">A cancellation token to cancel operation.</param>
+ /// <returns>
+ /// Returns <see cref="T:System.Threading.Tasks.Task`1" />. The task object representing the asynchronous operation.
+ /// </returns>
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
+ Requires.NotNull(request, "request");
+ cancellationToken.ThrowIfCancellationRequested();
+ this.ApplyOAuthParameters(request);
+ return base.SendAsync(request, cancellationToken);
+ }
+
+ /// <summary>
+ /// Calculates the signature for the specified buffer.
+ /// </summary>
+ /// <param name="signedPayload">The payload to calculate the signature for.</param>
+ /// <returns>The signature.</returns>
+ protected abstract byte[] Sign(byte[] signedPayload);
+
+ /// <summary>
+ /// Gets the signature method to include in the oauth_signature_method parameter.
+ /// </summary>
+ /// <value>
+ /// The signature method.
+ /// </value>
+ protected abstract string SignatureMethod { get; }
+
+ /// <summary>
+ /// Gets the "ConsumerSecret&amp;AccessTokenSecret" string, allowing either property to be empty or null.
+ /// </summary>
+ /// <returns>The concatenated string.</returns>
+ /// <remarks>
+ /// This is useful in the PLAINTEXT and HMAC-SHA1 signature algorithms.
+ /// </remarks>
+ protected string GetConsumerAndTokenSecretString() {
+ var builder = new StringBuilder();
+ builder.Append(UrlEscape(this.ConsumerSecret ?? string.Empty));
+ builder.Append("&");
+ builder.Append(UrlEscape(this.AccessTokenSecret ?? string.Empty));
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Escapes a value for transport in a URI, per RFC 3986.
+ /// </summary>
+ /// <param name="value">The value to escape. Null and empty strings are OK.</param>
+ /// <returns>The escaped value. Never null.</returns>
+ private static string UrlEscape(string value) {
+ return MessagingUtilities.EscapeUriDataStringRfc3986(value ?? string.Empty);
+ }
+
+ /// <summary>
+ /// Returns the OAuth 1.0 timestamp for the current time.
+ /// </summary>
+ private static string ToTimeStamp(DateTime dateTime) {
+ Requires.Argument(dateTime.Kind == DateTimeKind.Utc, "dateTime", "UTC time required");
+ TimeSpan ts = dateTime - epoch;
+ long secondsSinceEpoch = (long)ts.TotalSeconds;
+ return secondsSinceEpoch.ToString(CultureInfo.InvariantCulture);
+ }
+
+ /// <summary>
+ /// Constructs the "Base String URI" as described in http://tools.ietf.org/html/rfc5849#section-3.4.1.2
+ /// </summary>
+ /// <param name="requestUri">The request URI.</param>
+ /// <returns>The string to include in the signature base string.</returns>
+ private static string GetBaseStringUri(Uri requestUri) {
+ Requires.NotNull(requestUri, "requestUri");
+
+ var endpoint = new UriBuilder(requestUri);
+ endpoint.Query = null;
+ endpoint.Fragment = null;
+ return endpoint.Uri.AbsoluteUri;
+ }
+
+ /// <summary>
+ /// Constructs the "Signature Base String" as described in http://tools.ietf.org/html/rfc5849#section-3.4.1
+ /// </summary>
+ /// <param name="request">The HTTP request message.</param>
+ /// <returns>The signature base string.</returns>
+ private string ConstructSignatureBaseString(HttpRequestMessage request, NameValueCollection oauthParameters) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(oauthParameters, "oauthParameters");
+
+ var builder = new StringBuilder();
+ builder.Append(UrlEscape(request.Method.ToString().ToUpperInvariant()));
+ builder.Append("&");
+ builder.Append(UrlEscape(GetBaseStringUri(request.RequestUri)));
+ builder.Append("&");
+ builder.Append(UrlEscape(this.GetNormalizedParameters(request, oauthParameters)));
+
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Generates a string of random characters for use as a nonce.
+ /// </summary>
+ /// <returns>The nonce string.</returns>
+ private string GenerateUniqueFragment() {
+ return MessagingUtilities.GetRandomString(this.NonceLength, AllowedCharacters);
+ }
+
+ /// <summary>
+ /// Gets the "oauth_" prefixed parameters that should be added to an outbound request.
+ /// </summary>
+ /// <returns>A collection of name=value pairs.</returns>
+ private NameValueCollection GetOAuthParameters() {
+ var nvc = new NameValueCollection(8);
+ nvc.Add("oauth_version", "1.0");
+ nvc.Add("oauth_nonce", this.GenerateUniqueFragment());
+ nvc.Add("oauth_timestamp", ToTimeStamp(DateTime.UtcNow));
+ nvc.Add("oauth_signature_method", this.SignatureMethod);
+ nvc.Add("oauth_consumer_key", this.ConsumerKey);
+ if (!string.IsNullOrEmpty(this.AccessToken)) {
+ nvc.Add("oauth_token", this.AccessToken);
+ }
+
+ return nvc;
+ }
+
+ /// <summary>
+ /// Gets a normalized string of the query string parameters included in the request and the additional OAuth parameters.
+ /// </summary>
+ /// <param name="request">The HTTP request.</param>
+ /// <param name="oauthParameters">The oauth parameters that will be added to the request.</param>
+ /// <returns>The normalized string of parameters to included in the signature base string.</returns>
+ private string GetNormalizedParameters(HttpRequestMessage request, NameValueCollection oauthParameters) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(oauthParameters, "oauthParameters");
+
+ NameValueCollection nvc;
+ if (request.RequestUri.Query != null) {
+ // NameValueCollection does support non-unique keys, as long as you use it carefully.
+ nvc = HttpUtility.ParseQueryString(request.RequestUri.Query);
+
+ // Remove any parameters beginning with "oauth_"
+ var keysToRemove = nvc.Cast<string>().Where(k => k.StartsWith(Protocol.ParameterPrefix, StringComparison.Ordinal)).ToList();
+ foreach (string key in keysToRemove) {
+ nvc.Remove(key);
+ }
+ } else {
+ nvc = new NameValueCollection(8);
+ }
+
+ // Add OAuth parameters.
+ nvc.Add(oauthParameters);
+
+ // Now convert the NameValueCollection into an ordered list, and properly escape all keys and value while we're at it.
+ var list = new List<KeyValuePair<string, string>>(nvc.Count);
+ foreach (var pair in nvc.AsKeyValuePairs()) {
+ string escapedKey = UrlEscape(pair.Key);
+ string escapedValue = UrlEscape(pair.Value ?? string.Empty); // value can be null if no "=" appears in the query string for this key.
+ list.Add(new KeyValuePair<string, string>(escapedKey, escapedValue));
+ }
+
+ // Sort the parameters
+ list.Sort((kv1, kv2) => {
+ int compare = string.Compare(kv1.Key, kv2.Key, StringComparison.Ordinal);
+ if (compare != 0) {
+ return compare;
+ }
+
+ return string.Compare(kv1.Value, kv2.Value, StringComparison.Ordinal);
+ });
+
+ // Convert this sorted list into a single concatenated string.
+ var normalizedParameterString = new StringBuilder();
+ foreach (var pair in list) {
+ if (normalizedParameterString.Length > 0) {
+ normalizedParameterString.Append("&");
+ }
+
+ normalizedParameterString.Append(pair.Key);
+ normalizedParameterString.Append("=");
+ normalizedParameterString.Append(pair.Value);
+ }
+
+ return normalizedParameterString.ToString();
+ }
+
+ /// <summary>
+ /// Applies OAuth authorization to the specified request.
+ /// </summary>
+ private void ApplyOAuthParameters(HttpRequestMessage request) {
+ Requires.NotNull(request, "request");
+
+ var oauthParameters = this.GetOAuthParameters();
+ string signature = this.GetSignature(request, oauthParameters);
+ oauthParameters.Add("oauth_signature", signature);
+
+ // Add parameters and signature to request.
+ switch (this.Location) {
+ case OAuthParametersLocation.AuthorizationHttpHeader:
+ request.Headers.Authorization = new AuthenticationHeaderValue(Protocol.AuthorizationHeaderScheme, MessagingUtilities.AssembleAuthorizationHeader(oauthParameters.AsKeyValuePairs()));
+ break;
+ case OAuthParametersLocation.QueryString:
+ var uriBuilder = new UriBuilder(request.RequestUri);
+ uriBuilder.AppendQueryArgs(oauthParameters.AsKeyValuePairs());
+ request.RequestUri = uriBuilder.Uri;
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Gets the OAuth 1.0 signature to apply to the specified request.
+ /// </summary>
+ /// <param name="request">The outbound HTTP request.</param>
+ /// <param name="oauthParameters">The oauth parameters.</param>
+ /// <returns>
+ /// The value for the "oauth_signature" parameter.
+ /// </returns>
+ private string GetSignature(HttpRequestMessage request, NameValueCollection oauthParameters) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(oauthParameters, "oauthParameters");
+
+ string signatureBaseString = this.ConstructSignatureBaseString(request, oauthParameters);
+ byte[] signatureBaseStringBytes = Encoding.ASCII.GetBytes(signatureBaseString);
+ byte[] signatureBytes = this.Sign(signatureBaseStringBytes);
+ string signatureString = Convert.ToBase64String(signatureBytes);
+ return signatureString;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs
index b76d6ad..f1b7e81 100644
--- a/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs
+++ b/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs
@@ -38,6 +38,11 @@ namespace DotNetOpenAuth.OpenId {
/// </remarks>
public class UntrustedWebRequestHandler : HttpMessageHandler {
/// <summary>
+ /// The inner handler.
+ /// </summary>
+ private readonly InternalWebRequestHandler innerHandler;
+
+ /// <summary>
/// The set of URI schemes allowed in untrusted web requests.
/// </summary>
private ICollection<string> allowableSchemes = new List<string> { "http", "https" };
@@ -69,11 +74,6 @@ namespace DotNetOpenAuth.OpenId {
private int maximumRedirections = Configuration.MaximumRedirections;
/// <summary>
- /// The inner handler.
- /// </summary>
- private readonly InternalWebRequestHandler innerHandler;
-
- /// <summary>
/// Initializes a new instance of the <see cref="UntrustedWebRequestHandler" /> class.
/// </summary>
/// <param name="innerHandler">