Skip to content

Commit 0b28e70

Browse files
authored
[Communication] - Phone Number Management - Added AAD Auth Support (Azure#18476)
* Using DefaultAzureCredential for managed identity * Removed AzureKeyCredential * Added tests * Using DefaultAzureCredential for managed identity * Added FakeCredential for playback tests * Addressed comments * Initial implementation * Removed unused import * changed core version * Used connection string object * Updated readme and changelog * Fixed exceptions
1 parent a043bdd commit 0b28e70

File tree

8 files changed

+145
-15
lines changed

8 files changed

+145
-15
lines changed

sdk/communication/azure-communication-administration/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Release History
22

33
## 1.0.0-beta.4 (Unreleased)
4-
4+
- Added support for Azure Active Directory Authentication for both clients.
55

66
## 1.0.0-beta.3 (2020-11-16)
77
### Added

sdk/communication/azure-communication-administration/README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ Acquired phone numbers can come with many capabilities, depending on the country
3131
There are two forms of authentication to use the Administration SDK:
3232

3333
### Azure Active Directory Token Authentication
34-
Currently, Azure Active Directory (AAD) authentication is only possible for CommunicationIdentityClient.
35-
The `DefaultAzureCredential` object must be passed to the `CommunicationIdentityClientBuilder` via
36-
the credential() funtion. Endpoint and httpClient must also be set
34+
A `DefaultAzureCredential` object must be passed to the `CommunicationIdentityClientBuilder` or
35+
`PhoneNumberClientBuilder` via the credential() funtion. Endpoint and httpClient must also be set
3736
via the endpoint() and httpClient() functions respectively.
3837

3938
`AZURE_CLIENT_SECRET`, `AZURE_CLIENT_ID` and `AZURE_TENANT_ID` environment variables
@@ -90,7 +89,22 @@ CommunicationIdentityClient communicationIdentityClient = new CommunicationIdent
9089
.buildClient();
9190
```
9291
### Initializing Phone Number Client
92+
As it was mentioned before, the PhoneNumberClientBuilder is also enabled to use Azure Active Directory Authentication
93+
<!-- embedme ./src/samples/java/com/azure/communication/administration/ReadmeSamples.java#L398-L407 -->
94+
```java
95+
String endpoint = "https://<RESOURCE_NAME>.communication.azure.com";
96+
97+
// Create an HttpClient builder of your choice and customize it
98+
HttpClient httpClient = new NettyAsyncHttpClientBuilder().build();
99+
100+
PhoneNumberClient phoneNumberClient = new PhoneNumberClientBuilder()
101+
.endpoint(endpoint)
102+
.credential(new DefaultAzureCredentialBuilder().build())
103+
.httpClient(httpClient)
104+
.buildClient();
105+
```
93106

107+
Using the endpoint and access key from the communication resource to authenticate is also posible.
94108
<!-- embedme ./src/samples/java/com/azure/communication/administration/ReadmeSamples.java#L128-L139 -->
95109
```java
96110
// You can find your endpoint and access token from your resource in the Azure Portal

sdk/communication/azure-communication-administration/src/main/java/com/azure/communication/administration/PhoneNumberClientBuilder.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
import com.azure.communication.common.ConnectionString;
99
import com.azure.communication.common.HmacAuthenticationPolicy;
1010
import com.azure.core.annotation.ServiceClientBuilder;
11+
import com.azure.core.credential.TokenCredential;
1112
import com.azure.core.http.HttpClient;
1213
import com.azure.core.http.HttpPipeline;
1314
import com.azure.core.http.HttpPipelineBuilder;
15+
import com.azure.core.http.policy.BearerTokenAuthenticationPolicy;
1416
import com.azure.core.http.policy.CookiePolicy;
1517
import com.azure.core.http.policy.HttpLogDetailLevel;
1618
import com.azure.core.http.policy.HttpLogOptions;
@@ -44,7 +46,8 @@ public final class PhoneNumberClientBuilder {
4446
private HttpPipeline pipeline;
4547
private HttpClient httpClient;
4648
private HttpLogOptions httpLogOptions;
47-
private CommunicationClientCredential credential;
49+
private CommunicationClientCredential accessKeyCredential;
50+
private TokenCredential tokenCredential;
4851
private Configuration configuration;
4952
private final List<HttpPipelinePolicy> additionalPolicies = new ArrayList<>();
5053

@@ -108,10 +111,23 @@ public PhoneNumberClientBuilder httpLogOptions(HttpLogOptions httpLogOptions) {
108111
*/
109112
public PhoneNumberClientBuilder accessKey(String accessKey) {
110113
Objects.requireNonNull(accessKey, "'accessKey' cannot be null.");
111-
this.credential = new CommunicationClientCredential(accessKey);
114+
this.accessKeyCredential = new CommunicationClientCredential(accessKey);
112115
return this;
113116
}
114117

118+
/**
119+
* Sets the {@link TokenCredential} used to authenticate HTTP requests.
120+
*
121+
* @param tokenCredential {@link TokenCredential} used to authenticate HTTP requests.
122+
* @return The updated {@link CommunicationIdentityClientBuilder} object.
123+
* @throws NullPointerException If {@code tokenCredential} is null.
124+
*/
125+
public PhoneNumberClientBuilder credential(TokenCredential tokenCredential) {
126+
this.tokenCredential = Objects.requireNonNull(tokenCredential, "'tokenCredential' cannot be null.");
127+
return this;
128+
}
129+
130+
115131
/**
116132
* Set the endpoint and CommunicationClientCredential for authorization
117133
*
@@ -200,8 +216,20 @@ PhoneNumberAsyncClient createPhoneNumberAsyncClient(PhoneNumberAdminClientImpl p
200216
return new PhoneNumberAsyncClient(phoneNumberAdminClient);
201217
}
202218

203-
HmacAuthenticationPolicy createAuthenticationPolicy(CommunicationClientCredential communicationClientCredential) {
204-
return new HmacAuthenticationPolicy(communicationClientCredential);
219+
HttpPipelinePolicy createAuthenticationPolicy() {
220+
if (this.tokenCredential != null && this.accessKeyCredential != null) {
221+
throw logger.logExceptionAsError(
222+
new IllegalArgumentException("Both 'credential' and 'accessKey' are set. Just one may be used."));
223+
}
224+
if (this.tokenCredential != null) {
225+
return new BearerTokenAuthenticationPolicy(
226+
this.tokenCredential, "https://communication.azure.com//.default");
227+
} else if (this.accessKeyCredential != null) {
228+
return new HmacAuthenticationPolicy(this.accessKeyCredential);
229+
} else {
230+
throw logger.logExceptionAsError(
231+
new NullPointerException("Missing credential information while building a client."));
232+
}
205233
}
206234

207235
UserAgentPolicy createUserAgentPolicy(
@@ -229,7 +257,6 @@ private void validateRequiredFields() {
229257
Objects.requireNonNull(this.endpoint);
230258

231259
if (this.pipeline == null) {
232-
Objects.requireNonNull(this.credential);
233260
Objects.requireNonNull(this.httpClient);
234261
}
235262
}
@@ -250,7 +277,7 @@ private HttpPipeline createHttpPipeline() {
250277
List<HttpPipelinePolicy> policyList = new ArrayList<>();
251278

252279
// Add required policies
253-
policyList.add(this.createAuthenticationPolicy(this.credential));
280+
policyList.add(this.createAuthenticationPolicy());
254281
policyList.add(this.createUserAgentPolicy(
255282
this.getHttpLogOptions().getApplicationId(),
256283
PROPERTIES.get(SDK_NAME),

sdk/communication/azure-communication-administration/src/samples/java/com/azure/communication/administration/ReadmeSamples.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,4 +387,25 @@ public CommunicationIdentityClient createCommunicationIdentityClientWithAAD() {
387387

388388
return communicationIdentityClient;
389389
}
390+
391+
/**
392+
* Sample code for creating a sync Phone Number Client using AAD authentication.
393+
*
394+
* @return the Phone Number Client.
395+
*/
396+
public PhoneNumberClient createPhoneNumberClientWithAAD() {
397+
// You can find your endpoint and access key from your resource in the Azure Portal
398+
String endpoint = "https://<RESOURCE_NAME>.communication.azure.com";
399+
400+
// Create an HttpClient builder of your choice and customize it
401+
HttpClient httpClient = new NettyAsyncHttpClientBuilder().build();
402+
403+
PhoneNumberClient phoneNumberClient = new PhoneNumberClientBuilder()
404+
.endpoint(endpoint)
405+
.credential(new DefaultAzureCredentialBuilder().build())
406+
.httpClient(httpClient)
407+
.buildClient();
408+
409+
return phoneNumberClient;
410+
}
390411
}

sdk/communication/azure-communication-administration/src/test/java/com/azure/communication/administration/PhoneNumberAsyncClientIntegrationTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,21 @@ public void createAsyncPhoneNumberClientWithConnectionString(HttpClient httpClie
6969
.verifyComplete();
7070
}
7171

72+
@ParameterizedTest
73+
@MethodSource("com.azure.core.test.TestBase#getHttpClients")
74+
public void createAsyncPhoneNumberClientWithManagedIdentity(HttpClient httpClient) {
75+
PhoneNumberAsyncClient phoneNumberAsyncClient = getClientBuilderUsingManagedIdentity(httpClient).buildAsyncClient();
76+
assertNotNull(phoneNumberAsyncClient);
77+
78+
// Smoke test using phoneNumberAsyncClient to list all phone numbers
79+
PagedFlux<AcquiredPhoneNumber> pagedFlux = phoneNumberAsyncClient.listAllPhoneNumbers(LOCALE);
80+
StepVerifier.create(pagedFlux.next())
81+
.assertNext(item -> {
82+
assertNotNull(item.getPhoneNumber());
83+
})
84+
.verifyComplete();
85+
}
86+
7287
@ParameterizedTest
7388
@MethodSource("com.azure.core.test.TestBase#getHttpClients")
7489
public void listAllPhoneNumbers(HttpClient httpClient) {

sdk/communication/azure-communication-administration/src/test/java/com/azure/communication/administration/PhoneNumberClientBuilderTest.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
package com.azure.communication.administration;
44

55
import com.azure.communication.administration.implementation.PhoneNumberAdminClientImpl;
6-
import com.azure.communication.common.CommunicationClientCredential;
76
import com.azure.communication.common.HmacAuthenticationPolicy;
87
import com.azure.core.http.HttpClient;
98
import com.azure.core.http.HttpPipeline;
@@ -411,8 +410,6 @@ private class ClientBuilderSpyHelper {
411410
final AtomicReference<HttpLogOptions> defaultHttpLogOptionsRef = new AtomicReference<>();
412411
final ArgumentCaptor<PhoneNumberAdminClientImpl> phoneNumberAdminClientArg =
413412
ArgumentCaptor.forClass(PhoneNumberAdminClientImpl.class);
414-
final ArgumentCaptor<CommunicationClientCredential> credentialArg =
415-
ArgumentCaptor.forClass(CommunicationClientCredential.class);
416413
final ArgumentCaptor<String> uaPolicyAppIdArg = ArgumentCaptor.forClass(String.class);
417414
final ArgumentCaptor<String> uaPolicySdkNameArg = ArgumentCaptor.forClass(String.class);
418415
final ArgumentCaptor<String> uaPolicySdkVersionArg = ArgumentCaptor.forClass(String.class);
@@ -429,7 +426,7 @@ private void initializeSpies() {
429426
this.authenticationPolicyRef.set((HmacAuthenticationPolicy) invocation.callRealMethod());
430427
return this.authenticationPolicyRef.get();
431428
};
432-
doAnswer(createCommunicationClientCredentialPolicy).when(this.clientBuilder).createAuthenticationPolicy(any());
429+
doAnswer(createCommunicationClientCredentialPolicy).when(this.clientBuilder).createAuthenticationPolicy();
433430

434431
Answer<UserAgentPolicy> createUserAgentPolicy = (invocation) -> {
435432
this.userAgentPolicyRef.set(mock(UserAgentPolicy.class));
@@ -469,7 +466,7 @@ void capturePhoneNumberAdminClientImpl() {
469466

470467
void captureHttpPipelineSettings() {
471468
verify(this.clientBuilder, times(1))
472-
.createAuthenticationPolicy(this.credentialArg.capture());
469+
.createAuthenticationPolicy();
473470
verify(this.clientBuilder, times(1))
474471
.createUserAgentPolicy(
475472
this.uaPolicyAppIdArg.capture(),

sdk/communication/azure-communication-administration/src/test/java/com/azure/communication/administration/PhoneNumberIntegrationTestBase.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@
22
// Licensed under the MIT License.
33
package com.azure.communication.administration;
44

5+
import java.time.OffsetDateTime;
56
import java.util.ArrayList;
67
import java.util.List;
78
import java.util.StringJoiner;
89
import java.util.function.Function;
910
import java.util.regex.Matcher;
1011
import java.util.regex.Pattern;
1112

13+
import com.azure.communication.common.ConnectionString;
14+
import com.azure.core.credential.AccessToken;
15+
import com.azure.core.credential.TokenCredential;
16+
import com.azure.core.credential.TokenRequestContext;
1217
import com.azure.core.http.HttpClient;
1318
import com.azure.core.test.TestBase;
1419
import com.azure.core.test.TestMode;
1520
import com.azure.core.util.Configuration;
1621
import com.azure.core.util.CoreUtils;
22+
import com.azure.identity.DefaultAzureCredentialBuilder;
23+
24+
import reactor.core.publisher.Mono;
1725

1826
public class PhoneNumberIntegrationTestBase extends TestBase {
1927
private static final String ENV_ACCESS_KEY =
@@ -89,6 +97,25 @@ protected PhoneNumberClientBuilder getClientBuilderWithConnectionString(HttpClie
8997
return builder;
9098
}
9199

100+
protected PhoneNumberClientBuilder getClientBuilderUsingManagedIdentity(HttpClient httpClient) {
101+
PhoneNumberClientBuilder builder = new PhoneNumberClientBuilder();
102+
builder
103+
.endpoint(new ConnectionString(CONNECTION_STRING).getEndpoint())
104+
.httpClient(httpClient == null ? interceptorManager.getPlaybackClient() : httpClient);
105+
106+
if (getTestMode() == TestMode.PLAYBACK) {
107+
builder.credential(new FakeCredentials());
108+
} else {
109+
builder.credential(new DefaultAzureCredentialBuilder().build());
110+
}
111+
112+
if (getTestMode() == TestMode.RECORD) {
113+
builder.addPolicy(interceptorManager.getRecordPolicy());
114+
}
115+
116+
return builder;
117+
}
118+
92119
private String redact(String content, Matcher matcher, String replacement) {
93120
while (matcher.find()) {
94121
String captureGroup = matcher.group(1);
@@ -103,4 +130,10 @@ private String redact(String content, Matcher matcher, String replacement) {
103130
protected PhoneNumberClientBuilder addLoggingPolicy(PhoneNumberClientBuilder builder, String testName) {
104131
return builder.addPolicy(new CommunicationLoggerPolicy(testName));
105132
}
133+
static class FakeCredentials implements TokenCredential {
134+
@Override
135+
public Mono<AccessToken> getToken(TokenRequestContext tokenRequestContext) {
136+
return Mono.just(new AccessToken("someFakeToken", OffsetDateTime.MAX));
137+
}
138+
}
106139
}

0 commit comments

Comments
 (0)