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

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

commit baefcd58ac3843695fbc838a768aa966d6eed6c1
Author: Sandor Molnar <[email protected]>
AuthorDate: Mon Aug 10 16:58:20 2026 +0200

    KNOX-3408 - Move act-claim/ActorChainPrincipal coverage into 
TokenExchangeHandlerTest; drop redundant 
JWTFederationFilterHandleTokenExchangeTest
---
 ...JWTFederationFilterHandleTokenExchangeTest.java | 262 ---------------------
 .../jwt/filter/TokenExchangeHandlerTest.java       |  29 +++
 2 files changed, 29 insertions(+), 262 deletions(-)

diff --git 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterHandleTokenExchangeTest.java
 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterHandleTokenExchangeTest.java
deleted file mode 100644
index ca23a5ae7..000000000
--- 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterHandleTokenExchangeTest.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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.knox.gateway.provider.federation;
-
-import com.nimbusds.jose.crypto.RSASSASigner;
-import com.nimbusds.jwt.SignedJWT;
-import 
org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter;
-import 
org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
-import org.apache.knox.gateway.security.ActorChainPrincipal;
-import org.apache.knox.gateway.security.CommonTokenConstants;
-import org.apache.knox.gateway.security.ImpersonatedPrincipal;
-import org.apache.knox.gateway.security.PrimaryPrincipal;
-import org.apache.knox.gateway.security.SubjectUtils;
-import org.apache.knox.gateway.security.TokenExchangePrincipal;
-import 
org.apache.knox.gateway.services.security.token.JWTokenAttributesBuilder;
-import org.apache.knox.gateway.services.security.token.impl.JWTToken;
-import org.easymock.EasyMock;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.security.Principal;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
-import static 
org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter.JWT_DEFAULT_ISSUER;
-
-/**
- * Unit tests for the {@link JWTFederationFilter#handleTokenExchange} method 
(OIDC
- * delegation path). Each test verifies the Subject constructed.
- *
- * <p>These tests use {@link TestJWTFederationFilter} with {@link 
TestJWTokenAuthority} (static key,
- * no mocking needed — both tokens use Knox issuer {@code JWT_DEFAULT_ISSUER} 
which is in the
- * static expected-issuers list).
- *
- * <p>The filter's {@code continueWithEstablishedSecurityContext} runs
- * {@code Subject.doAs(subject, () -> chain.doFilter(request, response))}. The
- * {@link AbstractJWTFilterTest.TestFilterChain} captures {@code 
SubjectUtils.getCurrentSubject()}
- * from within that doAs context, which is exactly the Subject built. All 
principal
- * assertions use {@code chain.subject.getPrincipals(XxxPrincipal.class)}.
- */
-public class JWTFederationFilterHandleTokenExchangeTest extends 
AbstractJWTFilterTest {
-
-  static final String ACTOR_ISSUER = "https://actor.oidc.example.com";;
-
-  @Before
-  public void setUp() throws Exception {
-    handler = new TestJWTFederationFilter();
-    ((TestJWTFederationFilter) handler).setTokenService(new 
TestJWTokenAuthority(publicKey));
-    handler.init(new TestFilterConfig(getProperties()));
-  }
-
-  @Override
-  protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) {
-    EasyMock.expect(request.getHeader("Authorization"))
-        .andReturn(JWTFederationFilter.BEARER + jwt.serialize()).anyTimes();
-  }
-
-  @Override
-  protected void setGarbledTokenOnRequest(HttpServletRequest request, 
SignedJWT jwt) {
-    EasyMock.expect(request.getHeader("Authorization"))
-        .andReturn(JWTFederationFilter.BEARER + "ljm" + 
jwt.serialize()).anyTimes();
-  }
-
-  @Override
-  protected String getAudienceProperty() {
-    return JWTFederationFilter.KNOX_TOKEN_AUDIENCES;
-  }
-
-  @Override
-  protected String getVerificationPemProperty() {
-    return JWTFederationFilter.TOKEN_VERIFICATION_PEM;
-  }
-
-  /**
-   * When both subject_token and actor_token are present, the filter 
establishes the actor
-   * (from actor_token.sub) as the PrimaryPrincipal in the resulting Subject.
-   */
-  @Test
-  public void testActorAndSubjectTokensSetActorAsPrimaryPrincipal() throws 
Exception {
-    SignedJWT subjectJwt = getJWT(JWT_DEFAULT_ISSUER, "k8s-sa",
-        new Date(System.currentTimeMillis() + 60000), privateKey);
-    SignedJWT actorJwt = getJWT(JWT_DEFAULT_ISSUER, "actor-svc",
-        new Date(System.currentTimeMillis() + 60000), privateKey);
-
-    HttpServletRequest request = 
buildTokenExchangeRequest(subjectJwt.serialize(), actorJwt.serialize());
-    EasyMock.replay(request);
-    HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
-    EasyMock.replay(response);
-
-    TestFilterChain chain = new TestFilterChain();
-    handler.doFilter(request, response, chain);
-
-    Assert.assertTrue("doFilterCalled should be true", chain.doFilterCalled);
-    Set<PrimaryPrincipal> principals = 
chain.subject.getPrincipals(PrimaryPrincipal.class);
-    Assert.assertEquals("Expected exactly one PrimaryPrincipal", 1, 
principals.size());
-    Assert.assertEquals("Expected actor as PrimaryPrincipal", "actor-svc",
-        ((Principal) principals.toArray()[0]).getName());
-  }
-
-  /**
-   * When subject_token and actor_token have different issuers, the filter 
creates a
-   * TokenExchangePrincipal that carries the subject and actor identities with 
their respective
-   * issuers. Using different issuers ensures that all four TEP fields can be 
asserted
-   * unambiguously.
-   *
-   * <p>Both issuers are added to the static {@code jwt.expected.issuer} 
whitelist —
-   * {@code TestJWTokenAuthority} accepts either token because they are signed 
with
-   * the same test key.
-   */
-  @Test
-  public void testActorAndSubjectTokensCreateTokenExchangePrincipal() throws 
Exception {
-    Properties props = getProperties();
-    props.setProperty(AbstractJWTFilter.JWT_EXPECTED_ISSUER, 
JWT_DEFAULT_ISSUER + "," + ACTOR_ISSUER);
-    handler.init(new TestFilterConfig(props));
-
-    SignedJWT subjectJwt = getJWT(JWT_DEFAULT_ISSUER, "k8s-sa",
-        new Date(System.currentTimeMillis() + 60000), privateKey);
-    SignedJWT actorJwt = getJWT(ACTOR_ISSUER, "actor-svc",
-        new Date(System.currentTimeMillis() + 60000), privateKey);
-
-    HttpServletRequest request = 
buildTokenExchangeRequest(subjectJwt.serialize(), actorJwt.serialize());
-    EasyMock.replay(request);
-    HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
-    EasyMock.replay(response);
-
-    TestFilterChain chain = new TestFilterChain();
-    handler.doFilter(request, response, chain);
-
-    Assert.assertTrue("doFilterCalled should be true", chain.doFilterCalled);
-    TokenExchangePrincipal tep = 
SubjectUtils.getTokenExchangePrincipal(chain.subject);
-    Assert.assertNotNull("TokenExchangePrincipal should be present", tep);
-    Assert.assertEquals("Subject principal name", "k8s-sa", 
tep.getSubjectPrincipalName());
-    Assert.assertEquals("Actor principal name", "actor-svc", 
tep.getActorPrincipalName());
-    Assert.assertEquals("Subject issuer", JWT_DEFAULT_ISSUER, 
tep.getSubjectIssuer());
-    Assert.assertEquals("Actor issuer", ACTOR_ISSUER, tep.getActorIssuer());
-  }
-
-  /**
-   * When subject_token carries an {@code act} claim (a prior delegation 
chain), the filter
-   * extracts it and creates an {@code ActorChainPrincipal} in the resulting 
Subject so that
-   * the delegation history is preserved through the filter pipeline.
-   */
-  @Test
-  public void testSubjectTokenWithActClaimCreatesActorChainPrincipal() throws 
Exception {
-    List<Map<String, Object>> actorChainData = List.of(Map.of("sub", 
"prior-actor"));
-    JWTToken subjectToken = new JWTToken(new JWTokenAttributesBuilder()
-        .setUserName("k8s-sa")
-        .setIssuer(JWT_DEFAULT_ISSUER)
-        .setAlgorithm("RS256")
-        .setExpires(System.currentTimeMillis() + 60000)
-        .setActorChain(actorChainData)
-        .build());
-    subjectToken.sign(new RSASSASigner(privateKey));
-
-    SignedJWT actorJwt = getJWT(JWT_DEFAULT_ISSUER, "actor-svc",
-        new Date(System.currentTimeMillis() + 60000), privateKey);
-
-    HttpServletRequest request = 
buildTokenExchangeRequest(subjectToken.toString(), actorJwt.serialize());
-    EasyMock.replay(request);
-    HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
-    EasyMock.replay(response);
-
-    TestFilterChain chain = new TestFilterChain();
-    handler.doFilter(request, response, chain);
-
-    Assert.assertTrue("doFilterCalled should be true", chain.doFilterCalled);
-    Set<ActorChainPrincipal> actorChainPrincipals = 
chain.subject.getPrincipals(ActorChainPrincipal.class);
-    Assert.assertFalse("ActorChainPrincipal should be present", 
actorChainPrincipals.isEmpty());
-    ActorChainPrincipal acp = actorChainPrincipals.iterator().next();
-    Assert.assertEquals("Expected current actor from act claim", 
"prior-actor", acp.getCurrentActor());
-  }
-
-  /**
-   * With no actor_token provided, the filter proceeds successfully and the 
resulting Subject
-   * has the subject itself as PrimaryPrincipal, no TokenExchangePrincipal, 
and no
-   * ImpersonatedPrincipal.
-   */
-  @Test
-  public void testSubjectTokenOnlySucceeds() throws Exception {
-    SignedJWT subjectJwt = getJWT(JWT_DEFAULT_ISSUER, "k8s-sa",
-        new Date(System.currentTimeMillis() + 60000), privateKey);
-
-    HttpServletRequest request = 
buildTokenExchangeRequestSubjectOnly(subjectJwt.serialize());
-    EasyMock.replay(request);
-    HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
-    EasyMock.replay(response);
-
-    TestFilterChain chain = new TestFilterChain();
-    handler.doFilter(request, response, chain);
-
-    Assert.assertTrue("doFilterCalled should be true", chain.doFilterCalled);
-    Set<PrimaryPrincipal> principals = 
chain.subject.getPrincipals(PrimaryPrincipal.class);
-    Assert.assertEquals("Expected exactly one PrimaryPrincipal", 1, 
principals.size());
-    Assert.assertEquals("Subject should be its own PrimaryPrincipal", "k8s-sa",
-        ((java.security.Principal) principals.toArray()[0]).getName());
-    Assert.assertNull("No TokenExchangePrincipal expected for subject-only 
exchange",
-        SubjectUtils.getTokenExchangePrincipal(chain.subject));
-    Assert.assertTrue("ImpersonatedPrincipal set should be empty",
-        chain.subject.getPrincipals(ImpersonatedPrincipal.class).isEmpty());
-  }
-
-  /**
-   * Builds a token-exchange request mock with both subject_token and 
actor_token parameters.
-   * The caller must call {@code EasyMock.replay(request)} before using the 
returned mock.
-   *
-   * @param subjectToken serialized subject JWT
-   * @param actorToken   serialized actor JWT
-   * @return a NiceMock HttpServletRequest configured for a token-exchange 
grant
-   */
-  private HttpServletRequest buildTokenExchangeRequest(String subjectToken, 
String actorToken) {
-    HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-    EasyMock.expect(request.getRequestURL())
-        .andReturn(new StringBuffer(SERVICE_URL)).anyTimes();
-    EasyMock.expect(request.getParameter(CommonTokenConstants.GRANT_TYPE))
-        .andReturn(JWTFederationFilter.TOKEN_EXCHANGE).anyTimes();
-    EasyMock.expect(request.getParameter(JWTFederationFilter.SUBJECT_TOKEN))
-        .andReturn(subjectToken).anyTimes();
-    EasyMock.expect(request.getParameter(JWTFederationFilter.ACTOR_TOKEN))
-        .andReturn(actorToken).anyTimes();
-    return request;
-  }
-
-  /**
-   * Builds a token-exchange request mock with subject_token only. The 
actor_token parameter
-   * is not mocked, so the NiceMock returns null for {@code 
getParameter(ACTOR_TOKEN)}.
-   * The caller must call {@code EasyMock.replay(request)} before using the 
returned mock.
-   *
-   * @param subjectToken serialized subject JWT
-   * @return a NiceMock HttpServletRequest configured for a subject-only 
token-exchange grant
-   */
-  private HttpServletRequest buildTokenExchangeRequestSubjectOnly(String 
subjectToken) {
-    HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-    EasyMock.expect(request.getRequestURL())
-        .andReturn(new StringBuffer(SERVICE_URL)).anyTimes();
-    EasyMock.expect(request.getParameter(CommonTokenConstants.GRANT_TYPE))
-        .andReturn(JWTFederationFilter.TOKEN_EXCHANGE).anyTimes();
-    EasyMock.expect(request.getParameter(JWTFederationFilter.SUBJECT_TOKEN))
-        .andReturn(subjectToken).anyTimes();
-    // ACTOR_TOKEN not mocked — NiceMock returns null for 
getParameter(ACTOR_TOKEN)
-    return request;
-  }
-}
diff --git 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/jwt/filter/TokenExchangeHandlerTest.java
 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/jwt/filter/TokenExchangeHandlerTest.java
index 9b7608828..49561ad66 100644
--- 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/jwt/filter/TokenExchangeHandlerTest.java
+++ 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/jwt/filter/TokenExchangeHandlerTest.java
@@ -21,9 +21,11 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import org.apache.knox.gateway.security.ActorChainPrincipal;
 import org.apache.knox.gateway.security.PrimaryPrincipal;
 import org.apache.knox.gateway.security.TokenExchangePrincipal;
 import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
 import org.easymock.EasyMock;
 import org.junit.Before;
 import org.junit.Test;
@@ -38,6 +40,7 @@ import java.io.IOException;
 import java.text.ParseException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Unit tests for {@link TokenExchangeHandler} covering the RFC 8693 
request-validation and
@@ -152,6 +155,22 @@ public class TokenExchangeHandlerTest {
     assertEquals("svc-dataservice", tep.getActorPrincipalName());
   }
 
+  @Test
+  public void testSubjectTokenWithActClaimCreatesActorChainPrincipal() throws 
Exception {
+    // subject_token already carries a prior delegation chain (an 'act' claim) 
...
+    filter.valid.put("subtok", jwtWithActClaim("alice", "KNOXSSO", 
Map.of("sub", "prior-actor")));
+    filter.valid.put("acttok", jwt("svc-dataservice", "https://k8s";));
+    handler.handle(request("subtok", JWT_TYPE, "acttok", JWT_TYPE), response, 
chain);
+
+    assertTrue(filter.continued);
+    assertNotNull(filter.establishedSubject);
+    // ... which is preserved as an ActorChainPrincipal in the exchanged 
Subject
+    final Set<ActorChainPrincipal> actorChainPrincipals =
+        filter.establishedSubject.getPrincipals(ActorChainPrincipal.class);
+    assertFalse("ActorChainPrincipal should be present", 
actorChainPrincipals.isEmpty());
+    assertEquals("prior-actor", 
actorChainPrincipals.iterator().next().getCurrentActor());
+  }
+
   @Test
   public void testSubjectValidationFailureDoesNotEstablishContext() throws 
Exception {
     // "subtok" is not in the valid map -> parseAndValidateJWT returns null 
(error already sent)
@@ -192,6 +211,16 @@ public class TokenExchangeHandlerTest {
     return jwt;
   }
 
+  private static JWT jwtWithActClaim(String subject, String issuer, 
Map<String, Object> actClaim) {
+    final JWT jwt = EasyMock.createNiceMock(JWT.class);
+    EasyMock.expect(jwt.getSubject()).andReturn(subject).anyTimes();
+    EasyMock.expect(jwt.getIssuer()).andReturn(issuer).anyTimes();
+    // subject_token carries a prior delegation chain via its 'act' claim
+    
EasyMock.expect(jwt.getClaimAsObject(JWTToken.ACT_CLAIM)).andReturn(actClaim).anyTimes();
+    EasyMock.replay(jwt);
+    return jwt;
+  }
+
   /**
    * A {@link JWTFederationFilter} whose validation and context-establishment 
callbacks are
    * replaced with recording stubs, so the handler's own logic can be 
exercised in isolation.

Reply via email to