Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding in feature to generate SAS from Storage Clients #15972

Merged
merged 9 commits into from
Nov 3, 2020
160 changes: 160 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Azure.Core.Pipeline;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Cryptography;
using Azure.Storage.Sas;
using Azure.Storage.Shared;
using Metadata = System.Collections.Generic.IDictionary<string, string>;
using Tags = System.Collections.Generic.IDictionary<string, string>;
Expand Down Expand Up @@ -152,6 +153,17 @@ public virtual string Name
}
}

/// <summary>
/// The <see cref="StorageSharedKeyCredential"/> used to authenticate and generate SAS
/// </summary>
private StorageSharedKeyCredential _storageSharedKeyCredential;

/// <summary>
/// Determines whether the client is able to generate a SAS.
/// If the client is authenticated with a <see cref="StorageSharedKeyCredential"/>.
/// </summary>
public bool CanGenerateSasUri => _storageSharedKeyCredential != null;

#region ctors
/// <summary>
/// Initializes a new instance of the <see cref="BlobBaseClient"/>
Expand Down Expand Up @@ -226,6 +238,7 @@ public BlobBaseClient(string connectionString, string blobContainerName, string
_customerProvidedKey = options.CustomerProvidedKey;
_clientSideEncryption = options._clientSideEncryptionOptions?.Clone();
_encryptionScope = options.EncryptionScope;
_storageSharedKeyCredential = StorageSharedKeyCredential.ParseConnectionString(connectionString);
amnguye marked this conversation as resolved.
Show resolved Hide resolved
BlobErrors.VerifyHttpsCustomerProvidedKey(_uri, _customerProvidedKey);
BlobErrors.VerifyCpkAndEncryptionScopeNotBothSet(_customerProvidedKey, _encryptionScope);
}
Expand Down Expand Up @@ -271,6 +284,7 @@ public BlobBaseClient(Uri blobUri, BlobClientOptions options = default)
public BlobBaseClient(Uri blobUri, StorageSharedKeyCredential credential, BlobClientOptions options = default)
: this(blobUri, credential.AsPolicy(), options)
{
_storageSharedKeyCredential = credential;
}

/// <summary>
Expand Down Expand Up @@ -4101,6 +4115,152 @@ private async Task<Response> SetTagsInternal(
}
}
#endregion

#region GenerateSAS
/// <summary>
/// The <see cref="GetSasBuilder"/> returns a <see cref="BlobSasBuilder"/> that
/// sets the respective properties in the BlobSasBuilder from the client.
/// </summary>
/// <param name="permissions">
/// Specifies the list of permissions that can be set in the SasBuilder
/// See <see cref="BlobSasPermissions"/>.
/// </param>
/// <param name="expiresOn">
/// Specifies when to set the expires time in the sas builder
/// </param>
/// <returns>
/// A <see cref="BlobSasBuilder"/> on successfully deleting.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
public BlobSasBuilder GetSasBuilder(
BlobSasPermissions permissions,
DateTimeOffset expiresOn)
amnguye marked this conversation as resolved.
Show resolved Hide resolved
{
BlobSasBuilder sasBuilder = new BlobSasBuilder
{
Version = Version.ToString(),
amnguye marked this conversation as resolved.
Show resolved Hide resolved
BlobContainerName = BlobContainerName,
BlobName = Name,
ExpiresOn = expiresOn,
Resource = "b"
};
sasBuilder.SetPermissions(permissions);
return sasBuilder;
}

/// <summary>
/// The <see cref="GenerateSasUri"/> returns a Uri that
/// generates a Service SAS based on the Client properties and builder passed.
///
/// For more information, see
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas">
/// Consturcting a Service SAS</see>
/// </summary>
/// <param name="builder">
/// Used to generate a Shared Access Signature (SAS)
/// </param>
/// <returns>
/// A <see cref="BlobSasBuilder"/> on successfully deleting.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
public Uri GenerateSasUri(
BlobSasBuilder builder)
amnguye marked this conversation as resolved.
Show resolved Hide resolved
{
builder = builder ?? throw Errors.ArgumentNull(nameof(builder));
builder.BlobContainerName = string.IsNullOrEmpty(builder.BlobContainerName) ? BlobContainerName : builder.BlobContainerName;
amnguye marked this conversation as resolved.
Show resolved Hide resolved
builder.BlobName = string.IsNullOrEmpty(builder.BlobName) ? Name : builder.BlobName;
amnguye marked this conversation as resolved.
Show resolved Hide resolved
if (!builder.BlobContainerName.Equals(BlobContainerName, StringComparison.InvariantCulture))
{
// TODO: throw proper exception for non-matching builder name
// e.g. containerName doesn't match or leave the containerName in builder
// should be left empty. Or should we always default to the client's ContainerName
// and chug along if they don't match?
throw Errors.SasNamesNotMatching(
nameof(builder.BlobContainerName),
nameof(BlobSasBuilder),
nameof(BlobContainerName));
amnguye marked this conversation as resolved.
Show resolved Hide resolved
}
if (!builder.BlobName.Equals(Name, StringComparison.InvariantCulture))
{
// TODO: throw proper exception for non-matching builder name
// e.g. containerName doesn't match or leave the containerName in builder
// should be left empty. Or should we always default to the client's ContainerName
// and chug along if they don't match?
throw Errors.SasNamesNotMatching(
nameof(builder.BlobName),
nameof(BlobSasBuilder),
nameof(Name));
}
UriBuilder sasUri = new UriBuilder(Uri);
sasUri.Query = builder.ToSasQueryParameters(_storageSharedKeyCredential).ToString();
return sasUri.Uri;
amnguye marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// The <see cref="GenerateUserDelegationSasUri"/> returns a Uri that
/// generates a User Delegation SAS based on the Client properties and builder passed.
///
/// For more information, see
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas">
/// Constructing a User Delegation SAS</see>.
/// </summary>
/// <param name="builder">
/// Used to generate a Shared Access Signature (SAS).
/// </param>
/// <param name="delegationKey">
/// User Delegation Key used to generate the User Delegation SAS
/// </param>
/// <returns>
/// A <see cref="BlobSasBuilder"/> on successfully deleting.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
public Uri GenerateUserDelegationSasUri(
amnguye marked this conversation as resolved.
Show resolved Hide resolved
BlobSasBuilder builder,
UserDelegationKey delegationKey)
{
builder = builder ?? throw Errors.ArgumentNull(nameof(builder));
builder.BlobContainerName = string.IsNullOrEmpty(builder.BlobContainerName) ? BlobContainerName : builder.BlobContainerName;
builder.BlobName = string.IsNullOrEmpty(builder.BlobName) ? Name : builder.BlobName;
if (!builder.BlobContainerName.Equals(BlobContainerName, StringComparison.InvariantCulture))
{
// TODO: throw proper exception for non-matching builder name
// e.g. containerName doesn't match or leave the containerName in builder
// should be left empty. Or should we always default to the client's ContainerName
// and chug along if they don't match?
throw Errors.SasNamesNotMatching(
nameof(builder.BlobContainerName),
nameof(BlobSasBuilder),
nameof(BlobContainerName));
}
if (!builder.BlobName.Equals(Name, StringComparison.InvariantCulture))
{
// TODO: throw proper exception for non-matching builder name
// e.g. containerName doesn't match or leave the containerName in builder
// should be left empty. Or should we always default to the client's ContainerName
// and chug along if they don't match?
throw Errors.SasNamesNotMatching(
nameof(builder.BlobName),
nameof(BlobSasBuilder),
nameof(Name));
}
if (string.IsNullOrEmpty(AccountName))
{
throw Errors.SasEmptyParam(nameof(AccountName));
}
UriBuilder sasUri = new UriBuilder(Uri);
sasUri.Query = builder.ToSasQueryParameters(delegationKey, AccountName).ToString();
return sasUri.Uri;
}
#endregion
}

/// <summary>
Expand Down
138 changes: 137 additions & 1 deletion sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ public virtual string Name
}
}

/// <summary>
/// The <see cref="StorageSharedKeyCredential"/> used to authenticate and generate SAS
/// </summary>
private readonly StorageSharedKeyCredential _storageSharedKeyCredential;

/// <summary>
/// Determines whether the client is able to generate a SAS.
/// If the client is authenticated with a <see cref="StorageSharedKeyCredential"/>.
/// </summary>
public bool CanGenerateSasUri => _storageSharedKeyCredential != null;

#region ctor
/// <summary>
/// Initializes a new instance of the <see cref="BlobContainerClient"/>
Expand Down Expand Up @@ -210,6 +221,7 @@ public BlobContainerClient(string connectionString, string blobContainerName, Bl
_clientDiagnostics = new ClientDiagnostics(options);
_customerProvidedKey = options.CustomerProvidedKey;
_encryptionScope = options.EncryptionScope;
_storageSharedKeyCredential = StorageSharedKeyCredential.ParseConnectionString(connectionString);
amnguye marked this conversation as resolved.
Show resolved Hide resolved
BlobErrors.VerifyHttpsCustomerProvidedKey(_uri, _customerProvidedKey);
BlobErrors.VerifyCpkAndEncryptionScopeNotBothSet(_customerProvidedKey, _encryptionScope);
}
Expand All @@ -229,7 +241,7 @@ public BlobContainerClient(string connectionString, string blobContainerName, Bl
/// every request.
/// </param>
public BlobContainerClient(Uri blobContainerUri, BlobClientOptions options = default)
: this(blobContainerUri, (HttpPipelinePolicy)null, options)
: this(blobContainerUri, (HttpPipelinePolicy)null, options)
{
}

Expand All @@ -253,6 +265,7 @@ public BlobContainerClient(Uri blobContainerUri, BlobClientOptions options = def
public BlobContainerClient(Uri blobContainerUri, StorageSharedKeyCredential credential, BlobClientOptions options = default)
: this(blobContainerUri, credential.AsPolicy(), options)
{
_storageSharedKeyCredential = credential;
}

/// <summary>
Expand Down Expand Up @@ -2875,5 +2888,128 @@ await GetBlobClient(blobName).DeleteIfExistsAsync(
.ConfigureAwait(false);

#endregion DeleteBlob

#region GenerateSAS
/// <summary>
/// The <see cref="GetSasBuilder"/> returns a <see cref="BlobSasBuilder"/> that
/// sets the respective properties in the BlobSasBuilder from the client.
///
/// Note that properties in the returned builder will not set the BlobName
/// </summary>
/// <param name="permissions">
/// Specifies the list of permissions that can be set in the SasBuilder
/// See <see cref="BlobContainerSasPermissions"/>.
/// </param>
/// <param name="expiresOn">
/// Specifies when to set the expires time in the sas builder
/// </param>
/// <returns>
/// A <see cref="BlobSasBuilder"/> on successfully deleting.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
public BlobSasBuilder GetSasBuilder(
BlobContainerSasPermissions permissions,
DateTimeOffset expiresOn)
{
BlobSasBuilder sasBuilder = new BlobSasBuilder
{
Version = Version.ToString(),
BlobContainerName = Name,
ExpiresOn = expiresOn,
Resource = "c"
};
sasBuilder.SetPermissions(permissions);
return sasBuilder;
}

/// <summary>
/// The <see cref="GenerateSasUri"/> returns a Uri that
/// generates a Service SAS based on the Client properties and builder passed.
///
/// For more information, see
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas">
/// Consturcting a Service SAS</see>
/// </summary>
/// <param name="builder">
/// Used to generate a Shared Access Signature (SAS)
/// </param>
/// <returns>
/// A <see cref="BlobSasBuilder"/> on successfully deleting.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
public Uri GenerateSasUri(
BlobSasBuilder builder)
{
builder = builder ?? throw Errors.ArgumentNull(nameof(builder));
builder.BlobContainerName = string.IsNullOrEmpty(builder.BlobContainerName) ? Name : builder.BlobContainerName;
amnguye marked this conversation as resolved.
Show resolved Hide resolved
if (!builder.BlobContainerName.Equals(Name, StringComparison.InvariantCulture))
{
// TODO: throw proper exception for non-matching builder name
// e.g. containerName doesn't match or leave the containerName in builder
// should be left empty. Or should we always default to the client's ContainerName
// and chug along if they don't match?
throw Errors.SasNamesNotMatching(
nameof(builder.BlobContainerName),
nameof(BlobSasBuilder),
nameof(Name));
}
UriBuilder sasUri = new UriBuilder(Uri);
sasUri.Query = builder.ToSasQueryParameters(_storageSharedKeyCredential).ToString();
return sasUri.Uri;
}

/// <summary>
/// The <see cref="GenerateUserDelegationSasUri"/> returns a Uri that
/// generates a User Delegation SAS based on the Client properties and builder passed.
///
/// For more information, see
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas">
/// Constructing a User Delegation SAS</see>.
/// </summary>
/// <param name="builder">
/// Used to generate a Shared Access Signature (SAS).
/// </param>
/// <param name="delegationKey">
/// User Delegation Key used to generate the User Delegation SAS
/// </param>
/// <returns>
/// A <see cref="BlobSasBuilder"/> on successfully deleting.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
public Uri GenerateUserDelegationSasUri(
BlobSasBuilder builder,
UserDelegationKey delegationKey)
{
builder = builder ?? throw Errors.ArgumentNull(nameof(builder));
builder.BlobContainerName = string.IsNullOrEmpty(builder.BlobContainerName) ? Name : builder.BlobContainerName;
if (!builder.BlobContainerName.Equals(Name, StringComparison.InvariantCulture))
{
// TODO: throw proper exception for non-matching builder name
// e.g. containerName doesn't match or leave the containerName in builder
// should be left empty. Or should we always default to the client's ContainerName
// and chug along if they don't match?
throw Errors.SasNamesNotMatching(
nameof(builder.BlobContainerName),
nameof(BlobSasBuilder),
nameof(Name));
}
if (string.IsNullOrEmpty(AccountName))
{
throw Errors.SasEmptyParam(nameof(AccountName));
}
UriBuilder sasUri = new UriBuilder(Uri);
sasUri.Query = builder.ToSasQueryParameters(delegationKey, AccountName).ToString();
return sasUri.Uri;
}
#endregion
}
}
Loading