Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
namespace Azure.Storage.Blobs
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobAudience : System.IEquatable<Azure.Storage.Blobs.BlobAudience>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public BlobAudience(string audience) { throw null; }
public static Azure.Storage.Blobs.BlobAudience PublicAudience { get { throw null; } }
public static Azure.Storage.Blobs.BlobAudience BlobServiceAccountAudience(string storageAccountName) { throw null; }
public bool Equals(Azure.Storage.Blobs.BlobAudience other) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object obj) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
public override string ToString() { throw null; }
}
public partial class BlobClient : Azure.Storage.Blobs.Specialized.BlobBaseClient
{
protected BlobClient() { }
Expand Down Expand Up @@ -52,6 +67,7 @@ public BlobClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredential c
public partial class BlobClientOptions : Azure.Core.ClientOptions
{
public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion version = Azure.Storage.Blobs.BlobClientOptions.ServiceVersion.V2023_08_03) { }
public Azure.Storage.Blobs.BlobAudience? Audience { get { throw null; } set { } }
public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } }
public bool EnableTenantDiscovery { get { throw null; } set { } }
public string EncryptionScope { get { throw null; } set { } }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
namespace Azure.Storage.Blobs
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobAudience : System.IEquatable<Azure.Storage.Blobs.BlobAudience>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public BlobAudience(string audience) { throw null; }
public static Azure.Storage.Blobs.BlobAudience PublicAudience { get { throw null; } }
public static Azure.Storage.Blobs.BlobAudience BlobServiceAccountAudience(string storageAccountName) { throw null; }
public bool Equals(Azure.Storage.Blobs.BlobAudience other) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object obj) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
public override string ToString() { throw null; }
}
public partial class BlobClient : Azure.Storage.Blobs.Specialized.BlobBaseClient
{
protected BlobClient() { }
Expand Down Expand Up @@ -52,6 +67,7 @@ public BlobClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredential c
public partial class BlobClientOptions : Azure.Core.ClientOptions
{
public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion version = Azure.Storage.Blobs.BlobClientOptions.ServiceVersion.V2023_08_03) { }
public Azure.Storage.Blobs.BlobAudience? Audience { get { throw null; } set { } }
public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } }
public bool EnableTenantDiscovery { get { throw null; } set { } }
public string EncryptionScope { get { throw null; } set { } }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
namespace Azure.Storage.Blobs
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobAudience : System.IEquatable<Azure.Storage.Blobs.BlobAudience>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public BlobAudience(string audience) { throw null; }
public static Azure.Storage.Blobs.BlobAudience PublicAudience { get { throw null; } }
public static Azure.Storage.Blobs.BlobAudience BlobServiceAccountAudience(string storageAccountName) { throw null; }
public bool Equals(Azure.Storage.Blobs.BlobAudience other) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object obj) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
public override string ToString() { throw null; }
}
public partial class BlobClient : Azure.Storage.Blobs.Specialized.BlobBaseClient
{
protected BlobClient() { }
Expand Down Expand Up @@ -52,6 +67,7 @@ public BlobClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredential c
public partial class BlobClientOptions : Azure.Core.ClientOptions
{
public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion version = Azure.Storage.Blobs.BlobClientOptions.ServiceVersion.V2023_08_03) { }
public Azure.Storage.Blobs.BlobAudience? Audience { get { throw null; } set { } }
public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } }
public bool EnableTenantDiscovery { get { throw null; } set { } }
public string EncryptionScope { get { throw null; } set { } }
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/Azure.Storage.Blobs/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/storage/Azure.Storage.Blobs",
"Tag": "net/storage/Azure.Storage.Blobs_b46f524eaf"
"Tag": "net/storage/Azure.Storage.Blobs_42626018f4"
}
65 changes: 65 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/BlobAudience.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Azure.Core;

namespace Azure.Storage.Blobs
{
/// <summary>
/// Audiences available for Blobs
/// </summary>
public readonly partial struct BlobAudience : IEquatable<BlobAudience>
{
private readonly string _audience;

/// <summary>
/// Intializes new instance of <see cref="BlobAudience"/>.
/// </summary>
/// <param name="audience">
/// The Azure Active Directory audience to use when forming authorization scopes.
/// For the Language service, this value corresponds to a URL that identifies the Azure cloud where the resource is located.
/// For more information: <see href="https://learn.microsoft.com/en-us/azure/storage/blobs/authorize-access-azure-active-directory" />.
/// </param>
/// <remarks>Please use one of the static constant members over creating a custom value unless you have specific scenario for doing so.</remarks>
public BlobAudience(string audience)
{
Argument.AssertNotNullOrEmpty(audience, nameof(audience));
_audience = audience;
}

private const string _publicAudience = "https://storage.azure.com/.default";

/// <summary>
/// Default Audience. Use to acquire a token for authorizing requests to any Azure Storage account
///
/// Resource ID: &quot;https://storage.azure.com/.default &quot;.
///
/// If no audience is specified, this is the default value.
/// </summary>
public static BlobAudience PublicAudience { get; } = new(_publicAudience);
Comment thread
amnguye marked this conversation as resolved.

/// <summary>
/// The service endpoint for a given storage account.
/// Use this method to acquire a token for authorizing requests to that specific Azure Storage account and service only.
/// </summary>
/// <param name="storageAccountName"></param>
/// <returns></returns>
public static BlobAudience BlobServiceAccountAudience(string storageAccountName) => new($"https://{storageAccountName}.blob.core.windows.net/.default");
Comment thread
amnguye marked this conversation as resolved.
Outdated

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object obj) => obj is BlobAudience other && Equals(other);
/// <inheritdoc />
public bool Equals(BlobAudience other) => string.Equals(_audience, other._audience, StringComparison.InvariantCultureIgnoreCase);

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => _audience?.GetHashCode() ?? 0;
/// <inheritdoc />
public override string ToString() => _audience;
Comment thread
amnguye marked this conversation as resolved.
Outdated
}
}
14 changes: 8 additions & 6 deletions sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,14 @@ public BlobBaseClient(Uri blobUri, AzureSasCredential credential, BlobClientOpti
/// </param>
public BlobBaseClient(Uri blobUri, TokenCredential credential, BlobClientOptions options = default)
: this(
blobUri,
credential.AsPolicy(options),
options,
storageSharedKeyCredential: null,
sasCredential: null,
tokenCredential: credential)
blobUri,
credential.AsPolicy(
options?.Audience != null ? options.Audience.Value.ToString() : BlobAudience.PublicAudience.ToString(),
options),
options,
storageSharedKeyCredential: null,
sasCredential: null,
tokenCredential: credential)
{
Errors.VerifyHttpsTokenAuth(blobUri);
}
Expand Down
6 changes: 6 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,11 @@ internal HttpPipeline Build(object credentials)

/// <inheritdoc />
public bool EnableTenantDiscovery { get; set; }

/// <summary>
/// Gets or sets the Audience to use for authentication with Azure Active Directory (AAD). The audience is not considered when using a shared key.
/// </summary>
/// <value>If <c>null</c>, <see cref="BlobAudience.PublicAudience" /> will be assumed.</value>
public BlobAudience? Audience { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,10 @@ public BlobContainerClient(Uri blobContainerUri, TokenCredential credential, Blo
Errors.VerifyHttpsTokenAuth(blobContainerUri);
Argument.AssertNotNull(blobContainerUri, nameof(blobContainerUri));
_uri = blobContainerUri;
_authenticationPolicy = credential.AsPolicy(options);

BlobAudience audience = options?.Audience != null ? options.Audience.Value : BlobAudience.PublicAudience;

_authenticationPolicy = credential.AsPolicy(audience.ToString(), options);
options ??= new BlobClientOptions();

_clientConfiguration = new BlobClientConfiguration(
Expand Down
8 changes: 7 additions & 1 deletion sdk/storage/Azure.Storage.Blobs/src/BlobServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,13 @@ public BlobServiceClient(Uri serviceUri, AzureSasCredential credential, BlobClie
/// every request.
/// </param>
public BlobServiceClient(Uri serviceUri, TokenCredential credential, BlobClientOptions options = default)
: this(serviceUri, credential.AsPolicy(options), credential, options ?? new BlobClientOptions())
: this(
serviceUri,
credential.AsPolicy(
options?.Audience != null ? options.Audience.Value.ToString() : BlobAudience.PublicAudience.ToString(),
options),
credential,
options ?? new BlobClientOptions())
{
Errors.VerifyHttpsTokenAuth(serviceUri);
}
Expand Down
113 changes: 113 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/tests/AppendBlobClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,119 @@ public async Task Ctor_AzureSasCredential_VerifyNoSasInUri()
e => e.Message.Contains($"You cannot use {nameof(AzureSasCredential)} when the resource URI also contains a Shared Access Signature"));
}

[RecordedTest]
public async Task Ctor_DefaultAudience()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();

AppendBlobClient blob = InstrumentClient(test.Container.GetAppendBlobClient(GetNewBlobName()));
await blob.CreateIfNotExistsAsync();

// Act - Create new blob client with the OAuth Credential and Audience
BlobClientOptions options = GetOptionsWithAudience(BlobAudience.PublicAudience);

BlobUriBuilder uriBuilder = new BlobUriBuilder(new Uri(Tenants.TestConfigOAuth.BlobServiceEndpoint))
{
BlobContainerName = blob.BlobContainerName,
BlobName = blob.Name
};

AppendBlobClient aadBlob = InstrumentClient(new AppendBlobClient(
uriBuilder.ToUri(),
Tenants.GetOAuthCredential(),
options));

// Assert
bool exists = await aadBlob.ExistsAsync();
Assert.IsTrue(exists);
}

[RecordedTest]
public async Task Ctor_CustomAudience()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();

AppendBlobClient blob = InstrumentClient(test.Container.GetAppendBlobClient(GetNewBlobName()));
await blob.CreateIfNotExistsAsync();

// Act - Create new blob client with the OAuth Credential and Audience
BlobClientOptions options = GetOptionsWithAudience(new BlobAudience($"https://{test.Container.AccountName}.blob.core.windows.net/.default"));

BlobUriBuilder uriBuilder = new BlobUriBuilder(new Uri(Tenants.TestConfigOAuth.BlobServiceEndpoint))
{
BlobContainerName = blob.BlobContainerName,
BlobName = blob.Name
};

AppendBlobClient aadBlob = InstrumentClient(new AppendBlobClient(
uriBuilder.ToUri(),
Tenants.GetOAuthCredential(),
options));

// Assert
bool exists = await aadBlob.ExistsAsync();
Assert.IsTrue(exists);
}

[RecordedTest]
public async Task Ctor_StorageAccountAudience()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();

AppendBlobClient blob = InstrumentClient(test.Container.GetAppendBlobClient(GetNewBlobName()));
await blob.CreateIfNotExistsAsync();

// Act - Create new blob client with the OAuth Credential and Audience
BlobClientOptions options = GetOptionsWithAudience(BlobAudience.BlobServiceAccountAudience(test.Container.AccountName));

BlobUriBuilder uriBuilder = new BlobUriBuilder(new Uri(Tenants.TestConfigOAuth.BlobServiceEndpoint))
{
BlobContainerName = blob.BlobContainerName,
BlobName = blob.Name
};

AppendBlobClient aadBlob = InstrumentClient(new AppendBlobClient(
uriBuilder.ToUri(),
Tenants.GetOAuthCredential(),
options));

// Assert
bool exists = await aadBlob.ExistsAsync();
Assert.IsTrue(exists);
}

[RecordedTest]
public async Task Ctor_AudienceError()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();

AppendBlobClient blob = InstrumentClient(test.Container.GetAppendBlobClient(GetNewBlobName()));
await blob.CreateIfNotExistsAsync();

// Act - Create new blob client with the OAuth Credential and Audience
BlobClientOptions options = GetOptionsWithAudience(new BlobAudience("https://badaudience.blob.core.windows.net"));

BlobUriBuilder uriBuilder = new BlobUriBuilder(new Uri(Tenants.TestConfigOAuth.BlobServiceEndpoint))
{
BlobContainerName = blob.BlobContainerName,
BlobName = blob.Name
};

AppendBlobClient aadBlob = InstrumentClient(new AppendBlobClient(
uriBuilder.ToUri(),
Tenants.GetOAuthCredential(),
options));

// Assert
await TestHelper.AssertExpectedExceptionAsync<Identity.AuthenticationFailedException>(
aadBlob.ExistsAsync(),
e => Assert.IsTrue(e.Message.Contains("ClientSecretCredential authentication")));
}

[RecordedTest]
public void WithSnapshot()
{
Expand Down
Loading