< Summary

Class:Azure.Storage.StorageSharedKeyPipelinePolicy
Assembly:Azure.Storage.Blobs
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\StorageSharedKeyPipelinePolicy.cs
Covered lines:56
Uncovered lines:1
Coverable lines:57
Total lines:144
Line coverage:98.2% (56 of 57)
Covered branches:31
Total branches:32
Branch coverage:96.8% (31 of 32)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
OnSendingRequest(...)-100%100%
BuildStringToSign(...)-100%100%
BuildCanonicalizedHeaders(...)-100%100%
BuildCanonicalizedResource(...)-88.89%83.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>
 421633        public StorageSharedKeyPipelinePolicy(StorageSharedKeyCredential credentials)
 421634            => _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        {
 2371642            base.OnSendingRequest(message);
 43
 44            // Add a x-ms-date header
 45            if (IncludeXMsDate)
 46            {
 2371647                var date = DateTimeOffset.UtcNow.ToString("r", CultureInfo.InvariantCulture);
 2371648                message.Request.Headers.SetValue(Constants.HeaderNames.Date, date);
 49            }
 50
 2371651            var stringToSign = BuildStringToSign(message);
 2371652            var signature = StorageSharedKeyCredentialInternals.ComputeSasSignature(_credentials, stringToSign);
 53
 2371654            var key = new AuthenticationHeaderValue(Constants.HeaderNames.SharedKey, _credentials.AccountName + ":" + si
 2371655            message.Request.Headers.SetValue(Constants.HeaderNames.Authorization, key);
 2371656        }
 57
 58        private string BuildStringToSign(HttpMessage message)
 59        {
 60            // https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
 61
 2371662            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentEncoding, out var contentEncoding);
 2371663            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentLanguage, out var contentLanguage);
 2371664            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentLength, out var contentLength);
 2371665            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentMD5, out var contentMD5);
 2371666            message.Request.Headers.TryGetValue(Constants.HeaderNames.ContentType, out var contentType);
 2371667            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfModifiedSince, out var ifModifiedSince);
 2371668            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfMatch, out var ifMatch);
 2371669            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfNoneMatch, out var ifNoneMatch);
 2371670            message.Request.Headers.TryGetValue(Constants.HeaderNames.IfUnmodifiedSince, out var ifUnmodifiedSince);
 2371671            message.Request.Headers.TryGetValue(Constants.HeaderNames.Range, out var range);
 72
 2371673            var stringToSign = string.Join("\n",
 2371674                message.Request.Method.ToString().ToUpperInvariant(),
 2371675                contentEncoding ?? "",
 2371676                contentLanguage ?? "",
 2371677                contentLength == "0" ? "" : contentLength ?? "",
 2371678                contentMD5 ?? "", // todo: fix base 64 VALUE
 2371679                contentType ?? "",
 2371680                "", // Empty date because x-ms-date is expected (as per web page above)
 2371681                ifModifiedSince ?? "",
 2371682                ifMatch ?? "",
 2371683                ifNoneMatch ?? "",
 2371684                ifUnmodifiedSince ?? "",
 2371685                range ?? "",
 2371686                BuildCanonicalizedHeaders(message),
 2371687                BuildCanonicalizedResource(message.Request.Uri.ToUri()));
 2371688            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).
 2371695            var sb = new StringBuilder();
 23864096            foreach (var headerName in
 2371697                message.Request.Headers
 16472098                .Select(h => h.Name.ToLowerInvariant())
 16472099                .Where(name => name.StartsWith(Constants.HeaderNames.XMsPrefix, StringComparison.OrdinalIgnoreCase))
 23716100#pragma warning disable CA1308 // Normalize strings to uppercase
 131178101                .OrderBy(name => name.Trim()))
 102#pragma warning restore CA1308 // Normalize strings to uppercase
 103            {
 107462104                if (sb.Length > 0)
 105                {
 83746106                    sb.Append('\n');
 107                }
 107462108                message.Request.Headers.TryGetValue(headerName, out var value);
 107462109                sb.Append(headerName).Append(':').Append(value);
 110            }
 23716111            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
 23716117            StringBuilder cr = new StringBuilder("/").Append(_credentials.AccountName);
 23716118            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
 23716123                cr.Append(resource.AbsolutePath);//EscapedPath()
 124            }
 125            else
 126            {
 127                // a slash is required to indicate the root path
 0128                cr.Append('/');
 129            }
 130
 23716131            System.Collections.Generic.IDictionary<string, string> parameters = resource.GetQueryParameters(); // Return
 23716132            if (parameters.Count > 0)
 133            {
 80460134                foreach (var name in parameters.Keys.OrderBy(key => key, StringComparer.Ordinal))
 135                {
 136#pragma warning disable CA1308 // Normalize strings to uppercase
 17436137                    cr.Append('\n').Append(name.ToLowerInvariant()).Append(':').Append(parameters[name]);
 138#pragma warning restore CA1308 // Normalize strings to uppercase
 139                }
 140            }
 23716141            return cr.ToString();
 142        }
 143    }
 144}