Skip to content

Commit 85e2b6c

Browse files
authored
[AppConfig] Added Audience Scope support (#44539)
* introduce ConfigurationAudience * added the configuration audience scope * added unit tests * Entra instead of EntraID * renaming * remove unnecessary changes * replace aad by entra
1 parent a3832e6 commit 85e2b6c

File tree

12 files changed

+188
-20
lines changed

12 files changed

+188
-20
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/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ az appconfig create --name <config-store-name> --resource-group <resource-group-
8080
### Authenticate the client
8181

8282
In order to interact with the App Configuration service you'll need to create an instance of the Configuration Client
83-
class. To make this possible you'll need the connection string of the Configuration Store. Alternatively, use AAD token
83+
class. To make this possible you'll need the connection string of the Configuration Store. Alternatively, use Entra token
8484
to connect to the service.
8585

8686
#### Use connection string
@@ -113,7 +113,7 @@ ConfigurationAsyncClient configurationClient = new ConfigurationClientBuilder()
113113
.buildAsyncClient();
114114
```
115115

116-
#### Use AAD token
116+
#### Use Entra token
117117

118118
Here we demonstrate using [DefaultAzureCredential][default_cred_ref]
119119
to authenticate as a service principal. However, the configuration client
@@ -162,7 +162,7 @@ configuration client.
162162
Constructing the client also requires your configuration store's URL, which you can
163163
get from the Azure CLI or the Azure Portal. In the Azure Portal, the URL can be found listed as the service "Endpoint".
164164

165-
```java readme-sample-aadAuthentication
165+
```java readme-sample-entraAuthentication
166166
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
167167
ConfigurationClient configurationClient = new ConfigurationClientBuilder()
168168
.credential(credential)

sdk/appconfiguration/azure-data-appconfiguration/TROUBLESHOOTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ ConfigurationClient configurationClient = new ConfigurationClientBuilder()
4141
.buildClient();
4242
// or
4343
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
44-
ConfigurationClient configurationClientAad = new ConfigurationClientBuilder()
44+
ConfigurationClient configurationClientEntraId = new ConfigurationClientBuilder()
4545
.credential(credential)
4646
.endpoint(endpoint)
4747
.httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS))

sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationAsyncClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
*
7272
* <p>In order to interact with the App Configuration service you'll need to create an instance of the
7373
* {@link com.azure.data.appconfiguration.ConfigurationAsyncClient} class. To make this possible you'll need the
74-
* connection string of the configuration store. Alternatively, you can use AAD authentication via
74+
* connection string of the configuration store. Alternatively, you can use Entra authentication via
7575
* <a href="https://learn.microsoft.com/java/api/overview/azure/identity-readme?view=azure-java-stable"> Azure Identity</a>
7676
* to connect to the service.</p>
7777
* <ol>

sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
*
7272
* <p>In order to interact with the App Configuration service you'll need to create an instance of the
7373
* {@link ConfigurationClient} class. To make this possible you'll need the connection
74-
* string of the configuration store. Alternatively, you can use AAD authentication via
74+
* string of the configuration store. Alternatively, you can use Entra authentication via
7575
* <a href="https://learn.microsoft.com/java/api/overview/azure/identity-readme?view=azure-java-stable"> Azure Identity</a>
7676
* to connect to the service.</p>
7777
* <ol>

sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.azure.data.appconfiguration.implementation.ConfigurationClientCredentials;
4444
import com.azure.data.appconfiguration.implementation.ConfigurationCredentialsPolicy;
4545
import com.azure.data.appconfiguration.implementation.SyncTokenPolicy;
46+
import com.azure.data.appconfiguration.models.ConfigurationAudience;
4647

4748
import java.net.MalformedURLException;
4849
import java.net.URL;
@@ -144,6 +145,8 @@ public final class ConfigurationClientBuilder implements TokenCredentialTrait<Co
144145
private Configuration configuration;
145146
private ConfigurationServiceVersion version;
146147

148+
private ConfigurationAudience audience;
149+
147150
/**
148151
* Constructs a new builder used to configure and build {@link ConfigurationClient ConfigurationClients} and
149152
* {@link ConfigurationAsyncClient ConfigurationAsyncClients}.
@@ -270,7 +273,7 @@ private HttpPipeline createDefaultHttpPipeline(SyncTokenPolicy syncTokenPolicy,
270273

271274
if (tokenCredential != null) {
272275
// User token based policy
273-
policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint)));
276+
policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, getDefaultScope(endpoint)));
274277
} else if (credentials != null) {
275278
// Use credentialS based policy
276279
policies.add(new ConfigurationCredentialsPolicy(credentials));
@@ -537,4 +540,36 @@ public ConfigurationClientBuilder serviceVersion(ConfigurationServiceVersion ver
537540
this.version = version;
538541
return this;
539542
}
543+
544+
/**
545+
* Sets the {@link ConfigurationAudience} to use for authentication with Microsoft Entra. The audience is not
546+
* considered when using a shared key.
547+
*
548+
* @param audience {@link ConfigurationAudience} of the service to be used when making requests.
549+
* @return The updated ConfigurationClientBuilder object.
550+
*/
551+
public ConfigurationClientBuilder audience(ConfigurationAudience audience) {
552+
this.audience = audience;
553+
return this;
554+
}
555+
556+
/**
557+
* Gets the default scope for the given endpoint.
558+
*
559+
* @param endpoint The endpoint to get the default scope for.
560+
* @return The default scope for the given endpoint.
561+
*/
562+
private String getDefaultScope(String endpoint) {
563+
String defaultValue = "/.default";
564+
if (audience == null || audience.toString().isEmpty()) {
565+
if (endpoint.endsWith("azconfig.azure.us") || endpoint.endsWith("appconfig.azure.us")) {
566+
return ConfigurationAudience.AZURE_US_GOVERNMENT + defaultValue;
567+
} else if (endpoint.endsWith("azconfig.azure.cn") || endpoint.endsWith("appconfig.azure.cn")) {
568+
return ConfigurationAudience.AZURE_CHINA + defaultValue;
569+
} else {
570+
return ConfigurationAudience.AZURE_PUBLIC_CLOUD + defaultValue;
571+
}
572+
}
573+
return audience + defaultValue;
574+
}
540575
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.data.appconfiguration.models;
5+
6+
import com.azure.core.util.ExpandableStringEnum;
7+
8+
import java.util.Collection;
9+
10+
/**
11+
* Configuration Audience is used to specify the target audience for the Azure App Configuration service.
12+
* Microsoft Entra audience is configurable via the {@link com.azure.data.appconfiguration.ConfigurationClientBuilder#audience(ConfigurationAudience)} method.
13+
*/
14+
public final class ConfigurationAudience extends ExpandableStringEnum<ConfigurationAudience> {
15+
/**
16+
* The Azure App Configuration service audience for China Cloud.
17+
*/
18+
public static final ConfigurationAudience AZURE_CHINA = fromString("https://appconfig.azure.cn");
19+
20+
/**
21+
* The Azure App Configuration service audience for US Government Cloud.
22+
*/
23+
public static final ConfigurationAudience AZURE_US_GOVERNMENT = fromString("https://appconfig.azure.us");
24+
25+
/**
26+
* The Azure App Configuration service audience for Public Cloud.
27+
*/
28+
public static final ConfigurationAudience AZURE_PUBLIC_CLOUD = fromString("https://appconfig.azure.com");
29+
30+
/**
31+
* Creates a new instance of ConfigurationAudience value.
32+
*
33+
* @deprecated Use the {@link #fromString(String)} factory method.
34+
*/
35+
@Deprecated
36+
public ConfigurationAudience() {
37+
}
38+
39+
/**
40+
* Creates or finds a ConfigurationAudience from its string representation.
41+
*
42+
* @param name a name to look for.
43+
* @return the corresponding ConfigurationAudience.
44+
*/
45+
public static ConfigurationAudience fromString(String name) {
46+
return fromString(name, ConfigurationAudience.class);
47+
}
48+
49+
/**
50+
* Gets known ConfigurationAudience values.
51+
*
52+
* @return known ConfigurationAudience values.
53+
*/
54+
public static Collection<ConfigurationAudience> values() {
55+
return values(ConfigurationAudience.class);
56+
}
57+
}

sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
* <p>In order to interact with the App Configuration service you'll need to create an instance of the Configuration
1919
* Client class. To make this possible you'll need the connection string of the configuration store. Alternatively,
20-
* you can use AAD authentication via
20+
* you can use Entra authentication via
2121
* <a href="https://learn.microsoft.com/java/api/overview/azure/identity-readme?view=azure-java-stable"> Azure Identity</a>
2222
* to connect to the service.</p>
2323
* <ol>

sdk/appconfiguration/azure-data-appconfiguration/src/samples/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The following sections provide several code snippets covering some of the most c
3232
- [Set a configuration setting to read only][sample_read_only]
3333
- [Clear read only from a configuration setting][sample_read_only]
3434
- [Conditional request a configuration setting][sample_conditional_request]
35-
- [AAD Authentication][sample_aad]
35+
- [Entra Authentication][sample_entra_authentication]
3636
- [HTTP client with proxy option][proxy_option]
3737
- [Feature Flag configuration setting][sample_feature_flag_setting]
3838
- [Secret Reference configuration setting][sample_secret_reference_setting]
@@ -65,7 +65,7 @@ This project welcomes contributions and suggestions. Find [more contributing][SD
6565
[sample_read_only]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/ReadOnlySample.java
6666
[sample_read_revision_history]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/ReadRevisionHistory.java
6767
[sample_read_revision_history_tags]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/ReadRevisionHistoryWIthTagsFilter.java
68-
[sample_aad]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/AadAuthentication.java
68+
[sample_entra_authentication]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/EntraIdAuthentication.java
6969
[sample_feature_flag_setting]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/FeatureFlagConfigurationSettingSample.java
7070
[sample_secret_reference_setting]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/SecretReferenceConfigurationSettingSample.java
7171
[sample_snapshot_CRU_usage]: https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/CreateSnapshot.java
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@
33

44
package com.azure.data.appconfiguration;
55

6+
import com.azure.core.util.Configuration;
67
import com.azure.data.appconfiguration.models.ConfigurationSetting;
78
import com.azure.identity.DefaultAzureCredential;
89
import com.azure.identity.DefaultAzureCredentialBuilder;
910

1011
/**
11-
* Sample demonstrates how to use AAD token to build a configuration client.
12+
* Sample demonstrates how to use Entra token to build a configuration client.
1213
*/
13-
public class AadAuthentication {
14+
public class EntraIdAuthentication {
1415
/**
15-
* Sample for how to use AAD token Authentication.
16+
* Sample for how to use Entra token Authentication.
1617
*
1718
* @param args Unused. Arguments to the program.
1819
*/
1920
public static void main(String[] args) {
2021
// The endpoint can be obtained by going to your App Configuration instance in the Azure portal
2122
// and navigating to "Overview" page. Looking for the "Endpoint" keyword.
22-
String endpoint = "{endpoint_value}";
23+
String endpoint = Configuration.getGlobalConfiguration().get("AZ_CONFIG_ENDPOINT");
2324

2425
// Default token credential could be obtained from Identity service.
2526
// It tries to create a valid credential in the following order:
@@ -30,7 +31,7 @@ public static void main(String[] args) {
3031
DefaultAzureCredential tokenCredential = new DefaultAzureCredentialBuilder().build();
3132

3233
final ConfigurationClient client = new ConfigurationClientBuilder()
33-
.credential(tokenCredential) // AAD authentication
34+
.credential(tokenCredential) // Entra authentication
3435
.endpoint(endpoint)
3536
.buildClient();
3637

sdk/appconfiguration/azure-data-appconfiguration/src/samples/java/com/azure/data/appconfiguration/ReadmeSamples.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ public void createAsyncClient() {
6969
// END: readme-sample-createAsyncClient
7070
}
7171

72-
public void aadAuthentication() {
73-
// BEGIN: readme-sample-aadAuthentication
72+
public void entraAuthentication() {
73+
// BEGIN: readme-sample-entraAuthentication
7474
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
7575
ConfigurationClient configurationClient = new ConfigurationClientBuilder()
7676
.credential(credential)
7777
.endpoint(endpoint)
7878
.buildClient();
79-
// END: readme-sample-aadAuthentication
79+
// END: readme-sample-entraAuthentication
8080
}
8181

8282
public void sqlExample() {
@@ -364,7 +364,7 @@ public void enableHttpLogging() {
364364
.buildClient();
365365
// or
366366
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
367-
ConfigurationClient configurationClientAad = new ConfigurationClientBuilder()
367+
ConfigurationClient configurationClientEntraId = new ConfigurationClientBuilder()
368368
.credential(credential)
369369
.endpoint(endpoint)
370370
.httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS))

sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/ConfigurationClientBuilderTest.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323
import com.azure.core.util.Header;
2424
import com.azure.data.appconfiguration.implementation.ClientConstants;
2525
import com.azure.data.appconfiguration.implementation.ConfigurationClientCredentials;
26+
import com.azure.data.appconfiguration.models.ConfigurationAudience;
2627
import com.azure.data.appconfiguration.models.ConfigurationSetting;
28+
import com.azure.identity.DefaultAzureCredentialBuilder;
2729
import org.junit.jupiter.api.Test;
2830
import org.junit.jupiter.params.ParameterizedTest;
2931
import org.junit.jupiter.params.provider.MethodSource;
3032
import reactor.core.publisher.Mono;
3133

34+
import java.lang.reflect.Method;
3235
import java.net.URI;
3336
import java.net.URISyntaxException;
3437
import java.time.Duration;
@@ -146,7 +149,7 @@ public void invalidConnectionStringSegmentCount() {
146149

147150
@Test
148151
@DoNotRecord
149-
public void nullAADCredential() {
152+
public void nullEntraCredential() {
150153
assertThrows(NullPointerException.class, () -> {
151154
final ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
152155
builder.endpoint(ENDPOINT).credential(null).buildAsyncClient();
@@ -299,4 +302,74 @@ private static URI getURI(String endpointFormat, String namespace, String domain
299302
exception);
300303
}
301304
}
305+
306+
@Test
307+
@DoNotRecord
308+
public void testGetUsGovScope() throws Exception {
309+
ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
310+
Method method = ConfigurationClientBuilder.class.getDeclaredMethod("getDefaultScope", String.class);
311+
method.setAccessible(true);
312+
313+
String expectedScope = "https://appconfig.azure.us/.default";
314+
315+
String legacyEndpoint = "https://example1.azconfig.azure.us";
316+
String actualScope = (String) method.invoke(builder, legacyEndpoint);
317+
assertEquals(expectedScope, actualScope);
318+
319+
String endpoint = "https://example1.appconfig.azure.us";
320+
actualScope = (String) method.invoke(builder, endpoint);
321+
assertEquals(expectedScope, actualScope);
322+
}
323+
324+
@Test
325+
@DoNotRecord
326+
public void testGetDefaultScope() throws Exception {
327+
ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
328+
Method method = ConfigurationClientBuilder.class.getDeclaredMethod("getDefaultScope", String.class);
329+
method.setAccessible(true);
330+
331+
String expectedScope = "https://appconfig.azure.com/.default";
332+
333+
String legacyEndpoint = "https://example1.azconfig.azure.com";
334+
String actualScope = (String) method.invoke(builder, legacyEndpoint);
335+
assertEquals(expectedScope, actualScope);
336+
337+
String endpoint = "https://example1.appconfig.azure.com";
338+
actualScope = (String) method.invoke(builder, endpoint);
339+
assertEquals(expectedScope, actualScope);
340+
}
341+
342+
@Test
343+
@DoNotRecord
344+
public void testGetChinaScope() throws Exception {
345+
ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
346+
Method method = ConfigurationClientBuilder.class.getDeclaredMethod("getDefaultScope", String.class);
347+
method.setAccessible(true);
348+
349+
String expectedScope = "https://appconfig.azure.cn/.default";
350+
351+
String legacyEndpoint = "https://example1.azconfig.azure.cn";
352+
String actualScope = (String) method.invoke(builder, legacyEndpoint);
353+
assertEquals(expectedScope, actualScope);
354+
355+
String endpoint = "https://example1.appconfig.azure.cn";
356+
actualScope = (String) method.invoke(builder, endpoint);
357+
assertEquals(expectedScope, actualScope);
358+
}
359+
360+
@Test
361+
@DoNotRecord
362+
public void testUserDefinedScope() throws Exception {
363+
String fakeEndpoint = "https://example1.azconfig.azure.com";
364+
ConfigurationClientBuilder builder
365+
= new ConfigurationClientBuilder().endpoint("https://example1.azconfig.azure.com")
366+
.credential(new DefaultAzureCredentialBuilder().build())
367+
.audience(ConfigurationAudience.AZURE_CHINA);
368+
builder.buildClient();
369+
Method method = ConfigurationClientBuilder.class.getDeclaredMethod("getDefaultScope", String.class);
370+
method.setAccessible(true);
371+
372+
String actualScope = (String) method.invoke(builder, fakeEndpoint);
373+
assertEquals(ConfigurationAudience.AZURE_CHINA + "/.default", actualScope);
374+
}
302375
}

0 commit comments

Comments
 (0)