Хранилище больших двоичных объектов Azure — Ошибка при попытке удалить большой двоичный объект с помощью API REST с помощью C#

#azure #azure-blob-storage #http-status-code-403

Вопрос:

Я работаю с хранилищем больших двоичных объектов Azure и C#. Мне нужно удалить большой двоичный объект с помощью API REST, но я получаю ошибку 403 Forbidden. Я использую ту же функцию для создания заголовка аутентификации для создания нового большого двоичного объекта, но она работает для put, но не для удаления. Это мой код:

 public bool DeleteBlob(string containerName, string fileName)
{
    string blobName = fileName;
    string method = "DELETE";
    string requestUri = $"https://{_accountName}.blob.core.windows.net/{containerName}/{blobName}";

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);

    string now = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);

    request.Method = method;
    request.Headers.Add("x-ms-version", "2020-10-02");
    request.Headers.Add("x-ms-date", now);
    request.Headers.Add("x-ms-delete-snapshots", "include");
    request.Headers.Add("Authorization", AuthorizationHeader("DELETE", request, containerName, blobName));

    bool result = false;

    using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
    {
        if (resp.StatusCode != HttpStatusCode.Accepted amp;amp; resp.StatusCode != HttpStatusCode.OK)
        {
            throw new Exception(resp.StatusDescription);
        }

        result = true;
    }

    return resu<
}

private string AuthorizationHeader(string method, HttpWebRequest request, string containerName, string blobName)
{
    string urlResource = $"/{_accountName}/{containerName}/{blobName}";
    string stringToSign = $"{method}nnn{request.ContentLength}nn{request.ContentType}nnnnnnn{GetCanonicalizedHeaders(request)}{GetCanonicalizedResource(request.RequestUri, _accountName)}";

    HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(_accountKey));
    string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

    return string.Format("{0} {1}:{2}", "SharedKey", _accountName, signature);
}
    
private string GetAuthorizationHeader(string method, HttpWebRequest request, string now, string containerName, string blobName)
{
    string urlResource = $"/{_accountName}/{containerName}/{blobName}";

    var canonicalizedStringToBuild = string.Format("{0}n{1}", now, $"/{_accountName}/"   urlResource);
    string signature;
    using (var hmac = new HMACSHA256(Convert.FromBase64String(_accountKey)))
    {
        byte[] dataToHmac = Encoding.UTF8.GetBytes(canonicalizedStringToBuild);
        signature = Convert.ToBase64String(hmac.ComputeHash(dataToHmac));
    }

    return string.Format($"{_accountName}:"   signature);
}
    
private string GetCanonicalizedHeaders(HttpWebRequest request)
{
    ArrayList headerNameList = new ArrayList();
    StringBuilder sb = new StringBuilder();
    foreach (string headerName in request.Headers.Keys)
    {
        if (headerName.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal))
        {
            headerNameList.Add(headerName.ToLowerInvariant());
        }
    }
    headerNameList.Sort();
    foreach (string headerName in headerNameList)
    {
        StringBuilder builder = new StringBuilder(headerName);
        string separator = ":";
        foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
        {
            string trimmedValue = headerValue.Replace("rn", String.Empty);
            builder.Append(separator);
            builder.Append(trimmedValue);
            separator = ",";
        }
        sb.Append(builder.ToString());
        sb.Append("n");
    }
    return sb.ToString();
}
    
private string GetCanonicalizedResource(Uri address, string accountName)
{
    StringBuilder str = new StringBuilder();
    StringBuilder builder = new StringBuilder("/");
    builder.Append(accountName);
    builder.Append(address.AbsolutePath);
    str.Append(builder.ToString());
    NameValueCollection values2 = new NameValueCollection();

    if (address.Query.Length > 0)
    {
        string query = address.Query.Remove(0, 1);
        var queryParts = query.Split('amp;');
        foreach (var queryPart in queryParts)
        {
            var parts = queryPart.Split('=');

            values2.Add(parts[0], parts[1]);
        }

        ArrayList list2 = new ArrayList(values2.AllKeys);
        list2.Sort();
        foreach (string str3 in list2)
        {
            StringBuilder builder3 = new StringBuilder(string.Empty);
            builder3.Append(str3);
            builder3.Append(":");
            builder3.Append(values2[str3]);
            str.Append("n");
            str.Append(builder3.ToString());
        }
    }

    return str.ToString();
}
 

Кто-нибудь может сказать мне, что я делаю не так? Как я уже отмечал, для создания нового большого двоичного объекта все работает нормально, но не для удаления. Я не знаю, отличается ли заголовок аутентификации для удаления (я знаю, что имя метода другое). Я не нашел много информации об этом.
Танкс.

Комментарии:

1. Есть ли причина, по которой вы пишете код для использования REST API вместо использования .Net SDK?

2. Привет, Гаурав. Спасибо за ваш ответ. Да, есть причина: я должен добавить эту функциональность в действие пользовательского рабочего процесса Dynamics 365, и невозможно добавить внешние сборки в проект C# кода. Поэтому мне нужно использовать API REST. Это системное ограничение.

Ответ №1:

Причина, по которой ваша операция удаления завершается 403 ошибкой, заключается stringToSign в том, что вы назначаете длину содержимого 0 , в то время как это должна быть пустая строка.

Пожалуйста, измените свой AuthorizationHeader код метода на код, указанный ниже, и ваша операция удаления большого двоичного объекта должна работать просто отлично:

 private string AuthorizationHeader(string method, HttpWebRequest request, string containerName, string blobName)
{
    string urlResource = $"/{_accountName}/{containerName}/{blobName}";
    var contentLength = request.ContentLength == 0 ? "" : request.ContentLength.ToString();
    string stringToSign = $"{method}nnn{contentLength}nn{request.ContentType}nnnnnnn{GetCanonicalizedHeaders(request)}{GetCanonicalizedResource(request.RequestUri, _accountName)}";

    HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(_accountKey));
    string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

    return string.Format("{0} {1}:{2}", "SharedKey", _accountName, signature);
}
 

То, что я сделал выше, — это установил значение длины содержимого в пустую строку, если длина содержимого равна 0.

Из этого link (курсив мой):

В текущей версии поле Длина содержимого должно быть пустой строкой, если длина содержимого запроса равна нулю. В версии 2014-02-14 и более ранних версиях длина содержимого была включена, даже если она равна нулю.