Skip to content

Commit

Permalink
Add logging to identity credentials (Azure#12268)
Browse files Browse the repository at this point in the history
* Add logging to identity credentials

* Checkstyle

* Clean up logging util

* Javadoc for TokenRequestContext

* Apply new signature everywhere

* Add logs for available environment variables

* Improve logging messages

* Clean up coding style

* Change success logging back to doOnNext()

* Checkstyle

* Checkstyle checkstyle
  • Loading branch information
jianghaolu authored Jul 1, 2020
1 parent 271ead1 commit 594723f
Show file tree
Hide file tree
Showing 16 changed files with 245 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
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.util.LoggingUtil;
import reactor.core.publisher.Mono;

import java.net.URI;
Expand All @@ -26,6 +28,7 @@ public class AuthorizationCodeCredential implements TokenCredential {
private final URI redirectUri;
private final IdentityClient identityClient;
private final AtomicReference<MsalAuthenticationAccount> cachedToken;
private final ClientLogger logger = new ClientLogger(AuthorizationCodeCredential.class);

/**
* Creates an AuthorizationCodeCredential with the given identity client options.
Expand Down Expand Up @@ -63,7 +66,9 @@ public Mono<AccessToken> getToken(TokenRequestContext request) {
cachedToken.set(new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId())));
return msalToken;
});
return (AccessToken) msalToken;
})
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

package com.azure.identity;

import com.azure.identity.implementation.IdentityClient;
import com.azure.identity.implementation.IdentityClientBuilder;
import com.azure.identity.implementation.IdentityClientOptions;

import reactor.core.publisher.Mono;

import com.azure.core.annotation.Immutable;
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.util.LoggingUtil;
import reactor.core.publisher.Mono;

/**
* A credential provider that provides token credentials based on Azure CLI
Expand All @@ -21,6 +21,7 @@
@Immutable
public class AzureCliCredential implements TokenCredential {
private final IdentityClient identityClient;
private final ClientLogger logger = new ClientLogger(AzureCliCredential.class);

/**
* Creates an AzureCliSecretCredential with default identity client options.
Expand All @@ -32,6 +33,8 @@ public class AzureCliCredential implements TokenCredential {

@Override
public Mono<AccessToken> getToken(TokenRequestContext request) {
return identityClient.authenticateWithAzureCli(request);
return identityClient.authenticateWithAzureCli(request)
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.azure.core.credential.TokenCredential;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.exception.ClientAuthenticationException;

import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand All @@ -25,6 +25,7 @@
*/
@Immutable
public class ChainedTokenCredential implements TokenCredential {
private final ClientLogger logger = new ClientLogger(getClass());
private final List<TokenCredential> credentials;
private final String unavailableError = this.getClass().getSimpleName() + " authentication failed. ---> ";

Expand All @@ -40,27 +41,32 @@ public class ChainedTokenCredential implements TokenCredential {
public Mono<AccessToken> getToken(TokenRequestContext request) {
List<CredentialUnavailableException> exceptions = new ArrayList<>(4);
return Flux.fromIterable(credentials)
.flatMap(p -> p.getToken(request).onErrorResume(Exception.class, t -> {
if (!t.getClass().getSimpleName().equals("CredentialUnavailableException")) {
return Mono.error(new ClientAuthenticationException(
.flatMap(p -> p.getToken(request)
.doOnNext(t -> logger.info("Azure Identity => Attempted credential {} returns a token",
p.getClass().getSimpleName()))
.onErrorResume(Exception.class, t -> {
if (!t.getClass().getSimpleName().equals("CredentialUnavailableException")) {
return Mono.error(new ClientAuthenticationException(
unavailableError + p.getClass().getSimpleName()
+ " authentication failed. Error Details: " + t.getMessage(),
+ " authentication failed. Error Details: " + t.getMessage(),
null, t));
}
exceptions.add((CredentialUnavailableException) t);
return Mono.empty();
}), 1)
.next()
.switchIfEmpty(Mono.defer(() -> {
// Chain Exceptions.
CredentialUnavailableException last = exceptions.get(exceptions.size() - 1);
for (int z = exceptions.size() - 2; z >= 0; z--) {
CredentialUnavailableException current = exceptions.get(z);
last = new CredentialUnavailableException(current.getMessage() + "\r\n" + last.getMessage(),
last.getCause());
}
return Mono.error(last);
}));
}
exceptions.add((CredentialUnavailableException) t);
logger.info("Azure Identity => Attempted credential {} is unavailable.",
p.getClass().getSimpleName());
return Mono.empty();
}), 1)
.next()
.switchIfEmpty(Mono.defer(() -> {
// Chain Exceptions.
CredentialUnavailableException last = exceptions.get(exceptions.size() - 1);
for (int z = exceptions.size() - 2; z >= 0; z--) {
CredentialUnavailableException current = exceptions.get(z);
last = new CredentialUnavailableException(current.getMessage() + "\r\n" + last.getMessage(),
last.getCause());
}
return Mono.error(last);
}));
}


Expand Down
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.util.LoggingUtil;
import reactor.core.publisher.Mono;

import java.util.Objects;
Expand All @@ -26,6 +28,7 @@
@Immutable
public class ClientCertificateCredential implements TokenCredential {
private final IdentityClient identityClient;
private final ClientLogger logger = new ClientLogger(ClientCertificateCredential.class);

/**
* Creates a ClientSecretCredential with default identity client options.
Expand All @@ -51,6 +54,8 @@ public class ClientCertificateCredential implements TokenCredential {
public Mono<AccessToken> getToken(TokenRequestContext request) {
return identityClient.authenticateWithConfidentialClientCache(request)
.onErrorResume(t -> Mono.empty())
.switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithConfidentialClient(request)));
.switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithConfidentialClient(request)))
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}
}
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.util.LoggingUtil;
import reactor.core.publisher.Mono;

import java.util.Objects;
Expand All @@ -26,6 +28,7 @@
@Immutable
public class ClientSecretCredential implements TokenCredential {
private final IdentityClient identityClient;
private final ClientLogger logger = new ClientLogger(ClientSecretCredential.class);

/**
* Creates a ClientSecretCredential with the given identity client options.
Expand All @@ -51,6 +54,8 @@ public class ClientSecretCredential implements TokenCredential {
public Mono<AccessToken> getToken(TokenRequestContext request) {
return identityClient.authenticateWithConfidentialClientCache(request)
.onErrorResume(t -> Mono.empty())
.switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithConfidentialClient(request)));
.switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithConfidentialClient(request)))
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.azure.identity.implementation.IdentityClientOptions;
import com.azure.identity.implementation.MsalAuthenticationAccount;
import com.azure.identity.implementation.MsalToken;
import com.azure.identity.implementation.util.LoggingUtil;
import reactor.core.publisher.Mono;

import java.util.concurrent.atomic.AtomicReference;
Expand Down Expand Up @@ -74,7 +75,9 @@ public Mono<AccessToken> getToken(TokenRequestContext request) {
}
return identityClient.authenticateWithDeviceCode(request, challengeConsumer);
}))
.map(this::updateCache);
.map(this::updateCache)
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}

/**
Expand Down Expand Up @@ -116,7 +119,7 @@ public Mono<AuthenticationRecord> authenticate() {
return authenticate(new TokenRequestContext().addScopes(defaultScope));
}

private MsalToken updateCache(MsalToken msalToken) {
private AccessToken updateCache(MsalToken msalToken) {
cachedToken.set(
new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.azure.core.util.Configuration;
import com.azure.core.util.logging.ClientLogger;
import com.azure.identity.implementation.IdentityClientOptions;
import com.azure.identity.implementation.util.LoggingUtil;
import reactor.core.publisher.Mono;

/**
Expand Down Expand Up @@ -56,19 +57,62 @@ public class EnvironmentCredential implements TokenCredential {
String certPath = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_CERTIFICATE_PATH);
String username = configuration.get(Configuration.PROPERTY_AZURE_USERNAME);
String password = configuration.get(Configuration.PROPERTY_AZURE_PASSWORD);
LoggingUtil.logAvailableEnvironmentVariables(logger, configuration);
if (verifyNotNull(clientId)) {
if (verifyNotNull(tenantId, clientSecret)) {
targetCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, identityClientOptions);
} else if (verifyNotNull(tenantId, certPath)) {
targetCredential = new ClientCertificateCredential(tenantId, clientId, certPath,
null, identityClientOptions);
} else if (verifyNotNull(username, password)) {
targetCredential = new UsernamePasswordCredential(clientId,
tenantId,
username,
password,
// 1 - Attempt ClientSecretCredential or ClientCertificateCredential
if (verifyNotNull(tenantId)) {
if (verifyNotNull(clientSecret)) {
// 1.1 Attempt ClientSecretCredential
logger.info("Azure Identity => EnvironmentCredential invoking ClientSecretCredential");
targetCredential = new ClientSecretCredential(tenantId, clientId, clientSecret,
identityClientOptions);
} else if (verifyNotNull(certPath)) {
// 1.2 Attempt ClientCertificateCredential
logger.info("Azure Identity => EnvironmentCredential invoking ClientCertificateCredential");
targetCredential = new ClientCertificateCredential(tenantId, clientId, certPath,
null, identityClientOptions);
} else {
// 1.3 Log error if neither is found
logger.error("Azure Identity => ERROR in EnvironmentCredential: Failed to create a "
+ "ClientSecretCredential or ClientCertificateCredential. Missing required environment "
+ "variable either {} or {}", Configuration.PROPERTY_AZURE_CLIENT_SECRET,
Configuration.PROPERTY_AZURE_CLIENT_CERTIFICATE_PATH);
}
} else if (verifyNotNull(clientSecret) || verifyNotNull(certPath)) {
// 1.4 Log error if secret / cert is found but tenant is missing
logger.error("Azure Identity => ERROR in EnvironmentCredential: Failed to create a "
+ "ClientSecretCredential or ClientCertificateCredential. Missing required environment "
+ "variable {}", Configuration.PROPERTY_AZURE_TENANT_ID);
}

// 2 - Attempt UsernamePasswordCredential (tenant not required)
if (targetCredential == null && verifyNotNull(username, password)) {
// 2.1 - both username and password found
logger.info("Azure Identity => EnvironmentCredential invoking UsernamePasswordCredential");
targetCredential = new UsernamePasswordCredential(clientId, tenantId, username, password,
identityClientOptions);
} else if (verifyNotNull(username) ^ verifyNotNull(password)) {
// 2.2 - only one is found, likely missing the other
logger.error("Azure Identity => ERROR in EnvironmentCredential: Failed to create a "
+ "UsernamePasswordCredential. Missing required environment variable {}",
username == null ? Configuration.PROPERTY_AZURE_USERNAME : Configuration.PROPERTY_AZURE_PASSWORD);
}

// 3 - cannot determine scenario based on clientId alone
if (targetCredential == null) {
String msg = String.format("Azure Identity => ERROR in EnvironmentCredential: Failed to determine an "
+ "authentication scheme based on the available environment variables. Please specify %1$s and "
+ "%2$s to authenticate through a ClientSecretCredential; %1$s and %3$s to authenticate through a "
+ "ClientCertificateCredential; or %4$s and %5$s to authenticate through a "
+ "UserPasswordCredential.", Configuration.PROPERTY_AZURE_TENANT_ID,
Configuration.PROPERTY_AZURE_CLIENT_SECRET, Configuration.PROPERTY_AZURE_CLIENT_CERTIFICATE_PATH,
Configuration.PROPERTY_AZURE_USERNAME, Configuration.PROPERTY_AZURE_PASSWORD);
logger.error(msg);
}
} else {
// 4 - not even clientId is available
logger.error("Azure Identity => ERROR in EnvironmentCredential: Missing required environment variable {}",
Configuration.PROPERTY_AZURE_CLIENT_ID);
}
tokenCredential = targetCredential;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import com.azure.core.credential.TokenCredential;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.util.CoreUtils;
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.IntelliJAuthMethodDetails;
import com.azure.identity.implementation.IntelliJCacheAccessor;
import com.azure.identity.implementation.MsalToken;
import com.azure.identity.implementation.util.LoggingUtil;
import reactor.core.publisher.Mono;

import java.util.concurrent.atomic.AtomicReference;
Expand All @@ -29,6 +31,7 @@ public class IntelliJCredential implements TokenCredential {
private static final String AZURE_TOOLS_FOR_INTELLIJ_CLIENT_ID = "61d65f5a-6e3b-468b-af73-a033f5098c5c";
private final IdentityClient identityClient;
private final AtomicReference<MsalToken> cachedToken;
private final ClientLogger logger = new ClientLogger(IntelliJCredential.class);

/**
* Creates an {@link IntelliJCredential} with default identity client options.
Expand Down Expand Up @@ -84,7 +87,9 @@ public Mono<AccessToken> getToken(TokenRequestContext request) {
Mono.defer(() -> identityClient.authenticateWithIntelliJ(request)))
.map(msalToken -> {
cachedToken.set(msalToken);
return msalToken;
});
return (AccessToken) msalToken;
})
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.azure.identity.implementation.IdentityClientOptions;
import com.azure.identity.implementation.MsalAuthenticationAccount;
import com.azure.identity.implementation.MsalToken;
import com.azure.identity.implementation.util.LoggingUtil;
import reactor.core.publisher.Mono;

import java.util.concurrent.atomic.AtomicReference;
Expand Down Expand Up @@ -78,7 +79,9 @@ public Mono<AccessToken> getToken(TokenRequestContext request) {
+ "code authentication.", request)));
}
return identityClient.authenticateWithBrowserInteraction(request, port);
})).map(this::updateCache);
})).map(this::updateCache)
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}

/**
Expand Down Expand Up @@ -112,7 +115,7 @@ public Mono<AuthenticationRecord> authenticate() {
return authenticate(new TokenRequestContext().addScopes(defaultScope));
}

private MsalToken updateCache(MsalToken msalToken) {
private AccessToken updateCache(MsalToken msalToken) {
cachedToken.set(
new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
Expand Down
Loading

0 comments on commit 594723f

Please sign in to comment.