From 43eeff06b9245429287301097467765707215ce1 Mon Sep 17 00:00:00 2001 From: Sandor Molnar Date: Mon, 15 Jun 2026 14:18:50 +0200 Subject: [PATCH] KNOX-3350: Populate groups in KnoxSSO cookie when configured --- .../service/knoxsso/WebSSOResource.java | 14 +++- .../service/knoxsso/WebSSOResourceTest.java | 67 +++++++++++++++++++ .../service/knoxtoken/TokenResource.java | 6 +- .../knox/gateway/security/SubjectUtils.java | 6 ++ 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java b/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java index 16e6762403..9b2bab19de 100644 --- a/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java +++ b/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import javax.annotation.PostConstruct; import javax.servlet.ServletContext; @@ -51,6 +52,7 @@ import org.apache.knox.gateway.audit.log4j.audit.Log4jAuditor; import org.apache.knox.gateway.config.GatewayConfig; import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.security.SubjectUtils; import org.apache.knox.gateway.services.GatewayServices; import org.apache.knox.gateway.services.ServiceType; import org.apache.knox.gateway.services.security.AliasService; @@ -84,6 +86,7 @@ public class WebSSOResource { private static final String SSO_COOKIE_TOKEN_TYPE_PARAM = "knoxsso.token.type"; private static final String SSO_COOKIE_TOKEN_AUDIENCES_PARAM = "knoxsso.token.audiences"; private static final String SSO_COOKIE_TOKEN_SIG_ALG = "knoxsso.token.sigalg"; + private static final String SSO_COOKIE_INCLUDE_GROUPS_PARAM = "knoxsso.token.include.groups"; private static final String SSO_COOKIE_TOKEN_WHITELIST_PARAM = "knoxsso.redirect.whitelist.regex"; private static final String SSO_SIGNINGKEY_KEYSTORE_NAME = "knoxsso.signingkey.keystore.name"; @@ -111,6 +114,7 @@ public class WebSSOResource { private List targetAudiences = new ArrayList<>(); private boolean enableSession; private String signatureAlgorithm; + private boolean includeGroups; private List ssoExpectedparams = new ArrayList<>(); private String clusterName; private String tokenIssuer; @@ -224,6 +228,8 @@ private void handleCookieSetup() { } final String configuredTokenType = context.getInitParameter(SSO_COOKIE_TOKEN_TYPE_PARAM); tokenType = StringUtils.isBlank(configuredTokenType) ? JOSEObjectType.JWT.getType() : configuredTokenType; + + includeGroups = Boolean.parseBoolean(context.getInitParameter(SSO_COOKIE_INCLUDE_GROUPS_PARAM)); } @GET @@ -314,7 +320,9 @@ private Response getAuthenticationToken(int statusCode) { .setSigningKeystorePassphrase(signingKeystorePassphrase) .setManaged(tokenStateService != null) .setType(tokenType) + .setGroups(groups()) .build(); + JWT token = tokenAuthority.issueToken(jwtAttributes); // Coverity CID 1327959 @@ -349,11 +357,13 @@ private Response getAuthenticationToken(int statusCode) { // todo log return error response } - - return Response.seeOther(location).entity("{ \"redirectTo\" : " + original + " }").build(); } + Set groups() { + return includeGroups ? SubjectUtils.getCurrentGroupPrincipalNames() : null; + } + protected String getOriginalUrlFromQueryParams() { String original = request.getParameter(ORIGINAL_URL_REQUEST_PARAM); StringBuilder buf = new StringBuilder(original); diff --git a/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java b/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java index f0e1194784..8fb334ce85 100644 --- a/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java +++ b/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java @@ -688,6 +688,55 @@ public void testCustomSigningKey() throws Exception { assertTrue(authority.verifyToken(parsedToken, customPublicKey)); } + @Test + public void testIncludeGroupsTrue() throws Exception { + testIncludeGroups(Boolean.TRUE, Collections.singleton("group1"), true); + } + + @Test + public void testIncludeGroupsFalse() throws Exception { + testIncludeGroups(Boolean.FALSE, Collections.singleton("group1"), false); + } + + @Test + public void testIncludeGroupsOmitted() throws Exception { + testIncludeGroups(null, Collections.singleton("group1"), false); + } + + private void testIncludeGroups(Boolean includeGroupsParam, Set groupsToReturn, boolean expectedInToken) throws Exception { + final boolean includeGroups = includeGroupsParam != null && includeGroupsParam; + configureCommonExpectations(includeGroups ? Map.of("knoxsso.token.include.groups", includeGroupsParam.toString()) : Map.of()); + + final TestWebSSOResource webSSOResponse = new TestWebSSOResource(includeGroups ? groupsToReturn : null); + webSSOResponse.request = request; + webSSOResponse.response = responseWrapper; + webSSOResponse.context = context; + webSSOResponse.init(); + + // Issue a token + webSSOResponse.doGet(); + + // Check the cookie + final Cookie cookie = responseWrapper.getCookie("hadoop-jwt"); + assertNotNull(cookie); + + final JWT parsedToken = new JWTToken(cookie.getValue()); + assertEquals("alice", parsedToken.getSubject()); + assertTrue(authority.verifyToken(parsedToken)); + + // Verify the groups + List tokenGroups = (List) parsedToken.getClaimAsObject(JWTToken.KNOX_GROUPS_CLAIM); + if (expectedInToken) { + assertNotNull(tokenGroups); + assertEquals(groupsToReturn.size(), tokenGroups.size()); + for (String group : groupsToReturn) { + assertTrue(tokenGroups.contains(group)); + } + } else { + Assert.assertNull(tokenGroups); + } + } + @Test public void testConcurrentSessionLimitHit() throws Exception { configureCommonExpectations(Collections.emptyMap(), false, false, false); @@ -809,6 +858,24 @@ public void testGetOriginalUrlFromQueryParams() throws Exception { } + private static class TestWebSSOResource extends WebSSOResource { + private final Set groups; + + private TestWebSSOResource(Set groups) { + this.groups = groups; + } + + + @Override + Set groups() { + try { + return groups; + } catch (Exception e) { + return null; + } + } + } + /** * A wrapper for HttpServletResponseWrapper to store the cookies */ 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 8da6f137b1..c52af6df5e 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 @@ -1149,11 +1149,7 @@ private boolean shouldIncludeGroups() { } protected Set groups() { - Subject subject = SubjectUtils.getCurrentSubject(); - Set groups = subject.getPrincipals(GroupPrincipal.class).stream() - .map(GroupPrincipal::getName) - .collect(Collectors.toSet()); - return groups; + return SubjectUtils.getCurrentGroupPrincipalNames(); } protected void addArbitraryTokenMetadata(TokenMetadata tokenMetadata) { diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java index 11db9113fb..d3d61bdc7f 100644 --- a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; /** * General utility methods for interrogating the standard java Subject @@ -94,6 +95,11 @@ public static Set getCurrentGroupPrincipals() { return subject == null ? Collections.emptySet() : getGroupPrincipals(subject); } + public static Set getCurrentGroupPrincipalNames() { + final Set groupPrincipals = getCurrentGroupPrincipals(); + return groupPrincipals.isEmpty() ? Collections.emptySet() : groupPrincipals.stream().map(Principal::getName).collect(Collectors.toSet()); + } + public static Set getGroupPrincipals(Subject subject) { return subject.getPrincipals(GroupPrincipal.class); }