Skip to content

Various fixes/improvements #756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ public Set<IAccount> get() {
(clientApplication.clientId());

} catch (Exception ex) {
clientApplication.log.error(
LogHelper.createMessage("Execution of " + this.getClass() + " failed.",
msalRequest.headers().getHeaderCorrelationIdValue()), ex);
clientApplication.log.warn(
LogHelper.createMessage(
String.format("Execution of %s failed: %s", this.getClass(), ex.getMessage()),
msalRequest.headers().getHeaderCorrelationIdValue()));

throw new CompletionException(ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ private AuthenticationResult acquireTokenWithDeviceCode(DeviceCode deviceCode,

while (getCurrentSystemTimeInSeconds() < expirationTimeInSeconds) {
if (deviceCodeFlowRequest.futureReference().get().isCancelled()) {
throw new InterruptedException("Acquire token Device Code Flow was interrupted");
throw new InterruptedException("Device code flow was cancelled before acquiring a token");
}
if (deviceCodeFlowRequest.futureReference().get().isCompletedExceptionally()) {
throw new InterruptedException("Device code flow had an exception before acquiring a token");
}
try {
return acquireTokenByAuthorisationGrantSupplier.execute();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private AuthorizationResult getAuthorizationResultFromHttpListener() {
expirationTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + 1;
}

while (result == null && !interactiveRequest.futureReference().get().isCancelled()) {
while (result == null && !interactiveRequest.futureReference().get().isDone()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timeout in interactive flow is not something MSALs do and it's not something we ask app dev to do either. It can take a really long time to perform auth - e.g. I was just asked to MFA and then to change password.

But anyway, it looks like this is based on public API, so it's fine.

Copy link
Collaborator Author

@Avery-Dunn Avery-Dunn Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interactive flow we have a timeout parameter that would cause this loop to end after a default 120 seconds:

This ended up being kind of a weird situation. In Java 8 there wasn't really a timeout option for ending a future (there was a timeout for waiting for the result), however in Java 9 they started introducing methods for completing the future after a timeout. Since we target Java 8 as a minimum but also want to support newer versions, this means

  • Java 8 customers need that httpPollingTimeoutInSeconds as a timeout to end the interactive flow
  • Java 9+ customers can use the newer timeout methods in Java's CompletableFuture API, and will be confused if MSAL doesn't follow those timeouts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment on what "isDone" is doing here? I think it returns true when cancel() is called, or when the future completes with success?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDone returns true when a future is cancelled (like the current behavior), plus when it completes normally or with an exception. In other words, isDone covers any situation where we can't return the auth result, and there's no point in continuing this loop.

By only checking isCancelled there were scenarios where thread spun off to complete futures could continue to pointlessly run (such as when the app developers sets a timeout in the future #741).

if (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) > expirationTime) {
LOG.warn(String.format("Listener timed out after %S seconds, no authorization code was returned from the server during that time.", timeFromParameters));
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ AuthenticationResult execute() throws Exception {
throw new MsalClientException(AuthenticationErrorMessage.NO_TOKEN_IN_CACHE, AuthenticationErrorCode.CACHE_MISS);
}

log.info("Returning token from cache");
log.debug("Returning token from cache");

return res;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ public IAuthenticationResult get() {
msalRequest.requestContext().correlationId(),
error);

logException(ex);
clientApplication.log.warn(
LogHelper.createMessage(
String.format("Execution of %s failed: %s", this.getClass(), ex.getMessage()),
msalRequest.headers().getHeaderCorrelationIdValue()));

throw new CompletionException(ex);
}
}
Expand Down Expand Up @@ -135,26 +139,6 @@ private void logResult(AuthenticationResult result, HttpHeaders headers) {
}
}

private void logException(Exception ex) {

String logMessage = LogHelper.createMessage(
"Execution of " + this.getClass() + " failed.",
msalRequest.headers().getHeaderCorrelationIdValue());

if (ex instanceof MsalClientException) {
MsalClientException exception = (MsalClientException) ex;
if (exception.errorCode() != null && exception.errorCode().equalsIgnoreCase(AuthenticationErrorCode.CACHE_MISS)) {
clientApplication.log.debug(logMessage, ex);
return;
}
} else if (ex instanceof MsalAzureSDKException) {
clientApplication.log.debug(ex.getMessage(), ex);
return;
}

clientApplication.log.error(logMessage, ex);
}

private ApiEvent initializeApiEvent(MsalRequest msalRequest) {
ApiEvent apiEvent = new ApiEvent(clientApplication.logPii());
msalRequest.requestContext().telemetryRequestId(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ final class HttpHeaders {
static final String PRODUCT_VERSION_HEADER_NAME = "x-client-VER";
static final String PRODUCT_VERSION_HEADER_VALUE = getProductVersion();

static final String CPU_HEADER_NAME = "x-client-CPU";
static final String CPU_HEADER_VALUE = System.getProperty("os.arch");

static final String OS_HEADER_NAME = "x-client-OS";
static final String OS_HEADER_VALUE = System.getProperty("os.name");

Expand Down Expand Up @@ -78,7 +75,6 @@ private void initializeHeaders(Map<String, String> extraHttpHeaders) {
init.accept(PRODUCT_HEADER_NAME, PRODUCT_HEADER_VALUE);
init.accept(PRODUCT_VERSION_HEADER_NAME, PRODUCT_VERSION_HEADER_VALUE);
init.accept(OS_HEADER_NAME, OS_HEADER_VALUE);
init.accept(CPU_HEADER_NAME, CPU_HEADER_VALUE);
init.accept(REQUEST_CORRELATION_ID_IN_RESPONSE_HEADER_NAME, REQUEST_CORRELATION_ID_IN_RESPONSE_HEADER_VALUE);
init.accept(CORRELATION_ID_HEADER_NAME, this.correlationIdHeaderValue);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
final class LogHelper {

static String createMessage(String originalMessage, String correlationId) {
return String.format("[Correlation ID: %s] " + originalMessage,
correlationId);
return String.format("[Correlation ID: %s] %s", correlationId, originalMessage);
}

static String getPiiScrubbedDetails(Throwable ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ public void run() {
(clientApplication.clientId(), account);

} catch (Exception ex) {
clientApplication.log.error(
LogHelper.createMessage("Execution of " + this.getClass() + " failed.",
requestContext.correlationId()), ex);
clientApplication.log.warn(
LogHelper.createMessage(
String.format("Execution of %s failed: %s", this.getClass(), ex.getMessage()),
requestContext.correlationId()));

throw new CompletionException(ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ void testHttpHeaderConstructor() {
assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME), "app-name");
assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version");
assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id");
Expand All @@ -70,7 +69,6 @@ void testHttpHeaderConstructor_valuesNotSet() {
assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE);
assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME));
assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME));
assertNotNull(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME));
Expand Down Expand Up @@ -163,7 +161,6 @@ void testHttpHeaderConstructor_extraHttpHeadersOverwriteLibraryHeaders() {
assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE);
assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version");
assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id");

Expand Down