[Storage Files Service] File sas (#568)

* File sas

* rebase on lastest master
This commit is contained in:
JinmingHu 2020-09-04 14:46:24 +08:00 committed by GitHub
parent caf705c863
commit 07208d1720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 620 additions and 13 deletions

View File

@ -182,7 +182,7 @@ namespace Azure { namespace Storage { namespace Blobs {
* shared access signature, and the service version to use when handling requests made with this
* shared access signature.
*/
std::string Version = c_ApiVersion;
std::string Version = Details::c_defaultSasVersion;
/**
* @brief The optional signed protocol field specifies the protocol permitted for a
@ -271,8 +271,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Sets the permissions for the blob container SAS.
*
* @param
* permissions The allowed permissions.
* @param permissions The allowed permissions.
*/
void SetPermissions(BlobContainerSasPermissions permissions)
{
@ -282,8 +281,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Sets the permissions for the blob SAS.
*
* @param permissions The
* allowed permissions.
* @param permissions The allowed permissions.
*/
void SetPermissions(BlobSasPermissions permissions);
@ -291,10 +289,8 @@ namespace Azure { namespace Storage { namespace Blobs {
* @brief Uses the SharedKeyCredential to sign this shared access signature, to produce
* the proper SAS query parameters for authentication requests.
*
* @param credential
* The storage account's shared key credential.
* @return The SAS query parameters used for
* authenticating requests.
* @param credential The storage account's shared key credential.
* @return The SAS query parameters used for authenticating requests.
*/
std::string ToSasQueryParameters(const SharedKeyCredential& credential);
@ -302,11 +298,9 @@ namespace Azure { namespace Storage { namespace Blobs {
* @brief Uses an account's user delegation key to sign this shared access signature, to
* produce the proper SAS query parameters for authentication requests.
*
* @param
* credential UserDelegationKey retruned from BlobServiceClient.GetUserDelegationKey.
* @param credential UserDelegationKey retruned from BlobServiceClient.GetUserDelegationKey.
* @param accountName The name of the storage account.
* @return The SAS query parameters
* used for authenticating requests.
* @return The SAS query parameters used for authenticating requests.
*/
std::string ToSasQueryParameters(
const UserDelegationKey& userDelegationKey,

View File

@ -16,6 +16,9 @@ namespace Azure { namespace Storage {
namespace Blobs {
struct BlobSasBuilder;
}
namespace Files { namespace Shares {
struct ShareSasBuilder;
}} // namespace Files::Shares
struct SharedKeyCredential
{
@ -35,6 +38,7 @@ namespace Azure { namespace Storage {
private:
friend class SharedKeyPolicy;
friend struct Blobs::BlobSasBuilder;
friend struct Files::Shares::ShareSasBuilder;
friend struct AccountSasBuilder;
std::string GetAccountKey() const
{

View File

@ -12,6 +12,7 @@ set (AZURE_STORAGE_SHARES_HEADER
inc/azure/storage/files/shares/share_file_client.hpp
inc/azure/storage/files/shares/share_options.hpp
inc/azure/storage/files/shares/share_responses.hpp
inc/azure/storage/files/shares/share_sas_builder.hpp
inc/azure/storage/files/shares/share_service_client.hpp
inc/azure/storage/files/shares/shares.hpp
)
@ -20,6 +21,7 @@ set (AZURE_STORAGE_SHARES_SOURCE
src/share_client.cpp
src/share_directory_client.cpp
src/share_file_client.cpp
src/share_sas_builder.cpp
src/share_service_client.cpp
)
@ -47,6 +49,7 @@ target_sources(
test/share_directory_client_test.hpp
test/share_file_client_test.cpp
test/share_file_client_test.hpp
test/share_sas_test.cpp
test/share_service_client_test.cpp
test/share_service_client_test.hpp
)

View File

@ -0,0 +1,240 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#pragma once
#include <string>
#include "azure/core/nullable.hpp"
#include "azure/storage/common/account_sas_builder.hpp"
#include "azure/storage/common/constants.hpp"
namespace Azure { namespace Storage { namespace Files { namespace Shares {
/**
* @brief Specifies which resources are accessible via the shared access signature.
*/
enum class ShareSasResource
{
/**
* @brief Grants access to the content and metadata of the file.
*/
Share,
/**
* @brief Grants access to the content and metadata of any file in the share, and to the list of
* directories and files in the share.
*/
File,
};
/**
* @brief The list of permissions that can be set for a file share's access policy.
*/
enum class ShareSasPermissions
{
/**
* @brief Indicates that Read is permitted.
*/
Read = 1,
/**
* @brief Indicates that Write is permitted.
*/
Write = 2,
/**
* @brief Indicates that Delete is permitted.
*/
Delete = 4,
/**
* @brief Indicates that List is permitted.
*/
List = 8,
/**
* @brief Indicates that Create is permitted.
*/
Create = 16,
/**
* @beirf Indicates that all permissions are set.
*/
All = ~0,
};
inline ShareSasPermissions operator|(ShareSasPermissions lhs, ShareSasPermissions rhs)
{
using type = std::underlying_type_t<ShareSasPermissions>;
return static_cast<ShareSasPermissions>(static_cast<type>(lhs) | static_cast<type>(rhs));
}
inline ShareSasPermissions operator&(ShareSasPermissions lhs, ShareSasPermissions rhs)
{
using type = std::underlying_type_t<ShareSasPermissions>;
return static_cast<ShareSasPermissions>(static_cast<type>(lhs) & static_cast<type>(rhs));
}
std::string ShareSasPermissionsToString(ShareSasPermissions permissions);
/**
* @brief The list of permissions that can be set for a share file's access policy.
*/
enum class ShareFileSasPermissions
{
/**
* @brief Indicates that Read is permitted.
*/
Read = 1,
/**
* @brief Indicates that Write is permitted.
*/
Write = 2,
/**
* @brief Indicates that Delete is permitted.
*/
Delete = 4,
/**
* @brief Indicates that Create is permitted.
*/
Create = 8,
/**
* @beirf Indicates that all permissions are set.
*/
All = ~0,
};
inline ShareFileSasPermissions operator|(ShareFileSasPermissions lhs, ShareFileSasPermissions rhs)
{
using type = std::underlying_type_t<ShareFileSasPermissions>;
return static_cast<ShareFileSasPermissions>(static_cast<type>(lhs) | static_cast<type>(rhs));
}
inline ShareFileSasPermissions operator&(ShareFileSasPermissions lhs, ShareFileSasPermissions rhs)
{
using type = std::underlying_type_t<ShareFileSasPermissions>;
return static_cast<ShareFileSasPermissions>(static_cast<type>(lhs) & static_cast<type>(rhs));
}
/**
* @brief ShareSasBuilder is used to generate a Shared Access Signature (SAS) for an Azure
* Storage share or file.
*/
struct ShareSasBuilder
{
/**
* @brief The storage service version to use to authenticate requests made with this
* shared access signature, and the service version to use when handling requests made with this
* shared access signature.
*/
std::string Version = Azure::Storage::Details::c_defaultSasVersion;
/**
* @brief The optional signed protocol field specifies the protocol permitted for a
* request made with the SAS.
*/
SasProtocol Protocol;
/**
* @brief Optionally specify the time at which the shared access signature becomes
* valid.
*/
Azure::Core::Nullable<std::string> StartsOn;
/**
* @brief The time at which the shared access signature becomes invalid. This field must
* be omitted if it has been specified in an associated stored access policy.
*/
std::string ExpiresOn;
/**
* @brief Specifies an IP address or a range of IP addresses from which to accept
* requests. If the IP address from which the request originates does not match the IP address
* or address range specified on the SAS token, the request is not authenticated. When
* specifying a range of IP addresses, note that the range is inclusive.
*/
Azure::Core::Nullable<std::string> IPRange;
/**
* @brief An optional unique value up to 64 characters in length that correlates to an
* access policy specified for the share.
*/
std::string Identifier;
/**
* @brief The name of the file share being made accessible.
*/
std::string ShareName;
/**
* @brief The name of the share file being made accessible, or empty for a share SAS..
*/
std::string FilePath;
/**
* @brief Specifies which resources are accessible via the shared access signature.
*/
ShareSasResource Resource;
/**
* @brief Override the value returned for Cache-Control response header..
*/
std::string CacheControl;
/**
* @brief Override the value returned for Content-Disposition response header..
*/
std::string ContentDisposition;
/**
* @brief Override the value returned for Content-Encoding response header..
*/
std::string ContentEncoding;
/**
* @brief Override the value returned for Content-Language response header..
*/
std::string ContentLanguage;
/**
* @brief Override the value returned for Content-Type response header..
*/
std::string ContentType;
/**
* @brief Sets the permissions for the share SAS.
*
* @param permissions The allowed permissions.
*/
void SetPermissions(ShareSasPermissions permissions)
{
Permissions = ShareSasPermissionsToString(permissions);
}
/**
* @brief Sets the permissions for the share SAS.
*
* @param permissions The allowed permissions.
*/
void SetPermissions(ShareFileSasPermissions permissions);
/**
* @brief Uses the SharedKeyCredential to sign this shared access signature, to produce
* the proper SAS query parameters for authentication requests.
*
* @param credential The storage account's shared key credential.
* @return The SAS query parameters used for authenticating requests.
*/
std::string ToSasQueryParameters(const SharedKeyCredential& credential);
private:
std::string Permissions;
};
}}}} // namespace Azure::Storage::Files::Shares

View File

@ -0,0 +1,145 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/storage/files/shares/share_sas_builder.hpp"
#include "azure/core/http/http.hpp"
#include "azure/storage/common/crypt.hpp"
namespace Azure { namespace Storage { namespace Files { namespace Shares {
namespace {
std::string ShareSasResourceToString(ShareSasResource resource)
{
if (resource == ShareSasResource::Share)
{
return "s";
}
else if (resource == ShareSasResource::File)
{
return "f";
}
else
{
throw std::runtime_error("unknown ShareSasResource value");
}
}
} // namespace
std::string ShareSasPermissionsToString(ShareSasPermissions permissions)
{
std::string permissions_str;
// The order matters
if ((permissions & ShareSasPermissions::Read) == ShareSasPermissions::Read)
{
permissions_str += "r";
}
if ((permissions & ShareSasPermissions::Create) == ShareSasPermissions::Create)
{
permissions_str += "c";
}
if ((permissions & ShareSasPermissions::Write) == ShareSasPermissions::Write)
{
permissions_str += "w";
}
if ((permissions & ShareSasPermissions::Delete) == ShareSasPermissions::Delete)
{
permissions_str += "d";
}
if ((permissions & ShareSasPermissions::List) == ShareSasPermissions::List)
{
permissions_str += "l";
}
return permissions_str;
}
void ShareSasBuilder::SetPermissions(ShareFileSasPermissions permissions)
{
Permissions.clear();
// The order matters
if ((permissions & ShareFileSasPermissions::Read) == ShareFileSasPermissions::Read)
{
Permissions += "r";
}
if ((permissions & ShareFileSasPermissions::Create) == ShareFileSasPermissions::Create)
{
Permissions += "c";
}
if ((permissions & ShareFileSasPermissions::Write) == ShareFileSasPermissions::Write)
{
Permissions += "w";
}
if ((permissions & ShareFileSasPermissions::Delete) == ShareFileSasPermissions::Delete)
{
Permissions += "d";
}
}
std::string ShareSasBuilder::ToSasQueryParameters(const SharedKeyCredential& credential)
{
std::string canonicalName = "/file/" + credential.AccountName + "/" + ShareName;
if (Resource == ShareSasResource::File)
{
canonicalName += "/" + FilePath;
}
std::string protocol = SasProtocolToString(Protocol);
std::string resource = ShareSasResourceToString(Resource);
std::string stringToSign = Permissions + "\n" + (StartsOn.HasValue() ? StartsOn.GetValue() : "")
+ "\n" + ExpiresOn + "\n" + canonicalName + "\n" + Identifier + "\n"
+ (IPRange.HasValue() ? IPRange.GetValue() : "") + "\n" + protocol + "\n" + Version + "\n"
+ CacheControl + "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage
+ "\n" + ContentType;
std::string signature
= Base64Encode(Details::HmacSha256(stringToSign, Base64Decode(credential.GetAccountKey())));
Azure::Core::Http::Url builder;
builder.AppendQuery("sv", Version);
builder.AppendQuery("spr", protocol);
if (StartsOn.HasValue())
{
builder.AppendQuery("st", StartsOn.GetValue());
}
if (!ExpiresOn.empty())
{
builder.AppendQuery("se", ExpiresOn);
}
if (IPRange.HasValue())
{
builder.AppendQuery("sip", IPRange.GetValue());
}
if (!Identifier.empty())
{
builder.AppendQuery("si", Identifier);
}
builder.AppendQuery("sr", resource);
if (!Permissions.empty())
{
builder.AppendQuery("sp", Permissions);
}
builder.AppendQuery("sig", signature, true);
if (!CacheControl.empty())
{
builder.AppendQuery("rscc", CacheControl);
}
if (!ContentDisposition.empty())
{
builder.AppendQuery("rscd", ContentDisposition);
}
if (!ContentEncoding.empty())
{
builder.AppendQuery("rsce", ContentEncoding);
}
if (!ContentLanguage.empty())
{
builder.AppendQuery("rscl", ContentLanguage);
}
if (!ContentType.empty())
{
builder.AppendQuery("rsct", ContentType);
}
return builder.GetAbsoluteUrl();
}
}}}} // namespace Azure::Storage::Files::Shares

View File

@ -0,0 +1,221 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "share_client_test.hpp"
#include "azure/storage/files/shares/share_sas_builder.hpp"
namespace Azure { namespace Storage { namespace Test {
TEST_F(FileShareClientTest, FileSasTest)
{
std::string fileName = RandomString();
Files::Shares::ShareSasBuilder fileSasBuilder;
fileSasBuilder.Protocol = SasProtocol::HttpsAndHtttp;
fileSasBuilder.StartsOn = ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(5));
fileSasBuilder.ExpiresOn
= ToIso8601(std::chrono::system_clock::now() + std::chrono::minutes(60));
fileSasBuilder.ShareName = m_shareName;
fileSasBuilder.FilePath = fileName;
fileSasBuilder.Resource = Files::Shares::ShareSasResource::File;
Files::Shares::ShareSasBuilder shareSasBuilder = fileSasBuilder;
shareSasBuilder.FilePath.clear();
shareSasBuilder.Resource = Files::Shares::ShareSasResource::Share;
auto keyCredential
= Details::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;
auto accountName = keyCredential->AccountName;
auto fileServiceClient0 = Files::Shares::ServiceClient::CreateFromConnectionString(
StandardStorageConnectionString());
auto shareClient0 = fileServiceClient0.GetShareClient(m_shareName);
auto fileClient0 = shareClient0.GetFileClient(fileName);
std::string shareUri = shareClient0.GetUri();
std::string fileUri = fileClient0.GetUri();
auto verifyFileRead = [&](const std::string& sas) {
int64_t fileSize = 512;
fileClient0.Create(fileSize);
auto fileClient = Files::Shares::FileClient(fileUri + sas);
auto downloadedContent = fileClient.Download();
EXPECT_EQ(
ReadBodyStream(downloadedContent->BodyStream).size(), static_cast<std::size_t>(fileSize));
};
auto verifyFileCreate = [&](const std::string& sas) {
int64_t fileSize = 512;
auto fileClient = Files::Shares::FileClient(fileUri + sas);
EXPECT_NO_THROW(fileClient.Create(fileSize));
};
auto verifyFileWrite = [&](const std::string& sas) {
int64_t fileSize = 512;
fileClient0.Create(fileSize);
auto fileClient = Files::Shares::FileClient(fileUri + sas);
std::string fileContent = "a";
EXPECT_NO_THROW(fileClient.UploadFrom(
reinterpret_cast<const uint8_t*>(fileContent.data()), fileContent.size()));
};
auto verifyFileDelete = [&](const std::string& sas) {
int64_t fileSize = 512;
fileClient0.Create(fileSize);
auto fileClient = Files::Shares::FileClient(fileUri + sas);
EXPECT_NO_THROW(fileClient.Delete());
};
auto verifyFileList = [&](const std::string& sas) {
auto shareClient = Files::Shares::ShareClient(shareUri + sas);
EXPECT_NO_THROW(shareClient.ListFilesAndDirectoriesSegment());
};
for (auto permissions :
{Files::Shares::ShareSasPermissions::Read,
Files::Shares::ShareSasPermissions::Write,
Files::Shares::ShareSasPermissions::Delete,
Files::Shares::ShareSasPermissions::List,
Files::Shares::ShareSasPermissions::Create,
Files::Shares::ShareSasPermissions::All})
{
shareSasBuilder.SetPermissions(permissions);
auto sasToken = shareSasBuilder.ToSasQueryParameters(*keyCredential);
if ((permissions & Files::Shares::ShareSasPermissions::Read)
== Files::Shares::ShareSasPermissions::Read)
{
verifyFileRead(sasToken);
}
if ((permissions & Files::Shares::ShareSasPermissions::Write)
== Files::Shares::ShareSasPermissions::Write)
{
verifyFileWrite(sasToken);
}
if ((permissions & Files::Shares::ShareSasPermissions::Delete)
== Files::Shares::ShareSasPermissions::Delete)
{
verifyFileDelete(sasToken);
}
if ((permissions & Files::Shares::ShareSasPermissions::List)
== Files::Shares::ShareSasPermissions::List)
{
verifyFileList(sasToken);
}
if ((permissions & Files::Shares::ShareSasPermissions::Create)
== Files::Shares::ShareSasPermissions::Create)
{
verifyFileCreate(sasToken);
}
}
for (auto permissions :
{Files::Shares::ShareFileSasPermissions::Read,
Files::Shares::ShareFileSasPermissions::Write,
Files::Shares::ShareFileSasPermissions::Delete,
Files::Shares::ShareFileSasPermissions::Create})
{
fileSasBuilder.SetPermissions(permissions);
auto sasToken = fileSasBuilder.ToSasQueryParameters(*keyCredential);
if ((permissions & Files::Shares::ShareFileSasPermissions::Read)
== Files::Shares::ShareFileSasPermissions::Read)
{
verifyFileRead(sasToken);
}
if ((permissions & Files::Shares::ShareFileSasPermissions::Write)
== Files::Shares::ShareFileSasPermissions::Write)
{
verifyFileWrite(sasToken);
}
if ((permissions & Files::Shares::ShareFileSasPermissions::Delete)
== Files::Shares::ShareFileSasPermissions::Delete)
{
verifyFileDelete(sasToken);
}
if ((permissions & Files::Shares::ShareFileSasPermissions::Create)
== Files::Shares::ShareFileSasPermissions::Create)
{
verifyFileCreate(sasToken);
}
}
fileSasBuilder.SetPermissions(Files::Shares::ShareFileSasPermissions::All);
// Expires
{
Files::Shares::ShareSasBuilder builder2 = fileSasBuilder;
builder2.StartsOn = ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(5));
builder2.ExpiresOn = ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(1));
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_THROW(verifyFileRead(sasToken), StorageError);
}
// Without start time
{
Files::Shares::ShareSasBuilder builder2 = fileSasBuilder;
builder2.StartsOn.Reset();
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_NO_THROW(verifyFileRead(sasToken));
}
// IP
{
Files::Shares::ShareSasBuilder builder2 = fileSasBuilder;
builder2.IPRange = "0.0.0.0-0.0.0.1";
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_THROW(verifyFileRead(sasToken), StorageError);
builder2.IPRange = "0.0.0.0-255.255.255.255";
sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_NO_THROW(verifyFileRead(sasToken));
}
// Identifier
{
Files::Shares::SignedIdentifier identifier;
identifier.Id = RandomString(64);
identifier.Policy.Start
= ToIso8601(std::chrono::system_clock::now() - std::chrono::minutes(5));
identifier.Policy.Expiry
= ToIso8601(std::chrono::system_clock::now() + std::chrono::minutes(60));
identifier.Policy.Permission
= Files::Shares::ShareSasPermissionsToString(Files::Shares::ShareSasPermissions::Read);
m_shareClient->SetAccessPolicy({identifier});
Files::Shares::ShareSasBuilder builder2 = fileSasBuilder;
builder2.StartsOn.Reset();
builder2.ExpiresOn.clear();
builder2.SetPermissions(static_cast<Files::Shares::ShareSasPermissions>(0));
builder2.Identifier = identifier.Id;
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_NO_THROW(verifyFileRead(sasToken));
}
// response headers override
{
Files::Shares::FileShareHttpHeaders headers;
headers.ContentType = "application/x-binary";
headers.ContentLanguage = "en-US";
headers.ContentDisposition = "attachment";
headers.CacheControl = "no-cache";
headers.ContentEncoding = "identify";
Files::Shares::ShareSasBuilder builder2 = fileSasBuilder;
builder2.ContentType = "application/x-binary";
builder2.ContentLanguage = "en-US";
builder2.ContentDisposition = "attachment";
builder2.CacheControl = "no-cache";
builder2.ContentEncoding = "identify";
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
auto fileClient = Files::Shares::FileClient(fileUri + sasToken);
fileClient0.Create(0);
auto p = fileClient.GetProperties();
EXPECT_EQ(p->HttpHeaders.ContentType, headers.ContentType);
EXPECT_EQ(p->HttpHeaders.ContentLanguage, headers.ContentLanguage);
EXPECT_EQ(p->HttpHeaders.ContentDisposition, headers.ContentDisposition);
EXPECT_EQ(p->HttpHeaders.CacheControl, headers.CacheControl);
EXPECT_EQ(p->HttpHeaders.ContentEncoding, headers.ContentEncoding);
}
}
}}} // namespace Azure::Storage::Test