This is an automated email from the ASF dual-hosted git repository.

smolnar82 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new da08af1f0 KNOX-3403: RFC 8693 token exchange: use the impersonated 
subject as the token sub on non-server-managed topologies (#1336)
da08af1f0 is described below

commit da08af1f0f44a437d3e9cf3e019215013e2f1df0
Author: Sandor Molnar <[email protected]>
AuthorDate: Fri Jul 31 07:11:35 2026 +0200

    KNOX-3403: RFC 8693 token exchange: use the impersonated subject as the 
token sub on non-server-managed topologies (#1336)
---
 .../gateway/service/knoxtoken/TokenResource.java   | 25 ++++++-----
 .../knoxtoken/TokenServiceResourceTest.java        | 51 ++++++++++++++++++++++
 2 files changed, 64 insertions(+), 12 deletions(-)

diff --git 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 8da6f137b..6d876648b 100644
--- 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++ 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -920,18 +920,19 @@ public class TokenResource {
   protected UserContext buildUserContext(HttpServletRequest request) {
     String userName = request.getUserPrincipal().getName();
     String createdBy = null;
-    // checking the doAs user only makes sense if tokens are managed (this is 
where we store the userName/createdBy information)
-    // and if impersonation was enabled before (on HadoopAuth or 
identity-assertion level) so the the current subject has at least one 
ImpersonatedPrincipal principal
-    if (tokenStateService != null) {
-      final Subject subject = SubjectUtils.getCurrentSubject();
-      if (subject != null && SubjectUtils.isImpersonating(subject)) {
-        String primaryPrincipalName = 
SubjectUtils.getPrimaryPrincipalName(subject);
-        String impersonatedPrincipalName = 
SubjectUtils.getImpersonatedPrincipalName(subject);
-        if (!primaryPrincipalName.equals(impersonatedPrincipalName)) {
-          createdBy = primaryPrincipalName;
-          userName = impersonatedPrincipalName;
-          log.tokenImpersonationSuccess(createdBy, userName);
-        }
+    // When impersonation is in effect, the issued token's subject must be the 
effective (impersonated)
+    // identity, not the primary principal. This applies to traditional doAs 
as well as RFC 8693 token
+    // exchange (where the actor is the primary principal and the subject is 
the impersonated one).
+    // This is independent of server-managed state: createdBy is only 
persisted for managed tokens (see
+    // persistTokenDetails), so computing it here is harmless when there is no 
token state service.
+    final Subject subject = SubjectUtils.getCurrentSubject();
+    if (subject != null && SubjectUtils.isImpersonating(subject)) {
+      String primaryPrincipalName = 
SubjectUtils.getPrimaryPrincipalName(subject);
+      String impersonatedPrincipalName = 
SubjectUtils.getImpersonatedPrincipalName(subject);
+      if (!primaryPrincipalName.equals(impersonatedPrincipalName)) {
+        createdBy = primaryPrincipalName;
+        userName = impersonatedPrincipalName;
+        log.tokenImpersonationSuccess(createdBy, userName);
       }
     }
     return new UserContext(userName, createdBy);
diff --git 
a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
 
b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 281ea90ea..8398e168b 100644
--- 
a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ 
b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -2220,4 +2220,55 @@ public class TokenServiceResourceTest {
 
     EasyMock.verify(request, context);
   }
+
+  /**
+   * KNOX-3403: On a NON-server-managed topology, an impersonated request 
(traditional doAs or RFC 8693
+   * token exchange, where the actor is the primary principal and the subject 
is the impersonated one)
+   * must still issue a token whose {@code sub} is the impersonated subject. 
Previously buildUserContext
+   * only applied the impersonated identity when {@code tokenStateService != 
null}, so on a non-managed
+   * topology the {@code sub} incorrectly fell back to the primary principal 
(the actor).
+   */
+  @Test
+  @SuppressForbidden
+  public void testImpersonatedTokenSubjectOnNonServerManagedTopology() throws 
Exception {
+    final String primaryUser = "admin";      // primary principal 
(authenticated caller / actor)
+    final String impersonatedUser = "bob";  // impersonated subject (distinct 
from USER_NAME)
+
+    // No serverManagedTssEnabled argument -> token state service is absent 
(non-server-managed).
+    configureCommonExpectations(createDelegatedAuthContextExpectations(true, 
true));
+    Subject subject = createSubjectWithOptionalImpersonation(primaryUser, 
impersonatedUser);
+    JWTToken parsedToken = getTokenWithSubject(subject);
+
+    // The token subject must be the impersonated user, not the primary.
+    assertEquals("Non-server-managed impersonated token must use the 
impersonated subject as sub",
+            impersonatedUser, parsedToken.getSubject());
+
+    // The 'act' claim still records the primary user (delegated auth enabled).
+    Object actClaim = parsedToken.getClaimAsObject(JWTToken.ACT_CLAIM);
+    assertNotNull("RFC 8693 'act' claim should be present", actClaim);
+    assertTrue("'act' claim should be a Map", actClaim instanceof Map);
+    @SuppressWarnings("unchecked")
+    Map<String, Object> actClaimMap = (Map<String, Object>) actClaim;
+    assertEquals("'act' claim should contain the primary user's subject", 
primaryUser, actClaimMap.get("sub"));
+
+    EasyMock.verify(request, context);
+  }
+
+  /**
+   * KNOX-3403: sanity check that decoupling the impersonated-sub from 
server-managed does not change
+   * the non-impersonating case - the token {@code sub} remains the 
authenticated (primary) user on a
+   * non-server-managed topology.
+   */
+  @Test
+  @SuppressForbidden
+  public void testNonImpersonatedTokenSubjectOnNonServerManagedTopology() 
throws Exception {
+    configureCommonExpectations(createDelegatedAuthContextExpectations(true, 
false));
+    Subject subject = createSubjectWithOptionalImpersonation(USER_NAME, null);
+    JWTToken parsedToken = getTokenWithSubject(subject);
+
+    assertEquals(USER_NAME, parsedToken.getSubject());
+    assertNull("'act' claim should NOT be present without impersonation", 
parsedToken.getClaimAsObject(JWTToken.ACT_CLAIM));
+
+    EasyMock.verify(request, context);
+  }
 }

Reply via email to