Skip to content

Commit 008216f

Browse files
authored
[BugFix-AppConfig] Single authentication support only in client builder (Azure#34038)
1 parent 9f5c9b2 commit 008216f

File tree

3 files changed

+80
-51
lines changed

3 files changed

+80
-51
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
### Breaking Changes
88

99
### Bugs Fixed
10-
10+
- Fixed the bug that multiple authentications coexist per builder. App Configuration client builder should only
11+
support single authentication per builder instance.
12+
- Moved the validation of authentication to client builder's build method.
13+
1114
### Other Changes
1215

1316
## 1.4.3 (2023-03-16)

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

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,12 @@ public final class ConfigurationClientBuilder implements
132132
.set("Accept", "application/vnd.microsoft.azconfig.kv+json"));
133133
}
134134

135-
private final ClientLogger logger = new ClientLogger(ConfigurationClientBuilder.class);
135+
private static final ClientLogger LOGGER = new ClientLogger(ConfigurationClientBuilder.class);
136136
private final List<HttpPipelinePolicy> perCallPolicies = new ArrayList<>();
137137
private final List<HttpPipelinePolicy> perRetryPolicies = new ArrayList<>();
138138

139139
private ClientOptions clientOptions;
140+
private String connectionString;
140141
private ConfigurationClientCredentials credential;
141142
private TokenCredential tokenCredential;
142143

@@ -202,30 +203,66 @@ public ConfigurationAsyncClient buildAsyncClient() {
202203
* Builds an instance of ConfigurationClientImpl with the provided parameters.
203204
*
204205
* @return an instance of ConfigurationClientImpl.
206+
* @throws NullPointerException If {@code connectionString} is null.
207+
* @throws IllegalArgumentException If {@code connectionString} is an empty string, the {@code connectionString}
208+
* secret is invalid, or the HMAC-SHA256 MAC algorithm cannot be instantiated.
209+
* @throws IllegalArgumentException if {@code tokenCredential} is not null. App Configuration builder support single
210+
* authentication per builder instance.
205211
*/
206212
private AzureAppConfigurationImpl buildInnerClient(SyncTokenPolicy syncTokenPolicy) {
213+
String endpointLocal = null;
214+
ConfigurationClientCredentials credentialsLocal = null;
215+
TokenCredential tokenCredentialLocal = null;
216+
// validate the authentication setup
217+
if (tokenCredential == null && connectionString == null) {
218+
throw LOGGER.logExceptionAsError(new NullPointerException("'tokenCredential' and 'connectionString' "
219+
+ "both can not be null. Set one authentication before creating client."));
220+
} else if (tokenCredential != null && connectionString != null) {
221+
throw LOGGER.logExceptionAsError(new IllegalArgumentException("Multiple forms of authentication found. "
222+
+ "TokenCredential should be null if using connection string, vice versa."));
223+
} else if (tokenCredential == null) {
224+
if (connectionString.isEmpty()) {
225+
throw LOGGER.logExceptionAsError(
226+
new IllegalArgumentException("'connectionString' cannot be an empty string."));
227+
}
228+
try {
229+
credentialsLocal = new ConfigurationClientCredentials(connectionString);
230+
endpointLocal = credentialsLocal.getBaseUri();
231+
} catch (InvalidKeyException err) {
232+
throw LOGGER.logExceptionAsError(new IllegalArgumentException("The secret contained within the"
233+
+ " connection string is invalid and cannot instantiate the HMAC-SHA256 algorithm.", err));
234+
} catch (NoSuchAlgorithmException err) {
235+
throw LOGGER.logExceptionAsError(
236+
new IllegalArgumentException("HMAC-SHA256 MAC algorithm cannot be instantiated.", err));
237+
}
238+
} else {
239+
tokenCredentialLocal = this.tokenCredential;
240+
}
241+
207242
// Service version
208243
ConfigurationServiceVersion serviceVersion = (version != null)
209244
? version
210245
: ConfigurationServiceVersion.getLatest();
211246
// Don't share the default auto-created pipeline between App Configuration client instances.
212247
return new AzureAppConfigurationImplBuilder()
213-
.pipeline(pipeline == null ? createHttpPipeline(syncTokenPolicy) : pipeline)
248+
.pipeline(pipeline == null ? createDefaultHttpPipeline(
249+
syncTokenPolicy, credentialsLocal, tokenCredentialLocal) : pipeline)
214250
.apiVersion(serviceVersion.getVersion())
215-
.endpoint(endpoint)
251+
.endpoint(endpointLocal)
216252
.buildClient();
217253
}
218254

219-
private HttpPipeline createHttpPipeline(SyncTokenPolicy syncTokenPolicy) {
255+
private HttpPipeline createDefaultHttpPipeline(SyncTokenPolicy syncTokenPolicy,
256+
ConfigurationClientCredentials credentials, TokenCredential tokenCredential) {
220257
// Global Env configuration store
221258
Configuration buildConfiguration = (configuration == null)
222259
? Configuration.getGlobalConfiguration()
223260
: configuration;
224261

225262
// Endpoint
226263
String buildEndpoint = endpoint;
227-
if (tokenCredential == null) {
228-
buildEndpoint = getBuildEndpoint();
264+
if (credentials != null) {
265+
buildEndpoint = credentials.getBaseUri();
229266
}
230267
// endpoint cannot be null, which is required in request authentication
231268
Objects.requireNonNull(buildEndpoint, "'Endpoint' is required and can not be null.");
@@ -249,13 +286,13 @@ private HttpPipeline createHttpPipeline(SyncTokenPolicy syncTokenPolicy) {
249286
if (tokenCredential != null) {
250287
// User token based policy
251288
policies.add(
252-
new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", buildEndpoint)));
253-
} else if (credential != null) {
254-
// Use credential based policy
255-
policies.add(new ConfigurationCredentialsPolicy(credential));
289+
new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint)));
290+
} else if (credentials != null) {
291+
// Use credentialS based policy
292+
policies.add(new ConfigurationCredentialsPolicy(credentials));
256293
} else {
257-
// Throw exception that credential and tokenCredential cannot be null
258-
throw logger.logExceptionAsError(
294+
// Throw exception that credentials and tokenCredential cannot be null
295+
throw LOGGER.logExceptionAsError(
259296
new IllegalArgumentException("Missing credential information while building a client."));
260297
}
261298
policies.add(syncTokenPolicy);
@@ -297,7 +334,7 @@ public ConfigurationClientBuilder endpoint(String endpoint) {
297334
try {
298335
new URL(endpoint);
299336
} catch (MalformedURLException ex) {
300-
throw logger.logExceptionAsWarning(new IllegalArgumentException("'endpoint' must be a valid URL", ex));
337+
throw LOGGER.logExceptionAsWarning(new IllegalArgumentException("'endpoint' must be a valid URL", ex));
301338
}
302339
this.endpoint = endpoint;
303340
return this;
@@ -334,31 +371,10 @@ public ConfigurationClientBuilder clientOptions(ClientOptions clientOptions) {
334371
* @param connectionString Connection string in the format "endpoint={endpoint_value};id={id_value};
335372
* secret={secret_value}"
336373
* @return The updated ConfigurationClientBuilder object.
337-
* @throws NullPointerException If {@code connectionString} is null.
338-
* @throws IllegalArgumentException If {@code connectionString} is an empty string, the {@code connectionString}
339-
* secret is invalid, or the HMAC-SHA256 MAC algorithm cannot be instantiated.
340374
*/
341375
@Override
342376
public ConfigurationClientBuilder connectionString(String connectionString) {
343-
Objects.requireNonNull(connectionString, "'connectionString' cannot be null.");
344-
345-
if (connectionString.isEmpty()) {
346-
throw logger.logExceptionAsError(
347-
new IllegalArgumentException("'connectionString' cannot be an empty string."));
348-
}
349-
350-
try {
351-
this.credential = new ConfigurationClientCredentials(connectionString);
352-
} catch (InvalidKeyException err) {
353-
throw logger.logExceptionAsError(new IllegalArgumentException(
354-
"The secret contained within the connection string is invalid and cannot instantiate the HMAC-SHA256"
355-
+ " algorithm.", err));
356-
} catch (NoSuchAlgorithmException err) {
357-
throw logger.logExceptionAsError(
358-
new IllegalArgumentException("HMAC-SHA256 MAC algorithm cannot be instantiated.", err));
359-
}
360-
361-
this.endpoint = credential.getBaseUri();
377+
this.connectionString = connectionString;
362378
return this;
363379
}
364380

@@ -369,12 +385,9 @@ public ConfigurationClientBuilder connectionString(String connectionString) {
369385
*
370386
* @param tokenCredential {@link TokenCredential} used to authorize requests sent to the service.
371387
* @return The updated ConfigurationClientBuilder object.
372-
* @throws NullPointerException If {@code credential} is null.
373388
*/
374389
@Override
375390
public ConfigurationClientBuilder credential(TokenCredential tokenCredential) {
376-
// token credential can not be null value
377-
Objects.requireNonNull(tokenCredential);
378391
this.tokenCredential = tokenCredential;
379392
return this;
380393
}
@@ -443,7 +456,7 @@ public ConfigurationClientBuilder addPolicy(HttpPipelinePolicy policy) {
443456
@Override
444457
public ConfigurationClientBuilder httpClient(HttpClient client) {
445458
if (this.httpClient != null && client == null) {
446-
logger.info("HttpClient is being set to 'null' when it was previously configured.");
459+
LOGGER.info("HttpClient is being set to 'null' when it was previously configured.");
447460
}
448461

449462
this.httpClient = client;
@@ -468,7 +481,7 @@ public ConfigurationClientBuilder httpClient(HttpClient client) {
468481
@Override
469482
public ConfigurationClientBuilder pipeline(HttpPipeline pipeline) {
470483
if (this.pipeline != null && pipeline == null) {
471-
logger.info("HttpPipeline is being set to 'null' when it was previously configured.");
484+
LOGGER.info("HttpPipeline is being set to 'null' when it was previously configured.");
472485
}
473486

474487
this.pipeline = pipeline;
@@ -543,15 +556,5 @@ public ConfigurationClientBuilder serviceVersion(ConfigurationServiceVersion ver
543556
this.version = version;
544557
return this;
545558
}
546-
547-
private String getBuildEndpoint() {
548-
if (endpoint != null) {
549-
return endpoint;
550-
} else if (credential != null) {
551-
return credential.getBaseUri();
552-
} else {
553-
return null;
554-
}
555-
}
556559
}
557560

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
package com.azure.data.appconfiguration;
55

6+
import com.azure.core.credential.AccessToken;
7+
import com.azure.core.credential.TokenCredential;
68
import com.azure.core.exception.HttpResponseException;
79
import com.azure.core.http.HttpClient;
810
import com.azure.core.http.policy.ExponentialBackoffOptions;
@@ -28,6 +30,7 @@
2830
import java.net.URI;
2931
import java.net.URISyntaxException;
3032
import java.time.Duration;
33+
import java.time.OffsetDateTime;
3134
import java.util.Collections;
3235
import java.util.Locale;
3336
import java.util.Objects;
@@ -83,6 +86,26 @@ public void emptyConnectionString() {
8386
});
8487
}
8588

89+
@Test
90+
@DoNotRecord
91+
public void nullCredentials() {
92+
assertThrows(NullPointerException.class, () -> {
93+
final ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
94+
builder.buildClient();
95+
});
96+
}
97+
98+
@Test
99+
@DoNotRecord
100+
public void multipleCredentialsExist() {
101+
assertThrows(IllegalArgumentException.class, () -> {
102+
final ConfigurationClientBuilder builder = new ConfigurationClientBuilder();
103+
TokenCredential credentials = request -> Mono.just(new AccessToken("this_is_a_token", OffsetDateTime.MAX));
104+
builder.connectionString(FAKE_CONNECTION_STRING)
105+
.credential(credentials).buildClient();
106+
});
107+
}
108+
86109
@Test
87110
@DoNotRecord
88111
public void missingSecretKey() {

0 commit comments

Comments
 (0)