This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/master by this push:
new d4e3bf340a refactor: extract shared AuthTokenExtractor for the gRPC
and HTTP auth paths (#642)
d4e3bf340a is described below
commit d4e3bf340a65a9579b5e9350ec8b3b8cb785c8dd
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Sun Jun 7 21:18:52 2026 -0400
refactor: extract shared AuthTokenExtractor for the gRPC and HTTP auth
paths (#642)
GrpcAuthInterceptor and HttpAuthDecorator independently stripped the Bearer
prefix, parsed the x-claims JSON header (each with its own ObjectMapper), and
built the AuthzToken. A new AuthTokenExtractor (stripBearer / parseClaims /
buildAuthzToken, with one shared ObjectMapper) provides those three steps; each
transport keeps its own concerns: the gRPC UNAUTHENTICATED close, the HTTP
UNAUTHORIZED return and per-header claims fallback, and the UserContext
lifecycle. Behavior-preserving a [...]
---
.../server/grpc/config/AuthTokenExtractor.java | 69 ++++++++++++++++++++++
.../server/grpc/config/GrpcAuthInterceptor.java | 27 +--------
.../server/grpc/config/HttpAuthDecorator.java | 22 +------
3 files changed, 75 insertions(+), 43 deletions(-)
diff --git
a/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/AuthTokenExtractor.java
b/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/AuthTokenExtractor.java
new file mode 100644
index 0000000000..b1edae9af7
--- /dev/null
+++
b/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/AuthTokenExtractor.java
@@ -0,0 +1,69 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.server.grpc.config;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.airavata.model.security.proto.AuthzToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Shared parsing of the Bearer access token and the {@code x-claims} header
used by both the gRPC
+ * ({@link GrpcAuthInterceptor}) and HTTP ({@link HttpAuthDecorator})
authentication paths. Transport-specific
+ * concerns (rejecting missing tokens, per-transport header fallbacks,
UserContext lifecycle) stay in the callers.
+ */
+public final class AuthTokenExtractor {
+
+ private static final Logger log =
LoggerFactory.getLogger(AuthTokenExtractor.class);
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ private AuthTokenExtractor() {}
+
+ /** Returns the bearer access token, or {@code null} if the header is
absent or not a Bearer token. */
+ public static String stripBearer(String authHeader) {
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ return authHeader.substring(7);
+ }
+ return null;
+ }
+
+ /** Parses the {@code x-claims} JSON header into a mutable claims map;
empty on absence or parse failure. */
+ public static Map<String, String> parseClaims(String claimsHeader) {
+ if (claimsHeader != null && !claimsHeader.isBlank()) {
+ try {
+ return objectMapper.readValue(claimsHeader, new
TypeReference<Map<String, String>>() {});
+ } catch (Exception e) {
+ log.warn("Failed to parse x-claims: {}", e.getMessage());
+ }
+ }
+ return new HashMap<>();
+ }
+
+ /** Builds an AuthzToken from the access token and (possibly augmented)
claims map. */
+ public static AuthzToken buildAuthzToken(String accessToken, Map<String,
String> claimsMap) {
+ return AuthzToken.newBuilder()
+ .setAccessToken(accessToken)
+ .putAllClaimsMap(claimsMap)
+ .build();
+ }
+}
diff --git
a/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/GrpcAuthInterceptor.java
b/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/GrpcAuthInterceptor.java
index 901cfdf1c8..c7a716e957 100644
---
a/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/GrpcAuthInterceptor.java
+++
b/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/GrpcAuthInterceptor.java
@@ -19,15 +19,12 @@
*/
package org.apache.airavata.server.grpc.config;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.grpc.ForwardingServerCallListener;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
-import java.util.HashMap;
import java.util.Map;
import org.apache.airavata.config.UserContext;
import org.apache.airavata.model.security.proto.AuthzToken;
@@ -39,7 +36,6 @@ import org.springframework.stereotype.Component;
public class GrpcAuthInterceptor implements ServerInterceptor {
private static final Logger log =
LoggerFactory.getLogger(GrpcAuthInterceptor.class);
- private static final ObjectMapper objectMapper = new ObjectMapper();
private static final Metadata.Key<String> AUTHORIZATION_KEY =
Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
@@ -50,31 +46,14 @@ public class GrpcAuthInterceptor implements
ServerInterceptor {
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call, Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
- String authHeader = headers.get(AUTHORIZATION_KEY);
- String accessToken = null;
- if (authHeader != null && authHeader.startsWith("Bearer ")) {
- accessToken = authHeader.substring(7);
- }
-
+ String accessToken =
AuthTokenExtractor.stripBearer(headers.get(AUTHORIZATION_KEY));
if (accessToken == null) {
call.close(Status.UNAUTHENTICATED.withDescription("Missing
authorization metadata"), new Metadata());
return new ServerCall.Listener<>() {};
}
- Map<String, String> claimsMap = new HashMap<>();
- String claimsHeader = headers.get(X_CLAIMS_KEY);
- if (claimsHeader != null && !claimsHeader.isBlank()) {
- try {
- claimsMap = objectMapper.readValue(claimsHeader, new
TypeReference<Map<String, String>>() {});
- } catch (Exception e) {
- log.warn("Failed to parse x-claims metadata: {}",
e.getMessage());
- }
- }
-
- AuthzToken authzToken = AuthzToken.newBuilder()
- .setAccessToken(accessToken)
- .putAllClaimsMap(claimsMap)
- .build();
+ Map<String, String> claimsMap =
AuthTokenExtractor.parseClaims(headers.get(X_CLAIMS_KEY));
+ AuthzToken authzToken =
AuthTokenExtractor.buildAuthzToken(accessToken, claimsMap);
UserContext.setAuthzToken(authzToken);
ServerCall.Listener<ReqT> delegate = next.startCall(call, headers);
diff --git
a/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/HttpAuthDecorator.java
b/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/HttpAuthDecorator.java
index f7e105737a..352e0b6f7d 100644
---
a/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/HttpAuthDecorator.java
+++
b/airavata-server/src/main/java/org/apache/airavata/server/grpc/config/HttpAuthDecorator.java
@@ -19,15 +19,12 @@
*/
package org.apache.airavata.server.grpc.config;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.server.DecoratingHttpServiceFunction;
import com.linecorp.armeria.server.HttpService;
import com.linecorp.armeria.server.ServiceRequestContext;
-import java.util.HashMap;
import java.util.Map;
import org.apache.airavata.config.Constants;
import org.apache.airavata.config.UserContext;
@@ -38,29 +35,16 @@ import org.slf4j.LoggerFactory;
public class HttpAuthDecorator implements DecoratingHttpServiceFunction {
private static final Logger log =
LoggerFactory.getLogger(HttpAuthDecorator.class);
- private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public HttpResponse serve(HttpService delegate, ServiceRequestContext ctx,
HttpRequest req) throws Exception {
- String authHeader = req.headers().get("authorization");
- String accessToken = null;
- if (authHeader != null && authHeader.startsWith("Bearer ")) {
- accessToken = authHeader.substring(7);
- }
-
+ String accessToken =
AuthTokenExtractor.stripBearer(req.headers().get("authorization"));
if (accessToken == null) {
return HttpResponse.of(HttpStatus.UNAUTHORIZED);
}
- Map<String, String> claimsMap = new HashMap<>();
- String claimsHeader = req.headers().get("x-claims");
- if (claimsHeader != null && !claimsHeader.isBlank()) {
- try {
- claimsMap = objectMapper.readValue(claimsHeader, new
TypeReference<Map<String, String>>() {});
- } catch (Exception e) {
- log.warn("Failed to parse x-claims header: {}",
e.getMessage());
- }
- }
+ Map<String, String> claimsMap =
+ AuthTokenExtractor.parseClaims(req.headers().get("x-claims"));
// Fall back to individual headers
if (!claimsMap.containsKey(Constants.USER_NAME)) {