[ 
https://issues.apache.org/jira/browse/KNOX-3347?focusedWorklogId=1025418&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1025418
 ]

ASF GitHub Bot logged work on KNOX-3347:
----------------------------------------

                Author: ASF GitHub Bot
            Created on: 16/Jun/26 14:18
            Start Date: 16/Jun/26 14:18
    Worklog Time Spent: 10m 
      Work Description: lmccay commented on code in PR #1262:
URL: https://github.com/apache/knox/pull/1262#discussion_r3421471389


##########
gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java:
##########
@@ -405,6 +415,132 @@ private boolean 
authenticateWithCookies(HttpServletRequest request, HttpServletR
     return false;
   }
 
+  /**
+   * Handle RFC 8693 token exchange flow.
+   *
+   * <p>This method validates both the subject_token and actor_token 
parameters,
+   * creates a TokenExchangePrincipal with the identity information from both 
tokens,
+   * and establishes a Subject with the actor as the PrimaryPrincipal.</p>
+   *
+   * <p>The TokenExchangePrincipal signals to the identity assertion layer that
+   * impersonation should be established with the subject as the 
ImpersonatedPrincipal.</p>
+   *
+   * @param request the HTTP request containing subject_token and actor_token 
parameters
+   * @param response the HTTP response
+   * @param chain the filter chain
+   * @throws IOException if an I/O error occurs
+   * @throws ServletException if a servlet error occurs
+   */
+  private void handleTokenExchange(HttpServletRequest request, 
HttpServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    // Extract subject_token (required)
+    String subjectTokenValue = request.getParameter(SUBJECT_TOKEN);
+    if (subjectTokenValue == null || subjectTokenValue.isEmpty()) {
+      handleValidationError(request, response, 
HttpServletResponse.SC_BAD_REQUEST,
+          "RFC 8693 token exchange requires subject_token parameter");
+      return;
+    }
+
+    // Extract actor_token (required for proper token exchange)
+    String actorTokenValue = request.getParameter(ACTOR_TOKEN);
+    if (actorTokenValue == null || actorTokenValue.isEmpty()) {
+      handleValidationError(request, response, 
HttpServletResponse.SC_BAD_REQUEST,
+          "RFC 8693 token exchange requires actor_token parameter");
+      return;
+    }
+
+    try {
+      // Parse and validate subject_token
+      JWT subjectToken = parseAndValidateJWT(request, response, chain, 
subjectTokenValue);
+      if (subjectToken == null) {
+        // Validation failed, error response already sent
+        return;
+      }
+
+      // Parse and validate actor_token
+      JWT actorToken = parseAndValidateJWT(request, response, chain, 
actorTokenValue);
+      if (actorToken == null) {
+        // Validation failed, error response already sent
+        return;
+      }
+
+      // Create Subject with actor as PrimaryPrincipal and 
TokenExchangePrincipal
+      Subject subject = createSubjectForTokenExchange(subjectToken, 
actorToken);
+
+      continueWithEstablishedSecurityContext(subject, request, response, 
chain);
+
+    } catch (ParseException e) {
+      LOGGER.failedToParsePasscodeToken(e);
+      handleValidationError(request, response, 
HttpServletResponse.SC_UNAUTHORIZED,
+          "Failed to parse token in token exchange: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Parse and validate a JWT token.
+   *
+   * @param request the HTTP request
+   * @param response the HTTP response
+   * @param chain the filter chain
+   * @param tokenValue the JWT string to parse
+   * @return the parsed and validated JWT, or null if validation failed
+   * @throws ParseException if the JWT cannot be parsed
+   * @throws IOException if an I/O error occurs during validation
+   * @throws ServletException if a servlet error occurs during validation
+   */
+  private JWT parseAndValidateJWT(HttpServletRequest request, 
HttpServletResponse response,
+                                  FilterChain chain, String tokenValue)
+      throws ParseException, IOException, ServletException {
+    JWT token = new JWTToken(tokenValue);
+    if (validateToken(request, response, chain, token)) {
+      return token;
+    }
+    // Validation failed - error response already sent by validateToken
+    return null;
+  }
+
+  /**
+   * Create a Subject for RFC 8693 token exchange with proper principal setup.
+   *
+   * @param subjectToken the validated subject token
+   * @param actorToken the validated actor token
+   * @return a Subject configured for token exchange
+   */
+  private Subject createSubjectForTokenExchange(JWT subjectToken, JWT 
actorToken) {
+    // Extract identities from the tokens
+    String subjectPrincipalName = subjectToken.getSubject();
+    String subjectIssuer = subjectToken.getIssuer();
+    String actorPrincipalName = actorToken.getSubject();
+    String actorIssuer = actorToken.getIssuer();
+    // Create principals for the Subject
+    // PrimaryPrincipal is the ACTOR (the authenticated party)
+    org.apache.knox.gateway.security.PrimaryPrincipal primaryPrincipal =
+        new 
org.apache.knox.gateway.security.PrimaryPrincipal(actorPrincipalName);
+
+    // TokenExchangePrincipal carries metadata for identity assertion layer
+    org.apache.knox.gateway.security.TokenExchangePrincipal 
tokenExchangePrincipal =
+        new org.apache.knox.gateway.security.TokenExchangePrincipalImpl(
+            subjectPrincipalName, subjectIssuer, actorPrincipalName, 
actorIssuer);
+
+    // Extract actor chain from subject_token (if present) using existing logic
+    java.util.List<java.util.Map<String, Object>> actorChain =
+        
org.apache.knox.gateway.services.security.token.TokenUtils.extractActorChain(subjectToken);
+
+    // Create Subject with all necessary principals
+    java.util.Set<java.security.Principal> principals = new 
java.util.HashSet<>();
+    principals.add(primaryPrincipal);
+    principals.add(tokenExchangePrincipal);
+
+    // Add ActorChainPrincipal if actor chain exists in subject_token
+    if (actorChain != null && !actorChain.isEmpty()) {
+      principals.add(new 
org.apache.knox.gateway.security.ActorChainPrincipalImpl(actorChain));

Review Comment:
   got it



##########
gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java:
##########
@@ -405,6 +415,132 @@ private boolean 
authenticateWithCookies(HttpServletRequest request, HttpServletR
     return false;
   }
 
+  /**
+   * Handle RFC 8693 token exchange flow.
+   *
+   * <p>This method validates both the subject_token and actor_token 
parameters,
+   * creates a TokenExchangePrincipal with the identity information from both 
tokens,
+   * and establishes a Subject with the actor as the PrimaryPrincipal.</p>
+   *
+   * <p>The TokenExchangePrincipal signals to the identity assertion layer that
+   * impersonation should be established with the subject as the 
ImpersonatedPrincipal.</p>
+   *
+   * @param request the HTTP request containing subject_token and actor_token 
parameters
+   * @param response the HTTP response
+   * @param chain the filter chain
+   * @throws IOException if an I/O error occurs
+   * @throws ServletException if a servlet error occurs
+   */
+  private void handleTokenExchange(HttpServletRequest request, 
HttpServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    // Extract subject_token (required)
+    String subjectTokenValue = request.getParameter(SUBJECT_TOKEN);
+    if (subjectTokenValue == null || subjectTokenValue.isEmpty()) {
+      handleValidationError(request, response, 
HttpServletResponse.SC_BAD_REQUEST,
+          "RFC 8693 token exchange requires subject_token parameter");
+      return;
+    }
+
+    // Extract actor_token (required for proper token exchange)
+    String actorTokenValue = request.getParameter(ACTOR_TOKEN);
+    if (actorTokenValue == null || actorTokenValue.isEmpty()) {
+      handleValidationError(request, response, 
HttpServletResponse.SC_BAD_REQUEST,
+          "RFC 8693 token exchange requires actor_token parameter");
+      return;
+    }
+
+    try {
+      // Parse and validate subject_token
+      JWT subjectToken = parseAndValidateJWT(request, response, chain, 
subjectTokenValue);
+      if (subjectToken == null) {
+        // Validation failed, error response already sent
+        return;
+      }
+
+      // Parse and validate actor_token
+      JWT actorToken = parseAndValidateJWT(request, response, chain, 
actorTokenValue);
+      if (actorToken == null) {
+        // Validation failed, error response already sent
+        return;
+      }
+
+      // Create Subject with actor as PrimaryPrincipal and 
TokenExchangePrincipal
+      Subject subject = createSubjectForTokenExchange(subjectToken, 
actorToken);
+
+      continueWithEstablishedSecurityContext(subject, request, response, 
chain);
+
+    } catch (ParseException e) {
+      LOGGER.failedToParsePasscodeToken(e);
+      handleValidationError(request, response, 
HttpServletResponse.SC_UNAUTHORIZED,
+          "Failed to parse token in token exchange: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Parse and validate a JWT token.
+   *
+   * @param request the HTTP request
+   * @param response the HTTP response
+   * @param chain the filter chain
+   * @param tokenValue the JWT string to parse
+   * @return the parsed and validated JWT, or null if validation failed
+   * @throws ParseException if the JWT cannot be parsed
+   * @throws IOException if an I/O error occurs during validation
+   * @throws ServletException if a servlet error occurs during validation
+   */
+  private JWT parseAndValidateJWT(HttpServletRequest request, 
HttpServletResponse response,
+                                  FilterChain chain, String tokenValue)
+      throws ParseException, IOException, ServletException {
+    JWT token = new JWTToken(tokenValue);
+    if (validateToken(request, response, chain, token)) {
+      return token;
+    }
+    // Validation failed - error response already sent by validateToken
+    return null;
+  }
+
+  /**
+   * Create a Subject for RFC 8693 token exchange with proper principal setup.
+   *
+   * @param subjectToken the validated subject token
+   * @param actorToken the validated actor token
+   * @return a Subject configured for token exchange
+   */
+  private Subject createSubjectForTokenExchange(JWT subjectToken, JWT 
actorToken) {
+    // Extract identities from the tokens
+    String subjectPrincipalName = subjectToken.getSubject();
+    String subjectIssuer = subjectToken.getIssuer();
+    String actorPrincipalName = actorToken.getSubject();
+    String actorIssuer = actorToken.getIssuer();
+    // Create principals for the Subject
+    // PrimaryPrincipal is the ACTOR (the authenticated party)
+    org.apache.knox.gateway.security.PrimaryPrincipal primaryPrincipal =
+        new 
org.apache.knox.gateway.security.PrimaryPrincipal(actorPrincipalName);
+
+    // TokenExchangePrincipal carries metadata for identity assertion layer
+    org.apache.knox.gateway.security.TokenExchangePrincipal 
tokenExchangePrincipal =
+        new org.apache.knox.gateway.security.TokenExchangePrincipalImpl(
+            subjectPrincipalName, subjectIssuer, actorPrincipalName, 
actorIssuer);
+
+    // Extract actor chain from subject_token (if present) using existing logic
+    java.util.List<java.util.Map<String, Object>> actorChain =
+        
org.apache.knox.gateway.services.security.token.TokenUtils.extractActorChain(subjectToken);
+
+    // Create Subject with all necessary principals
+    java.util.Set<java.security.Principal> principals = new 
java.util.HashSet<>();
+    principals.add(primaryPrincipal);
+    principals.add(tokenExchangePrincipal);
+
+    // Add ActorChainPrincipal if actor chain exists in subject_token
+    if (actorChain != null && !actorChain.isEmpty()) {
+      principals.add(new 
org.apache.knox.gateway.security.ActorChainPrincipalImpl(actorChain));
+    }
+
+    @SuppressWarnings("rawtypes")
+    java.util.HashSet emptySet = new java.util.HashSet();

Review Comment:
   got it





Issue Time Tracking
-------------------

    Worklog Id:     (was: 1025418)
    Time Spent: 50m  (was: 40m)

> Introduce TokenExchangePrincipal for extending Act claim for token_exchange
> ---------------------------------------------------------------------------
>
>                 Key: KNOX-3347
>                 URL: https://issues.apache.org/jira/browse/KNOX-3347
>             Project: Apache Knox
>          Issue Type: Improvement
>          Components: JWT
>            Reporter: Larry McCay
>            Priority: Major
>             Fix For: 3.0.0
>
>          Time Spent: 50m
>  Remaining Estimate: 0h
>
> Currently, the ActorChainPrincipal includes whatever act chain was in the 
> Subject token from the token_exchange. The presence of the 
> ImpersonatedPrincipal is currently only added by the identity assertion 
> provider based on a doAs and proxyuser based impersonation. This is required 
> for the new actor to be added to the nested 'act' claim.
> Let's add not the use of the TokenExchangePrincipal to the identity assertion 
> logic that sets the ImpersonatedPrincipal in addition to the doAs pattern.
>  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to