Skip to content

Commit

Permalink
User Auth API in Username Password Credential (#11891)
Browse files Browse the repository at this point in the history
  • Loading branch information
g2vinay authored Jun 9, 2020
1 parent b702365 commit 7a4cbee
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -73,13 +74,7 @@ public Mono<AccessToken> 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);
}

/**
Expand All @@ -97,8 +92,8 @@ public Mono<AccessToken> getToken(TokenRequestContext request) {
*/
public Mono<AuthenticationRecord> 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());
}

/**
Expand All @@ -120,4 +115,12 @@ public Mono<AuthenticationRecord> authenticate() {
}
return authenticate(new TokenRequestContext().addScopes(defaultScope));
}

private MsalToken updateCache(MsalToken msalToken) {
cachedToken.set(
new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId())));
return msalToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -77,14 +78,7 @@ public Mono<AccessToken> 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);
}

/**
Expand All @@ -98,8 +92,8 @@ public Mono<AccessToken> getToken(TokenRequestContext request) {
*/
public Mono<AuthenticationRecord> 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());
}

/**
Expand All @@ -117,4 +111,13 @@ public Mono<AuthenticationRecord> authenticate() {
}
return authenticate(new TokenRequestContext().addScopes(defaultScope));
}

private MsalToken updateCache(MsalToken msalToken) {
cachedToken.set(
new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId())));
return msalToken;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -26,7 +28,9 @@ public class UsernamePasswordCredential implements TokenCredential {
private final String username;
private final String password;
private final IdentityClient identityClient;
private final AtomicReference<MsalToken> cachedToken;
private final String authorityHost;
private final AtomicReference<MsalAuthenticationAccount> cachedToken;
private final ClientLogger logger = new ClientLogger(UsernamePasswordCredential.class);

/**
* Creates a UserCredential with the given identity client options.
Expand All @@ -50,21 +54,54 @@ public class UsernamePasswordCredential implements TokenCredential {
.identityClientOptions(identityClientOptions)
.build();
cachedToken = new AtomicReference<>();
this.authorityHost = identityClientOptions.getAuthorityHost();
}

@Override
public Mono<AccessToken> 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<AuthenticationRecord> 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<AuthenticationRecord> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ public String environment() {
public String username() {
return authenticationRecord.getUsername();
}

public AuthenticationRecord getAuthenticationRecord() {
return authenticationRecord;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}
}

0 comments on commit 7a4cbee

Please sign in to comment.