diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java index 9636a1353f86d..17dd36d423a36 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java @@ -12,6 +12,7 @@ import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; import com.azure.identity.implementation.MsalAuthenticationAccount; +import com.azure.identity.implementation.MsalToken; import reactor.core.publisher.Mono; import java.util.concurrent.atomic.AtomicReference; @@ -73,13 +74,7 @@ public Mono getToken(TokenRequestContext request) { } return identityClient.authenticateWithDeviceCode(request, challengeConsumer); })) - .map(msalToken -> { - cachedToken.set( - new MsalAuthenticationAccount( - new AuthenticationRecord(msalToken.getAuthenticationResult(), - identityClient.getTenantId()))); - return msalToken; - }); + .map(this::updateCache); } /** @@ -97,8 +92,8 @@ public Mono getToken(TokenRequestContext request) { */ public Mono authenticate(TokenRequestContext request) { return Mono.defer(() -> identityClient.authenticateWithDeviceCode(request, challengeConsumer)) - .map(msalToken -> new AuthenticationRecord(msalToken.getAuthenticationResult(), - identityClient.getTenantId())); + .map(this::updateCache) + .map(msalToken -> cachedToken.get().getAuthenticationRecord()); } /** @@ -120,4 +115,12 @@ public Mono authenticate() { } return authenticate(new TokenRequestContext().addScopes(defaultScope)); } + + private MsalToken updateCache(MsalToken msalToken) { + cachedToken.set( + new MsalAuthenticationAccount( + new AuthenticationRecord(msalToken.getAuthenticationResult(), + identityClient.getTenantId()))); + return msalToken; + } } 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 6d11fe7e74a94..5a7b2e6432922 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 @@ -12,6 +12,7 @@ import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; import com.azure.identity.implementation.MsalAuthenticationAccount; +import com.azure.identity.implementation.MsalToken; import reactor.core.publisher.Mono; import java.util.concurrent.atomic.AtomicReference; @@ -77,14 +78,7 @@ public Mono getToken(TokenRequestContext request) { + "code authentication.", request))); } return identityClient.authenticateWithBrowserInteraction(request, port); - })) - .map(msalToken -> { - cachedToken.set( - new MsalAuthenticationAccount( - new AuthenticationRecord(msalToken.getAuthenticationResult(), - identityClient.getTenantId()))); - return msalToken; - }); + })).map(this::updateCache); } /** @@ -98,8 +92,8 @@ public Mono getToken(TokenRequestContext request) { */ public Mono authenticate(TokenRequestContext request) { return Mono.defer(() -> identityClient.authenticateWithBrowserInteraction(request, port)) - .map(msalToken -> new AuthenticationRecord(msalToken.getAuthenticationResult(), - identityClient.getTenantId())); + .map(this::updateCache) + .map(msalToken -> cachedToken.get().getAuthenticationRecord()); } /** @@ -117,4 +111,13 @@ public Mono authenticate() { } return authenticate(new TokenRequestContext().addScopes(defaultScope)); } + + private MsalToken updateCache(MsalToken msalToken) { + cachedToken.set( + new MsalAuthenticationAccount( + new AuthenticationRecord(msalToken.getAuthenticationResult(), + identityClient.getTenantId()))); + return msalToken; + } + } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java index a0ed76fbb2c3a..80c0839cc19ce 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java @@ -7,9 +7,11 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; +import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; +import com.azure.identity.implementation.MsalAuthenticationAccount; import com.azure.identity.implementation.MsalToken; import reactor.core.publisher.Mono; @@ -26,7 +28,9 @@ public class UsernamePasswordCredential implements TokenCredential { private final String username; private final String password; private final IdentityClient identityClient; - private final AtomicReference cachedToken; + private final String authorityHost; + private final AtomicReference cachedToken; + private final ClientLogger logger = new ClientLogger(UsernamePasswordCredential.class); /** * Creates a UserCredential with the given identity client options. @@ -50,21 +54,54 @@ public class UsernamePasswordCredential implements TokenCredential { .identityClientOptions(identityClientOptions) .build(); cachedToken = new AtomicReference<>(); + this.authorityHost = identityClientOptions.getAuthorityHost(); } @Override public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount()) + return identityClient.authenticateWithPublicClientCache(request, cachedToken.get()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); } }).switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithUsernamePassword(request, username, password))) - .map(msalToken -> { - cachedToken.set(msalToken); - return msalToken; - }); + .map(this::updateCache); + } + + /** + * Authenticates the user using the specified username and password. + * + * @param request The details of the authentication request. + * + * @return The {@link AuthenticationRecord} of the authenticated account. + */ + public Mono authenticate(TokenRequestContext request) { + return Mono.defer(() -> identityClient.authenticateWithUsernamePassword(request, username, password)) + .map(this::updateCache) + .map(msalToken -> cachedToken.get().getAuthenticationRecord()); + } + + /** + * Authenticates the user using the specified username and password. + * + * @return The {@link AuthenticationRecord} of the authenticated account. + */ + public Mono authenticate() { + String defaultScope = KnownAuthorityHosts.getDefaultScope(authorityHost); + if (defaultScope == null) { + return Mono.error(logger.logExceptionAsError(new CredentialUnavailableException("Authenticating in this " + + "environment requires specifying a TokenRequestContext."))); + } + return authenticate(new TokenRequestContext().addScopes(defaultScope)); + } + + private MsalToken updateCache(MsalToken msalToken) { + cachedToken.set( + new MsalAuthenticationAccount( + new AuthenticationRecord(msalToken.getAuthenticationResult(), + identityClient.getTenantId()))); + return msalToken; } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalAuthenticationAccount.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalAuthenticationAccount.java index c87a80cdfe5f3..ac9d36cc6b379 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalAuthenticationAccount.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalAuthenticationAccount.java @@ -27,4 +27,8 @@ public String environment() { public String username() { return authenticationRecord.getUsername(); } + + public AuthenticationRecord getAuthenticationRecord() { + return authenticationRecord; + } } diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java index 73c27fd637088..f7b4baa952a7b 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @@ -133,4 +134,33 @@ public void testInvalidParameters() throws Exception { Assert.assertTrue(e.getMessage().contains("username")); } } + + @Test + public void testValidAuthenticate() throws Exception { + // setup + String username = "testuser"; + String password = "P@ssw0rd"; + String token1 = "token1"; + TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com"); + OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); + + + + // mock + IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); + when(identityClient.authenticateWithUsernamePassword(eq(request1), eq(username), eq(password))) + .thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); + PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + + // test + UsernamePasswordCredential credential = + new UsernamePasswordCredentialBuilder().clientId(clientId) + .username(username).password(password).build(); + StepVerifier.create(credential.authenticate(request1)) + .expectNextMatches(authenticationRecord -> authenticationRecord.getAuthority() + .equals("http://login.microsoftonline.com") + && authenticationRecord.getUsername().equals("testuser") + && authenticationRecord.getHomeAccountId() != null) + .verifyComplete(); + } }