Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions sdk/storage/Azure.Storage.Blobs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 12.19.0-beta.1 (Unreleased)

### Features Added
- Added support for BlobClientOptions.Audience

### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,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.Models.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 Expand Up @@ -347,6 +348,24 @@ internal BlobAppendInfo() { }
public System.DateTimeOffset LastModified { get { throw null; } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobAudience : System.IEquatable<Azure.Storage.Blobs.Models.BlobAudience>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public BlobAudience(string value) { throw null; }
public static Azure.Storage.Blobs.Models.BlobAudience PublicAudience { get { throw null; } }
public bool Equals(Azure.Storage.Blobs.Models.BlobAudience other) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object obj) { throw null; }
public static Azure.Storage.Blobs.Models.BlobAudience GetBlobServiceAccountAudience(string storageAccountName) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
public static bool operator ==(Azure.Storage.Blobs.Models.BlobAudience left, Azure.Storage.Blobs.Models.BlobAudience right) { throw null; }
public static implicit operator Azure.Storage.Blobs.Models.BlobAudience (string value) { throw null; }
public static bool operator !=(Azure.Storage.Blobs.Models.BlobAudience left, Azure.Storage.Blobs.Models.BlobAudience right) { throw null; }
public override string ToString() { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobBlock : System.IEquatable<Azure.Storage.Blobs.Models.BlobBlock>
{
private readonly object _dummy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,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.Models.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 Expand Up @@ -347,6 +348,24 @@ internal BlobAppendInfo() { }
public System.DateTimeOffset LastModified { get { throw null; } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobAudience : System.IEquatable<Azure.Storage.Blobs.Models.BlobAudience>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public BlobAudience(string value) { throw null; }
public static Azure.Storage.Blobs.Models.BlobAudience PublicAudience { get { throw null; } }
public bool Equals(Azure.Storage.Blobs.Models.BlobAudience other) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object obj) { throw null; }
public static Azure.Storage.Blobs.Models.BlobAudience GetBlobServiceAccountAudience(string storageAccountName) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
public static bool operator ==(Azure.Storage.Blobs.Models.BlobAudience left, Azure.Storage.Blobs.Models.BlobAudience right) { throw null; }
public static implicit operator Azure.Storage.Blobs.Models.BlobAudience (string value) { throw null; }
public static bool operator !=(Azure.Storage.Blobs.Models.BlobAudience left, Azure.Storage.Blobs.Models.BlobAudience right) { throw null; }
public override string ToString() { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobBlock : System.IEquatable<Azure.Storage.Blobs.Models.BlobBlock>
{
private readonly object _dummy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,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.Models.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 Expand Up @@ -347,6 +348,24 @@ internal BlobAppendInfo() { }
public System.DateTimeOffset LastModified { get { throw null; } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobAudience : System.IEquatable<Azure.Storage.Blobs.Models.BlobAudience>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public BlobAudience(string value) { throw null; }
public static Azure.Storage.Blobs.Models.BlobAudience PublicAudience { get { throw null; } }
public bool Equals(Azure.Storage.Blobs.Models.BlobAudience other) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object obj) { throw null; }
public static Azure.Storage.Blobs.Models.BlobAudience GetBlobServiceAccountAudience(string storageAccountName) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
public static bool operator ==(Azure.Storage.Blobs.Models.BlobAudience left, Azure.Storage.Blobs.Models.BlobAudience right) { throw null; }
public static implicit operator Azure.Storage.Blobs.Models.BlobAudience (string value) { throw null; }
public static bool operator !=(Azure.Storage.Blobs.Models.BlobAudience left, Azure.Storage.Blobs.Models.BlobAudience right) { throw null; }
public override string ToString() { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct BlobBlock : System.IEquatable<Azure.Storage.Blobs.Models.BlobBlock>
{
private readonly object _dummy;
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_c772765837"
}
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(
string.IsNullOrEmpty(options?.Audience?.ToString()) ? BlobAudience.PublicAudience.CreateDefaultScope() : options.Audience.Value.CreateDefaultScope(),
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);

string audienceScope = string.IsNullOrEmpty(options?.Audience?.ToString()) ? BlobAudience.PublicAudience.CreateDefaultScope() : options.Audience.Value.CreateDefaultScope();

_authenticationPolicy = credential.AsPolicy(audienceScope, 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(
string.IsNullOrEmpty(options?.Audience?.ToString()) ? BlobAudience.PublicAudience.CreateDefaultScope() : options.Audience.Value.CreateDefaultScope(),
options),
credential,
options ?? new BlobClientOptions())
{
Errors.VerifyHttpsTokenAuth(serviceUri);
}
Expand Down
85 changes: 85 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/Models/BlobAudience.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.ComponentModel;
using Azure.Core;

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

/// <summary>
/// Intializes new instance of <see cref="BlobAudience"/>.
/// </summary>
/// <param name="value">
/// 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 value)
{
Argument.AssertNotNullOrEmpty(value, nameof(value));
_value = value;
}

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

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

/// <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">
/// The storage account name used to populate the service endpoint.
/// </param>
/// <returns></returns>
public static BlobAudience GetBlobServiceAccountAudience(string storageAccountName) => new($"https://{storageAccountName}.blob.core.windows.net/");

/// <summary> Determines if two <see cref="BlobAudience"/> values are the same. </summary>
public static bool operator ==(BlobAudience left, BlobAudience right) => left.Equals(right);
/// <summary> Determines if two <see cref="BlobAudience"/> values are not the same. </summary>
public static bool operator !=(BlobAudience left, BlobAudience right) => !left.Equals(right);
/// <summary> Converts a string to a <see cref="BlobAudience"/>. </summary>
public static implicit operator BlobAudience(string value) => new BlobAudience(value);

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

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => _value?.GetHashCode() ?? 0;
/// <inheritdoc />
public override string ToString() => _value;

/// <summary>
/// Creates a scope with the respective audience and the default scope.
/// </summary>
/// <returns></returns>
internal string CreateDefaultScope()
{
if (_value.EndsWith("/", StringComparison.InvariantCultureIgnoreCase))
{
return $"{(_value)}{Constants.DefaultScope}";
}
return $"{(_value)}/{Constants.DefaultScope}";
}
}
}
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/"));

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.GetBlobServiceAccountAudience(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(),
new MockCredential(),
options));

// Assert
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
aadBlob.ExistsAsync(),
e => Assert.AreEqual(BlobErrorCode.InvalidAuthenticationInfo.ToString(), e.ErrorCode));
}

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