From c2594f5d52acb26cb93fc0871853e4ecdebbe071 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 00:30:40 +0300 Subject: [PATCH 01/16] feat: abstracts re-usable cae claims extraction to separate class --- .../ContinuousAccessEvaluationClaims.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java new file mode 100644 index 000000000..ed17efac4 --- /dev/null +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java @@ -0,0 +1,47 @@ +package com.microsoft.kiota.http; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import okhttp3.Response; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ContinuousAccessEvaluationClaims { + + private static final Pattern bearerPattern = + Pattern.compile("^Bearer\\s.*", Pattern.CASE_INSENSITIVE); + private static final Pattern claimsPattern = + Pattern.compile("\\s?claims=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); + + private static final String wwwAuthenticateHeader = "WWW-Authenticate"; + + public static @Nullable String getClaimsFromResponse(@Nonnull Response response) { + if (response == null || response.code() != 401) { + return null; + } + final List authenticateHeader = response.headers(wwwAuthenticateHeader); + if (!authenticateHeader.isEmpty()) { + String rawHeaderValue = null; + for (final String authenticateEntry : authenticateHeader) { + final Matcher matcher = bearerPattern.matcher(authenticateEntry); + if (matcher.matches()) { + rawHeaderValue = authenticateEntry.replaceFirst("^Bearer\\s", ""); + break; + } + } + if (rawHeaderValue != null) { + final String[] parameters = rawHeaderValue.split(","); + for (final String parameter : parameters) { + final Matcher matcher = claimsPattern.matcher(parameter); + if (matcher.matches()) { + return matcher.group(1); + } + } + } + } + return null; + } +} From 16b6396d8a42f67172b7951e7d09c4273f7e2629 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 00:33:58 +0300 Subject: [PATCH 02/16] Update request adapter to use abstracted cae claims class --- .../kiota/http/OkHttpRequestAdapter.java | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index 028afe64c..3bfae11ff 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -753,11 +753,6 @@ private String getHeaderValue(final Response response, String key) { return null; } - private static final Pattern bearerPattern = - Pattern.compile("^Bearer\\s.*", Pattern.CASE_INSENSITIVE); - private static final Pattern claimsPattern = - Pattern.compile("\\s?claims=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); - /** Key used for events when an authentication challenge is returned by the API */ @Nonnull public static final String authenticateChallengedEventKey = "com.microsoft.kiota.authenticate_challenge_received"; @@ -804,26 +799,7 @@ String getClaimsFromResponse( && (claims == null || claims.isEmpty()) && // we avoid infinite loops and retry only once (requestInfo.content == null || requestInfo.content.markSupported())) { - final List authenticateHeader = response.headers("WWW-Authenticate"); - if (!authenticateHeader.isEmpty()) { - String rawHeaderValue = null; - for (final String authenticateEntry : authenticateHeader) { - final Matcher matcher = bearerPattern.matcher(authenticateEntry); - if (matcher.matches()) { - rawHeaderValue = authenticateEntry.replaceFirst("^Bearer\\s", ""); - break; - } - } - if (rawHeaderValue != null) { - final String[] parameters = rawHeaderValue.split(","); - for (final String parameter : parameters) { - final Matcher matcher = claimsPattern.matcher(parameter); - if (matcher.matches()) { - return matcher.group(1); - } - } - } - } + return ContinuousAccessEvaluationClaims.getClaimsFromResponse(response); } return null; } From c8aea76a172d21ec87d1a162c6306a54bdc6263b Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 01:05:42 +0300 Subject: [PATCH 03/16] feat: Adds AuthorizationHandler initialized withauth provider --- .../http/middleware/AuthorizationHandler.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java new file mode 100644 index 000000000..6b5fc3147 --- /dev/null +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -0,0 +1,125 @@ +package com.microsoft.kiota.http.middleware; + +import com.microsoft.kiota.RequestInformation; +import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; +import com.microsoft.kiota.http.ContinuousAccessEvaluationClaims; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import static com.microsoft.kiota.http.TelemetrySemanticConventions.HTTP_REQUEST_RESEND_COUNT; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class AuthorizationHandler implements Interceptor { + + private final BaseBearerTokenAuthenticationProvider authenticationProvider; + private static final String authorizationHeaderKey = "Authorization"; + + /** + * Instantiates a new AuthorizationHandler. + * @param authenticationProvider the authentication provider. + */ + public AuthorizationHandler(BaseBearerTokenAuthenticationProvider authenticationProvider) { + this.authenticationProvider = authenticationProvider; + } + + @Override + public Response intercept(final Chain chain) throws IOException { + Objects.requireNonNull(chain, "parameter chain cannot be null"); + final Request request = chain.request(); + + final Span span = + ObservabilityHelper.getSpanForRequest(request, "AuthorizationHandler_Intercept"); + Scope scope = null; + if (span != null) { + scope = span.makeCurrent(); + span.setAttribute("com.microsoft.kiota.handler.authorization.enable", true); + } + + try { + // Auth provider already added auth header + if (request.headers().names().contains(authorizationHeaderKey)) { + span.setAttribute("com.microsoft.kiota.handler.authorization.token_present", true); + return chain.proceed(request); + } + + authenticateRequest(request, null, span); + Response response = chain.proceed(chain.request()); + + if (response != null && response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) { + return response; + } + + // Attempt CAE claims challenge + String claims = ContinuousAccessEvaluationClaims.getClaimsFromResponse(response); + if (claims == null || claims.isEmpty()) { + return response; + } + + span.addEvent("com.microsoft.kiota.handler.authorization.challenge_received"); + + // We cannot replay one-shot requests after claims challenge + boolean isRequestBodyOneShot = request != null && request.body() != null && request.body().isOneShot(); + if (isRequestBodyOneShot) { + return response; + } + + response.close(); + final HashMap additionalContext = new HashMap<>(); + additionalContext.put("claims", claims); + // Retry claims challenge only once + authenticateRequest(request, additionalContext, span); + span.setAttribute(HTTP_REQUEST_RESEND_COUNT, 1); + return chain.proceed(request); + } finally { + if (scope != null) { + scope.close(); + } + if (span != null) { + span.end(); + } + } + } + + private void authenticateRequest( + @Nonnull Request request, + @Nullable Map additionalAuthenticationContext, + @Nonnull Span span) { + final RequestInformation requestInformation = getRequestInformation(request); + authenticationProvider.authenticateRequest( + requestInformation, additionalAuthenticationContext); + // Update native request with headers added to requestInformation + if (requestInformation.headers.containsKey(authorizationHeaderKey)) { + span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); + Set authorizationHeaderValues = + requestInformation.headers.get(authorizationHeaderKey); + if (!authorizationHeaderValues.isEmpty()) { + Request.Builder requestBuilder = request.newBuilder(); + for (String value : authorizationHeaderValues) { + requestBuilder.addHeader(authorizationHeaderKey, value); + } + } + } + } + + private RequestInformation getRequestInformation(final Request request) { + RequestInformation requestInformation = new RequestInformation(); + requestInformation.setUri(request.url().uri()); + for (String headerName : request.headers().names()) { + requestInformation.headers.add(headerName, request.header(headerName)); + } + return requestInformation; + } +} From 71d9d1cd3ef039b099d7ac478dbdda22dc3c888c Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 01:10:07 +0300 Subject: [PATCH 04/16] feat: Adds KiotaClientFactory method to add auth handler to middleware chain --- .../microsoft/kiota/http/KiotaClientFactory.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java index cb81973c8..ccede3cfe 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java @@ -1,5 +1,7 @@ package com.microsoft.kiota.http; +import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; +import com.microsoft.kiota.http.middleware.AuthorizationHandler; import com.microsoft.kiota.http.middleware.HeadersInspectionHandler; import com.microsoft.kiota.http.middleware.ParametersNameDecodingHandler; import com.microsoft.kiota.http.middleware.RedirectHandler; @@ -13,6 +15,8 @@ import okhttp3.OkHttpClient; import java.time.Duration; +import java.util.Arrays; +import java.util.List; /** This class is used to build the HttpClient instance used by the core service. */ public class KiotaClientFactory { @@ -23,7 +27,7 @@ private KiotaClientFactory() {} * @return an OkHttpClient Builder instance. */ @Nonnull public static OkHttpClient.Builder create() { - return create(null); + return create(createDefaultInterceptors()); } /** @@ -48,6 +52,13 @@ private KiotaClientFactory() {} return builder; } + @Nonnull public static OkHttpClient.Builder create( + @Nonnull BaseBearerTokenAuthenticationProvider authenticationProvider) { + List interceptors = Arrays.asList(createDefaultInterceptors()); + interceptors.add(new AuthorizationHandler(authenticationProvider)); + return create((Interceptor[]) interceptors.toArray()); + } + /** * Creates the default interceptors for the client. * @return an array of interceptors. From e2331a4c4163e95daa8c1a1ee6ba472b753a829e Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 01:10:59 +0300 Subject: [PATCH 05/16] Style enhancements --- .../com/microsoft/kiota/http/OkHttpRequestAdapter.java | 1 - .../kiota/http/middleware/AuthorizationHandler.java | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java index 3bfae11ff..b634cf7da 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/OkHttpRequestAdapter.java @@ -48,7 +48,6 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** RequestAdapter implementation for OkHttp */ diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 6b5fc3147..5c5af32ea 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -1,11 +1,14 @@ package com.microsoft.kiota.http.middleware; +import static com.microsoft.kiota.http.TelemetrySemanticConventions.HTTP_REQUEST_RESEND_COUNT; + import com.microsoft.kiota.RequestInformation; import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; import com.microsoft.kiota.http.ContinuousAccessEvaluationClaims; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Scope; + import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -13,8 +16,6 @@ import okhttp3.Request; import okhttp3.Response; -import static com.microsoft.kiota.http.TelemetrySemanticConventions.HTTP_REQUEST_RESEND_COUNT; - import java.io.IOException; import java.net.HttpURLConnection; import java.util.HashMap; @@ -71,7 +72,8 @@ public Response intercept(final Chain chain) throws IOException { span.addEvent("com.microsoft.kiota.handler.authorization.challenge_received"); // We cannot replay one-shot requests after claims challenge - boolean isRequestBodyOneShot = request != null && request.body() != null && request.body().isOneShot(); + boolean isRequestBodyOneShot = + request != null && request.body() != null && request.body().isOneShot(); if (isRequestBodyOneShot) { return response; } @@ -100,7 +102,7 @@ private void authenticateRequest( final RequestInformation requestInformation = getRequestInformation(request); authenticationProvider.authenticateRequest( requestInformation, additionalAuthenticationContext); - // Update native request with headers added to requestInformation + // Update native request with auth header added to requestInformation if (requestInformation.headers.containsKey(authorizationHeaderKey)) { span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); Set authorizationHeaderValues = From 41f12e222bd02c294ab06a718b78341671656931 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Thu, 26 Sep 2024 12:04:10 +0300 Subject: [PATCH 06/16] Use auth provider's AccessTokenProvider to authenticate --- ...BaseBearerTokenAuthenticationProvider.java | 4 ++ .../ContinuousAccessEvaluationClaims.java | 2 +- .../kiota/http/KiotaClientFactory.java | 2 +- .../http/middleware/AuthorizationHandler.java | 49 ++++++++----------- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java b/components/abstractions/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java index d8dda1c47..819c63bf9 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/authentication/BaseBearerTokenAuthenticationProvider.java @@ -50,4 +50,8 @@ public void authenticateRequest( } } } + + public @Nonnull AccessTokenProvider getAccessTokenProvider() { + return this.accessTokenProvider; + } } diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java index ed17efac4..b6e993c15 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java @@ -9,7 +9,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class ContinuousAccessEvaluationClaims { +public final class ContinuousAccessEvaluationClaims { private static final Pattern bearerPattern = Pattern.compile("^Bearer\\s.*", Pattern.CASE_INSENSITIVE); diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java index ccede3cfe..fb56a9408 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java @@ -53,7 +53,7 @@ private KiotaClientFactory() {} } @Nonnull public static OkHttpClient.Builder create( - @Nonnull BaseBearerTokenAuthenticationProvider authenticationProvider) { + @Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { List interceptors = Arrays.asList(createDefaultInterceptors()); interceptors.add(new AuthorizationHandler(authenticationProvider)); return create((Interceptor[]) interceptors.toArray()); diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 5c5af32ea..36b8068c1 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -2,7 +2,7 @@ import static com.microsoft.kiota.http.TelemetrySemanticConventions.HTTP_REQUEST_RESEND_COUNT; -import com.microsoft.kiota.RequestInformation; +import com.microsoft.kiota.authentication.AccessTokenProvider; import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; import com.microsoft.kiota.http.ContinuousAccessEvaluationClaims; @@ -21,8 +21,12 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.Set; +/** + * This interceptor is responsible for adding the Authorization header to the request + * if the header is not already present. It also handles Continuous Access Evaluation (CAE) claims + * challenges if the token request was made using this interceptor. It does this using the provided AuthenticationProvider + */ public class AuthorizationHandler implements Interceptor { private final BaseBearerTokenAuthenticationProvider authenticationProvider; @@ -32,8 +36,8 @@ public class AuthorizationHandler implements Interceptor { * Instantiates a new AuthorizationHandler. * @param authenticationProvider the authentication provider. */ - public AuthorizationHandler(BaseBearerTokenAuthenticationProvider authenticationProvider) { - this.authenticationProvider = authenticationProvider; + public AuthorizationHandler(@Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { + this.authenticationProvider = Objects.requireNonNull(null, "AuthenticationProvider cannot be null"); } @Override @@ -96,32 +100,19 @@ public Response intercept(final Chain chain) throws IOException { } private void authenticateRequest( - @Nonnull Request request, - @Nullable Map additionalAuthenticationContext, - @Nonnull Span span) { - final RequestInformation requestInformation = getRequestInformation(request); - authenticationProvider.authenticateRequest( - requestInformation, additionalAuthenticationContext); - // Update native request with auth header added to requestInformation - if (requestInformation.headers.containsKey(authorizationHeaderKey)) { - span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); - Set authorizationHeaderValues = - requestInformation.headers.get(authorizationHeaderKey); - if (!authorizationHeaderValues.isEmpty()) { - Request.Builder requestBuilder = request.newBuilder(); - for (String value : authorizationHeaderValues) { - requestBuilder.addHeader(authorizationHeaderKey, value); - } - } - } - } + @Nonnull final Request request, + @Nullable final Map additionalAuthenticationContext, + @Nonnull final Span span) { - private RequestInformation getRequestInformation(final Request request) { - RequestInformation requestInformation = new RequestInformation(); - requestInformation.setUri(request.url().uri()); - for (String headerName : request.headers().names()) { - requestInformation.headers.add(headerName, request.header(headerName)); + final AccessTokenProvider accessTokenProvider = authenticationProvider.getAccessTokenProvider(); + if (!accessTokenProvider.getAllowedHostsValidator().isUrlHostValid(request.url().uri())) { + return; + } + final String accessToken = accessTokenProvider.getAuthorizationToken(request.url().uri(), additionalAuthenticationContext); + if (accessToken != null && !accessToken.isEmpty()) { + span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); + Request.Builder requestBuilder = request.newBuilder(); + requestBuilder.addHeader(authorizationHeaderKey, "Bearer " + accessToken); } - return requestInformation; } } From c8a71d5cbe6dd2aecdde8175a8a4a19552abd8fe Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 01:15:22 +0300 Subject: [PATCH 07/16] Add auth handler tests --- .../middleware/AuthorizationHandlerTest.java | 150 ++++++++++++++++++ .../HeadersInspectionHandlerTest.java | 3 +- .../ParametersNameDecodingHandlerTest.java | 4 +- .../UrlReplaceHandlerTest.java | 4 +- .../UserAgentHandlerTest.java | 3 +- 5 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java rename components/http/okHttp/src/test/java/com/microsoft/kiota/http/{ => middleware}/HeadersInspectionHandlerTest.java (97%) rename components/http/okHttp/src/test/java/com/microsoft/kiota/http/{ => middleware}/ParametersNameDecodingHandlerTest.java (95%) rename components/http/okHttp/src/test/java/com/microsoft/kiota/http/{ => middleware}/UrlReplaceHandlerTest.java (97%) rename components/http/okHttp/src/test/java/com/microsoft/kiota/http/{ => middleware}/UserAgentHandlerTest.java (97%) diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java new file mode 100644 index 000000000..dbc246b63 --- /dev/null +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java @@ -0,0 +1,150 @@ +package com.microsoft.kiota.http.middleware; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.microsoft.kiota.authentication.AccessTokenProvider; +import com.microsoft.kiota.authentication.AllowedHostsValidator; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; + +import okhttp3.Response; +import okhttp3.Interceptor.Chain; +import okhttp3.Request; +import okhttp3.RequestBody; + +public class AuthorizationHandlerTest { + + final String token = "token"; + final String tokenAfterCAE = "tokenAfterCAE"; + final String authHeader = "Authorization"; + final String prevAuthHeaderValue = "Bearer 123"; + final String newAuthHeaderValue = "Bearer " + token; + final String claimsChallengeHeaderValue = "Bearer" + + " authorization_uri=\"https://login.windows.net/common/oauth2/authorize\"," + + "error=\"insufficient_claims\"," + + "claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTYwNDEwNjY1MSJ9fX0=\""; + + @Test + void testDoesNotAddAuthorizationHeaderIfAlreadyPresent() throws IOException { + final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").addHeader("Authorization", "Bearer 123").build(); + final Chain mockChain = getMockChain(request, mock(Response.class)); + final AuthorizationHandler handler = new AuthorizationHandler(getMockAuthenticationProvider()); + Response response = handler.intercept(mockChain); + + assertTrue(response.request().headers().names().contains(authHeader)); + assertEquals(prevAuthHeaderValue, response.request().header(authHeader)); + } + + @Test + void testAddsAuthorizationHeaderIfNotPresent() throws IOException { + final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); + final Chain mockChain = getMockChain(request, mock(Response.class)); + final AuthorizationHandler handler = new AuthorizationHandler(getMockAuthenticationProvider()); + Response response = handler.intercept(mockChain); + + assertTrue(response.request().headers().names().contains(authHeader)); + assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + } + + @Test + void testAddsAuthHeaderOnlyToAllowedHosts() throws IOException { + final Request request = new Request.Builder().url("https://canary.graph.microsoft.com/v1.0/me").build(); + final Chain mockChain = getMockChain(request, mock(Response.class)); + final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); + final AuthorizationHandler handler = new AuthorizationHandler(authProvider); + Response response = handler.intercept(mockChain); + + assertTrue(!response.request().headers().names().contains(authHeader)); + } + + @Test + void testAttemptsCAEChallenge() throws IOException { + final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); + final Chain mockChain = getMockChain(request, getMockResponseWithClaimsChallengeHeader(request)); + final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); + final AuthorizationHandler handler = new AuthorizationHandler(authProvider); + Response response = handler.intercept(mockChain); + + assertTrue(response.request().headers().names().contains(authHeader)); + assertEquals("Bearer " + tokenAfterCAE, response.request().header(authHeader)); + } + + @Test + void testOtherRequestPropertiesAreNotAltered() throws IOException { + final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").addHeader("content-type", "application/json").get().build(); + final Chain mockChain = getMockChain(request, mock(Response.class)); + final AuthorizationHandler handler = new AuthorizationHandler(getMockAuthenticationProvider()); + Response response = handler.intercept(mockChain); + + assertEquals(request.url(), response.request().url()); + assertEquals(request.method(), response.request().method()); + assertTrue(response.request().headers().names().contains("content-type")); + assertEquals("application/json", response.request().header("content-type")); + assertTrue(response.request().headers().names().contains(authHeader)); + assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + } + + @Test + void testDoesNotRetryCAEChallengeForOneShotBodyRequests() throws IOException { + final RequestBody mockRequestBody = mock(RequestBody.class); + when(mockRequestBody.isOneShot()).thenReturn(true); + final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").post(mockRequestBody).build(); + final Chain mockChain = getMockChain(request, getMockResponseWithClaimsChallengeHeader(request)); + final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); + final AuthorizationHandler handler = new AuthorizationHandler(authProvider); + Response response = handler.intercept(mockChain); + + assertTrue(response.request().headers().names().contains(authHeader)); + assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + } + + private Chain getMockChain(Request mockRequest, Response mockResponse) throws IOException { + Chain mockChain = mock(Chain.class); + when(mockChain.request()).thenReturn(mockRequest); + when(mockChain.proceed(any(Request.class))) + .thenAnswer( + new Answer() { + public Response answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + Request request = (Request) args[0]; + when(mockResponse.request()).thenReturn(request); + return mockResponse; + } + }); + return mockChain; + } + + private BaseBearerTokenAuthenticationProvider getMockAuthenticationProvider() { + final AccessTokenProvider mockAccessTokenProvider = mock(AccessTokenProvider.class); + final AllowedHostsValidator allowedHostsValidator = new AllowedHostsValidator("graph.microsoft.com"); + when(mockAccessTokenProvider.getAllowedHostsValidator()).thenReturn(allowedHostsValidator); + when(mockAccessTokenProvider.getAuthorizationToken(any(URI.class), anyMap())).thenReturn(token, tokenAfterCAE); + final BaseBearerTokenAuthenticationProvider mockAuthenticationProvider = mock(BaseBearerTokenAuthenticationProvider.class); + when(mockAuthenticationProvider.getAccessTokenProvider()).thenReturn(mockAccessTokenProvider); + return mockAuthenticationProvider; + } + + private Response getMockResponseWithClaimsChallengeHeader(Request request) { + final Response mockResponse = mock(Response.class); + when(mockResponse.code()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED); + when(mockResponse.headers("WWW-Authenticate")).thenReturn(Arrays.asList(claimsChallengeHeaderValue)); + when(mockResponse.request()).thenReturn(request); + return mockResponse; + } +} diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/HeadersInspectionHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/HeadersInspectionHandlerTest.java similarity index 97% rename from components/http/okHttp/src/test/java/com/microsoft/kiota/http/HeadersInspectionHandlerTest.java rename to components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/HeadersInspectionHandlerTest.java index 4dfa1db88..a358fe724 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/HeadersInspectionHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/HeadersInspectionHandlerTest.java @@ -1,4 +1,4 @@ -package com.microsoft.kiota.http; +package com.microsoft.kiota.http.middleware; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -6,7 +6,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.microsoft.kiota.http.middleware.HeadersInspectionHandler; import com.microsoft.kiota.http.middleware.options.HeadersInspectionOption; import okhttp3.Headers; diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/ParametersNameDecodingHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/ParametersNameDecodingHandlerTest.java similarity index 95% rename from components/http/okHttp/src/test/java/com/microsoft/kiota/http/ParametersNameDecodingHandlerTest.java rename to components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/ParametersNameDecodingHandlerTest.java index f555ff457..2452c0526 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/ParametersNameDecodingHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/ParametersNameDecodingHandlerTest.java @@ -1,9 +1,9 @@ -package com.microsoft.kiota.http; +package com.microsoft.kiota.http.middleware; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import com.microsoft.kiota.http.middleware.ParametersNameDecodingHandler; +import com.microsoft.kiota.http.KiotaClientFactory; import com.microsoft.kiota.http.middleware.options.ParametersNameDecodingOption; import okhttp3.Interceptor; diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/UrlReplaceHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UrlReplaceHandlerTest.java similarity index 97% rename from components/http/okHttp/src/test/java/com/microsoft/kiota/http/UrlReplaceHandlerTest.java rename to components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UrlReplaceHandlerTest.java index 21b49281c..dd8d90283 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/UrlReplaceHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UrlReplaceHandlerTest.java @@ -1,9 +1,9 @@ -package com.microsoft.kiota.http; +package com.microsoft.kiota.http.middleware; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import com.microsoft.kiota.http.middleware.UrlReplaceHandler; +import com.microsoft.kiota.http.KiotaClientFactory; import com.microsoft.kiota.http.middleware.options.UrlReplaceHandlerOption; import okhttp3.Interceptor; diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/UserAgentHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UserAgentHandlerTest.java similarity index 97% rename from components/http/okHttp/src/test/java/com/microsoft/kiota/http/UserAgentHandlerTest.java rename to components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UserAgentHandlerTest.java index bcfca8fba..f46adb98a 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/UserAgentHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UserAgentHandlerTest.java @@ -1,11 +1,10 @@ -package com.microsoft.kiota.http; +package com.microsoft.kiota.http.middleware; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; -import com.microsoft.kiota.http.middleware.UserAgentHandler; import com.microsoft.kiota.http.middleware.options.UserAgentHandlerOption; import okhttp3.Headers; From 440ea17a7a537880beeb39162711c3d624379d18 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 01:16:20 +0300 Subject: [PATCH 08/16] Fix bugs in auth handler --- .../http/middleware/AuthorizationHandler.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 36b8068c1..1aff609d9 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -5,7 +5,9 @@ import com.microsoft.kiota.authentication.AccessTokenProvider; import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; import com.microsoft.kiota.http.ContinuousAccessEvaluationClaims; +import com.microsoft.kiota.http.ObservabilityOptions; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Scope; @@ -37,7 +39,7 @@ public class AuthorizationHandler implements Interceptor { * @param authenticationProvider the authentication provider. */ public AuthorizationHandler(@Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { - this.authenticationProvider = Objects.requireNonNull(null, "AuthenticationProvider cannot be null"); + this.authenticationProvider = Objects.requireNonNull(authenticationProvider, "AuthenticationProvider cannot be null"); } @Override @@ -46,7 +48,9 @@ public Response intercept(final Chain chain) throws IOException { final Request request = chain.request(); final Span span = - ObservabilityHelper.getSpanForRequest(request, "AuthorizationHandler_Intercept"); + GlobalOpenTelemetry.getTracer((new ObservabilityOptions()).getTracerInstrumentationName()) + .spanBuilder("AuthorizationHandler_Intercept") + .startSpan(); Scope scope = null; if (span != null) { scope = span.makeCurrent(); @@ -56,19 +60,21 @@ public Response intercept(final Chain chain) throws IOException { try { // Auth provider already added auth header if (request.headers().names().contains(authorizationHeaderKey)) { - span.setAttribute("com.microsoft.kiota.handler.authorization.token_present", true); + if (span != null) + span.setAttribute("com.microsoft.kiota.handler.authorization.token_present", true); return chain.proceed(request); } - authenticateRequest(request, null, span); - Response response = chain.proceed(chain.request()); + final HashMap additionalContext = new HashMap<>(); + additionalContext.put("parent-span", span); + final Response response = chain.proceed(authenticateRequest(request, additionalContext, span)); if (response != null && response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) { return response; } // Attempt CAE claims challenge - String claims = ContinuousAccessEvaluationClaims.getClaimsFromResponse(response); + final String claims = ContinuousAccessEvaluationClaims.getClaimsFromResponse(response); if (claims == null || claims.isEmpty()) { return response; } @@ -83,12 +89,10 @@ public Response intercept(final Chain chain) throws IOException { } response.close(); - final HashMap additionalContext = new HashMap<>(); additionalContext.put("claims", claims); // Retry claims challenge only once - authenticateRequest(request, additionalContext, span); span.setAttribute(HTTP_REQUEST_RESEND_COUNT, 1); - return chain.proceed(request); + return chain.proceed(authenticateRequest(request, additionalContext, span)); } finally { if (scope != null) { scope.close(); @@ -99,20 +103,22 @@ public Response intercept(final Chain chain) throws IOException { } } - private void authenticateRequest( + private Request authenticateRequest( @Nonnull final Request request, @Nullable final Map additionalAuthenticationContext, @Nonnull final Span span) { final AccessTokenProvider accessTokenProvider = authenticationProvider.getAccessTokenProvider(); if (!accessTokenProvider.getAllowedHostsValidator().isUrlHostValid(request.url().uri())) { - return; + return request; } final String accessToken = accessTokenProvider.getAuthorizationToken(request.url().uri(), additionalAuthenticationContext); if (accessToken != null && !accessToken.isEmpty()) { span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); Request.Builder requestBuilder = request.newBuilder(); requestBuilder.addHeader(authorizationHeaderKey, "Bearer " + accessToken); + return requestBuilder.build(); } + return request; } } From 932643a52d2f18492a0a4a128b4c9e39986d116b Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 01:41:49 +0300 Subject: [PATCH 09/16] Resolve spotbugs errors and style issues --- .../http/okHttp/spotBugsExcludeFilter.xml | 6 +- .../http/middleware/AuthorizationHandler.java | 40 +++--- .../middleware/AuthorizationHandlerTest.java | 115 +++++++++++------- 3 files changed, 98 insertions(+), 63 deletions(-) diff --git a/components/http/okHttp/spotBugsExcludeFilter.xml b/components/http/okHttp/spotBugsExcludeFilter.xml index 1ab81c79f..a242f453f 100644 --- a/components/http/okHttp/spotBugsExcludeFilter.xml +++ b/components/http/okHttp/spotBugsExcludeFilter.xml @@ -29,7 +29,7 @@ - + @@ -37,6 +37,6 @@ - + - \ No newline at end of file + diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 1aff609d9..7814b908a 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -16,6 +16,7 @@ import okhttp3.Interceptor; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; import java.io.IOException; @@ -38,8 +39,11 @@ public class AuthorizationHandler implements Interceptor { * Instantiates a new AuthorizationHandler. * @param authenticationProvider the authentication provider. */ - public AuthorizationHandler(@Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { - this.authenticationProvider = Objects.requireNonNull(authenticationProvider, "AuthenticationProvider cannot be null"); + public AuthorizationHandler( + @Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { + this.authenticationProvider = + Objects.requireNonNull( + authenticationProvider, "AuthenticationProvider cannot be null"); } @Override @@ -48,9 +52,10 @@ public Response intercept(final Chain chain) throws IOException { final Request request = chain.request(); final Span span = - GlobalOpenTelemetry.getTracer((new ObservabilityOptions()).getTracerInstrumentationName()) - .spanBuilder("AuthorizationHandler_Intercept") - .startSpan(); + GlobalOpenTelemetry.getTracer( + (new ObservabilityOptions()).getTracerInstrumentationName()) + .spanBuilder("AuthorizationHandler_Intercept") + .startSpan(); Scope scope = null; if (span != null) { scope = span.makeCurrent(); @@ -61,13 +66,16 @@ public Response intercept(final Chain chain) throws IOException { // Auth provider already added auth header if (request.headers().names().contains(authorizationHeaderKey)) { if (span != null) - span.setAttribute("com.microsoft.kiota.handler.authorization.token_present", true); + span.setAttribute( + "com.microsoft.kiota.handler.authorization.token_present", true); return chain.proceed(request); } final HashMap additionalContext = new HashMap<>(); additionalContext.put("parent-span", span); - final Response response = chain.proceed(authenticateRequest(request, additionalContext, span)); + final Request authenticatedRequest = + authenticateRequest(request, additionalContext, span); + final Response response = chain.proceed(authenticatedRequest); if (response != null && response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) { return response; @@ -82,9 +90,8 @@ public Response intercept(final Chain chain) throws IOException { span.addEvent("com.microsoft.kiota.handler.authorization.challenge_received"); // We cannot replay one-shot requests after claims challenge - boolean isRequestBodyOneShot = - request != null && request.body() != null && request.body().isOneShot(); - if (isRequestBodyOneShot) { + RequestBody requestBody = request.body(); + if (requestBody != null && requestBody.isOneShot()) { return response; } @@ -92,7 +99,9 @@ public Response intercept(final Chain chain) throws IOException { additionalContext.put("claims", claims); // Retry claims challenge only once span.setAttribute(HTTP_REQUEST_RESEND_COUNT, 1); - return chain.proceed(authenticateRequest(request, additionalContext, span)); + final Request authenticatedRequestAfterCAE = + authenticateRequest(request, additionalContext, span); + return chain.proceed(authenticatedRequestAfterCAE); } finally { if (scope != null) { scope.close(); @@ -103,16 +112,19 @@ public Response intercept(final Chain chain) throws IOException { } } - private Request authenticateRequest( + private @Nonnull Request authenticateRequest( @Nonnull final Request request, @Nullable final Map additionalAuthenticationContext, @Nonnull final Span span) { - final AccessTokenProvider accessTokenProvider = authenticationProvider.getAccessTokenProvider(); + final AccessTokenProvider accessTokenProvider = + authenticationProvider.getAccessTokenProvider(); if (!accessTokenProvider.getAllowedHostsValidator().isUrlHostValid(request.url().uri())) { return request; } - final String accessToken = accessTokenProvider.getAuthorizationToken(request.url().uri(), additionalAuthenticationContext); + final String accessToken = + accessTokenProvider.getAuthorizationToken( + request.url().uri(), additionalAuthenticationContext); if (accessToken != null && !accessToken.isEmpty()) { span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); Request.Builder requestBuilder = request.newBuilder(); diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java index dbc246b63..97c60cfaa 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java @@ -4,47 +4,49 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.microsoft.kiota.authentication.AccessTokenProvider; import com.microsoft.kiota.authentication.AllowedHostsValidator; +import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URI; -import java.util.Arrays; +import okhttp3.Interceptor.Chain; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import org.junit.jupiter.api.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; - -import okhttp3.Response; -import okhttp3.Interceptor.Chain; -import okhttp3.Request; -import okhttp3.RequestBody; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Arrays; public class AuthorizationHandlerTest { - final String token = "token"; - final String tokenAfterCAE = "tokenAfterCAE"; - final String authHeader = "Authorization"; - final String prevAuthHeaderValue = "Bearer 123"; - final String newAuthHeaderValue = "Bearer " + token; - final String claimsChallengeHeaderValue = "Bearer" + - " authorization_uri=\"https://login.windows.net/common/oauth2/authorize\"," + - "error=\"insufficient_claims\"," + - "claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTYwNDEwNjY1MSJ9fX0=\""; + private static final String token = "token"; + private static final String tokenAfterCAE = "tokenAfterCAE"; + private static final String authHeader = "Authorization"; + private static final String prevAuthHeaderValue = "Bearer 123"; + private static final String newAuthHeaderValue = "Bearer " + token; + private static final String claimsChallengeHeaderValue = + "Bearer authorization_uri=\"https://login.windows.net/common/oauth2/authorize\"," + + "error=\"insufficient_claims\"," + + "claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTYwNDEwNjY1MSJ9fX0=\""; @Test void testDoesNotAddAuthorizationHeaderIfAlreadyPresent() throws IOException { - final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").addHeader("Authorization", "Bearer 123").build(); + final Request request = + new Request.Builder() + .url("https://graph.microsoft.com/v1.0/me") + .addHeader("Authorization", "Bearer 123") + .build(); final Chain mockChain = getMockChain(request, mock(Response.class)); - final AuthorizationHandler handler = new AuthorizationHandler(getMockAuthenticationProvider()); + final AuthorizationHandler handler = + new AuthorizationHandler(getMockAuthenticationProvider()); Response response = handler.intercept(mockChain); assertTrue(response.request().headers().names().contains(authHeader)); @@ -53,9 +55,11 @@ void testDoesNotAddAuthorizationHeaderIfAlreadyPresent() throws IOException { @Test void testAddsAuthorizationHeaderIfNotPresent() throws IOException { - final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); + final Request request = + new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); final Chain mockChain = getMockChain(request, mock(Response.class)); - final AuthorizationHandler handler = new AuthorizationHandler(getMockAuthenticationProvider()); + final AuthorizationHandler handler = + new AuthorizationHandler(getMockAuthenticationProvider()); Response response = handler.intercept(mockChain); assertTrue(response.request().headers().names().contains(authHeader)); @@ -64,7 +68,8 @@ void testAddsAuthorizationHeaderIfNotPresent() throws IOException { @Test void testAddsAuthHeaderOnlyToAllowedHosts() throws IOException { - final Request request = new Request.Builder().url("https://canary.graph.microsoft.com/v1.0/me").build(); + final Request request = + new Request.Builder().url("https://canary.graph.microsoft.com/v1.0/me").build(); final Chain mockChain = getMockChain(request, mock(Response.class)); final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); final AuthorizationHandler handler = new AuthorizationHandler(authProvider); @@ -75,8 +80,10 @@ void testAddsAuthHeaderOnlyToAllowedHosts() throws IOException { @Test void testAttemptsCAEChallenge() throws IOException { - final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); - final Chain mockChain = getMockChain(request, getMockResponseWithClaimsChallengeHeader(request)); + final Request request = + new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); + final Chain mockChain = + getMockChain(request, getMockResponseWithClaimsChallengeHeader(request)); final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); final AuthorizationHandler handler = new AuthorizationHandler(authProvider); Response response = handler.intercept(mockChain); @@ -87,9 +94,15 @@ void testAttemptsCAEChallenge() throws IOException { @Test void testOtherRequestPropertiesAreNotAltered() throws IOException { - final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").addHeader("content-type", "application/json").get().build(); + final Request request = + new Request.Builder() + .url("https://graph.microsoft.com/v1.0/me") + .addHeader("content-type", "application/json") + .get() + .build(); final Chain mockChain = getMockChain(request, mock(Response.class)); - final AuthorizationHandler handler = new AuthorizationHandler(getMockAuthenticationProvider()); + final AuthorizationHandler handler = + new AuthorizationHandler(getMockAuthenticationProvider()); Response response = handler.intercept(mockChain); assertEquals(request.url(), response.request().url()); @@ -104,8 +117,13 @@ void testOtherRequestPropertiesAreNotAltered() throws IOException { void testDoesNotRetryCAEChallengeForOneShotBodyRequests() throws IOException { final RequestBody mockRequestBody = mock(RequestBody.class); when(mockRequestBody.isOneShot()).thenReturn(true); - final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").post(mockRequestBody).build(); - final Chain mockChain = getMockChain(request, getMockResponseWithClaimsChallengeHeader(request)); + final Request request = + new Request.Builder() + .url("https://graph.microsoft.com/v1.0/me") + .post(mockRequestBody) + .build(); + final Chain mockChain = + getMockChain(request, getMockResponseWithClaimsChallengeHeader(request)); final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); final AuthorizationHandler handler = new AuthorizationHandler(authProvider); Response response = handler.intercept(mockChain); @@ -118,32 +136,37 @@ private Chain getMockChain(Request mockRequest, Response mockResponse) throws IO Chain mockChain = mock(Chain.class); when(mockChain.request()).thenReturn(mockRequest); when(mockChain.proceed(any(Request.class))) - .thenAnswer( - new Answer() { - public Response answer(InvocationOnMock invocation) { - Object[] args = invocation.getArguments(); - Request request = (Request) args[0]; - when(mockResponse.request()).thenReturn(request); - return mockResponse; - } - }); + .thenAnswer( + new Answer() { + public Response answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + Request request = (Request) args[0]; + when(mockResponse.request()).thenReturn(request); + return mockResponse; + } + }); return mockChain; } private BaseBearerTokenAuthenticationProvider getMockAuthenticationProvider() { final AccessTokenProvider mockAccessTokenProvider = mock(AccessTokenProvider.class); - final AllowedHostsValidator allowedHostsValidator = new AllowedHostsValidator("graph.microsoft.com"); + final AllowedHostsValidator allowedHostsValidator = + new AllowedHostsValidator("graph.microsoft.com"); when(mockAccessTokenProvider.getAllowedHostsValidator()).thenReturn(allowedHostsValidator); - when(mockAccessTokenProvider.getAuthorizationToken(any(URI.class), anyMap())).thenReturn(token, tokenAfterCAE); - final BaseBearerTokenAuthenticationProvider mockAuthenticationProvider = mock(BaseBearerTokenAuthenticationProvider.class); - when(mockAuthenticationProvider.getAccessTokenProvider()).thenReturn(mockAccessTokenProvider); + when(mockAccessTokenProvider.getAuthorizationToken(any(URI.class), anyMap())) + .thenReturn(token, tokenAfterCAE); + final BaseBearerTokenAuthenticationProvider mockAuthenticationProvider = + mock(BaseBearerTokenAuthenticationProvider.class); + when(mockAuthenticationProvider.getAccessTokenProvider()) + .thenReturn(mockAccessTokenProvider); return mockAuthenticationProvider; } private Response getMockResponseWithClaimsChallengeHeader(Request request) { final Response mockResponse = mock(Response.class); when(mockResponse.code()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED); - when(mockResponse.headers("WWW-Authenticate")).thenReturn(Arrays.asList(claimsChallengeHeaderValue)); + when(mockResponse.headers("WWW-Authenticate")) + .thenReturn(Arrays.asList(claimsChallengeHeaderValue)); when(mockResponse.request()).thenReturn(request); return mockResponse; } From f04ca6ee876d91d7f65b8bba74ac60748a464715 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 02:03:12 +0300 Subject: [PATCH 10/16] Resolve javadoc warnings --- .../ContinuousAccessEvaluationClaims.java | 9 ++++ .../kiota/http/KiotaClientFactory.java | 5 ++ .../http/TelemetrySemanticConventions.java | 49 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java index b6e993c15..7c2bbf9da 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java @@ -9,6 +9,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Helper class to extract the claims from the WWW-Authenticate header in a response. + * https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation + */ public final class ContinuousAccessEvaluationClaims { private static final Pattern bearerPattern = @@ -18,6 +22,11 @@ public final class ContinuousAccessEvaluationClaims { private static final String wwwAuthenticateHeader = "WWW-Authenticate"; + /** + * Extracts the claims from the WWW-Authenticate header in a response. + * @param response the response to extract the claims from. + * @return the claims + */ public static @Nullable String getClaimsFromResponse(@Nonnull Response response) { if (response == null || response.code() != 401) { return null; diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java index fb56a9408..af34c458f 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java @@ -52,6 +52,11 @@ private KiotaClientFactory() {} return builder; } + /** + * Creates an OkHttpClient Builder with the default configuration and middleware including the AuthorizationHandler. + * @param authenticationProvider authentication provider to use for the AuthorizationHandler. + * @return an OkHttpClient Builder instance. + */ @Nonnull public static OkHttpClient.Builder create( @Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { List interceptors = Arrays.asList(createDefaultInterceptors()); diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/TelemetrySemanticConventions.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/TelemetrySemanticConventions.java index d5546409a..6fdb412fc 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/TelemetrySemanticConventions.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/TelemetrySemanticConventions.java @@ -5,30 +5,79 @@ import io.opentelemetry.api.common.AttributeKey; +/** + * This class contains the telemetry attribute keys used by this library. + */ public final class TelemetrySemanticConventions { private TelemetrySemanticConventions() {} // https://opentelemetry.io/docs/specs/semconv/attributes-registry/ + + /** + * HTTP Response status code + */ public static final AttributeKey HTTP_RESPONSE_STATUS_CODE = longKey("http.response.status_code"); // stable + + /** + * HTTP Request resend count + */ public static final AttributeKey HTTP_REQUEST_RESEND_COUNT = longKey("http.request.resend_count"); // stable + + /** + * HTTP Request method + */ public static final AttributeKey HTTP_REQUEST_METHOD = stringKey("http.request.method"); // stable + + /** + * Network connection protocol version + */ public static final AttributeKey NETWORK_PROTOCOL_VERSION = stringKey("network.protocol.version"); // stable + + /** + * Full HTTP request URL + */ public static final AttributeKey URL_FULL = stringKey("url.full"); // stable + + /** + * HTTP request URL scheme + */ public static final AttributeKey URL_SCHEME = stringKey("url.scheme"); // stable + + /** + * HTTP request destination server address + */ public static final AttributeKey SERVER_ADDRESS = stringKey("server.address"); // stable + + /** + * HTTP request destination server port + */ public static final AttributeKey SERVER_PORT = longKey("server.port"); // stable + /** + * HTTP response body size + */ public static final AttributeKey EXPERIMENTAL_HTTP_RESPONSE_BODY_SIZE = longKey("http.response.body.size"); // experimental + + /** + * HTTP request body size + */ public static final AttributeKey EXPERIMENTAL_HTTP_REQUEST_BODY_SIZE = longKey("http.request.body.size"); // experimental + /** + * HTTP response content type + */ public static final AttributeKey CUSTOM_HTTP_RESPONSE_CONTENT_TYPE = stringKey("http.response_content_type"); // custom + + /** + * HTTP request content type + */ public static final AttributeKey CUSTOM_HTTP_REQUEST_CONTENT_TYPE = stringKey("http.request_content_type"); // custom } From 5fa1f827b61f8da397af6f875e7d0b66db4e8f69 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 17:48:50 +0300 Subject: [PATCH 11/16] Apply suggestions from code review Co-authored-by: Vincent Biret --- .../microsoft/kiota/http/middleware/AuthorizationHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 7814b908a..71f7f24b8 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -32,7 +32,7 @@ */ public class AuthorizationHandler implements Interceptor { - private final BaseBearerTokenAuthenticationProvider authenticationProvider; + @Nonnull private final BaseBearerTokenAuthenticationProvider authenticationProvider; private static final String authorizationHeaderKey = "Authorization"; /** From 08c471b2750004f97ee5f451dc531a150ad53906 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 21:12:18 +0300 Subject: [PATCH 12/16] Increase code coverage and reliability --- .../ContinuousAccessEvaluationClaims.java | 4 +- .../kiota/http/KiotaClientFactory.java | 23 +++++++- .../http/middleware/AuthorizationHandler.java | 4 +- .../kiota/http/OkHttpRequestAdapterTest.java | 31 +---------- .../middleware/AuthorizationHandlerTest.java | 34 ++++++++++++ .../http/middleware/MockResponseHandler.java | 52 +++++++++++++++++++ 6 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/MockResponseHandler.java diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java index 7c2bbf9da..2431e2097 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java @@ -6,6 +6,7 @@ import okhttp3.Response; import java.util.List; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,7 +29,8 @@ public final class ContinuousAccessEvaluationClaims { * @return the claims */ public static @Nullable String getClaimsFromResponse(@Nonnull Response response) { - if (response == null || response.code() != 401) { + Objects.requireNonNull(response, "parameter response cannot be null"); + if (response.code() != 401) { return null; } final List authenticateHeader = response.headers(wwwAuthenticateHeader); diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java index af34c458f..a141b6f0b 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java @@ -15,6 +15,7 @@ import okhttp3.OkHttpClient; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -52,6 +53,16 @@ private KiotaClientFactory() {} return builder; } + /** + * Creates an OkHttpClient Builder with the default configuration and middleware. + * @param interceptors The interceptors to add to the client. Will default to createDefaultInterceptors() if null. + * @return an OkHttpClient Builder instance. + */ + @Nonnull public static OkHttpClient.Builder create(@Nullable final List interceptors) { + return create( + (new ArrayList<>(interceptors)).toArray(new Interceptor[interceptors.size()])); + } + /** * Creates an OkHttpClient Builder with the default configuration and middleware including the AuthorizationHandler. * @param authenticationProvider authentication provider to use for the AuthorizationHandler. @@ -59,9 +70,9 @@ private KiotaClientFactory() {} */ @Nonnull public static OkHttpClient.Builder create( @Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { - List interceptors = Arrays.asList(createDefaultInterceptors()); + ArrayList interceptors = createDefaultInterceptorsAsList(); interceptors.add(new AuthorizationHandler(authenticationProvider)); - return create((Interceptor[]) interceptors.toArray()); + return create(interceptors); } /** @@ -77,4 +88,12 @@ private KiotaClientFactory() {} new HeadersInspectionHandler() }; } + + /** + * Creates the default interceptors for the client. + * @return an array of interceptors. + */ + @Nonnull public static ArrayList createDefaultInterceptorsAsList() { + return new ArrayList<>(Arrays.asList(createDefaultInterceptors())); + } } diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 71f7f24b8..85fd13f0b 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -47,7 +47,7 @@ public AuthorizationHandler( } @Override - public Response intercept(final Chain chain) throws IOException { + public @Nonnull Response intercept(final Chain chain) throws IOException { Objects.requireNonNull(chain, "parameter chain cannot be null"); final Request request = chain.request(); @@ -115,7 +115,7 @@ public Response intercept(final Chain chain) throws IOException { private @Nonnull Request authenticateRequest( @Nonnull final Request request, @Nullable final Map additionalAuthenticationContext, - @Nonnull final Span span) { + final Span span) { final AccessTokenProvider accessTokenProvider = authenticationProvider.getAccessTokenProvider(); diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java index 636a97111..b14d32c84 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/OkHttpRequestAdapterTest.java @@ -9,6 +9,7 @@ import com.microsoft.kiota.NativeResponseHandler; import com.microsoft.kiota.RequestInformation; import com.microsoft.kiota.authentication.AuthenticationProvider; +import com.microsoft.kiota.http.middleware.MockResponseHandler; import com.microsoft.kiota.serialization.Parsable; import com.microsoft.kiota.serialization.ParsableFactory; import com.microsoft.kiota.serialization.ParseNode; @@ -20,7 +21,6 @@ import okhttp3.Call; import okhttp3.Callback; import okhttp3.Dispatcher; -import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Protocol; @@ -574,33 +574,4 @@ public ParseNodeFactory creatMockParseNodeFactory( when(mockFactory.getValidContentType()).thenReturn(validContentType); return mockFactory; } - - // Returns request body as response body - static class MockResponseHandler implements Interceptor { - @Override - public Response intercept(Chain chain) throws IOException { - final var request = chain.request(); - final var requestBody = request.body(); - if (request != null && requestBody != null) { - final var buffer = new Buffer(); - requestBody.writeTo(buffer); - return new Response.Builder() - .code(200) - .message("OK") - .protocol(Protocol.HTTP_1_1) - .request(request) - .body( - ResponseBody.create( - buffer.readByteArray(), - MediaType.parse("application/json"))) - .build(); - } - return new Response.Builder() - .code(200) - .message("OK") - .protocol(Protocol.HTTP_1_1) - .request(request) - .build(); - } - } } diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java index 97c60cfaa..ea66bc17c 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java @@ -10,8 +10,10 @@ import com.microsoft.kiota.authentication.AccessTokenProvider; import com.microsoft.kiota.authentication.AllowedHostsValidator; import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; +import com.microsoft.kiota.http.KiotaClientFactory; import okhttp3.Interceptor.Chain; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; @@ -132,6 +134,38 @@ void testDoesNotRetryCAEChallengeForOneShotBodyRequests() throws IOException { assertEquals(newAuthHeaderValue, response.request().header(authHeader)); } + @Test + void testDoesNotAttemptCAEChallengeIfNoClaimsPresent() throws IOException { + final Request request = + new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); + final Response mockResponse = mock(Response.class); + when(mockResponse.code()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED); + final Chain mockChain = getMockChain(request, mockResponse); + final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); + final AuthorizationHandler handler = new AuthorizationHandler(authProvider); + Response response = handler.intercept(mockChain); + + assertTrue(response.request().headers().names().contains(authHeader)); + assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + assertEquals(401, response.code()); + } + + @Test + void testAuthorizationHandlerAddedByClientFactory() throws IOException { + final BaseBearerTokenAuthenticationProvider authProvider = getMockAuthenticationProvider(); + OkHttpClient okHttpClient = + KiotaClientFactory.create(authProvider) + .addInterceptor(new MockResponseHandler()) + .build(); + + final Request request = + new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); + Response response = okHttpClient.newCall(request).execute(); + + assertTrue(response.request().headers().names().contains(authHeader)); + assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + } + private Chain getMockChain(Request mockRequest, Response mockResponse) throws IOException { Chain mockChain = mock(Chain.class); when(mockChain.request()).thenReturn(mockRequest); diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/MockResponseHandler.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/MockResponseHandler.java new file mode 100644 index 000000000..456597668 --- /dev/null +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/MockResponseHandler.java @@ -0,0 +1,52 @@ +package com.microsoft.kiota.http.middleware; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Response; +import okhttp3.ResponseBody; + +import okio.Buffer; + +import java.io.IOException; + +/** + * Returns the request body as the response body + */ +public class MockResponseHandler implements Interceptor { + private int statusCode; + + public MockResponseHandler(int statusCode) { + this.statusCode = statusCode; + } + + public MockResponseHandler() { + this.statusCode = 200; + } + + @Override + public Response intercept(Chain chain) throws IOException { + final var request = chain.request(); + final var requestBody = request.body(); + if (request != null && requestBody != null) { + final var buffer = new Buffer(); + requestBody.writeTo(buffer); + return new Response.Builder() + .code(this.statusCode) + .message("OK") + .protocol(Protocol.HTTP_1_1) + .request(request) + .body( + ResponseBody.create( + buffer.readByteArray(), MediaType.parse("application/json"))) + .build(); + } + return new Response.Builder() + .code(this.statusCode) + .message("OK") + .protocol(Protocol.HTTP_1_1) + .request(request) + .body(ResponseBody.create("", MediaType.parse("application/json"))) + .build(); + } +} From cde6dcd7e399a97116e04c5ab9ffc4cfc23ca5b2 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 21:21:49 +0300 Subject: [PATCH 13/16] Apply suggestions from code review Co-authored-by: Vincent Biret --- .../microsoft/kiota/http/middleware/AuthorizationHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 85fd13f0b..2f45c325f 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -90,7 +90,7 @@ public AuthorizationHandler( span.addEvent("com.microsoft.kiota.handler.authorization.challenge_received"); // We cannot replay one-shot requests after claims challenge - RequestBody requestBody = request.body(); + final RequestBody requestBody = request.body(); if (requestBody != null && requestBody.isOneShot()) { return response; } @@ -127,7 +127,7 @@ public AuthorizationHandler( request.url().uri(), additionalAuthenticationContext); if (accessToken != null && !accessToken.isEmpty()) { span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); - Request.Builder requestBuilder = request.newBuilder(); + final Request.Builder requestBuilder = request.newBuilder(); requestBuilder.addHeader(authorizationHeaderKey, "Bearer " + accessToken); return requestBuilder.build(); } From c9f333c18718e2420327a0d6756f2b50088e623e Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 21:38:42 +0300 Subject: [PATCH 14/16] Fix reliability issues --- .../java/com/microsoft/kiota/http/KiotaClientFactory.java | 3 +++ .../kiota/http/middleware/AuthorizationHandler.java | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java index a141b6f0b..68aa51bc1 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java @@ -59,6 +59,9 @@ private KiotaClientFactory() {} * @return an OkHttpClient Builder instance. */ @Nonnull public static OkHttpClient.Builder create(@Nullable final List interceptors) { + if (interceptors == null) { + return create(); + } return create( (new ArrayList<>(interceptors)).toArray(new Interceptor[interceptors.size()])); } diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index 2f45c325f..cf26b7b2a 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -47,6 +47,7 @@ public AuthorizationHandler( } @Override + @SuppressWarnings("UnknownNullness") public @Nonnull Response intercept(final Chain chain) throws IOException { Objects.requireNonNull(chain, "parameter chain cannot be null"); final Request request = chain.request(); @@ -87,7 +88,8 @@ public AuthorizationHandler( return response; } - span.addEvent("com.microsoft.kiota.handler.authorization.challenge_received"); + if (span != null) + span.addEvent("com.microsoft.kiota.handler.authorization.challenge_received"); // We cannot replay one-shot requests after claims challenge final RequestBody requestBody = request.body(); @@ -98,7 +100,9 @@ public AuthorizationHandler( response.close(); additionalContext.put("claims", claims); // Retry claims challenge only once - span.setAttribute(HTTP_REQUEST_RESEND_COUNT, 1); + if (span != null) { + span.setAttribute(HTTP_REQUEST_RESEND_COUNT, 1); + } final Request authenticatedRequestAfterCAE = authenticateRequest(request, additionalContext, span); return chain.proceed(authenticatedRequestAfterCAE); From 5a79840fc1e6741c6e6f89cc855f36b128217e3f Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 27 Sep 2024 22:22:47 +0300 Subject: [PATCH 15/16] Fix sonarcloud issues --- .../ContinuousAccessEvaluationClaims.java | 6 +- .../kiota/http/KiotaClientFactory.java | 4 +- .../http/middleware/AuthorizationHandler.java | 6 +- .../middleware/AuthorizationHandlerTest.java | 56 ++++++++++--------- .../middleware/UrlReplaceHandlerTest.java | 8 +-- .../http/middleware/UserAgentHandlerTest.java | 2 +- 6 files changed, 45 insertions(+), 37 deletions(-) diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java index 2431e2097..96295ed84 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/ContinuousAccessEvaluationClaims.java @@ -21,7 +21,9 @@ public final class ContinuousAccessEvaluationClaims { private static final Pattern claimsPattern = Pattern.compile("\\s?claims=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); - private static final String wwwAuthenticateHeader = "WWW-Authenticate"; + private static final String WWW_AUTHENTICATE_HEADER = "WWW-Authenticate"; + + private ContinuousAccessEvaluationClaims() {} /** * Extracts the claims from the WWW-Authenticate header in a response. @@ -33,7 +35,7 @@ public final class ContinuousAccessEvaluationClaims { if (response.code() != 401) { return null; } - final List authenticateHeader = response.headers(wwwAuthenticateHeader); + final List authenticateHeader = response.headers(WWW_AUTHENTICATE_HEADER); if (!authenticateHeader.isEmpty()) { String rawHeaderValue = null; for (final String authenticateEntry : authenticateHeader) { diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java index 68aa51bc1..5f86565e4 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/KiotaClientFactory.java @@ -73,7 +73,7 @@ private KiotaClientFactory() {} */ @Nonnull public static OkHttpClient.Builder create( @Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { - ArrayList interceptors = createDefaultInterceptorsAsList(); + ArrayList interceptors = new ArrayList<>(createDefaultInterceptorsAsList()); interceptors.add(new AuthorizationHandler(authenticationProvider)); return create(interceptors); } @@ -96,7 +96,7 @@ private KiotaClientFactory() {} * Creates the default interceptors for the client. * @return an array of interceptors. */ - @Nonnull public static ArrayList createDefaultInterceptorsAsList() { + @Nonnull public static List createDefaultInterceptorsAsList() { return new ArrayList<>(Arrays.asList(createDefaultInterceptors())); } } diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java index cf26b7b2a..c80b16177 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/AuthorizationHandler.java @@ -33,7 +33,7 @@ public class AuthorizationHandler implements Interceptor { @Nonnull private final BaseBearerTokenAuthenticationProvider authenticationProvider; - private static final String authorizationHeaderKey = "Authorization"; + private static final String AUTHORIZATION_HEADER = "Authorization"; /** * Instantiates a new AuthorizationHandler. @@ -65,7 +65,7 @@ public AuthorizationHandler( try { // Auth provider already added auth header - if (request.headers().names().contains(authorizationHeaderKey)) { + if (request.headers().names().contains(AUTHORIZATION_HEADER)) { if (span != null) span.setAttribute( "com.microsoft.kiota.handler.authorization.token_present", true); @@ -132,7 +132,7 @@ public AuthorizationHandler( if (accessToken != null && !accessToken.isEmpty()) { span.setAttribute("com.microsoft.kiota.handler.authorization.token_obtained", true); final Request.Builder requestBuilder = request.newBuilder(); - requestBuilder.addHeader(authorizationHeaderKey, "Bearer " + accessToken); + requestBuilder.addHeader(AUTHORIZATION_HEADER, "Bearer " + accessToken); return requestBuilder.build(); } return request; diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java index ea66bc17c..605aaf69d 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/AuthorizationHandlerTest.java @@ -27,14 +27,14 @@ import java.net.URI; import java.util.Arrays; -public class AuthorizationHandlerTest { - - private static final String token = "token"; - private static final String tokenAfterCAE = "tokenAfterCAE"; - private static final String authHeader = "Authorization"; - private static final String prevAuthHeaderValue = "Bearer 123"; - private static final String newAuthHeaderValue = "Bearer " + token; - private static final String claimsChallengeHeaderValue = +class AuthorizationHandlerTest { + + private static final String ACCESS_TOKEN_STRING = "token"; + private static final String TOKEN_AFTER_CAE = "TOKEN_AFTER_CAE"; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String PREV_AUTHORIZATION_HEADER_VALUE = "Bearer 123"; + private static final String NEW_AUTHORIZATION_HEADER_VALUE = "Bearer " + ACCESS_TOKEN_STRING; + private static final String CLAIMS_CHALLENGE_HEADER_VALUE = "Bearer authorization_uri=\"https://login.windows.net/common/oauth2/authorize\"," + "error=\"insufficient_claims\"," + "claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTYwNDEwNjY1MSJ9fX0=\""; @@ -51,8 +51,9 @@ void testDoesNotAddAuthorizationHeaderIfAlreadyPresent() throws IOException { new AuthorizationHandler(getMockAuthenticationProvider()); Response response = handler.intercept(mockChain); - assertTrue(response.request().headers().names().contains(authHeader)); - assertEquals(prevAuthHeaderValue, response.request().header(authHeader)); + assertTrue(response.request().headers().names().contains(AUTHORIZATION_HEADER)); + assertEquals( + PREV_AUTHORIZATION_HEADER_VALUE, response.request().header(AUTHORIZATION_HEADER)); } @Test @@ -64,8 +65,9 @@ void testAddsAuthorizationHeaderIfNotPresent() throws IOException { new AuthorizationHandler(getMockAuthenticationProvider()); Response response = handler.intercept(mockChain); - assertTrue(response.request().headers().names().contains(authHeader)); - assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + assertTrue(response.request().headers().names().contains(AUTHORIZATION_HEADER)); + assertEquals( + NEW_AUTHORIZATION_HEADER_VALUE, response.request().header(AUTHORIZATION_HEADER)); } @Test @@ -77,7 +79,7 @@ void testAddsAuthHeaderOnlyToAllowedHosts() throws IOException { final AuthorizationHandler handler = new AuthorizationHandler(authProvider); Response response = handler.intercept(mockChain); - assertTrue(!response.request().headers().names().contains(authHeader)); + assertTrue(!response.request().headers().names().contains(AUTHORIZATION_HEADER)); } @Test @@ -90,8 +92,8 @@ void testAttemptsCAEChallenge() throws IOException { final AuthorizationHandler handler = new AuthorizationHandler(authProvider); Response response = handler.intercept(mockChain); - assertTrue(response.request().headers().names().contains(authHeader)); - assertEquals("Bearer " + tokenAfterCAE, response.request().header(authHeader)); + assertTrue(response.request().headers().names().contains(AUTHORIZATION_HEADER)); + assertEquals("Bearer " + TOKEN_AFTER_CAE, response.request().header(AUTHORIZATION_HEADER)); } @Test @@ -111,8 +113,9 @@ void testOtherRequestPropertiesAreNotAltered() throws IOException { assertEquals(request.method(), response.request().method()); assertTrue(response.request().headers().names().contains("content-type")); assertEquals("application/json", response.request().header("content-type")); - assertTrue(response.request().headers().names().contains(authHeader)); - assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + assertTrue(response.request().headers().names().contains(AUTHORIZATION_HEADER)); + assertEquals( + NEW_AUTHORIZATION_HEADER_VALUE, response.request().header(AUTHORIZATION_HEADER)); } @Test @@ -130,8 +133,9 @@ void testDoesNotRetryCAEChallengeForOneShotBodyRequests() throws IOException { final AuthorizationHandler handler = new AuthorizationHandler(authProvider); Response response = handler.intercept(mockChain); - assertTrue(response.request().headers().names().contains(authHeader)); - assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + assertTrue(response.request().headers().names().contains(AUTHORIZATION_HEADER)); + assertEquals( + NEW_AUTHORIZATION_HEADER_VALUE, response.request().header(AUTHORIZATION_HEADER)); } @Test @@ -145,8 +149,9 @@ void testDoesNotAttemptCAEChallengeIfNoClaimsPresent() throws IOException { final AuthorizationHandler handler = new AuthorizationHandler(authProvider); Response response = handler.intercept(mockChain); - assertTrue(response.request().headers().names().contains(authHeader)); - assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + assertTrue(response.request().headers().names().contains(AUTHORIZATION_HEADER)); + assertEquals( + NEW_AUTHORIZATION_HEADER_VALUE, response.request().header(AUTHORIZATION_HEADER)); assertEquals(401, response.code()); } @@ -162,8 +167,9 @@ void testAuthorizationHandlerAddedByClientFactory() throws IOException { new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); Response response = okHttpClient.newCall(request).execute(); - assertTrue(response.request().headers().names().contains(authHeader)); - assertEquals(newAuthHeaderValue, response.request().header(authHeader)); + assertTrue(response.request().headers().names().contains(AUTHORIZATION_HEADER)); + assertEquals( + NEW_AUTHORIZATION_HEADER_VALUE, response.request().header(AUTHORIZATION_HEADER)); } private Chain getMockChain(Request mockRequest, Response mockResponse) throws IOException { @@ -188,7 +194,7 @@ private BaseBearerTokenAuthenticationProvider getMockAuthenticationProvider() { new AllowedHostsValidator("graph.microsoft.com"); when(mockAccessTokenProvider.getAllowedHostsValidator()).thenReturn(allowedHostsValidator); when(mockAccessTokenProvider.getAuthorizationToken(any(URI.class), anyMap())) - .thenReturn(token, tokenAfterCAE); + .thenReturn(ACCESS_TOKEN_STRING, TOKEN_AFTER_CAE); final BaseBearerTokenAuthenticationProvider mockAuthenticationProvider = mock(BaseBearerTokenAuthenticationProvider.class); when(mockAuthenticationProvider.getAccessTokenProvider()) @@ -200,7 +206,7 @@ private Response getMockResponseWithClaimsChallengeHeader(Request request) { final Response mockResponse = mock(Response.class); when(mockResponse.code()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED); when(mockResponse.headers("WWW-Authenticate")) - .thenReturn(Arrays.asList(claimsChallengeHeaderValue)); + .thenReturn(Arrays.asList(CLAIMS_CHALLENGE_HEADER_VALUE)); when(mockResponse.request()).thenReturn(request); return mockResponse; } diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UrlReplaceHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UrlReplaceHandlerTest.java index dd8d90283..0c04f014e 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UrlReplaceHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UrlReplaceHandlerTest.java @@ -18,7 +18,7 @@ class UrlReplaceHandlerTest { - private static final String defaultUsersWithTokenUrl = + private static final String DEFAULT_URL_WITH_TOKEN = "https://graph.microsoft.com/v1.0/users/TokenToReplace"; private static final HashMap defaultReplacementPairs = new HashMap<>(); @@ -27,12 +27,12 @@ void testUrlReplaceHandler_no_replacementPairs() throws IOException { Interceptor[] interceptors = new Interceptor[] {new UrlReplaceHandler(new UrlReplaceHandlerOption())}; final OkHttpClient client = KiotaClientFactory.create(interceptors).build(); - final Request request = new Request.Builder().url(defaultUsersWithTokenUrl).build(); + final Request request = new Request.Builder().url(DEFAULT_URL_WITH_TOKEN).build(); final Response response = client.newCall(request).execute(); assertNotNull(response); assertEquals( - defaultUsersWithTokenUrl, + DEFAULT_URL_WITH_TOKEN, response.request() .url() .toString()); // url should remain the same without replacement pairs @@ -46,7 +46,7 @@ void testUrlReplaceHandler_default_url() throws IOException { new UrlReplaceHandler(new UrlReplaceHandlerOption(defaultReplacementPairs)) }; final OkHttpClient client = KiotaClientFactory.create(interceptors).build(); - final Request request = new Request.Builder().url(defaultUsersWithTokenUrl).build(); + final Request request = new Request.Builder().url(DEFAULT_URL_WITH_TOKEN).build(); final Response response = client.newCall(request).execute(); final String expectedNewUrl = "https://graph.microsoft.com/v1.0/me"; diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UserAgentHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UserAgentHandlerTest.java index f46adb98a..1b8fe4498 100644 --- a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UserAgentHandlerTest.java +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/UserAgentHandlerTest.java @@ -59,8 +59,8 @@ void addsTheProductOnce() throws IOException { final UserAgentHandler handler = new UserAgentHandler(); final Request request = new Request.Builder().url("http://localhost").build(); when(mockChain.request()).thenReturn(request); + handler.intercept(mockChain); Response response = handler.intercept(mockChain); - response = handler.intercept(mockChain); final Request result = response.request(); assertNotNull(response); assertNotNull(result); From d2532da1c93b8e933bf12342718ae7088bdc810c Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 30 Sep 2024 12:23:34 +0300 Subject: [PATCH 16/16] Update CHANGELOG and bump version --- CHANGELOG.md | 7 +++++ README.md | 30 +++++++++---------- .../options/UserAgentHandlerOption.java | 2 +- gradle.properties | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdaa6be84..1faa2086e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.5.0] - 2024-09-30 + +### Added +- Adds an `AuthorizationHandler` that authenticates requests using a provided `BaseBearerTokenAuthenticationProvider`. Opting in to use this middleware can be done +via `KiotaClientFactory.create(authProvider)`. + + ## [1.4.0] - 2024-09-11 ### Changed diff --git a/README.md b/README.md index 960a94716..06e7245c8 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ Read more about Kiota [here](https://github.com/microsoft/kiota/blob/main/README In `build.gradle` in the `dependencies` section: ```Groovy -implementation 'com.microsoft.kiota:microsoft-kiota-abstractions:1.3.0' -implementation 'com.microsoft.kiota:microsoft-kiota-authentication-azure:1.3.0' -implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:1.3.0' -implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:1.3.0' -implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:1.3.0' -implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:1.3.0' -implementation 'com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.3.0' -implementation 'com.microsoft.kiota:microsoft-kiota-bundle:1.3.0' +implementation 'com.microsoft.kiota:microsoft-kiota-abstractions:1.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-authentication-azure:1.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:1.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:1.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:1.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:1.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-bundle:1.5.0' implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1' ``` @@ -40,37 +40,37 @@ In `pom.xml` in the `dependencies` section: com.microsoft.kiota microsoft-kiota-abstractions - 1.3.0 + 1.5.0 com.microsoft.kiota microsoft-kiota-authentication-azure - 1.3.0 + 1.5.0 com.microsoft.kiota microsoft-kiota-http-okHttp - 1.3.0 + 1.5.0 com.microsoft.kiota microsoft-kiota-serialization-json - 1.3.0 + 1.5.0 com.microsoft.kiota microsoft-kiota-serialization-text - 1.3.0 + 1.5.0 com.microsoft.kiota microsoft-kiota-serialization-form - 1.3.0 + 1.5.0 com.microsoft.kiota microsoft-kiota-serialization-multipart - 1.3.0 + 1.5.0 jakarta.annotation 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 c8dba6011..910546e9a 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.4.0"; + @Nonnull private String productVersion = "1.5.0"; /** * Gets the product name to be used in the user agent header diff --git a/gradle.properties b/gradle.properties index 434f746a4..d61e8397a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,7 +25,7 @@ org.gradle.caching=true mavenGroupId = com.microsoft.kiota mavenMajorVersion = 1 -mavenMinorVersion = 4 +mavenMinorVersion = 5 mavenPatchVersion = 0 mavenArtifactSuffix =