Skip to content

Commit

Permalink
Support web apps for oauth based credentials (#13151)
Browse files Browse the repository at this point in the history
* Fix AuthorizationCodeCredential to support credential

* Skip integration tests

* unused imports

* Add same settings to InteractiveBrowserCredential

* Skip live tests again

* Checkstyle

* Checkstyle: line too long

* Revert integration test change

* Add some changelog

* Improve change log

* Fix IdentityClient ctr

* Fix javadoc
  • Loading branch information
jianghaolu authored Jul 16, 2020
1 parent 64a250c commit c15199d
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 50 deletions.
2 changes: 1 addition & 1 deletion sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Release History

## 1.1.0-beta.7 (Unreleased)

- Added support for web apps (confidential apps) for `InteractiveBrowserCredential` and `AuthorizationCodeCredential`. A client secret is required on the builder for web apps.

## 1.1.0-beta.6 (2020-07-10)
- Added `.getCredentials()` method to `DefaultAzureCredential` and `ChainedTokenCredential` and added option `.addAll(Collection<? extends TokenCredential>)` on `ChainedtokenCredentialBuilder`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@ public class AuthorizationCodeCredential implements TokenCredential {
* Creates an AuthorizationCodeCredential with the given identity client options.
*
* @param clientId the client ID of the application
* @param clientSecret the client secret of the application
* @param tenantId the tenant ID of the application
* @param authCode the Oauth 2.0 authorization code grant
* @param redirectUri the redirect URI used to authenticate to Azure Active Directory
* @param identityClientOptions the options for configuring the identity client
*/
AuthorizationCodeCredential(String clientId, String tenantId, String authCode, URI redirectUri,
IdentityClientOptions identityClientOptions) {
AuthorizationCodeCredential(String clientId, String clientSecret, String tenantId, String authCode,
URI redirectUri, IdentityClientOptions identityClientOptions) {
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(clientSecret)
.identityClientOptions(identityClientOptions)
.build();
this.cachedToken = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class AuthorizationCodeCredentialBuilder extends AadCredentialBuilderBase

private String authCode;
private String redirectUrl;
private String clientSecret;

/**
* Sets the authorization code on the builder.
Expand Down Expand Up @@ -60,6 +61,17 @@ public AuthorizationCodeCredentialBuilder allowUnencryptedCache(boolean allowUne
return this;
}

/**
* Sets the client secret for the authentication. This is required for AAD web apps. Do not set this for AAD native
* apps.
* @param clientSecret the secret value of the AAD application.
* @return the AuthorizationCodeCredentialBuilder itself
*/
public AuthorizationCodeCredentialBuilder clientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}

/**
* Sets whether to enable using the shared token cache. This is disabled by default.
*
Expand All @@ -84,8 +96,8 @@ public AuthorizationCodeCredential build() {
put("redirectUrl", redirectUrl);
}});
try {
return new AuthorizationCodeCredential(clientId, tenantId, authCode,
new URI(redirectUrl), identityClientOptions);
return new AuthorizationCodeCredential(clientId, clientSecret, tenantId, authCode, new URI(redirectUrl),
identityClientOptions);
} catch (URISyntaxException e) {
throw logger.logExceptionAsError(new RuntimeException(e));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ public class InteractiveBrowserCredential implements TokenCredential {
* {@code http://localhost:{port}} must be registered as a valid reply URL on the application.
*
* @param clientId the client ID of the application
* @param clientSecret the client secret of the application
* @param tenantId the tenant ID of the application
* @param port the port on which the credential will listen for the browser authentication result
* @param automaticAuthentication indicates whether automatic authentication should be attempted or not.
* @param identityClientOptions the options for configuring the identity client
*/
InteractiveBrowserCredential(String clientId, String tenantId, int port, boolean automaticAuthentication,
IdentityClientOptions identityClientOptions) {
String clientSecret, IdentityClientOptions identityClientOptions) {
this.port = port;
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(clientSecret)
.identityClientOptions(identityClientOptions)
.build();
cachedToken = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
public class InteractiveBrowserCredentialBuilder extends AadCredentialBuilderBase<InteractiveBrowserCredentialBuilder> {
private int port;
private boolean automaticAuthentication = true;
private String clientSecret;

/**
* Sets the port for the local HTTP server, for which {@code http://localhost:{port}} must be
Expand Down Expand Up @@ -67,6 +68,17 @@ public InteractiveBrowserCredentialBuilder authenticationRecord(AuthenticationRe
return this;
}

/**
* Sets the client secret for the authentication. This is required for AAD web apps. Do not set this for AAD native
* apps.
* @param clientSecret the secret value of the AAD application.
* @return the InteractiveBrowserCredentialBuilder itself
*/
public InteractiveBrowserCredentialBuilder clientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}

/**
* Disables the automatic authentication and prevents the {@link InteractiveBrowserCredential} from automatically
* prompting the user. If automatic authentication is disabled a {@link AuthenticationRequiredException}
Expand All @@ -82,7 +94,6 @@ public InteractiveBrowserCredentialBuilder disableAutomaticAuthentication() {
return this;
}


/**
* Creates a new {@link InteractiveBrowserCredential} with the current configurations.
*
Expand All @@ -93,7 +104,7 @@ public InteractiveBrowserCredential build() {
put("clientId", clientId);
put("port", port);
}});
return new InteractiveBrowserCredential(clientId, tenantId, port, automaticAuthentication,
return new InteractiveBrowserCredential(clientId, tenantId, port, automaticAuthentication, clientSecret,
identityClientOptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -550,13 +550,19 @@ public Mono<MsalToken> authenticateWithVsCodeCredential(TokenRequestContext requ
*/
public Mono<MsalToken> authenticateWithAuthorizationCode(TokenRequestContext request, String authorizationCode,
URI redirectUrl) {
return publicClientApplicationAccessor.getValue()
.flatMap(pc -> Mono.fromFuture(() -> pc.acquireToken(
AuthorizationCodeParameters.builder(authorizationCode, redirectUrl)
.scopes(new HashSet<>(request.getScopes()))
.build()))
.onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with authorization code",
null, t)).map(ar -> new MsalToken(ar, options)));
AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(authorizationCode, redirectUrl)
.scopes(new HashSet<>(request.getScopes()))
.build();
Mono<IAuthenticationResult> acquireToken;
if (clientSecret != null) {
acquireToken = confidentialClientApplicationAccessor.getValue()
.flatMap(pc -> Mono.fromFuture(() -> pc.acquireToken(parameters)));
} else {
acquireToken = publicClientApplicationAccessor.getValue()
.flatMap(pc -> Mono.fromFuture(() -> pc.acquireToken(parameters)));
}
return acquireToken.onErrorMap(t -> new ClientAuthenticationException(
"Failed to acquire token with authorization code", null, t)).map(ar -> new MsalToken(ar, options));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public IdentityClientBuilder certificatePassword(String certificatePassword) {
return this;
}


/**
* Sets the options for the client.
* @param identityClientOptions the options for the client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@ public void testValidSecretsWithTokenRefreshOffset() throws Exception {

// test
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(secret)
.tokenRefreshOffset(offset)
.build();
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(secret)
.tokenRefreshOffset(offset)
.build();
StepVerifier.create(credential.getToken(request1))
.expectNextMatches(accessToken -> token1.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
.expectNextMatches(accessToken -> token1.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
StepVerifier.create(credential.getToken(request2))
.expectNextMatches(accessToken -> token2.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
.expectNextMatches(accessToken -> token2.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public void testUseEnvironmentCredential() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request1))
.thenReturn(Mono.empty());
.thenReturn(Mono.empty());
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Expand Down Expand Up @@ -88,9 +88,9 @@ public void testUseManagedIdentityCredential() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.empty());
.thenReturn(Mono.empty());
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Expand All @@ -110,9 +110,9 @@ public void testUseAzureCliCredential() throws Exception {
// mock
IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.empty());
.thenReturn(Mono.empty());
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

IdentityClient identityClient = PowerMockito.mock(IdentityClient.class);
when(identityClient.authenticateWithAzureCli(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
Expand Down Expand Up @@ -156,22 +156,22 @@ public void testNoCredentialWorks() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

VisualStudioCodeCredential vscodeCredential = PowerMockito.mock(VisualStudioCodeCredential.class);
when(vscodeCredential.getToken(request))
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
PowerMockito.whenNew(VisualStudioCodeCredential.class).withAnyArguments()
.thenReturn(vscodeCredential);
.thenReturn(vscodeCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
StepVerifier.create(credential.getToken(request))
.expectErrorMatches(t -> t instanceof CredentialUnavailableException && t.getMessage()
.startsWith("EnvironmentCredential authentication unavailable. "))
.startsWith("EnvironmentCredential authentication unavailable. "))
.verify();
}

Expand All @@ -190,22 +190,22 @@ public void testCredentialUnavailable() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);
VisualStudioCodeCredential vscodeCredential = PowerMockito.mock(VisualStudioCodeCredential.class);
when(vscodeCredential.getToken(request))
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
PowerMockito.whenNew(VisualStudioCodeCredential.class).withAnyArguments()
.thenReturn(vscodeCredential);
.thenReturn(vscodeCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
.build();
.build();
StepVerifier.create(credential.getToken(request))
.expectErrorMatches(t -> t instanceof CredentialUnavailableException && t.getMessage()
.startsWith("EnvironmentCredential authentication unavailable. "))
.startsWith("EnvironmentCredential authentication unavailable. "))
.verify();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public void testAuthorizationCodeFlow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithAuthorizationCode(request, authCode1, redirectUri))
.expectNextMatches(accessToken -> token1.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand All @@ -267,7 +267,7 @@ public void testUserRefreshTokenflow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithPublicClientCache(request2, TestUtils.getMockMsalAccount(token1, expiresAt).block()))
.expectNextMatches(accessToken -> token2.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand All @@ -288,7 +288,7 @@ public void testUsernamePasswordCodeFlow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithUsernamePassword(request, username, password))
.expectNextMatches(accessToken -> token.equals(accessToken.getToken())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand All @@ -310,7 +310,7 @@ public void testBrowserAuthenicationCodeFlow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithBrowserInteraction(request, 4567))
.expectNextMatches(accessToken -> token.equals(accessToken.getToken())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand Down

0 comments on commit c15199d

Please sign in to comment.