aliyun-net-sdk-dybaseapi/Dybaseapi/MNS/Runtime/Internal/Auth/MNSSigner.cs (183 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;
using Aliyun.Acs.Dybaseapi.MNS.Runtime.Internal.Util;
using Aliyun.Acs.Dybaseapi.MNS.Util;
namespace Aliyun.Acs.Dybaseapi.MNS.Runtime.Internal.Auth
{
/// <summary>
/// A signer for generating MNS request header named 'Authorization'.
/// </summary>
internal class MNSSigner : IServiceSigner
{
#region Immutable Properties
static readonly Regex CompressWhitespaceRegex = new Regex("\\s+");
const SigningAlgorithm SignerAlgorithm = SigningAlgorithm.HmacSHA1;
#endregion
#region Public Signing Methods
public void Sign(IRequest request,
RequestMetrics metrics,
string accessKeyId,
string secretAccessKey,
string stsToken)
{
var signingRequest = SignRequest(request, metrics, secretAccessKey);
var signingResult = new StringBuilder();
signingResult.AppendFormat("{0} {1}:{2}",
MNSConstants.MNS_AUTHORIZATION_HEADER_PREFIX,
accessKeyId,
signingRequest);
request.Headers[HttpHeader.AuthorizationHeader] = signingResult.ToString();
if (!string.IsNullOrEmpty(stsToken))
{
request.Headers[HttpHeader.SecurityToken] = stsToken;
}
}
public string SignRequest(IRequest request,
RequestMetrics metrics,
string secretAccessKey)
{
InitializeHeaders(request.Headers);
var parametersToCanonicalize = GetParametersToCanonicalize(request);
var canonicalParameters = CanonicalizeQueryParameters(parametersToCanonicalize);
var canonicalResource = CanonicalizeResource(canonicalParameters, request.ResourcePath);
var canonicalMNSHeaders = CanonoicalizeMNSHeaders(request.Headers);
var canonicalRequest = CanonicalizeRequest(request.HttpMethod,
request.Headers,
canonicalMNSHeaders,
canonicalResource);
if (metrics != null)
metrics.AddProperty(Metric.CanonicalRequest, canonicalRequest);
return ComputeSignature(secretAccessKey, canonicalRequest);
}
#endregion
#region Public Signing Helpers
/// <summary>
/// Initializes request headers.
/// </summary>
/// <param name="headers">The request headers</param>
private static void InitializeHeaders(IDictionary<string, string> headers)
{
// clean up any prior signature in the headers if resigning
headers.Remove(HttpHeader.AuthorizationHeader);
}
/// <summary>
/// Computes and returns an Service signature for the specified canonicalized request
/// </summary>
/// <param name="secretAccessKey"></param>
/// <param name="canonicalRequest"></param>
/// <returns></returns>
public static string ComputeSignature(string secretAccessKey, string canonicalRequest)
{
return ComputeKeyedHash(SignerAlgorithm, secretAccessKey, canonicalRequest);
}
/// <summary>
/// Compute and return the hash of a data blob using the specified key
/// </summary>
/// <param name="algorithm">Algorithm to use for hashing</param>
/// <param name="key">Hash key</param>
/// <param name="data">Data blob</param>
/// <returns>Hash of the data</returns>
public static string ComputeKeyedHash(SigningAlgorithm algorithm, string key, string data)
{
return CryptoUtilFactory.CryptoInstance.HMACSign(data, key, algorithm);
}
#endregion
#region Private Signing Helpers
protected static string CanonoicalizeMNSHeaders(IDictionary<string, string> headers)
{
var headersToCanonicalize = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (headers != null && headers.Count > 0)
{
foreach (var header in headers.Where(header =>
header.Key.ToLowerInvariant().StartsWith(MNSConstants.X_MNS_HEADER_PREFIX)))
{
headersToCanonicalize.Add(header.Key.ToLowerInvariant(), header.Value);
}
}
return CanonicalizeHeaders(headersToCanonicalize);
}
protected static string CanonicalizeResource(string canonicalQueryString, string resourcePath)
{
var canonicalResource = new StringBuilder();
canonicalResource.Append(CanonicalizeResourcePath(resourcePath));
if (canonicalQueryString != string.Empty)
canonicalResource.AppendFormat("?{0}", canonicalQueryString);
return canonicalResource.ToString();
}
/// <summary>
/// Returns the canonicalized resource path for the service endpoint
/// </summary>
/// <param name="resourcePath">Resource path for the request</param>
/// <returns>Canonicalized resource path for the endpoint</returns>
protected static string CanonicalizeResourcePath(string resourcePath)
{
if (string.IsNullOrEmpty(resourcePath) || resourcePath == "/")
return "/";
var pathSegments = resourcePath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var canonicalizedPath = new StringBuilder();
foreach (var segment in pathSegments)
{
canonicalizedPath.AppendFormat("/{0}", segment);
}
if (resourcePath.EndsWith("/", StringComparison.Ordinal))
canonicalizedPath.Append("/");
return canonicalizedPath.ToString();
}
/// <summary>
/// Computes and returns the canonical request.
/// </summary>
/// <param name="httpMethod">The http method used for the request</param>
/// <param name="headers">The entire request headers</param>
/// <param name="canonicalMNSHeaders">The canonicalMNSHeaders for the request</param>
/// <param name="canonicalResource">The canonicalResource for the request</param>
/// <returns>Canonicalised request as a string</returns>
protected static string CanonicalizeRequest(string httpMethod,
IDictionary<string, string> headers,
string canonicalMNSHeaders,
string canonicalResource)
{
var canonicalRequest = new StringBuilder();
canonicalRequest.AppendFormat("{0}\n", httpMethod);
var contentMD5 = string.Empty;
if (headers.ContainsKey(HttpHeader.ContentMD5Header))
contentMD5 = headers[HttpHeader.ContentMD5Header];
canonicalRequest.AppendFormat("{0}\n", contentMD5);
var contentType = string.Empty;
if (headers.ContainsKey(HttpHeader.ContentTypeHeader))
contentType = headers[HttpHeader.ContentTypeHeader];
canonicalRequest.AppendFormat("{0}\n", contentType);
canonicalRequest.AppendFormat("{0}\n", headers[HttpHeader.DateHeader]);
canonicalRequest.Append(canonicalMNSHeaders);
canonicalRequest.Append(canonicalResource);
return canonicalRequest.ToString();
}
/// <summary>
/// Computes the canonical headers with values for the request.
/// </summary>
/// <param name="sortedHeaders">All sorted request headers</param>
/// <returns>The canonical headers.</returns>
protected static string CanonicalizeHeaders(ICollection<KeyValuePair<string, string>> sortedHeaders)
{
if (sortedHeaders == null || sortedHeaders.Count == 0)
return string.Empty;
var builder = new StringBuilder();
foreach (var entry in sortedHeaders)
{
builder.Append(entry.Key.ToLower(CultureInfo.InvariantCulture));
builder.Append(":");
builder.Append(CompressSpaces(entry.Value));
builder.Append("\n");
}
return builder.ToString();
}
/// <summary>
/// Collects all subresources and query string parameters.
/// </summary>
/// <param name="request">The request being signed</param>
/// <returns>A set of parameters</returns>
protected static IDictionary<string, string> GetParametersToCanonicalize(IRequest request)
{
var parametersToCanonicalize = new Dictionary<string, string>();
if (request.SubResources != null && request.SubResources.Count > 0)
{
foreach (var subResource in request.SubResources)
{
parametersToCanonicalize.Add(subResource.Key, subResource.Value);
}
}
if (request.Parameters != null && request.Parameters.Count > 0)
{
foreach (var queryParameter in request.Parameters.Where(queryParameter => queryParameter.Value != null))
{
parametersToCanonicalize.Add(queryParameter.Key, queryParameter.Value);
}
}
return parametersToCanonicalize;
}
/// <summary>
/// Computes and returns the canonicalized query string
/// </summary>
/// <param name="parameters">The set of query string parameters</param>
/// <returns>The canonical query string parameters</returns>
protected static string CanonicalizeQueryParameters(IDictionary<string, string> parameters)
{
if (parameters == null || parameters.Count == 0)
return string.Empty;
var canonicalQueryString = new StringBuilder();
var queryParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
foreach (var p in queryParams)
{
if (canonicalQueryString.Length > 0)
canonicalQueryString.Append("&");
if (string.IsNullOrEmpty(p.Value))
canonicalQueryString.AppendFormat("{0}=", p.Key);
else
canonicalQueryString.AppendFormat("{0}={1}", p.Key, p.Value);
}
return canonicalQueryString.ToString();
}
static string CompressSpaces(string data)
{
if (data == null || !data.Contains(" "))
return data;
var compressed = CompressWhitespaceRegex.Replace(data, " ");
return compressed;
}
#endregion
}
}