diff --git a/changelog.txt b/changelog.txt index 9790cc593f..65abd07a7b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ vNext ---------- - [PATCH] Translate MFA token error to UIRequiredException instead of ServiceException (#2538) - [MINOR] Add Child Spans for Interactive Span (#2516) +- [MINOR] For MSAL CPP flows, match exact claims when deleting AT with intersecting scopes (#2548) Version 18.2.2 ---------- diff --git a/common/src/test/java/com/microsoft/identity/common/MsalCppOAuth2TokenCacheTest.java b/common/src/test/java/com/microsoft/identity/common/MsalCppOAuth2TokenCacheTest.java index 27612d7d0d..6f98b20843 100644 --- a/common/src/test/java/com/microsoft/identity/common/MsalCppOAuth2TokenCacheTest.java +++ b/common/src/test/java/com/microsoft/identity/common/MsalCppOAuth2TokenCacheTest.java @@ -534,4 +534,40 @@ public void saveATSansTargetThrowsException() throws ClientException { mTestBundle.mGeneratedRefreshToken ); } + + @Test + public void saveCredentialsWithSameTargetAndDifferentClaims() throws ClientException { + mTestBundle.mGeneratedAccessToken.setRequestedClaims("TestClaims"); + mCppCache.saveCredentials( + false, + mTestBundle.mGeneratedAccessToken + ); + + mTestBundle.mGeneratedAccessToken.setRequestedClaims(null); + mCppCache.saveCredentials( + false, + mTestBundle.mGeneratedAccessToken + ); + + List credentials = mCppCache.getCredentials(); + Assert.assertEquals(credentials.size(), 1); + + //Clear credentials and now match exact claims + mCppCache.clearCache(); + + mTestBundle.mGeneratedAccessToken.setRequestedClaims("TestClaims"); + mCppCache.saveCredentials( + true, + mTestBundle.mGeneratedAccessToken + ); + + mTestBundle.mGeneratedAccessToken.setRequestedClaims(null); + mCppCache.saveCredentials( + true, + mTestBundle.mGeneratedAccessToken + ); + + credentials = mCppCache.getCredentials(); + Assert.assertEquals(credentials.size(), 2); + } } diff --git a/common4j/src/main/com/microsoft/identity/common/java/cache/MsalCppOAuth2TokenCache.java b/common4j/src/main/com/microsoft/identity/common/java/cache/MsalCppOAuth2TokenCache.java index d6f4faf5e6..635e09737d 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/cache/MsalCppOAuth2TokenCache.java +++ b/common4j/src/main/com/microsoft/identity/common/java/cache/MsalCppOAuth2TokenCache.java @@ -128,6 +128,15 @@ public IAccountCredentialCache getAccountCredentialCache() { * @throws ClientException If the supplied Account or Credential are null or schema invalid. */ public synchronized void saveCredentials(@NonNull final Credential... credentials) throws ClientException { + saveCredentials(false, credentials); + } + + /** + * @param credentials list of Credential which can include AccessTokenRecord, IdTokenRecord and RefreshTokenRecord. + * @param mustMatchExactClaims If true, match exact claims when deleting AT with intersecting scopes. + * @throws ClientException If the supplied Account or Credential are null or schema invalid. + */ + public synchronized void saveCredentials(boolean mustMatchExactClaims, @NonNull final Credential... credentials) throws ClientException { if (credentials.length == 0) { throw new ClientException("Credential array passed in is null or empty"); } @@ -148,7 +157,7 @@ public synchronized void saveCredentials(@NonNull final Credential... credential } } - saveCredentialsInternal(credentials); + saveCredentialsInternal(mustMatchExactClaims, credentials); } /** diff --git a/common4j/src/main/com/microsoft/identity/common/java/cache/MsalOAuth2TokenCache.java b/common4j/src/main/com/microsoft/identity/common/java/cache/MsalOAuth2TokenCache.java index 2aac30b932..d8fd522e6c 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/cache/MsalOAuth2TokenCache.java +++ b/common4j/src/main/com/microsoft/identity/common/java/cache/MsalOAuth2TokenCache.java @@ -1635,13 +1635,17 @@ private void saveAccounts(final AccountRecord... accounts) { } void saveCredentialsInternal(final Credential... credentials) { + saveCredentialsInternal(false, credentials); + } + + void saveCredentialsInternal(boolean mustMatchExactClaims, final Credential... credentials) { for (final Credential credential : credentials) { if (credential == null) { continue; } if (credential instanceof AccessTokenRecord) { - deleteAccessTokensWithIntersectingScopes((AccessTokenRecord) credential); + deleteAccessTokensWithIntersectingScopes((AccessTokenRecord) credential, mustMatchExactClaims); } mAccountCredentialCache.saveCredential(credential); @@ -1707,7 +1711,7 @@ void validateCacheArtifacts( } private void deleteAccessTokensWithIntersectingScopes( - final AccessTokenRecord referenceToken) { + final AccessTokenRecord referenceToken, boolean mustMatchExactClaims) { final String methodName = "deleteAccessTokensWithIntersectingScopes"; final List accessTokens = mAccountCredentialCache.getCredentialsFilteredBy( @@ -1721,6 +1725,7 @@ private void deleteAccessTokensWithIntersectingScopes( null, // Wildcard (*) referenceToken.getAccessTokenType(), referenceToken.getRequestedClaims(), + mustMatchExactClaims, mAccountCredentialCache.getCredentials() );