Skip to content

Commit 947abe2

Browse files
authored
Translate MFA token error to UIRequiredException instead of ServiceException, Fixes AB#3090086 (#2538)
When MFA is required, the token error response was previously translated to a ServiceException instead of a UIRequiredException. With this update, the token error will be translated to a UIRequiredException when MFA is required. Customization of native authentication error descriptions will only occur when the native authentication interface is being used. [AB#3090086](https://identitydivision.visualstudio.com/Engineering/_workitems/edit/3090086)
1 parent 4e91526 commit 947abe2

File tree

3 files changed

+60
-48
lines changed

3 files changed

+60
-48
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
vNext
22
----------
3+
- [PATCH] Translate MFA token error to UIRequiredException instead of ServiceException (#2538)
34

45
Version 18.2.2
56
----------

common4j/src/main/com/microsoft/identity/common/java/controllers/ExceptionAdapter.java

Lines changed: 22 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import com.microsoft.identity.common.java.logging.Logger;
4343
import com.microsoft.identity.common.java.net.HttpResponse;
4444
import com.microsoft.identity.common.java.providers.microsoft.MicrosoftAuthorizationErrorResponse;
45-
import com.microsoft.identity.common.java.providers.microsoft.MicrosoftTokenErrorResponse;
4645
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationErrorResponse;
4746
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationResult;
4847
import com.microsoft.identity.common.java.providers.oauth2.TokenErrorResponse;
@@ -224,33 +223,7 @@ public static ServiceException getExceptionFromTokenErrorResponse(@NonNull final
224223

225224
final ServiceException outErr;
226225

227-
if (isNativeAuthenticationMFAError(errorResponse)) {
228-
ServiceException apiError = new ServiceException(
229-
errorResponse.getError(),
230-
errorResponse.getErrorDescription(),
231-
null);
232-
233-
String developerDescription = "Multi-factor authentication is required, which can't be fulfilled as part of this flow. Please sign out and perform a new sign in operation. Please see exception details for more information.";
234-
outErr = new ServiceException(
235-
errorResponse.getError(),
236-
developerDescription,
237-
apiError
238-
);
239-
} else if (isNativeAuthenticationResetPasswordRequiredError(errorResponse)) {
240-
ServiceException apiError = new ServiceException(
241-
errorResponse.getError(),
242-
errorResponse.getErrorDescription(),
243-
null);
244-
245-
String developerDescription = "User password change is required, which can't be fulfilled as part of this flow."+
246-
"Please reset the password and perform a new sign in operation. Please see exception details for more information.";
247-
outErr = new ServiceException(
248-
errorResponse.getError(),
249-
developerDescription,
250-
apiError
251-
);
252-
}
253-
else if (shouldBeConvertedToUiRequiredException(errorResponse.getError())) {
226+
if (shouldBeConvertedToUiRequiredException(errorResponse.getError())) {
254227
outErr = new UiRequiredException(
255228
errorResponse.getError(),
256229
errorResponse.getErrorDescription());
@@ -275,10 +248,17 @@ else if (shouldBeConvertedToUiRequiredException(errorResponse.getError())) {
275248
*/
276249
public static ServiceException convertToNativeAuthException(@NonNull final ServiceException exception) {
277250
final ServiceException outErr;
251+
String customDescription = "";
252+
if (isNativeAuthenticationMFAException(exception)) {
253+
customDescription = "Multi-factor authentication is required, which can't be fulfilled as part of this flow. Please sign out and perform a new sign in operation. Please see exception details for more information.";
254+
} else if (isNativeAuthenticationResetPasswordRequiredException(exception)) {
255+
customDescription = "User password change is required, which can't be fulfilled as part of this flow."+
256+
"Please reset the password and perform a new sign in operation. Please see exception details for more information.";
257+
}
278258

279259
outErr = new ServiceException(
280260
exception.getErrorCode(),
281-
exception.getMessage(),
261+
customDescription + exception.getMessage(),
282262
exception.getHttpStatusCode(),
283263
exception
284264
);
@@ -514,40 +494,34 @@ private static boolean isIntunePolicyRequiredError(
514494

515495
/**
516496
* Identifies whether an error is specific to native authentication MFA scenarios.
517-
* @param errorResponse
497+
* @param exception A ServiceException from which we will check the error code
518498
* @return true if errorResponse is a native authentication MFA error
519499
*/
520-
private static boolean isNativeAuthenticationMFAError(
521-
@NonNull final TokenErrorResponse errorResponse) {
522-
return doesErrorContainsErrorCode(50076, errorResponse);
500+
private static boolean isNativeAuthenticationMFAException(
501+
@NonNull final ServiceException exception) {
502+
return doesExceptionContainsErrorCode(50076, exception);
523503
}
524504

525505
/**
526506
* Identifies whether an error is specific to native authentication reset password required scenarios.
527-
* @param errorResponse
507+
* @param exception A ServiceException from which we will check the error code
528508
* @return true if errorResponse is a native authentication reset password required error
529509
*/
530-
private static boolean isNativeAuthenticationResetPasswordRequiredError(
531-
@NonNull final TokenErrorResponse errorResponse) {
532-
return doesErrorContainsErrorCode(50142, errorResponse);
510+
private static boolean isNativeAuthenticationResetPasswordRequiredException(
511+
@NonNull final ServiceException exception) {
512+
return doesExceptionContainsErrorCode(50142, exception);
533513
}
534514

535515
/**
536516
* Identifies whether an error contains a specific error code.
537517
* @param errorCode Error code to check
538-
* @param errorResponse A TokenErrorResponse from which we will check the error code
518+
* @param exception A ServiceException from which we will check the error code
539519
* @return true if errorResponse contains the error code
540520
*/
541-
private static boolean doesErrorContainsErrorCode(
521+
private static boolean doesExceptionContainsErrorCode(
542522
int errorCode,
543-
@NonNull final TokenErrorResponse errorResponse) {
544-
if (!(errorResponse instanceof MicrosoftTokenErrorResponse)) {
545-
return false;
546-
}
547-
548-
MicrosoftTokenErrorResponse microsoftTokenErrorResponse = ((MicrosoftTokenErrorResponse) errorResponse);
549-
return microsoftTokenErrorResponse.getErrorCodes() != null &&
550-
!microsoftTokenErrorResponse.getErrorCodes().isEmpty() &&
551-
microsoftTokenErrorResponse.getErrorCodes().contains((long) errorCode);
523+
@NonNull final ServiceException exception) {
524+
return exception.getCliTelemErrorCode() != null &&
525+
exception.getCliTelemErrorCode().equalsIgnoreCase(String.valueOf(errorCode));
552526
}
553527
}

common4j/src/test/com/microsoft/identity/common/java/controllers/ExceptionAdapterTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@
2525

2626
import com.microsoft.identity.common.java.exception.BaseException;
2727
import com.microsoft.identity.common.java.exception.ClientException;
28+
import com.microsoft.identity.common.java.exception.ServiceException;
2829
import com.microsoft.identity.common.java.exception.TerminalException;
30+
import com.microsoft.identity.common.java.exception.UiRequiredException;
31+
import com.microsoft.identity.common.java.providers.microsoft.MicrosoftTokenErrorResponse;
2932

3033
import org.junit.Assert;
3134
import org.junit.Test;
3235
import org.junit.runner.RunWith;
3336
import org.junit.runners.JUnit4;
3437

38+
import java.util.ArrayList;
39+
import java.util.Arrays;
3540
import java.util.concurrent.TimeoutException;
3641

3742
@RunWith(JUnit4.class)
@@ -45,6 +50,38 @@ public void testBaseExceptionFromException_TerminalException() throws Exception{
4550
Assert.assertEquals(e.getCause(), t);
4651
}
4752

53+
@Test
54+
public void testMFATokenErrorResponse_IsTranslatedToUIRequiredException() {
55+
final MicrosoftTokenErrorResponse tokenErrorResponse = new MicrosoftTokenErrorResponse();
56+
tokenErrorResponse.setError("invalid_grant");
57+
tokenErrorResponse.setErrorDescription("AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '7ae46e1'. Trace ID: 01276277-3a30020d900900 Correlation ID: 6209e18a-f89b-4f14-a05e-0371c6757adb Timestamp: 2024-11-14 13:09:18Z");
58+
tokenErrorResponse.setErrorCodes(new ArrayList<Long>(Arrays.asList(50076L)));
59+
tokenErrorResponse.setSubError("basic_action");
60+
61+
BaseException e = ExceptionAdapter.getExceptionFromTokenErrorResponse(tokenErrorResponse);
62+
Assert.assertTrue("Expected exception of UiRequiredException type", e instanceof UiRequiredException);
63+
Assert.assertEquals(e.getErrorCode(), tokenErrorResponse.getError());
64+
Assert.assertEquals(e.getMessage(), tokenErrorResponse.getErrorDescription());
65+
}
66+
67+
@Test
68+
public void testNativeAuthMFAException_ContainsCorrectDescription() {
69+
String description = "description";
70+
ServiceException outErr = new ServiceException("errorCode", description, null);
71+
outErr.setCliTelemErrorCode("50076");
72+
ServiceException result = ExceptionAdapter.convertToNativeAuthException(outErr);
73+
Assert.assertEquals("Multi-factor authentication is required, which can't be fulfilled as part of this flow. Please sign out and perform a new sign in operation. Please see exception details for more information." + description, result.getMessage());
74+
}
75+
76+
@Test
77+
public void testNativeAuthResetPasswordRequiredException_ContainsCorrectDescription() {
78+
String description = "description";
79+
ServiceException outErr = new ServiceException("errorCode", description, null);
80+
outErr.setCliTelemErrorCode("50142");
81+
ServiceException result = ExceptionAdapter.convertToNativeAuthException(outErr);
82+
Assert.assertEquals("User password change is required, which can't be fulfilled as part of this flow.Please reset the password and perform a new sign in operation. Please see exception details for more information." + description, result.getMessage());
83+
}
84+
4885
@Test
4986
public void testClientExceptionFromException_TimeoutException() {
5087
final TimeoutException t = new TimeoutException();

0 commit comments

Comments
 (0)