< Summary

Class:Azure.Storage.StorageSharedKeyPipelinePolicy
Assembly:Azure.Storage.Common
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\StorageSharedKeyPipelinePolicy.cs
Covered lines:53
Uncovered lines:4
Coverable lines:57
Total lines:144
Line coverage:92.9% (53 of 57)
Covered branches:26
Total branches:32
Branch coverage:81.2% (26 of 32)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
OnSendingRequest(...)-100%100%
BuildStringToSign(...)-100%95.45%
BuildCanonicalizedHeaders(...)-91.67%75%
BuildCanonicalizedResource(...)-66.67%33.33%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\StorageSharedKeyPipelinePolicy.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Globalization;
 6using System.Linq;
 7using System.Net.Http.Headers;
 8using System.Text;
 9using Azure.Core;
 10using Azure.Core.Pipeline;
 11
 12namespace Azure.Storage
 13{
 14    /// <summary>
 15    /// HttpPipelinePolicy to sign requests using an Azure Storage shared key.
 16    /// </summary>
 17    internal sealed class StorageSharedKeyPipelinePolicy : HttpPipelineSynchronousPolicy
 18    {
 19        /// <summary>
 20        /// Whether to always add the x-ms-date header.
 21        /// </summary>
 22        private const bool IncludeXMsDate = true;
 23
 24        /// <summary>
 25        /// Shared key credentials used to sign requests
 26        /// </summary>
 27        private readonly StorageSharedKeyCredential _credentials;
 28
 29        /// <summary>
 30        /// Create a new SharedKeyPipelinePolicy
 31        /// </summary>
 32        /// <param name="credentials">SharedKeyCredentials to authenticate requests.</param>
 233        public StorageSharedKeyPipelinePolicy(StorageSharedKeyCredential credentials)
 234            => _credentials = credentials;
 35
 36        /// <summary>
 37        /// Sign the request using the shared key credentials.
 38        /// </summary>
 39        /// <param name="message">The message with the request to sign.</param>
 40        public override void OnSendingRequest(HttpMessage message)
 41        {
 442            base.OnSendingRequest(message);
 43
 44            // Add a x-ms-date header
 45            if (IncludeXMsDate)
 46            {
 447                var date = DateTimeOffset.UtcNow.ToString("r", CultureInfo.InvariantCulture);
 448                message.Request.Headers.SetValue(Constants.HeaderNames.Date, date);
 49            }
 50
 451            var stringToSign = BuildStringToSign(message);
 452            var signature = StorageSharedKeyCredentialInternals.ComputeSasSignature(_credentials, stringToSign);
 53
 454            var key = new AuthenticationHeaderValue(Constants.HeaderNames.SharedKey, _credentials.AccountName + ":" + si
 455            message.Request.Headers.SetValue(Constants.HeaderNames.Authorization, key);
 456        }
 57
 58        private string BuildStringToSign(HttpMessage message)
 59        {
 60            // https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
 61
 462            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentEncoding, out var contentEncoding);
 463            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentLanguage, out var contentLanguage);
 464            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentLength, out var contentLength);
 465            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentMD5, out var contentMD5);
 466            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentType, out var contentType);
 467            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfModifiedSince, out var ifModifiedSince);
 468            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfMatch, out var ifMatch);
 469            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfNoneMatch, out var ifNoneMatch);
 470            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfUnmodifiedSince, out var ifUnmodifiedSince);
 471            message.Request.Headers.TryGetValue(Constants.HeaderNames.Range, out var range);
 72
 473            var stringToSign = string.Join("\n",
 474                message.Request.Method.ToString().ToUpperInvariant(),
 475                contentEncoding ?? "",
 476                contentLanguage ?? "",
 477                contentLength == "0" ? "" : contentLength ?? "",
 478                contentMD5 ?? "", // todo: fix base 64 VALUE
 479                contentType ?? "",
 480                "", // Empty date because x-ms-date is expected (as per web page above)
 481                ifModifiedSince ?? "",
 482                ifMatch ?? "",
 483                ifNoneMatch ?? "",
 484                ifUnmodifiedSince ?? "",
 485                range ?? "",
 486                BuildCanonicalizedHeaders(message),
 487                BuildCanonicalizedResource(message.Request.Uri.ToUri()));
 488            return stringToSign;
 89        }
 90
 91        private static string BuildCanonicalizedHeaders(HttpMessage message)
 92        {
 93            // Grab all the "x-ms-*" headers, trim whitespace, lowercase, sort,
 94            // and combine them with their values (separated by a colon).
 495            var sb = new StringBuilder();
 1296            foreach (var headerName in
 497                message.Request.Headers
 1098                .Select(h => h.Name.ToLowerInvariant())
 1099                .Where(name => name.StartsWith(Constants.HeaderNames.XMsPrefix, StringComparison.OrdinalIgnoreCase))
 4100#pragma warning disable CA1308 // Normalize strings to uppercase
 8101                .OrderBy(name => name.Trim()))
 102#pragma warning restore CA1308 // Normalize strings to uppercase
 103            {
 4104                if (sb.Length > 0)
 105                {
 0106                    sb.Append('\n');
 107                }
 4108                message.Request.Headers.TryGetValue(headerName, out var value);
 4109                sb.Append(headerName).Append(':').Append(value);
 110            }
 4111            return sb.ToString();
 112        }
 113
 114        private string BuildCanonicalizedResource(Uri resource)
 115        {
 116            // https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
 4117            StringBuilder cr = new StringBuilder("/").Append(_credentials.AccountName);
 4118            if (resource.AbsolutePath.Length > 0)
 119            {
 120                // Any portion of the CanonicalizedResource string that is derived from
 121                // the resource's URI should be encoded exactly as it is in the URI.
 122                // -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
 4123                cr.Append(resource.AbsolutePath);//EscapedPath()
 124            }
 125            else
 126            {
 127                // a slash is required to indicate the root path
 0128                cr.Append('/');
 129            }
 130
 4131            System.Collections.Generic.IDictionary<string, string> parameters = resource.GetQueryParameters(); // Return
 4132            if (parameters.Count > 0)
 133            {
 0134                foreach (var name in parameters.Keys.OrderBy(key => key, StringComparer.Ordinal))
 135                {
 136#pragma warning disable CA1308 // Normalize strings to uppercase
 0137                    cr.Append('\n').Append(name.ToLowerInvariant()).Append(':').Append(parameters[name]);
 138#pragma warning restore CA1308 // Normalize strings to uppercase
 139                }
 140            }
 4141            return cr.ToString();
 142        }
 143    }
 144}