From b18682ea73a417c6ce744188df859d0e4d5c3fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Bj=C3=B8rnstad?= Date: Wed, 20 Mar 2024 00:01:12 +0100 Subject: [PATCH 1/2] Fix UnsupportedOperationException when not providing scopes to AzureIdentityAccessTokenProvider Fixes microsoftgraph/msgraph-sdk-java#1882 --- CHANGELOG.md | 2 + .../AzureIdentityAccessTokenProvider.java | 2 +- .../AzureIdentityAccessTokenProviderTest.java | 65 ++++++++++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b82841bd..f6d53ec9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Fixed a bug where not providing scopes to `AzureIdentityAccessTokenProvider` failed with `UnsupportedOperationException` when attempting to fetch the token. [microsoftgraph/msgraph-sdk-java#1882](https://github.com/microsoftgraph/msgraph-sdk-java/issues/1882) + ## [1.0.6] - 2023-03-04 ### Changed diff --git a/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java b/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java index 162d02747..572e2c5be 100644 --- a/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java +++ b/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java @@ -63,7 +63,7 @@ public AzureIdentityAccessTokenProvider( if (scopes == null) { _scopes = new ArrayList(); } else { - _scopes = Arrays.asList(scopes); + _scopes = new ArrayList<>(Arrays.asList(scopes)); } if (allowedHosts == null || allowedHosts.length == 0) { _hostValidator = new AllowedHostsValidator(); diff --git a/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java b/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java index 8b7ef127b..dcce0347f 100644 --- a/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java +++ b/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java @@ -1,19 +1,27 @@ package com.microsoft.kiota.authentication; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; public class AzureIdentityAccessTokenProviderTest { @@ -41,4 +49,57 @@ void testNonLocalhostHttpUrlIsInvalid(String urlString) { accessTokenProvider.getAuthorizationToken( new URI(urlString), new HashMap<>())); } + + @Test + void testKeepUserProvidedScopes() throws URISyntaxException { + var tokenCredential = mock(TokenCredential.class); + String[] userProvidedScopes = { + "https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Application.Read" + }; + var accessTokenProvider = + new AzureIdentityAccessTokenProvider( + tokenCredential, new String[] {}, userProvidedScopes); + assertScopes(tokenCredential, accessTokenProvider, userProvidedScopes); + } + + @Test + void testConfigureDefaultScopeWhenScopesNotProvided() throws URISyntaxException { + var tokenCredential = mock(TokenCredential.class); + var accessTokenProvider = + new AzureIdentityAccessTokenProvider(tokenCredential, new String[] {}); + assertScopes( + tokenCredential, + accessTokenProvider, + new String[] {"https://graph.microsoft.com/.default"}); + } + + @ParameterizedTest + @NullAndEmptySource + void testConfigureDefaultScopeWhenScopesNullOrEmpty(String[] nullOrEmptyUserProvidedScopes) + throws URISyntaxException { + var tokenCredential = mock(TokenCredential.class); + var accessTokenProvider = + new AzureIdentityAccessTokenProvider( + tokenCredential, new String[] {}, nullOrEmptyUserProvidedScopes); + assertScopes( + tokenCredential, + accessTokenProvider, + new String[] {"https://graph.microsoft.com/.default"}); + } + + private static void assertScopes( + TokenCredential tokenCredential, + AzureIdentityAccessTokenProvider accessTokenProvider, + String[] expectedScopes) + throws URISyntaxException { + var tokenRequestContextArgumentCaptor = ArgumentCaptor.forClass(TokenRequestContext.class); + when(tokenCredential.getTokenSync(tokenRequestContextArgumentCaptor.capture())) + .thenReturn(mock(AccessToken.class)); + + accessTokenProvider.getAuthorizationToken( + new URI("https://graph.microsoft.com"), new HashMap<>()); + + List actualScopes = tokenRequestContextArgumentCaptor.getValue().getScopes(); + assertLinesMatch(actualScopes, Arrays.asList(expectedScopes)); + } } From a96157223e3f30698cb7bda987223a7bf4e87b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Bj=C3=B8rnstad?= Date: Wed, 20 Mar 2024 16:40:39 +0100 Subject: [PATCH 2/2] Updated AzureIdentityAccessTokenProvider to be equivalent to the dotnet implementation with regard to internal handling of scopes * Updated to version 1.1.1 in gradle.properties * Updated to version 1.1.1 in UserAgentHandlerOption * Fixed junit assert usage. API is expected, actual. Not actual, expected --- .../AzureIdentityAccessTokenProvider.java | 15 ++++++++++----- .../AzureIdentityAccessTokenProviderTest.java | 2 +- .../options/UserAgentHandlerOption.java | 2 +- gradle.properties | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java b/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java index 572e2c5be..b95911616 100644 --- a/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java +++ b/components/authentication/azure/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java @@ -128,13 +128,18 @@ public AzureIdentityAccessTokenProvider( "com.microsoft.kiota.authentication.additional_claims_provided", decodedClaim != null && !decodedClaim.isEmpty()); - final TokenRequestContext context = new TokenRequestContext(); - if (_scopes.isEmpty()) { - _scopes.add(uri.getScheme() + "://" + uri.getHost() + "/.default"); + List scopes; + if (!_scopes.isEmpty()) { + scopes = new ArrayList<>(_scopes); + } else { + scopes = new ArrayList<>(); + scopes.add(uri.getScheme() + "://" + uri.getHost() + "/.default"); } - context.setScopes(_scopes); + + final TokenRequestContext context = new TokenRequestContext(); + context.setScopes(scopes); span.setAttribute( - "com.microsoft.kiota.authentication.scopes", String.join("|", _scopes)); + "com.microsoft.kiota.authentication.scopes", String.join("|", scopes)); if (decodedClaim != null && !decodedClaim.isEmpty()) { context.setClaims(decodedClaim); } diff --git a/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java b/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java index dcce0347f..3dd49bb1f 100644 --- a/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java +++ b/components/authentication/azure/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java @@ -100,6 +100,6 @@ private static void assertScopes( new URI("https://graph.microsoft.com"), new HashMap<>()); List actualScopes = tokenRequestContextArgumentCaptor.getValue().getScopes(); - assertLinesMatch(actualScopes, Arrays.asList(expectedScopes)); + assertLinesMatch(Arrays.asList(expectedScopes), actualScopes); } } diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java index 85f12d7d4..f39687827 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java @@ -13,7 +13,7 @@ public UserAgentHandlerOption() {} private boolean enabled = true; @Nonnull private String productName = "kiota-java"; - @Nonnull private String productVersion = "1.0.6"; + @Nonnull private String productVersion = "1.1.1"; /** * Gets the product name to be used in the user agent header diff --git a/gradle.properties b/gradle.properties index 5b73eeac9..ed1814fba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ org.gradle.caching=true mavenGroupId = com.microsoft.kiota mavenMajorVersion = 1 mavenMinorVersion = 1 -mavenPatchVersion = 0 +mavenPatchVersion = 1 mavenArtifactSuffix = #These values are used to run functional tests