From 739939e3aff84b577e48f0a46551ffd465490d96 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 18 May 2022 15:28:47 -0400 Subject: [PATCH 01/10] - adds base infrastructure for CAE in java abstractions --- .../kiota/authentication/AccessTokenProvider.java | 5 ++++- .../AnonymousAuthenticationProvider.java | 3 ++- .../authentication/AuthenticationProvider.java | 5 ++++- .../BaseBearerTokenAuthenticationProvider.java | 13 +++++++++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AccessTokenProvider.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AccessTokenProvider.java index 241f620f66..996c8bc3bc 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AccessTokenProvider.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AccessTokenProvider.java @@ -1,19 +1,22 @@ package com.microsoft.kiota.authentication; import java.net.URI; +import java.util.Map; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** Returns access tokens */ public interface AccessTokenProvider { /** * This method returns the access token for the provided url. * @param uri The target URI to get an access token for. + * @param additionalAuthenticationContext Additional authentication context to pass to the authentication library. * @return A CompletableFuture that holds the access token. */ @Nonnull - CompletableFuture getAuthorizationToken(@Nonnull final URI uri); + CompletableFuture getAuthorizationToken(@Nonnull final URI uri, @Nullable final Map additionalAuthenticationContext); /** * Returns the allowed hosts validator. * @return The allowed hosts validator. diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AnonymousAuthenticationProvider.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AnonymousAuthenticationProvider.java index 93cb21e3db..f274eaf7c2 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AnonymousAuthenticationProvider.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AnonymousAuthenticationProvider.java @@ -2,11 +2,12 @@ import com.microsoft.kiota.RequestInformation; +import java.util.Map; import java.util.concurrent.CompletableFuture; /** This authentication provider does not perform any authentication. */ public class AnonymousAuthenticationProvider implements AuthenticationProvider { - public CompletableFuture authenticateRequest(final RequestInformation request) { + public CompletableFuture authenticateRequest(final RequestInformation request, final Map additionalAuthenticationContext) { return CompletableFuture.completedFuture(null); } } \ No newline at end of file diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AuthenticationProvider.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AuthenticationProvider.java index cadcaf50af..9704cbab93 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AuthenticationProvider.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/AuthenticationProvider.java @@ -2,17 +2,20 @@ import com.microsoft.kiota.RequestInformation; +import java.util.Map; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** Authenticates the application request. */ public interface AuthenticationProvider { /** * Authenticates the application request. * @param request the request to authenticate. + * @param additionalAuthenticationContext Additional authentication context to pass to the authentication library. * @return a CompletableFuture to await for the authentication to be completed. */ @Nonnull - CompletableFuture authenticateRequest(@Nonnull final RequestInformation request); + CompletableFuture authenticateRequest(@Nonnull final RequestInformation request, @Nullable final Map additionalAuthenticationContext); } diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java index 68b6efb675..22bb105802 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java @@ -5,6 +5,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.CompletableFuture; +import java.util.Map; import java.util.Objects; import javax.annotation.Nonnull; @@ -16,8 +17,16 @@ public BaseBearerTokenAuthenticationProvider(@Nonnull final AccessTokenProvider } private final AccessTokenProvider accessTokenProvider; private final static String authorizationHeaderKey = "Authorization"; - public CompletableFuture authenticateRequest(final RequestInformation request) { + private final static String ClaimsKey = "claims"; + public CompletableFuture authenticateRequest(final RequestInformation request, final Map additionalAuthenticationContext) { Objects.requireNonNull(request); + + if (request.getRequestHeaders().keySet().contains(authorizationHeaderKey) && + additionalAuthenticationContext != null && + additionalAuthenticationContext.containsKey(ClaimsKey)) + { + request.removeRequestHeader(authorizationHeaderKey); + } if(!request.getRequestHeaders().keySet().contains(authorizationHeaderKey)) { final URI targetUri; try { @@ -25,7 +34,7 @@ public CompletableFuture authenticateRequest(final RequestInformation requ } catch (URISyntaxException e) { return CompletableFuture.failedFuture(e); } - return this.accessTokenProvider.getAuthorizationToken(targetUri) + return this.accessTokenProvider.getAuthorizationToken(targetUri, additionalAuthenticationContext) .thenApply(token -> { if(token != null && !token.isEmpty()) { // Not an error, just no need to authenticate as we might have been given an external URL from the main API (large file upload, etc.) From 6678225f99cf865d2e97fc44887d7f487f8f90a9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 18 May 2022 15:59:47 -0400 Subject: [PATCH 02/10] - adds claims set up for java azure access provider --- authentication/java/azure/lib/build.gradle | 1 + .../AzureIdentityAccessTokenProvider.java | 23 +++++++-- .../AzureIdentityAccessTokenProviderTest.java | 48 +++++++++++++++++++ .../kiota/authentication/LibraryTest.java | 13 ----- 4 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java delete mode 100644 authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/LibraryTest.java diff --git a/authentication/java/azure/lib/build.gradle b/authentication/java/azure/lib/build.gradle index 7d06d19e81..49e3e93261 100644 --- a/authentication/java/azure/lib/build.gradle +++ b/authentication/java/azure/lib/build.gradle @@ -29,6 +29,7 @@ repositories { dependencies { // Use JUnit Jupiter API for testing. testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation "org.mockito:mockito-core:3.+" // Use JUnit Jupiter Engine for testing. testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' diff --git a/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java b/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java index 7b9e693f87..450b322389 100644 --- a/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java +++ b/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java @@ -2,12 +2,15 @@ import java.net.URI; import java.util.List; +import java.util.Map; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Objects; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; @@ -41,17 +44,31 @@ public AzureIdentityAccessTokenProvider(@Nonnull final TokenCredential tokenCred _hostValidator = new AllowedHostsValidator(allowedHosts); } } + private final static String ClaimsKey = "claims"; @Nonnull - public CompletableFuture getAuthorizationToken(@Nonnull final URI uri) { + public CompletableFuture getAuthorizationToken(@Nonnull final URI uri, @Nullable final Map additionalAuthenticationContext) { if(!_hostValidator.isUrlHostValid(uri)) { return CompletableFuture.completedFuture(""); } if(!uri.getScheme().equalsIgnoreCase("https")) { return CompletableFuture.failedFuture(new IllegalArgumentException("Only https is supported")); } - return this.creds.getToken(new TokenRequestContext() {{ + + String decodedClaim = null; + + if(additionalAuthenticationContext != null && additionalAuthenticationContext.containsKey(ClaimsKey) && additionalAuthenticationContext.get(ClaimsKey) instanceof String) { + final String rawClaim = (String) additionalAuthenticationContext.get(ClaimsKey); + decodedClaim = new String(Base64.getDecoder().decode(rawClaim)); + } + + final TokenRequestContext context = new TokenRequestContext() {{ this.setScopes(_scopes); - }}).toFuture().thenApply(r -> r.getToken()); + + }}; + if(decodedClaim != null) { + context.setClaims(decodedClaim); + } + return this.creds.getToken(context).toFuture().thenApply(r -> r.getToken()); } @Nonnull public AllowedHostsValidator getAllowedHostsValidator() { diff --git a/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java b/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java new file mode 100644 index 0000000000..25f9ace9c5 --- /dev/null +++ b/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java @@ -0,0 +1,48 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package com.microsoft.kiota.authentication; + +import org.junit.jupiter.api.Test; + +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; + +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; +import com.microsoft.kiota.HttpMethod; +import com.microsoft.kiota.RequestInformation; + +class AzureIdentityAccessTokenProviderTest { + @Test void testAddsClaimsToTheTokenContext() throws URISyntaxException { + final var credentialMock = mock(TokenCredential.class); + when(credentialMock.getToken(any(TokenRequestContext.class))).thenAnswer(r -> { + final var context = (TokenRequestContext) r.getArgument(0); + assertNotNull(context.getClaims()); + return Mono.just(mock(AccessToken.class)); + }); + final var authenticationProvider = new AzureIdentityAuthenticationProvider(credentialMock, null, "User.Read"); + final var testRequest = new RequestInformation() {{ + this.httpMethod = HttpMethod.GET; + this.setUri(new URI("https://graph.microsoft.com/v1.0/me")); + }}; + final var additionalContext = new HashMap() {{ + this.put("claims", "eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTY1MjgxMzUwOCJ9fX0="); + }}; + authenticationProvider.authenticateRequest(testRequest, additionalContext); + + verify(credentialMock, times(1)).getToken(any(TokenRequestContext.class)); + } +} diff --git a/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/LibraryTest.java b/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/LibraryTest.java deleted file mode 100644 index ba6a57985a..0000000000 --- a/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/LibraryTest.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This Java source file was generated by the Gradle 'init' task. - */ -package com.microsoft.kiota.authentication; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -class LibraryTest { - @Test void testSomeLibraryMethod() { - assertTrue(true, "someLibraryMethod should return 'true'"); - } -} From 873ee785ad65a8b83197fabe2638293e6efa4009 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 08:16:06 -0400 Subject: [PATCH 03/10] - removed unused import --- .../authentication/AzureIdentityAccessTokenProviderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java b/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java index 25f9ace9c5..5da7c653f5 100644 --- a/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java +++ b/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java @@ -9,7 +9,6 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; From 50bd73fe975ee6aeee25eba1f8447a55d72bdf95 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 10:11:43 -0400 Subject: [PATCH 04/10] - aligns mockito reference Signed-off-by: Vincent Biret --- authentication/java/azure/lib/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/authentication/java/azure/lib/build.gradle b/authentication/java/azure/lib/build.gradle index 49e3e93261..d9ea1d11fe 100644 --- a/authentication/java/azure/lib/build.gradle +++ b/authentication/java/azure/lib/build.gradle @@ -29,7 +29,8 @@ repositories { dependencies { // Use JUnit Jupiter API for testing. testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' - testImplementation "org.mockito:mockito-core:3.+" + testImplementation 'org.mockito:mockito-inline:4.5.1' + // Use JUnit Jupiter Engine for testing. testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' From 7320d8dae3cc15d3578dfbd0512a2a682becf338 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 13:26:06 -0400 Subject: [PATCH 05/10] - adds support for CAE in java HTTP Signed-off-by: Vincent Biret --- .../AzureIdentityAccessTokenProviderTest.java | 5 +- .../kiota/http/OkHttpRequestAdapter.java | 61 ++++++++++++++++--- .../kiota/http/OkHttpRequestAdapterTest.java | 45 +++++++++++++- 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java b/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java index 5da7c653f5..6d729716e2 100644 --- a/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java +++ b/authentication/java/azure/lib/src/test/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProviderTest.java @@ -25,11 +25,12 @@ import com.microsoft.kiota.RequestInformation; class AzureIdentityAccessTokenProviderTest { - @Test void testAddsClaimsToTheTokenContext() throws URISyntaxException { + @Test + void testAddsClaimsToTheTokenContext() throws URISyntaxException { final var credentialMock = mock(TokenCredential.class); when(credentialMock.getToken(any(TokenRequestContext.class))).thenAnswer(r -> { final var context = (TokenRequestContext) r.getArgument(0); - assertNotNull(context.getClaims()); + assertEquals(context.getClaims(), "{\"access_token\":{\"nbf\":{\"essential\":true, \"value\":\"1652813508\"}}}"); return Mono.just(mock(AccessToken.class)); }); final var authenticationProvider = new AzureIdentityAuthenticationProvider(credentialMock, null, "User.Read"); diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index b918bc60be..2d687a8ad3 100644 --- a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -9,6 +9,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.Period; +import java.util.regex.Pattern; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -102,7 +103,7 @@ public CompletableFuture> sendC Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); Objects.requireNonNull(factory, "parameter factory cannot be null"); - return this.getHttpResponseMessage(requestInfo) + return this.getHttpResponseMessage(requestInfo, null) .thenCompose(response -> { if(responseHandler == null) { try { @@ -130,7 +131,7 @@ public CompletableFuture sendAsync(@Nonn Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); Objects.requireNonNull(factory, "parameter factory cannot be null"); - return this.getHttpResponseMessage(requestInfo) + return this.getHttpResponseMessage(requestInfo, null) .thenCompose(response -> { if(responseHandler == null) { try { @@ -158,7 +159,7 @@ private String getMediaTypeAndSubType(final MediaType mediaType) { } @Nonnull public CompletableFuture sendPrimitiveAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings) { - return this.getHttpResponseMessage(requestInfo) + return this.getHttpResponseMessage(requestInfo, null) .thenCompose(response -> { if(responseHandler == null) { try { @@ -226,7 +227,7 @@ public CompletableFuture sendPrimitiveAsync(@Nonnull fina public CompletableFuture> sendPrimitiveCollectionAsync(@Nonnull final RequestInformation requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler, @Nullable final HashMap> errorMappings) { Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); - return this.getHttpResponseMessage(requestInfo) + return this.getHttpResponseMessage(requestInfo, null) .thenCompose(response -> { if(responseHandler == null) { try { @@ -288,10 +289,15 @@ private Response throwFailedResponse(final Response response, final HashMap getHttpResponseMessage(@Nonnull final RequestInformation requestInfo) { + private final static String claimsKey = "claims"; + private CompletableFuture getHttpResponseMessage(@Nonnull final RequestInformation requestInfo, @Nullable final String claims) { Objects.requireNonNull(requestInfo, "parameter requestInfo cannot be null"); this.setBaseUrlForRequestInformation(requestInfo); - return this.authProvider.authenticateRequest(requestInfo).thenCompose(x -> { + final Map additionalContext = new HashMap<>(); + if(claims != null && !claims.isEmpty()) { + additionalContext.put(claimsKey, claims); + } + return this.authProvider.authenticateRequest(requestInfo, additionalContext).thenCompose(x -> { try { final OkHttpCallbackFutureWrapper wrapper = new OkHttpCallbackFutureWrapper(); this.client.newCall(getRequestFromRequestInformation(requestInfo)).enqueue(wrapper); @@ -301,8 +307,47 @@ private CompletableFuture getHttpResponseMessage(@Nonnull final Reques result.completeExceptionally(ex); return result; } - }); - + }).thenCompose(x -> this.retryCAEResponseIfRequired(x, requestInfo, claims)); + } + private final static Pattern bearerPattern = Pattern.compile("^Bearer\\s.*", Pattern.CASE_INSENSITIVE); + private final static Pattern claimsPattern = Pattern.compile("\\s?claims=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); + private CompletableFuture retryCAEResponseIfRequired(@Nonnull final Response response, @Nonnull final RequestInformation requestInfo, @Nullable final String claims) { + final var responseClaims = this.getClaimsFromResponse(response, requestInfo, claims); + if (responseClaims != null && !responseClaims.isEmpty()) { + if(requestInfo.content != null && requestInfo.content.markSupported()) { + requestInfo.content.reset(); + } + return this.getHttpResponseMessage(requestInfo, claims); + } + + return CompletableFuture.completedFuture(response); + } + String getClaimsFromResponse(@Nonnull final Response response, @Nonnull final RequestInformation requestInfo, @Nullable final String claims) { + if(response.code() == 401 && + (claims == null || claims.isEmpty()) && // we avoid infinite loops and retry only once + (requestInfo.content == null || requestInfo.content.markSupported())) { + final var authenticateHeader = response.headers("WWW-Authenticate"); + if(authenticateHeader != null && !authenticateHeader.isEmpty()) { + String rawHeaderValue = null; + for(final var authenticateEntry: authenticateHeader) { + final var matcher = bearerPattern.matcher(authenticateEntry); + if(matcher.matches()) { + rawHeaderValue = authenticateEntry.replaceFirst("^Bearer\\s", ""); + break; + } + } + if (rawHeaderValue != null) { + final var parameters = rawHeaderValue.split(","); + for(final var parameter: parameters) { + final var matcher = claimsPattern.matcher(parameter); + if(matcher.matches()) { + return matcher.group(1); + } + } + } + } + } + return null; } private void setBaseUrlForRequestInformation(@Nonnull final RequestInformation requestInfo) { Objects.requireNonNull(requestInfo); diff --git a/http/java/okhttp/lib/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java b/http/java/okhttp/lib/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java index 2aa595ae3b..67f8c1b38a 100644 --- a/http/java/okhttp/lib/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java +++ b/http/java/okhttp/lib/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java @@ -6,8 +6,49 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Response; +import okhttp3.Request; + +import java.lang.InterruptedException; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletableFuture; +import java.net.URI; +import java.net.URISyntaxException; + +import com.microsoft.kiota.authentication.AuthenticationProvider; +import com.microsoft.kiota.RequestInformation; +import com.microsoft.kiota.HttpMethod; + + class OkHttpRequestAdapterTest { - @Test void testSomeHttpCoreMethod() { - assertTrue(true, "someLibraryMethod should return 'true'"); + @Test + void testGetsClaims() throws IOException, URISyntaxException, InterruptedException, ExecutionException { + final var authenticationProvider = mock(AuthenticationProvider.class); + final var response = new Response.Builder() + .code(401) + .message("Unauthenticated") + .header("WWW-Authenticate", "Bearer realm=\"\", authorization_uri=\"https://login.microsoftonline.com/common/oauth2/authorize\", client_id=\"00000003-0000-0000-c000-000000000000\", error=\"insufficient_claims\", claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTY1MjgxMzUwOCJ9fX0=\"") + .protocol(Protocol.HTTP_2) + .request(new Request.Builder().url("https://example.com").build()) + .build(); + + final var requestAdapter = new OkHttpRequestAdapter(authenticationProvider); + final var requestInfo = new RequestInformation() {{ + this.httpMethod = HttpMethod.GET; + this.setUri(new URI("https://graph.microsoft.com/v1.0/me")); + }}; + final var result = requestAdapter.getClaimsFromResponse(response, requestInfo, null); + assertEquals("eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTY1MjgxMzUwOCJ9fX0=", result); } } From 820ab0e7339a1e32fa3a8066672607e5811665c1 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 13:41:00 -0400 Subject: [PATCH 06/10] - adds changelog entry for CAE support in java Signed-off-by: Vincent Biret --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ab526e0f..308a32a7d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for discriminator downcast in PHP. [#1255](https://github.com/microsoft/kiota/issues/1255) - Added support for multiple collections indexing under the same parent. - Added code exclusions placeholder in the generation. (oneOf) +- Added support for continuous access evaluation in Java. [#1179](https://github.com/microsoft/kiota/issues/1179) ### Changed From af3d15bdde61c590023329ae167844cbd231f0df Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 14:47:57 -0400 Subject: [PATCH 07/10] - fixes null ref exp in parameters names handler in java Signed-off-by: Vincent Biret --- .../kiota/http/middleware/ParametersNameDecodingHandler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/middleware/ParametersNameDecodingHandler.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/middleware/ParametersNameDecodingHandler.java index 2d4a8386f7..7fda95e6fe 100644 --- a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/middleware/ParametersNameDecodingHandler.java +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/middleware/ParametersNameDecodingHandler.java @@ -40,6 +40,9 @@ public Response intercept(@Nonnull final Chain chain) throws IOException { return chain.proceed(request); } var query = originalUri.query(); + if (query == null || query.isEmpty()) { + return chain.proceed(request); + } final var symbolsToReplace = new ArrayList>(nameOption.parametersToDecode.length); for (final char charToReplace : nameOption.parametersToDecode) { symbolsToReplace.add(new SimpleEntry("%" + String.format("%x", (int)charToReplace), String.valueOf(charToReplace))); From 856dcce6a07e89864e3cc1297395d2d881352745 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 14:48:56 -0400 Subject: [PATCH 08/10] - fixes missing connection close in CAE retry java Signed-off-by: Vincent Biret --- .../kiota/http/OkHttpRequestAdapter.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index 2d687a8ad3..ddabe78c79 100644 --- a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -251,10 +251,11 @@ public CompletableFuture> sendPrimitiveCollectio }); } private ParseNode getRootParseNode(final Response response) throws IOException { - final ResponseBody body = response.body(); - try (final InputStream rawInputStream = body.byteStream()) { - final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); - return rootNode; + try (final ResponseBody body = response.body()) { + try (final InputStream rawInputStream = body.byteStream()) { + final ParseNode rootNode = pNodeFactory.getParseNode(getMediaTypeAndSubType(body.contentType()), rawInputStream); + return rootNode; + } } } private boolean shouldReturnNull(final Response response) { @@ -315,8 +316,13 @@ private CompletableFuture retryCAEResponseIfRequired(@Nonnull final Re final var responseClaims = this.getClaimsFromResponse(response, requestInfo, claims); if (responseClaims != null && !responseClaims.isEmpty()) { if(requestInfo.content != null && requestInfo.content.markSupported()) { - requestInfo.content.reset(); + try { + requestInfo.content.reset(); + } catch (IOException ex) { + return CompletableFuture.failedFuture(ex); + } } + response.close(); return this.getHttpResponseMessage(requestInfo, claims); } From 42a1b419f8062ff8452db59c30daf83bec91eb45 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 16:09:35 -0400 Subject: [PATCH 09/10] - fixes infinite loop in CAE implementation for java Signed-off-by: Vincent Biret --- .../kiota/authentication/AzureIdentityAccessTokenProvider.java | 3 +-- .../java/com/microsoft/kiota/http/OkHttpRequestAdapter.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java b/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java index 450b322389..57c96dd5ea 100644 --- a/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java +++ b/authentication/java/azure/lib/src/main/java/com/microsoft/kiota/authentication/AzureIdentityAccessTokenProvider.java @@ -63,9 +63,8 @@ public CompletableFuture getAuthorizationToken(@Nonnull final URI uri, @ final TokenRequestContext context = new TokenRequestContext() {{ this.setScopes(_scopes); - }}; - if(decodedClaim != null) { + if(decodedClaim != null && !decodedClaim.isEmpty()) { context.setClaims(decodedClaim); } return this.creds.getToken(context).toFuture().thenApply(r -> r.getToken()); diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index ddabe78c79..c60d5c9d01 100644 --- a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -323,7 +323,7 @@ private CompletableFuture retryCAEResponseIfRequired(@Nonnull final Re } } response.close(); - return this.getHttpResponseMessage(requestInfo, claims); + return this.getHttpResponseMessage(requestInfo, responseClaims); } return CompletableFuture.completedFuture(response); From 906d8f7c5b9b10fa022c914b7336e29c597e4ec3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 16:11:51 -0400 Subject: [PATCH 10/10] - bumps java libs version numbers Signed-off-by: Vincent Biret --- abstractions/java/lib/build.gradle | 2 +- authentication/java/azure/lib/build.gradle | 4 ++-- http/java/okhttp/lib/build.gradle | 4 ++-- serialization/java/json/lib/build.gradle | 4 ++-- serialization/java/text/lib/build.gradle | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/abstractions/java/lib/build.gradle b/abstractions/java/lib/build.gradle index 9366aad46c..8e15f4ec34 100644 --- a/abstractions/java/lib/build.gradle +++ b/abstractions/java/lib/build.gradle @@ -49,7 +49,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-abstractions' - version '1.0.35' + version '1.0.36' from(components.java) } } diff --git a/authentication/java/azure/lib/build.gradle b/authentication/java/azure/lib/build.gradle index d9ea1d11fe..177705c326 100644 --- a/authentication/java/azure/lib/build.gradle +++ b/authentication/java/azure/lib/build.gradle @@ -38,7 +38,7 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:31.1-jre' api 'com.azure:azure-core:1.28.0' - api 'com.microsoft.kiota:kiota-abstractions:1.0.25' + api 'com.microsoft.kiota:kiota-abstractions:1.0.36' } publishing { @@ -55,7 +55,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-authentication-azure' - version '1.0.6' + version '1.0.7' from(components.java) } } diff --git a/http/java/okhttp/lib/build.gradle b/http/java/okhttp/lib/build.gradle index 6453ec2ec4..4314437665 100644 --- a/http/java/okhttp/lib/build.gradle +++ b/http/java/okhttp/lib/build.gradle @@ -39,7 +39,7 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:31.1-jre' api 'com.squareup.okhttp3:okhttp:4.9.3' - api 'com.microsoft.kiota:kiota-abstractions:1.0.34' + api 'com.microsoft.kiota:kiota-abstractions:1.0.36' } publishing { @@ -56,7 +56,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-http-okhttplibrary' - version '1.0.20' + version '1.0.21' from(components.java) } } diff --git a/serialization/java/json/lib/build.gradle b/serialization/java/json/lib/build.gradle index 696351a718..ac60e888ea 100644 --- a/serialization/java/json/lib/build.gradle +++ b/serialization/java/json/lib/build.gradle @@ -36,7 +36,7 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:31.1-jre' api 'com.google.code.gson:gson:2.9.0' - api 'com.microsoft.kiota:kiota-abstractions:1.0.31' + api 'com.microsoft.kiota:kiota-abstractions:1.0.36' } publishing { @@ -53,7 +53,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-serialization-json' - version '1.0.10' + version '1.0.11' from(components.java) } } diff --git a/serialization/java/text/lib/build.gradle b/serialization/java/text/lib/build.gradle index 8a625835be..9d1fb7a977 100644 --- a/serialization/java/text/lib/build.gradle +++ b/serialization/java/text/lib/build.gradle @@ -35,7 +35,7 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:31.1-jre' - api 'com.microsoft.kiota:kiota-abstractions:1.0.29' + api 'com.microsoft.kiota:kiota-abstractions:1.0.36' } publishing { @@ -52,7 +52,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-serialization-text' - version '1.0.9' + version '1.0.10' from(components.java) } }