Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
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)
{
BlobSasBuilder sasBuilder = new BlobSasBuilder
{
Version = Version.ToString(),
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)
{
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));
}
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) ? 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);
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;
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