diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java index 17b4244b..5519cce1 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java @@ -108,7 +108,7 @@ void acquireTokenInteractive_ManagedUser_InstanceAware() { @Test void acquireTokenInteractive_Ciam() { - User user = labUserProvider.getCiamUser(); + User user = labUserProvider.getCiamCudUser(); Map extraQueryParameters = new HashMap<>(); diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java index 2a77eda5..9cca7017 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java @@ -48,7 +48,6 @@ void acquireTokenClientCredentials_ClientCertificate() throws Exception { void acquireTokenClientCredentials_ClientSecret() throws Exception { AppCredentialProvider appProvider = new AppCredentialProvider(AzureEnvironment.AZURE); final String clientId = appProvider.getLabVaultAppId(); - final String password = appProvider.getLabVaultPassword(); IClientCredential credential = CertificateHelper.getClientCertificate(); assertAcquireTokenCommon(clientId, credential, TestConstants.MICROSOFT_AUTHORITY); @@ -68,7 +67,7 @@ void acquireTokenClientCredentials_ClientAssertion() throws Exception { @Test void acquireTokenClientCredentials_ClientSecret_Ciam() throws Exception { - User user = labUserProvider.getCiamUser(); + User user = labUserProvider.getCiamCudUser(); String clientId = user.getAppId(); AppCredentialProvider appProvider = new AppCredentialProvider(AzureEnvironment.CIAM); diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java index bb6930bb..6f352fdf 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java @@ -39,8 +39,7 @@ void acquireTokenWithOBO_Managed(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - assertNotNull(result); - assertNotNull(result.accessToken()); + assertResultNotNull(result); } @ParameterizedTest @@ -63,8 +62,7 @@ void acquireTokenWithOBO_testCache(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - assertNotNull(result1); - assertNotNull(result1.accessToken()); + assertResultNotNull(result1); // Same scope and userAssertion, should return cached tokens IAuthenticationResult result2 = @@ -82,8 +80,7 @@ void acquireTokenWithOBO_testCache(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - assertNotNull(result3); - assertNotNull(result3.accessToken()); + assertResultNotNull(result3); assertNotEquals(result2.accessToken(), result3.accessToken()); // Scope 2, should return cached token @@ -105,8 +102,7 @@ void acquireTokenWithOBO_testCache(String environment) throws Exception { .build()). get(); - assertNotNull(result5); - assertNotNull(result5.accessToken()); + assertResultNotNull(result5); assertNotEquals(result5.accessToken(), result4.accessToken()); assertNotEquals(result5.accessToken(), result2.accessToken()); @@ -121,13 +117,17 @@ void acquireTokenWithOBO_testCache(String environment) throws Exception { .build()). get(); - assertNotNull(result6); - assertNotNull(result6.accessToken()); + assertResultNotNull(result6); assertNotEquals(result6.accessToken(), result5.accessToken()); assertNotEquals(result6.accessToken(), result4.accessToken()); assertNotEquals(result6.accessToken(), result2.accessToken()); } + private void assertResultNotNull(IAuthenticationResult result) { + assertNotNull(result); + assertNotNull(result.accessToken()); + } + private String getAccessToken() throws Exception { LabUserProvider labUserProvider = LabUserProvider.getInstance(); diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java index 8a9c76d7..09904411 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java @@ -94,7 +94,7 @@ void acquireTokenWithUsernamePassword_Ciam() throws Exception { Map extraQueryParameters = new HashMap<>(); - User user = labUserProvider.getCiamUser(); + User user = labUserProvider.getCiamCudUser(); PublicClientApplication pca = PublicClientApplication.builder(user.getAppId()) .authority("https://" + user.getLabName() + ".ciamlogin.com/") .build(); diff --git a/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java b/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java index 2a458449..9aca04bb 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java @@ -7,7 +7,6 @@ public class AppCredentialProvider { private KeyVaultSecretsProvider keyVaultSecretsProvider; private String labVaultClientId; - private String labVaultPassword; private String clientId; @@ -19,7 +18,6 @@ public AppCredentialProvider(String azureEnvironment) { keyVaultSecretsProvider = new KeyVaultSecretsProvider(); labVaultClientId = keyVaultSecretsProvider.getSecret(LabConstants.APP_ID_KEY_VAULT_SECRET); - labVaultPassword = keyVaultSecretsProvider.getSecret(LabConstants.APP_PASSWORD_KEY_VAULT_SECRET); switch (azureEnvironment) { case AzureEnvironment.AZURE: @@ -65,8 +63,4 @@ public String getOboAppPassword() { public String getLabVaultAppId() { return labVaultClientId; } - - public String getLabVaultPassword() { - return labVaultPassword; - } } diff --git a/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java b/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java index 79397eb9..16630cd0 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java @@ -14,7 +14,7 @@ public class LabConstants { public final static String USER_MSA_USERNAME_URL = "https://msidlabs.vault.azure.net/secrets/MSA-MSIDLAB4-UserName"; public final static String USER_MSA_PASSWORD_URL = "https://msidlabs.vault.azure.net/secrets/MSA-MSIDLAB4-Password"; public final static String OBO_APP_PASSWORD_URL = "https://msidlabs.vault.azure.net/secrets/TodoListServiceV2-OBO"; - public final static String CIAM_KEY_VAULT_SECRET_KEY = "https://msidlabs.vault.azure.net/secrets/MSIDLABCIAM2-cc"; + public final static String CIAM_KEY_VAULT_SECRET_KEY = "https://msidlabs.vault.azure.net/secrets/MSIDLABCIAM6-cc"; public final static String ARLINGTON_APP_ID = "cb7faed4-b8c0-49ee-b421-f5ed16894c83"; public final static String ARLINGTON_OBO_APP_ID = "c0555d2d-02f2-4838-802e-3463422e571d"; diff --git a/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java b/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java index 8fd5b307..248a8b94 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java @@ -105,14 +105,6 @@ public User getUserByGuestHomeAzureEnvironments(String guestEnvironment, String return getLabUser(query); } - public User getCiamUser() { - - UserQueryParameters query = new UserQueryParameters(); - query.parameters.put(UserQueryParameters.FEDERATION_PROVIDER, FederationProvider.CIAM); - - return getLabUser(query); - } - public User getCiamCudUser() { UserQueryParameters query = new UserQueryParameters(); query.parameters.put(UserQueryParameters.FEDERATION_PROVIDER, FederationProvider.CIAMCUD); diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByClientCredentialSupplier.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByClientCredentialSupplier.java index 41a756cf..98f2d7db 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByClientCredentialSupplier.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByClientCredentialSupplier.java @@ -26,6 +26,7 @@ AuthenticationResult execute() throws Exception { SilentParameters parameters = SilentParameters .builder(this.clientCredentialRequest.parameters.scopes()) .claims(this.clientCredentialRequest.parameters.claims()) + .tenant(this.clientCredentialRequest.parameters.tenant()) .build(); RequestContext context = new RequestContext( diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByOnBehalfOfSupplier.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByOnBehalfOfSupplier.java index 2089969f..36158217 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByOnBehalfOfSupplier.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByOnBehalfOfSupplier.java @@ -26,6 +26,7 @@ AuthenticationResult execute() throws Exception { SilentParameters parameters = SilentParameters .builder(this.onBehalfOfRequest.parameters.scopes()) .claims(this.onBehalfOfRequest.parameters.claims()) + .tenant(this.onBehalfOfRequest.parameters.tenant()) .build(); RequestContext context = new RequestContext( diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTests.java new file mode 100644 index 00000000..78181f8a --- /dev/null +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTests.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +@ExtendWith(MockitoExtension.class) +class OnBehalfOfTests { + + private String getSuccessfulResponse(String accessToken) { + return "{\"access_token\":\""+accessToken+"\",\"expires_in\": \""+ 60*60*1000 +"\",\"token_type\":" + + "\"Bearer\",\"client_id\":\"client_id\",\"Content-Type\":\"text/html; charset=utf-8\"}"; + } + + private HttpResponse expectedResponse(int statusCode, String response) { + Map> headers = new HashMap>(); + headers.put("Content-Type", Collections.singletonList("application/json")); + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.statusCode(statusCode); + httpResponse.body(response); + httpResponse.addHeaders(headers); + + return httpResponse; + } + + @Test + void OnBehalfOf_InternalCacheLookup_Success() throws Exception { + DefaultHttpClient httpClientMock = mock(DefaultHttpClient.class); + + when(httpClientMock.send(any(HttpRequest.class))).thenReturn(expectedResponse(200, getSuccessfulResponse("token"))); + + ConfidentialClientApplication cca = + ConfidentialClientApplication.builder("clientId", ClientCredentialFactory.createFromSecret("password")) + .authority("https://login.microsoftonline.com/tenant/") + .instanceDiscovery(false) + .validateAuthority(false) + .httpClient(httpClientMock) + .build(); + + OnBehalfOfParameters parameters = OnBehalfOfParameters.builder(Collections.singleton("scopes"), new UserAssertion(TestHelper.signedToken)).build(); + + IAuthenticationResult result = cca.acquireToken(parameters).get(); + IAuthenticationResult result2 = cca.acquireToken(parameters).get(); + + //OBO flow should perform an internal cache lookup, so similar parameters should only cause one HTTP client call + assertEquals(result.accessToken(), result2.accessToken()); + verify(httpClientMock, times(1)).send(any()); + } + + @Test + void OnBehalfOf_TenantOverride() throws Exception { + DefaultHttpClient httpClientMock = mock(DefaultHttpClient.class); + + ConfidentialClientApplication cca = + ConfidentialClientApplication.builder("clientId", ClientCredentialFactory.createFromSecret("password")) + .authority("https://login.microsoftonline.com/tenant") + .instanceDiscovery(false) + .validateAuthority(false) + .httpClient(httpClientMock) + .build(); + + when(httpClientMock.send(any(HttpRequest.class))).thenReturn(expectedResponse(200, getSuccessfulResponse("appTenantToken"))); + OnBehalfOfParameters parameters = OnBehalfOfParameters.builder(Collections.singleton("scopes"), new UserAssertion(TestHelper.signedToken)).build(); + + //The two acquireToken calls have the same parameters and should only cause one call from the HTTP client + IAuthenticationResult resultAppLevelTenant = cca.acquireToken(parameters).get(); + cca.acquireToken(parameters).get(); + assertEquals(1, cca.tokenCache.accessTokens.size()); + verify(httpClientMock, times(1)).send(any()); + + when(httpClientMock.send(any(HttpRequest.class))).thenReturn(expectedResponse(200, getSuccessfulResponse("requestTenantToken"))); + parameters = OnBehalfOfParameters.builder(Collections.singleton("scopes"), new UserAssertion(TestHelper.signedToken)).tenant("otherTenant").build(); + + //Overriding the tenant parameter in the request should lead to a new token call being made, but followup calls should not + IAuthenticationResult resultRequestLevelTenant = cca.acquireToken(parameters).get(); + cca.acquireToken(parameters).get(); + assertEquals(2, cca.tokenCache.accessTokens.size()); + verify(httpClientMock, times(2)).send(any()); + assertNotEquals(resultAppLevelTenant.accessToken(), resultRequestLevelTenant.accessToken()); + } +} \ No newline at end of file diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java index 5a87d1a6..da16ebce 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java @@ -3,6 +3,11 @@ package com.microsoft.aad.msal4j; +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; + import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -12,10 +17,16 @@ class TestHelper { - static String readResource(Class classInstance, String resource) throws IOException, URISyntaxException { - return new String( - Files.readAllBytes( - Paths.get(classInstance.getResource(resource).toURI()))); + //Signed JWT which should be enough to pass the parsing/validation in the library, useful if a unit test needs an + // assertion in a request or token in a response but that is not the focus of the test + static String signedToken = generateToken(); + + static String readResource(Class classInstance, String resource) { + try { + return new String(Files.readAllBytes(Paths.get(classInstance.getResource(resource).toURI()))); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } } static void deleteFileContent(Class classInstance, String resource) @@ -27,4 +38,21 @@ static void deleteFileContent(Class classInstance, String resource) fileWriter.write(""); fileWriter.close(); } + + static String generateToken() { + try { + RSAKey rsaJWK = new RSAKeyGenerator(2048) + .keyID("kid") + .generate(); + JWSObject jwsObject = new JWSObject( + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaJWK.getKeyID()).build(), + new Payload("payload")); + + jwsObject.sign(new RSASSASigner(rsaJWK)); + + return jwsObject.serialize(); + } catch (JOSEException e) { + throw new RuntimeException(e); + } + } }