diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 21bd86b155665..6e4d0cff5da83 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -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)` on `ChainedtokenCredentialBuilder`. diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java index ded57921603c2..1f42ff2c97e63 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java @@ -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<>(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java index c92e63932c074..f4a6c32f82bd1 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java @@ -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. @@ -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. * @@ -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)); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java index 49b68c4bf1b2e..6dd73969b0294 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java @@ -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<>(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java index b81d98d3c8e75..0c2a9175014ff 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java @@ -16,6 +16,7 @@ public class InteractiveBrowserCredentialBuilder extends AadCredentialBuilderBase { 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 @@ -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} @@ -82,7 +94,6 @@ public InteractiveBrowserCredentialBuilder disableAutomaticAuthentication() { return this; } - /** * Creates a new {@link InteractiveBrowserCredential} with the current configurations. * @@ -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); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index ff815afbbac0c..6842568ac80ca 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -550,13 +550,19 @@ public Mono authenticateWithVsCodeCredential(TokenRequestContext requ */ public Mono 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 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)); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java index 54a1b97be05f7..672e890d8e371 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientBuilder.java @@ -71,7 +71,6 @@ public IdentityClientBuilder certificatePassword(String certificatePassword) { return this; } - /** * Sets the options for the client. * @param identityClientOptions the options for the client. diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java index 162248f16e766..ac446bc06ad8e 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ClientSecretCredentialTest.java @@ -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 diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java index 21657842b1bc5..e5d3dbd1a9710 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java @@ -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(); @@ -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(); @@ -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)); @@ -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(); } @@ -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(); } } diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index afec805e3c626..fb0e48f62d190 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -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(); } @@ -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(); } @@ -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(); } @@ -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(); }