diff --git a/src/main/java/com/microsoft/aad/msal4j/AcquireDeviceCodeCallable.java b/src/main/java/com/microsoft/aad/msal4j/AcquireDeviceCodeCallable.java deleted file mode 100644 index 194e154f..00000000 --- a/src/main/java/com/microsoft/aad/msal4j/AcquireDeviceCodeCallable.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package com.microsoft.aad.msal4j; - -import java.util.Set; - -class AcquireDeviceCodeCallable extends MsalCallable { - private String clientId; - private String scopes; - - AcquireDeviceCodeCallable(PublicClientApplication clientApplication, - String clientId, Set scopes) { - super(clientApplication); - this.headers = new ClientDataHttpHeaders(clientApplication.getCorrelationId()); - this.clientId = clientId; - this.scopes = String.join(" ", scopes); - } - - DeviceCode execute() throws Exception { - clientApplication.authenticationAuthority.doInstanceDiscovery(clientApplication.isValidateAuthority(), - headers.getReadonlyHeaderMap(), clientApplication.getProxy(), clientApplication.getSslSocketFactory()); - return DeviceCodeRequest.execute(clientApplication.authenticationAuthority.getDeviceCodeEndpoint(), - clientId, scopes, headers.getReadonlyHeaderMap(), clientApplication.getProxy(), - clientApplication.getSslSocketFactory()); - } -} diff --git a/src/main/java/com/microsoft/aad/msal4j/AcquireTokenCallable.java b/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAuthorisationGrantSupplier.java similarity index 72% rename from src/main/java/com/microsoft/aad/msal4j/AcquireTokenCallable.java rename to src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAuthorisationGrantSupplier.java index e789e2cf..e0fde352 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AcquireTokenCallable.java +++ b/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAuthorisationGrantSupplier.java @@ -28,19 +28,16 @@ import com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant; import com.nimbusds.oauth2.sdk.SAML2BearerGrant; import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; -import org.apache.commons.codec.binary.Base64; - -import java.io.UnsupportedEncodingException; +import org.apache.commons.codec.binary.Base64;; import java.net.URLEncoder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -class AcquireTokenCallable extends MsalCallable { +public class AcquireTokenByAuthorisationGrantSupplier extends AuthenticationResultSupplier { + private AbstractMsalAuthorizationGrant authGrant; private ClientAuthentication clientAuth; - AcquireTokenCallable(ClientApplicationBase clientApplication, - AbstractMsalAuthorizationGrant authGrant, ClientAuthentication clientAuth) { + AcquireTokenByAuthorisationGrantSupplier(ClientApplicationBase clientApplication, + AbstractMsalAuthorizationGrant authGrant, ClientAuthentication clientAuth) { super(clientApplication); this.authGrant = authGrant; this.clientAuth = clientAuth; @@ -69,51 +66,6 @@ AuthenticationResult execute() throws Exception { return clientApplication.acquireTokenCommon(this.authGrant, this.clientAuth, this.headers); } - @Override - void logResult(AuthenticationResult result, ClientDataHttpHeaders headers) - throws NoSuchAlgorithmException, UnsupportedEncodingException { - - if (!StringHelper.isBlank(result.getAccessToken())) { - - String accessTokenHash = this.computeSha256Hash(result - .getAccessToken()); - if (!StringHelper.isBlank(result.getRefreshToken())) { - String refreshTokenHash = this.computeSha256Hash(result - .getRefreshToken()); - if(clientApplication.isLogPii()){ - clientApplication.log.debug(LogHelper.createMessage(String - .format("Access Token with hash '%s' and Refresh Token with hash '%s' returned", - accessTokenHash, refreshTokenHash), - headers.getHeaderCorrelationIdValue())); - } - else{ - clientApplication.log.debug(LogHelper.createMessage("Access Token and Refresh Token were returned", - headers.getHeaderCorrelationIdValue())); - } - } - else { - if(clientApplication.isLogPii()){ - clientApplication.log.debug(LogHelper.createMessage(String - .format("Access Token with hash '%s' returned", - accessTokenHash), - headers.getHeaderCorrelationIdValue())); - } - else{ - clientApplication.log.debug(LogHelper.createMessage("Access Token was returned", - headers.getHeaderCorrelationIdValue())); - } - } - } - } - - private String computeSha256Hash(String input) - throws NoSuchAlgorithmException, UnsupportedEncodingException { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(input.getBytes("UTF-8")); - byte[] hash = digest.digest(); - return Base64.encodeBase64URLSafeString(hash); - } - /** * @param authGrant */ @@ -165,7 +117,7 @@ AuthorizationGrant getAuthorizationGrantIntegrated(String userName) throws Excep // Get the realm information UserDiscoveryResponse userRealmResponse = UserDiscoveryRequest.execute( - userRealmEndpoint, + userRealmEndpoint, this.headers.getReadonlyHeaderMap(), clientApplication.getProxy(), clientApplication.getSslSocketFactory()); diff --git a/src/main/java/com/microsoft/aad/msal4j/AcquireTokenDeviceCodeFlowSupplier.java b/src/main/java/com/microsoft/aad/msal4j/AcquireTokenDeviceCodeFlowSupplier.java new file mode 100644 index 00000000..bffe727c --- /dev/null +++ b/src/main/java/com/microsoft/aad/msal4j/AcquireTokenDeviceCodeFlowSupplier.java @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package com.microsoft.aad.msal4j; + +import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import static com.microsoft.aad.msal4j.AdalErrorCode.AUTHORIZATION_PENDING; + +public class AcquireTokenDeviceCodeFlowSupplier extends AuthenticationResultSupplier { + + private ClientAuthentication clientAuth; + private String scopes; + private Consumer deviceCodeConsumer; + private AtomicReference> futureReference; + + AcquireTokenDeviceCodeFlowSupplier(PublicClientApplication clientApplication, ClientAuthentication clientAuth, + Set scopes, Consumer deviceCodeConsumer, + AtomicReference> futureReference) + { + super(clientApplication); + this.headers = new ClientDataHttpHeaders(clientApplication.getCorrelationId()); + this.clientAuth = clientAuth; + this.scopes = String.join(" ", scopes); + this.deviceCodeConsumer = deviceCodeConsumer; + + this.futureReference = futureReference; + } + + AuthenticationResult execute() throws Exception { + + clientApplication.authenticationAuthority.doInstanceDiscovery(clientApplication.isValidateAuthority(), + headers.getReadonlyHeaderMap(), clientApplication.getProxy(), clientApplication.getSslSocketFactory()); + + DeviceCode deviceCode = DeviceCodeRequest.execute(clientApplication.authenticationAuthority.getDeviceCodeEndpoint(), + clientAuth.getClientID().toString(), scopes, headers.getReadonlyHeaderMap(), clientApplication.getProxy(), + clientApplication.getSslSocketFactory()); + + deviceCodeConsumer.accept(deviceCode); + + MsalDeviceCodeAuthorizationGrant deviceCodeGrant = + new MsalDeviceCodeAuthorizationGrant(deviceCode, deviceCode.getScopes()); + + long expirationTimeInSeconds = + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + deviceCode.getExpiresIn(); + + AcquireTokenByAuthorisationGrantSupplier acquireTokenByAuthorisationGrantSupplier = + new AcquireTokenByAuthorisationGrantSupplier(clientApplication, deviceCodeGrant, clientAuth); + + while (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) < expirationTimeInSeconds) { + if(futureReference.get().isCancelled()){ + throw new InterruptedException("Acquire token Device Code Flow was interrupted"); + } + try { + return acquireTokenByAuthorisationGrantSupplier.execute(); + } + catch (AuthenticationException ex) { + if (ex.getErrorCode().equals(AUTHORIZATION_PENDING)) + { + TimeUnit.SECONDS.sleep(deviceCode.getInterval()); + } else { + throw ex; + } + } + } + throw new AuthenticationException("Expired Device code"); + } +} diff --git a/src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java b/src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java new file mode 100644 index 00000000..a8a446ac --- /dev/null +++ b/src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package com.microsoft.aad.msal4j; + +import org.apache.commons.codec.binary.Base64; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.CompletionException; +import java.util.function.Supplier; + +abstract class AuthenticationResultSupplier implements Supplier { + + ClientDataHttpHeaders headers; + ClientApplicationBase clientApplication; + + AuthenticationResultSupplier(ClientApplicationBase clientApplication) { + this.clientApplication = clientApplication; + } + + abstract AuthenticationResult execute() throws Exception; + + @Override + public AuthenticationResult get() { + AuthenticationResult result; + try { + result = execute(); + + logResult(result, headers); + } catch (Exception ex) { + clientApplication.log.error( + LogHelper.createMessage("Execution of " + this.getClass() + " failed.", + this.headers.getHeaderCorrelationIdValue()), ex); + + throw new CompletionException(ex); + } + return result; + } + + void logResult(AuthenticationResult result, ClientDataHttpHeaders headers) + { + if (!StringHelper.isBlank(result. getAccessToken())) { + + String accessTokenHash = this.computeSha256Hash(result + .getAccessToken()); + if (!StringHelper.isBlank(result.getRefreshToken())) { + String refreshTokenHash = this.computeSha256Hash(result + .getRefreshToken()); + if(clientApplication.isLogPii()){ + clientApplication.log.debug(LogHelper.createMessage(String + .format("Access Token with hash '%s' and Refresh Token with hash '%s' returned", + accessTokenHash, refreshTokenHash), + headers.getHeaderCorrelationIdValue())); + } + else{ + clientApplication.log.debug( + LogHelper.createMessage("Access Token and Refresh Token were returned", + headers.getHeaderCorrelationIdValue())); + } + } + else { + if(clientApplication.isLogPii()){ + clientApplication.log.debug(LogHelper.createMessage(String + .format("Access Token with hash '%s' returned", + accessTokenHash), + headers.getHeaderCorrelationIdValue())); + } + else{ + clientApplication.log.debug(LogHelper.createMessage("Access Token was returned", + headers.getHeaderCorrelationIdValue())); + } + } + } + } + + private String computeSha256Hash(String input) { + try{ + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(input.getBytes("UTF-8")); + byte[] hash = digest.digest(); + return Base64.encodeBase64URLSafeString(hash); + } + catch (NoSuchAlgorithmException | UnsupportedEncodingException ex){ + clientApplication.log.warn( + LogHelper.createMessage("Failed to compute SHA-256 hash due to exception - ", + LogHelper.getPiiScrubbedDetails(ex))); + return "Failed to compute SHA-256 hash"; + } + } +} diff --git a/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java b/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java index a9df5eb7..cbf9ade8 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java +++ b/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java @@ -37,11 +37,7 @@ import java.net.URL; import java.util.Set; import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.function.Supplier; +import java.util.concurrent.*; /** * Abstract class containing common API methods and properties. @@ -121,23 +117,7 @@ protected CompletableFuture acquireToken( final AbstractMsalAuthorizationGrant authGrant, final ClientAuthentication clientAuth) { - Supplier supplier = () -> - { - AcquireTokenCallable callable = - new AcquireTokenCallable(this, authGrant, clientAuth); - - AuthenticationResult result; - try { - result = callable.execute(); - callable.logResult(result, callable.headers); - } catch (Exception ex) { - log.error(LogHelper.createMessage("Execution of " + this.getClass() + " failed.", - callable.headers.getHeaderCorrelationIdValue()), ex); - - throw new CompletionException(ex); - } - return result; - }; + AcquireTokenByAuthorisationGrantSupplier supplier = new AcquireTokenByAuthorisationGrantSupplier(this, authGrant, clientAuth); CompletableFuture future = executorService != null ? CompletableFuture.supplyAsync(supplier, executorService) diff --git a/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java b/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java index 1cb7132f..eee6a85e 100644 --- a/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java +++ b/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java @@ -30,10 +30,9 @@ import org.slf4j.LoggerFactory; import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.Future; -import java.util.function.Supplier; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; public class PublicClientApplication extends ClientApplicationBase { @@ -76,7 +75,7 @@ public CompletableFuture acquireTokenByUsernamePassword(Se } /** - * Acquires a security token using integrated authentication flow. + * Acquires a security token using Windows integrated authentication flow. * * @param scopes scopes of the access request * @param username @@ -85,7 +84,7 @@ public CompletableFuture acquireTokenByUsernamePassword(Se * {@link AuthenticationResult} of the call. It contains Access * Token, Refresh Token and the Access Token's expiration time. */ - public CompletableFuture acquireTokenByKerberosAuth(Set scopes, String username) { + public CompletableFuture acquireTokenByIntegratedWindowsAuth(Set scopes, String username) { validateNotEmpty("scopes", scopes); validateNotBlank("username", username); @@ -94,38 +93,42 @@ public CompletableFuture acquireTokenByKerberosAuth(Set acquireDeviceCode(Set scopes) { + public CompletableFuture acquireTokenByDeviceCodeFlow(Set scopes, + Consumer deviceCodeConsumer) + { validateDeviceCodeRequestInput(scopes); - Supplier supplier = () -> - { - AcquireDeviceCodeCallable callable = - new AcquireDeviceCodeCallable(this, clientId, scopes); - - DeviceCode result; - try { - result = callable.execute(); - callable.logResult(result, callable.headers); - } catch (Exception ex) { - log.error(LogHelper.createMessage("Execution of " + this.getClass() + " failed.", - callable.headers.getHeaderCorrelationIdValue()), ex); - - throw new CompletionException(ex); - } - return result; - }; - - CompletableFuture future = + AtomicReference> futureReference = new AtomicReference<>(); + + AcquireTokenDeviceCodeFlowSupplier supplier = + new AcquireTokenDeviceCodeFlowSupplier + (this, clientAuthentication, scopes, deviceCodeConsumer, futureReference); + + CompletableFuture future = executorService != null ? CompletableFuture.supplyAsync(supplier, executorService) : CompletableFuture.supplyAsync(supplier); + futureReference.set(future); + return future; } @@ -138,30 +141,6 @@ private void validateDeviceCodeRequestInput(Set scopes) { } } - /** - * Acquires security token from the authority using an device code previously received. - * - * @param deviceCode The device code result received from calling acquireDeviceCode. - * @return A {@link CompletableFuture} object representing the {@link AuthenticationResult} of the call. - * It contains AccessToken, Refresh Token and the Access Token's expiration time. - * @throws AuthenticationException thrown if authorization is pending or another error occurred. - * If the errorCode of the exception is AdalErrorCode.AUTHORIZATION_PENDING, - * the call needs to be retried until the AccessToken is returned. - * DeviceCode.interval - The minimum amount of time in seconds that the client - * SHOULD wait between polling requests to the token endpoint - */ - public CompletableFuture acquireTokenByDeviceCode(DeviceCode deviceCode) - throws AuthenticationException { - - validateNotNull("deviceCode", deviceCode); - validateNotBlank("deviceCode.getScopes()", deviceCode.getScopes()); - - final MsalDeviceCodeAuthorizationGrant deviceCodeGrant = - new MsalDeviceCodeAuthorizationGrant(deviceCode, deviceCode.getScopes()); - - return this.acquireToken(deviceCodeGrant, clientAuthentication); - } - public static class Builder extends ClientApplicationBase.Builder{ /** * Constructor to create instance of Builder of PublicClientApplication diff --git a/src/samples/public-client/DeviceCodeFlow.java b/src/samples/public-client/DeviceCodeFlow.java index b9083fd7..54cb873d 100644 --- a/src/samples/public-client/DeviceCodeFlow.java +++ b/src/samples/public-client/DeviceCodeFlow.java @@ -25,29 +25,40 @@ import com.microsoft.aad.msal4j.PublicClientApplication; import java.util.Collections; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; public class DeviceCodeFlow { public static void main(String args[]) throws Exception { - AuthenticationResult result = getAccessTokenByDeviceCodeGrant(); - - System.out.println("Access Token - " + result.getAccessToken()); - System.out.println("Refresh Token - " + result.getRefreshToken()); - System.out.println("ID Token - " + result.getIdToken()); + getAccessTokenByDeviceCodeGrant(); } - private static AuthenticationResult getAccessTokenByDeviceCodeGrant() throws Exception { + private static void getAccessTokenByDeviceCodeGrant() throws Exception { PublicClientApplication app = new PublicClientApplication.Builder(TestData.PUBLIC_CLIENT_ID) .authority(TestData.AUTHORITY) .build(); - Future deviceCodeFuture = app.acquireDeviceCode(Collections.singleton(TestData.GRAPH_DEFAULT_SCOPE)); - DeviceCode deviceCode = deviceCodeFuture.get(); + Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { + System.out.println(deviceCode.getMessage()); + }; + + CompletableFuture future = + app.acquireTokenByDeviceCodeFlow(Collections.singleton(TestData.GRAPH_DEFAULT_SCOPE), deviceCodeConsumer); - Future futureAuthenticationResult = app.acquireTokenByDeviceCode(deviceCode); + future.handle((res, ex) -> { + if(ex != null) { + System.out.println("Oops! We have an exception of type - " + ex.getClass()); + System.out.println("message - " + ex.getMessage()); + return "Unknown!"; + } + System.out.println("Returned ok - " + res); - AuthenticationResult result = futureAuthenticationResult.get(); + System.out.println("Access Token - " + res.getAccessToken()); + System.out.println("Refresh Token - " + res.getRefreshToken()); + System.out.println("ID Token - " + res.getIdToken()); + return res; + }); - return result; + future.join(); } } diff --git a/src/samples/public-client/IntegratedAuthFlow.java b/src/samples/public-client/IntegratedWindowsAuthFlow.java similarity index 92% rename from src/samples/public-client/IntegratedAuthFlow.java rename to src/samples/public-client/IntegratedWindowsAuthFlow.java index b8cdea45..5a43eb6c 100644 --- a/src/samples/public-client/IntegratedAuthFlow.java +++ b/src/samples/public-client/IntegratedWindowsAuthFlow.java @@ -27,7 +27,7 @@ import java.util.Collections; import java.util.concurrent.Future; -public class IntegratedAuthFlow { +public class IntegratedWindowsAuthFlow { public static void main(String args[]) throws Exception { AuthenticationResult result = getAccessTokenByIntegratedAuth(); @@ -43,7 +43,7 @@ private static AuthenticationResult getAccessTokenByIntegratedAuth() throws Exce .build(); Future futureAuthenticationResult = - app.acquireTokenByKerberosAuth(Collections.singleton(TestData.GRAPH_DEFAULT_SCOPE), TestData.USER_NAME); + app.acquireTokenByIntegratedWindowsAuth(Collections.singleton(TestData.GRAPH_DEFAULT_SCOPE), TestData.USER_NAME); AuthenticationResult result = futureAuthenticationResult.get(); diff --git a/src/samples/public-client/UsernamePasswordFlowAsync.java b/src/samples/public-client/UsernamePasswordFlow.java similarity index 97% rename from src/samples/public-client/UsernamePasswordFlowAsync.java rename to src/samples/public-client/UsernamePasswordFlow.java index 48054121..97ec6aef 100644 --- a/src/samples/public-client/UsernamePasswordFlowAsync.java +++ b/src/samples/public-client/UsernamePasswordFlow.java @@ -27,7 +27,7 @@ import java.util.Collections; import java.util.concurrent.CompletableFuture; -public class UsernamePasswordFlowAsync { +public class UsernamePasswordFlow { public static void main(String args[]) throws Exception { getAccessTokenFromUserCredentials(); @@ -54,6 +54,6 @@ private static void getAccessTokenFromUserCredentials() throws Exception { return res; }); - Thread.sleep(3000); + future.join(); } } diff --git a/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java b/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java index f2f47e0e..ce46089c 100644 --- a/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java +++ b/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java @@ -30,7 +30,8 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; import com.nimbusds.oauth2.sdk.http.CommonContentTypes; @@ -51,7 +52,6 @@ import static com.microsoft.aad.msal4j.TestConfiguration.AAD_CLIENT_ID; import static com.microsoft.aad.msal4j.TestConfiguration.AAD_HOST_NAME; import static com.microsoft.aad.msal4j.TestConfiguration.AAD_RESOURCE_ID; -import static com.microsoft.aad.msal4j.TestConfiguration.AAD_TENANT_ENDPOINT; import static com.microsoft.aad.msal4j.TestConfiguration.AAD_TENANT_NAME; import static com.microsoft.aad.msal4j.TestConfiguration.ADFS_TENANT_ENDPOINT; @@ -113,8 +113,28 @@ public void deviceCodeFlowTest() throws Exception { PowerMock.replay(HttpHelper.class); - Future result = app.acquireDeviceCode(Collections.singleton(AAD_RESOURCE_ID)); - DeviceCode deviceCode = result.get(); + AtomicReference deviceCodeCorrelationId = new AtomicReference<>(); + + Consumer deviceCodeConsumer = (DeviceCode deviceCode) ->{ + + // validate returned Device Code object + Assert.assertNotNull(deviceCode); + Assert.assertNotNull(deviceCode.getUserCode(), "DW83JNP2P"); + Assert.assertNotNull(deviceCode.getDeviceCode(), "DAQABAAEAAADRNYRQ3dhRFEeqWvq-yi6QodK2pb1iAA"); + Assert.assertNotNull(deviceCode.getVerificationUrl(), "https://aka.ms/devicelogin"); + Assert.assertNotNull(deviceCode.getExpiresIn(), "900"); + Assert.assertNotNull(deviceCode.getInterval(), "5"); + Assert.assertEquals(deviceCode.getMessage(), "To sign in, use a web browser" + + " to open the page https://aka.ms/devicelogin and enter the code DW83JNP2P to authenticate."); + Assert.assertNotNull(deviceCode.getCorrelationId()); + + deviceCodeCorrelationId.set(deviceCode.getCorrelationId()); + }; + + PowerMock.replay(app); + + AuthenticationResult authResult = + app.acquireTokenByDeviceCodeFlow(Collections.singleton(AAD_RESOURCE_ID), deviceCodeConsumer).get(); // validate HTTP GET request used to get device code URL url = new URL(capturedUrl.getValue()); @@ -129,25 +149,9 @@ public void deviceCodeFlowTest() throws Exception { Assert.assertEquals(getQueryMap(url.getQuery()), expectedQueryParams); - // validate returned Device Code object - Assert.assertNotNull(deviceCode); - Assert.assertNotNull(deviceCode.getUserCode(), "DW83JNP2P"); - Assert.assertNotNull(deviceCode.getDeviceCode(), "DAQABAAEAAADRNYRQ3dhRFEeqWvq-yi6QodK2pb1iAA"); - Assert.assertNotNull(deviceCode.getVerificationUrl(), "https://aka.ms/devicelogin"); - Assert.assertNotNull(deviceCode.getExpiresIn(), "900"); - Assert.assertNotNull(deviceCode.getInterval(), "5"); - Assert.assertNotNull(deviceCode.getMessage(), "To sign in, use a web browser" + - " to open the page https://aka.ms/devicelogin and enter the code DW83JNP2P to authenticate."); - Assert.assertNotNull(deviceCode.getCorrelationId()); - - PowerMock.replay(app); - - Future authResult = app.acquireTokenByDeviceCode(deviceCode); - authResult.get(); - // make sure same correlation id is used for acquireDeviceCode and acquireTokenByDeviceCode calls Assert.assertEquals(capturedClientDataHttpHeaders.getValue().getReadonlyHeaderMap(). - get(ClientDataHttpHeaders.CORRELATION_ID_HEADER_NAME), deviceCode.getCorrelationId()); + get(ClientDataHttpHeaders.CORRELATION_ID_HEADER_NAME), deviceCodeCorrelationId.get()); Assert.assertNotNull(authResult); PowerMock.verify(); @@ -162,7 +166,7 @@ public void executeAcquireDeviceCode_AdfsAuthorityUsed_IllegalArgumentExceptionT .authority(ADFS_TENANT_ENDPOINT) .validateAuthority(false).build(); - app.acquireDeviceCode(Collections.singleton(AAD_RESOURCE_ID)); + app.acquireTokenByDeviceCodeFlow(Collections.singleton(AAD_RESOURCE_ID), (DeviceCode deviceCode) -> {}); } @Test