Skip to content

Commit 1dae4ca

Browse files
[AppConfig] Add support for specifying aad token audience (Azure#48176)
* feat: add support for aad token audience * pr feedback * update client remarks * add live tests * update assets.json * update changelog.md
1 parent 832ba10 commit 1dae4ca

10 files changed

+287
-7
lines changed

sdk/appconfiguration/Azure.Data.AppConfiguration/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Added support for specifying the token credential's Microsoft Entra audience when creating a client.
8+
79
### Breaking Changes
810

911
### Bugs Fixed

sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net8.0.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
namespace Azure.Data.AppConfiguration
22
{
3+
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
4+
public readonly partial struct ConfigurationAudience : System.IEquatable<Azure.Data.AppConfiguration.ConfigurationAudience>
5+
{
6+
private readonly object _dummy;
7+
private readonly int _dummyPrimitive;
8+
public ConfigurationAudience(string value) { throw null; }
9+
public static Azure.Data.AppConfiguration.ConfigurationAudience AzureChina { get { throw null; } }
10+
public static Azure.Data.AppConfiguration.ConfigurationAudience AzureGovernment { get { throw null; } }
11+
public static Azure.Data.AppConfiguration.ConfigurationAudience AzurePublicCloud { get { throw null; } }
12+
public bool Equals(Azure.Data.AppConfiguration.ConfigurationAudience other) { throw null; }
13+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
14+
public override bool Equals(object obj) { throw null; }
15+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
16+
public override int GetHashCode() { throw null; }
17+
public static bool operator ==(Azure.Data.AppConfiguration.ConfigurationAudience left, Azure.Data.AppConfiguration.ConfigurationAudience right) { throw null; }
18+
public static implicit operator Azure.Data.AppConfiguration.ConfigurationAudience (string value) { throw null; }
19+
public static bool operator !=(Azure.Data.AppConfiguration.ConfigurationAudience left, Azure.Data.AppConfiguration.ConfigurationAudience right) { throw null; }
20+
public override string ToString() { throw null; }
21+
}
322
public partial class ConfigurationClient
423
{
524
protected ConfigurationClient() { }
@@ -74,6 +93,7 @@ public static partial class ConfigurationClientExtensions
7493
public partial class ConfigurationClientOptions : Azure.Core.ClientOptions
7594
{
7695
public ConfigurationClientOptions(Azure.Data.AppConfiguration.ConfigurationClientOptions.ServiceVersion version = Azure.Data.AppConfiguration.ConfigurationClientOptions.ServiceVersion.V2023_11_01) { }
96+
public Azure.Data.AppConfiguration.ConfigurationAudience? Audience { get { throw null; } set { } }
7797
public enum ServiceVersion
7898
{
7999
V1_0 = 0,

sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.netstandard2.0.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
namespace Azure.Data.AppConfiguration
22
{
3+
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
4+
public readonly partial struct ConfigurationAudience : System.IEquatable<Azure.Data.AppConfiguration.ConfigurationAudience>
5+
{
6+
private readonly object _dummy;
7+
private readonly int _dummyPrimitive;
8+
public ConfigurationAudience(string value) { throw null; }
9+
public static Azure.Data.AppConfiguration.ConfigurationAudience AzureChina { get { throw null; } }
10+
public static Azure.Data.AppConfiguration.ConfigurationAudience AzureGovernment { get { throw null; } }
11+
public static Azure.Data.AppConfiguration.ConfigurationAudience AzurePublicCloud { get { throw null; } }
12+
public bool Equals(Azure.Data.AppConfiguration.ConfigurationAudience other) { throw null; }
13+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
14+
public override bool Equals(object obj) { throw null; }
15+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
16+
public override int GetHashCode() { throw null; }
17+
public static bool operator ==(Azure.Data.AppConfiguration.ConfigurationAudience left, Azure.Data.AppConfiguration.ConfigurationAudience right) { throw null; }
18+
public static implicit operator Azure.Data.AppConfiguration.ConfigurationAudience (string value) { throw null; }
19+
public static bool operator !=(Azure.Data.AppConfiguration.ConfigurationAudience left, Azure.Data.AppConfiguration.ConfigurationAudience right) { throw null; }
20+
public override string ToString() { throw null; }
21+
}
322
public partial class ConfigurationClient
423
{
524
protected ConfigurationClient() { }
@@ -74,6 +93,7 @@ public static partial class ConfigurationClientExtensions
7493
public partial class ConfigurationClientOptions : Azure.Core.ClientOptions
7594
{
7695
public ConfigurationClientOptions(Azure.Data.AppConfiguration.ConfigurationClientOptions.ServiceVersion version = Azure.Data.AppConfiguration.ConfigurationClientOptions.ServiceVersion.V2023_11_01) { }
96+
public Azure.Data.AppConfiguration.ConfigurationAudience? Audience { get { throw null; } set { } }
7797
public enum ServiceVersion
7898
{
7999
V1_0 = 0,

sdk/appconfiguration/Azure.Data.AppConfiguration/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "net",
44
"TagPrefix": "net/appconfiguration/Azure.Data.AppConfiguration",
5-
"Tag": "net/appconfiguration/Azure.Data.AppConfiguration_dca2b5967f"
5+
"Tag": "net/appconfiguration/Azure.Data.AppConfiguration_c0a9828a83"
66
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.ComponentModel;
6+
7+
namespace Azure.Data.AppConfiguration
8+
{
9+
/// <summary> Cloud audiences available for AppConfiguration. </summary>
10+
public readonly struct ConfigurationAudience : IEquatable<ConfigurationAudience>
11+
{
12+
private readonly string _value;
13+
private const string AzureChinaValue = "https://appconfig.azure.cn";
14+
private const string AzureGovernmentValue = "https://appconfig.azure.us";
15+
private const string AzurePublicCloudValue = "https://appconfig.azure.com";
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="ConfigurationAudience"/> object.
19+
/// </summary>
20+
/// <param name="value">The Microsoft Entra audience to use when forming authorization scopes.
21+
/// For the App Configuration service, this value corresponds to a URL that identifies the Azure cloud where the resource is located.</param>
22+
/// <exception cref="ArgumentNullException"> <paramref name="value"/> is null. </exception>
23+
/// <remarks>Please use one of the constant members over creating a custom value unless you have special needs for doing so.</remarks>
24+
public ConfigurationAudience(string value)
25+
{
26+
Argument.AssertNotNullOrEmpty(value, nameof(value));
27+
_value = value;
28+
}
29+
30+
/// <summary> Azure China. </summary>
31+
public static ConfigurationAudience AzureChina { get; } = new ConfigurationAudience(AzureChinaValue);
32+
33+
/// <summary> Azure Government. </summary>
34+
public static ConfigurationAudience AzureGovernment { get; } = new ConfigurationAudience(AzureGovernmentValue);
35+
36+
/// <summary> Azure Public Cloud. </summary>
37+
public static ConfigurationAudience AzurePublicCloud { get; } = new ConfigurationAudience(AzurePublicCloudValue);
38+
39+
/// <summary> Determines if two <see cref="ConfigurationAudience"/> values are the same. </summary>
40+
public static bool operator ==(ConfigurationAudience left, ConfigurationAudience right) => left.Equals(right);
41+
/// <summary> Determines if two <see cref="ConfigurationAudience"/> values are not the same. </summary>
42+
public static bool operator !=(ConfigurationAudience left, ConfigurationAudience right) => !left.Equals(right);
43+
/// <summary> Converts a string to a <see cref="ConfigurationAudience"/>. </summary>
44+
public static implicit operator ConfigurationAudience(string value) => new ConfigurationAudience(value);
45+
46+
/// <inheritdoc />
47+
[EditorBrowsable(EditorBrowsableState.Never)]
48+
public override bool Equals(object obj) => obj is ConfigurationAudience other && Equals(other);
49+
/// <inheritdoc />
50+
public bool Equals(ConfigurationAudience other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase);
51+
52+
/// <inheritdoc />
53+
[EditorBrowsable(EditorBrowsableState.Never)]
54+
public override int GetHashCode() => _value?.GetHashCode() ?? 0;
55+
/// <inheritdoc />
56+
public override string ToString() => _value;
57+
}
58+
}

sdk/appconfiguration/Azure.Data.AppConfiguration/src/ConfigurationClient.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
using Azure.Core;
1111
using Azure.Core.Pipeline;
1212

13+
#pragma warning disable AZC0007
14+
1315
namespace Azure.Data.AppConfiguration
1416
{
1517
/// <summary>
@@ -73,14 +75,17 @@ public ConfigurationClient(Uri endpoint, TokenCredential credential)
7375
/// <param name="endpoint">The <see cref="Uri"/> referencing the app configuration storage.</param>
7476
/// <param name="credential">The token credential used to sign requests.</param>
7577
/// <param name="options">Options that allow configuration of requests sent to the configuration store.</param>
78+
/// <remarks> The <paramref name="credential"/>'s Microsoft Entra audience is configurable via the <see cref="ConfigurationClientOptions.Audience"/> property.
79+
/// If no token audience is set, Azure Public Cloud is used. If using an Azure sovereign cloud, configure the audience accordingly.
80+
/// </remarks>
7681
public ConfigurationClient(Uri endpoint, TokenCredential credential, ConfigurationClientOptions options)
7782
{
7883
Argument.AssertNotNull(endpoint, nameof(endpoint));
7984
Argument.AssertNotNull(credential, nameof(credential));
8085

8186
_endpoint = endpoint;
8287
_syncTokenPolicy = new SyncTokenPolicy();
83-
_pipeline = CreatePipeline(options, new BearerTokenAuthenticationPolicy(credential, GetDefaultScope(endpoint)), _syncTokenPolicy);
88+
_pipeline = CreatePipeline(options, new BearerTokenAuthenticationPolicy(credential, options.GetDefaultScope(endpoint)), _syncTokenPolicy);
8489
_apiVersion = options.Version;
8590

8691
ClientDiagnostics = new ClientDiagnostics(options, true);
@@ -142,9 +147,6 @@ private static HttpPipeline CreatePipeline(ConfigurationClientOptions options, H
142147
new ResponseClassifier());
143148
}
144149

145-
private static string GetDefaultScope(Uri uri)
146-
=> $"{uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped)}/.default";
147-
148150
/// <summary>
149151
/// Creates a <see cref="ConfigurationSetting"/> if the setting, uniquely identified by key and label, does not already exist in the configuration store.
150152
/// </summary>

sdk/appconfiguration/Azure.Data.AppConfiguration/src/ConfigurationClientOptions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ namespace Azure.Data.AppConfiguration
1313
public partial class ConfigurationClientOptions : ClientOptions
1414
{
1515
private const ServiceVersion LatestVersion = ServiceVersion.V2023_11_01;
16+
private const string AzConfigUsGovCloudHostName = "azconfig.azure.us";
17+
private const string AzConfigChinaCloudHostName = "azconfig.azure.cn";
18+
private const string AppConfigUsGovCloudHostName = "appconfig.azure.us";
19+
private const string AppConfigChinaCloudHostName = "appconfig.azure.cn";
1620

1721
/// <summary>
1822
/// The versions of the App Configuration service supported by this client library.
@@ -36,6 +40,12 @@ public enum ServiceVersion
3640
V2023_11_01 = 2
3741
}
3842

43+
/// <summary>
44+
/// Gets or sets the Audience to use for authentication with Microsoft Entra. The audience is not considered when using a shared key.
45+
/// </summary>
46+
/// <value>If <c>null</c>, <see cref="ConfigurationAudience.AzurePublicCloud" /> will be assumed.</value>
47+
public ConfigurationAudience? Audience { get; set; }
48+
3949
internal string Version { get; }
4050

4151
/// <summary>
@@ -58,5 +68,23 @@ public ConfigurationClientOptions(ServiceVersion version = LatestVersion)
5868
};
5969
this.ConfigureLogging();
6070
}
71+
72+
internal string GetDefaultScope(Uri uri)
73+
{
74+
if (string.IsNullOrEmpty(Audience?.ToString()))
75+
{
76+
string host = uri.GetComponents(UriComponents.Host, UriFormat.SafeUnescaped);
77+
return host switch
78+
{
79+
_ when host.EndsWith(AzConfigUsGovCloudHostName, StringComparison.InvariantCultureIgnoreCase) || host.EndsWith(AppConfigUsGovCloudHostName, StringComparison.InvariantCultureIgnoreCase)
80+
=> $"{ConfigurationAudience.AzureGovernment}/.default",
81+
_ when host.EndsWith(AzConfigChinaCloudHostName, StringComparison.InvariantCultureIgnoreCase) || host.EndsWith(AppConfigChinaCloudHostName, StringComparison.InvariantCultureIgnoreCase)
82+
=> $"{ConfigurationAudience.AzureChina}/.default",
83+
_ => $"{ConfigurationAudience.AzurePublicCloud}/.default"
84+
};
85+
}
86+
87+
return $"{Audience}/.default";
88+
}
6189
}
6290
}

sdk/appconfiguration/Azure.Data.AppConfiguration/tests/AppConfigurationTestEnvironment.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using System;
45
using Azure.Core.TestFramework;
6+
using Azure.Identity;
57

68
namespace Azure.Data.AppConfiguration
79
{
@@ -10,5 +12,27 @@ public class AppConfigurationTestEnvironment : TestEnvironment
1012
public string ConnectionString => GetRecordedVariable("APPCONFIGURATION_CONNECTION_STRING", options => options.HasSecretConnectionStringParameter("secret", SanitizedValue.Base64));
1113
public string Endpoint => GetRecordedVariable("APPCONFIGURATION_ENDPOINT_STRING");
1214
public string SecretId => GetRecordedVariable("KEYVAULT_SECRET_URL");
15+
16+
public ConfigurationAudience GetAudience()
17+
{
18+
Uri authorityHost = new(AuthorityHostUrl);
19+
20+
if (authorityHost == AzureAuthorityHosts.AzurePublicCloud)
21+
{
22+
return ConfigurationAudience.AzurePublicCloud;
23+
}
24+
25+
if (authorityHost == AzureAuthorityHosts.AzureChina)
26+
{
27+
return ConfigurationAudience.AzureChina;
28+
}
29+
30+
if (authorityHost == AzureAuthorityHosts.AzureGovernment)
31+
{
32+
return ConfigurationAudience.AzureGovernment;
33+
}
34+
35+
throw new NotSupportedException($"Cloud for authority host {authorityHost} is not supported.");
36+
}
1337
}
1438
}

0 commit comments

Comments
 (0)