Skip to content

Commit

Permalink
Merge pull request #1562 from microsoft/feat/auth-handler
Browse files Browse the repository at this point in the history
Adds Authorization Handler
  • Loading branch information
Ndiritu authored Sep 30, 2024
2 parents cba2a68 + d2532da commit b82a085
Show file tree
Hide file tree
Showing 18 changed files with 597 additions and 90 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
```

Expand All @@ -40,37 +40,37 @@ In `pom.xml` in the `dependencies` section:
<dependency>
<groupId>com.microsoft.kiota</groupId>
<artifactId>microsoft-kiota-abstractions</artifactId>
<version>1.3.0</version>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.kiota</groupId>
<artifactId>microsoft-kiota-authentication-azure</artifactId>
<version>1.3.0</version>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.kiota</groupId>
<artifactId>microsoft-kiota-http-okHttp</artifactId>
<version>1.3.0</version>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.kiota</groupId>
<artifactId>microsoft-kiota-serialization-json</artifactId>
<version>1.3.0</version>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.kiota</groupId>
<artifactId>microsoft-kiota-serialization-text</artifactId>
<version>1.3.0</version>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.kiota</groupId>
<artifactId>microsoft-kiota-serialization-form</artifactId>
<version>1.3.0</version>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.kiota</groupId>
<artifactId>microsoft-kiota-serialization-multipart</artifactId>
<version>1.3.0</version>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@ public void authenticateRequest(
}
}
}

public @Nonnull AccessTokenProvider getAccessTokenProvider() {
return this.accessTokenProvider;
}
}
6 changes: 3 additions & 3 deletions components/http/okHttp/spotBugsExcludeFilter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
</Match>
<Match>
<Bug pattern="CT_CONSTRUCTOR_THROW" />
<Class name="com.microsoft.kiota.http.HeadersInspectionHandlerTest" />
<Class name="com.microsoft.kiota.http.middleware.HeadersInspectionHandlerTest" />
</Match>
<Match>
<Bug pattern="CT_CONSTRUCTOR_THROW" />
<Class name="~com\.microsoft\.kiota\.http\.OkHttpRequestAdapterTest.*" />
</Match>
<Match>
<Bug pattern="CT_CONSTRUCTOR_THROW" />
<Class name="com.microsoft.kiota.http.UserAgentHandlerTest" />
<Class name="com.microsoft.kiota.http.middleware.UserAgentHandlerTest" />
</Match>
</FindBugsFilter>
</FindBugsFilter>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.microsoft.kiota.http;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;

import okhttp3.Response;

import java.util.List;
import java.util.Objects;
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 =
Pattern.compile("^Bearer\\s.*", Pattern.CASE_INSENSITIVE);
private static final Pattern claimsPattern =
Pattern.compile("\\s?claims=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE);

private static final String WWW_AUTHENTICATE_HEADER = "WWW-Authenticate";

private ContinuousAccessEvaluationClaims() {}

/**
* 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) {
Objects.requireNonNull(response, "parameter response cannot be null");
if (response.code() != 401) {
return null;
}
final List<String> authenticateHeader = response.headers(WWW_AUTHENTICATE_HEADER);
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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,6 +15,9 @@
import okhttp3.OkHttpClient;

import java.time.Duration;
import java.util.ArrayList;
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 {
Expand All @@ -23,7 +28,7 @@ private KiotaClientFactory() {}
* @return an OkHttpClient Builder instance.
*/
@Nonnull public static OkHttpClient.Builder create() {
return create(null);
return create(createDefaultInterceptors());
}

/**
Expand All @@ -48,6 +53,31 @@ 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<Interceptor> interceptors) {
if (interceptors == null) {
return create();
}
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.
* @return an OkHttpClient Builder instance.
*/
@Nonnull public static OkHttpClient.Builder create(
@Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) {
ArrayList<Interceptor> interceptors = new ArrayList<>(createDefaultInterceptorsAsList());
interceptors.add(new AuthorizationHandler(authenticationProvider));
return create(interceptors);
}

/**
* Creates the default interceptors for the client.
* @return an array of interceptors.
Expand All @@ -61,4 +91,12 @@ private KiotaClientFactory() {}
new HeadersInspectionHandler()
};
}

/**
* Creates the default interceptors for the client.
* @return an array of interceptors.
*/
@Nonnull public static List<Interceptor> createDefaultInterceptorsAsList() {
return new ArrayList<>(Arrays.asList(createDefaultInterceptors()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -753,11 +752,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";
Expand Down Expand Up @@ -804,26 +798,7 @@ String getClaimsFromResponse(
&& (claims == null || claims.isEmpty())
&& // we avoid infinite loops and retry only once
(requestInfo.content == null || requestInfo.content.markSupported())) {
final List<String> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long> HTTP_RESPONSE_STATUS_CODE =
longKey("http.response.status_code"); // stable

/**
* HTTP Request resend count
*/
public static final AttributeKey<Long> HTTP_REQUEST_RESEND_COUNT =
longKey("http.request.resend_count"); // stable

/**
* HTTP Request method
*/
public static final AttributeKey<String> HTTP_REQUEST_METHOD =
stringKey("http.request.method"); // stable

/**
* Network connection protocol version
*/
public static final AttributeKey<String> NETWORK_PROTOCOL_VERSION =
stringKey("network.protocol.version"); // stable

/**
* Full HTTP request URL
*/
public static final AttributeKey<String> URL_FULL = stringKey("url.full"); // stable

/**
* HTTP request URL scheme
*/
public static final AttributeKey<String> URL_SCHEME = stringKey("url.scheme"); // stable

/**
* HTTP request destination server address
*/
public static final AttributeKey<String> SERVER_ADDRESS = stringKey("server.address"); // stable

/**
* HTTP request destination server port
*/
public static final AttributeKey<Long> SERVER_PORT = longKey("server.port"); // stable

/**
* HTTP response body size
*/
public static final AttributeKey<Long> EXPERIMENTAL_HTTP_RESPONSE_BODY_SIZE =
longKey("http.response.body.size"); // experimental

/**
* HTTP request body size
*/
public static final AttributeKey<Long> EXPERIMENTAL_HTTP_REQUEST_BODY_SIZE =
longKey("http.request.body.size"); // experimental

/**
* HTTP response content type
*/
public static final AttributeKey<String> CUSTOM_HTTP_RESPONSE_CONTENT_TYPE =
stringKey("http.response_content_type"); // custom

/**
* HTTP request content type
*/
public static final AttributeKey<String> CUSTOM_HTTP_REQUEST_CONTENT_TYPE =
stringKey("http.request_content_type"); // custom
}
Loading

0 comments on commit b82a085

Please sign in to comment.