You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by ha...@apache.org on 2026/08/11 14:46:05 UTC
(knox) branch master updated: KNOX-3411: KnoxToken getUserTokens returns every user's token metadat… (#1344)
This is an automated email from the ASF dual-hosted git repository.
hanicz 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 049dc0cb8 KNOX-3411: KnoxToken getUserTokens returns every user's token metadat… (#1344)
049dc0cb8 is described below
commit 049dc0cb894f907efa920a1a2c10fb6ed56fb109
Author: hanicz <ha...@users.noreply.github.com>
AuthorDate: Tue Aug 11 16:45:59 2026 +0200
KNOX-3411: KnoxToken getUserTokens returns every user's token metadat… (#1344)
* KNOX-3411: KnoxToken getUserTokens returns every user's token metadata without a caller authorization check
* KNOX-3411: Add test clean-up
---
.../gateway/service/knoxtoken/TokenResource.java | 31 ++++++++
.../service/knoxtoken/TokenServiceMessages.java | 3 +
.../knoxtoken/TokenServiceResourceTest.java | 87 +++++++++++++++++++++-
3 files changed, 119 insertions(+), 2 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 5a4429d85..36fe65374 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
@@ -507,6 +507,14 @@ public class TokenResource {
final String createdBy = uriInfo.getQueryParameters().getFirst("createdBy");
final String userNameOrCreatedBy = uriInfo.getQueryParameters().getFirst("userNameOrCreatedBy");
final boolean allTokens = Boolean.parseBoolean(uriInfo.getQueryParameters().getFirst("allTokens"));
+
+ final String caller = SubjectUtils.getCurrentEffectivePrincipalName();
+ if (!isAuthorizedToSeeTokens(caller, userName, createdBy, userNameOrCreatedBy, allTokens)) {
+ log.unauthorizedGetUserTokensRequest(getTopologyName(), caller);
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("{\n \"error\": \"Caller (" + caller + ") is not authorized to see other users' tokens.\"\n}\n").build();
+ }
+
final Collection<KnoxToken> userTokens;
if (allTokens) {
userTokens = tokenStateService.getAllTokens();
@@ -540,6 +548,29 @@ public class TokenResource {
}
}
+ private boolean isAuthorizedToSeeTokens(String caller, String userName, String createdBy, String userNameOrCreatedBy, boolean allTokens) {
+ if (StringUtils.isBlank(caller)) {
+ return false;
+ }
+ final GatewayConfig config = (GatewayConfig) context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
+ if (config != null && config.canSeeAllTokens(caller)) {
+ return true;
+ }
+ if (allTokens) {
+ return false;
+ }
+ // an ordinary caller must scope the query to their own tokens
+ final boolean hasIdentifier = userName != null || createdBy != null || userNameOrCreatedBy != null;
+ return hasIdentifier
+ && requestedIsCallerIfPresent(caller, userName)
+ && requestedIsCallerIfPresent(caller, createdBy)
+ && requestedIsCallerIfPresent(caller, userNameOrCreatedBy);
+ }
+
+ private static boolean requestedIsCallerIfPresent(String caller, String requested) {
+ return requested == null || caller.equals(requested);
+ }
+
@GET
@Path(GET_TSS_STATUS_PATH)
@Produces({APPLICATION_JSON})
diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
index 278e42434..5341edd4a 100644
--- a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
+++ b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
@@ -77,6 +77,9 @@ public interface TokenServiceMessages {
@Message( level = MessageLevel.ERROR, text = "Knox Token service ({0}) rejected a bad set enabled flag request for token {1}: {2}")
void badSetEnabledFlagRequest(String topologyName, String tokenId, String error);
+ @Message( level = MessageLevel.ERROR, text = "Knox Token service ({0}) rejected an unauthorized getUserTokens request from caller ({1})")
+ void unauthorizedGetUserTokensRequest(String topologyName, String caller);
+
@Message( level = MessageLevel.DEBUG, text = "Knox Token service ({0}) stored state for token {1} ({2})")
void storedToken(String topologyName, String tokenDisplayText, String tokenId);
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 80a17fdce..4658f07d9 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
@@ -113,6 +113,7 @@ import org.apache.knox.gateway.services.token.impl.JDBCTokenStateService;
import org.apache.knox.gateway.util.AuthFilterUtils;
import org.apache.knox.gateway.util.JsonUtils;
import org.easymock.EasyMock;
+import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -135,6 +136,7 @@ public class TokenServiceResourceTest {
private JWTokenAuthority authority;
private TestTokenStateService tss = new TestTokenStateService();
private char[] hmacSecret;
+ private final Set<String> usersCanSeeAllTokens = new HashSet<>();
private enum TokenLifecycleOperation {
Renew,
@@ -153,6 +155,11 @@ public class TokenServiceResourceTest {
privateKey = (RSAPrivateKey) KPair.getPrivate();
}
+ @After
+ public void cleanUp() {
+ this.usersCanSeeAllTokens.clear();
+ }
+
private void configureCommonExpectations(Map<String, String> contextExpectations) throws Exception {
configureCommonExpectations(contextExpectations, null, null);
}
@@ -208,6 +215,8 @@ public class TokenServiceResourceTest {
EasyMock.expect(config.getServiceParameter(tokenStateServiceType, "impl")).andReturn(contextExpectations.get(tokenStateServiceType)).anyTimes();
}
EasyMock.expect(config.getKnoxTokenHashAlgorithm()).andReturn(HmacAlgorithms.HMAC_SHA_256.getName()).anyTimes();
+ EasyMock.expect(config.canSeeAllTokens(EasyMock.anyObject(String.class)))
+ .andAnswer(() -> usersCanSeeAllTokens.contains((String) EasyMock.getCurrentArguments()[0])).anyTimes();
EasyMock.expect(config.getMaximumNumberOfTokensPerUser())
.andReturn(contextExpectations.containsKey(KNOX_TOKEN_USER_LIMIT) ? Integer.parseInt(contextExpectations.get(KNOX_TOKEN_USER_LIMIT)) : -1).anyTimes();
EasyMock.expect(services.getService(ServiceType.TOKEN_STATE_SERVICE)).andReturn(tss).anyTimes();
@@ -1342,12 +1351,86 @@ public class TokenServiceResourceTest {
}
private Response getUserTokensResponse(TokenResource tokenResource, boolean createdBy) {
+ return getUserTokensResponse(tokenResource, createTestSubject(USER_NAME),
+ Collections.singletonMap(createdBy ? "createdBy" : "userName", USER_NAME));
+ }
+
+ private Response getUserTokensResponse(TokenResource tokenResource, Subject caller, Map<String, String> queryParams) {
final MultivaluedMap<String, String> queryParameters = new MultivaluedHashMap<>();
- queryParameters.put(createdBy ? "createdBy" : "userName", Arrays.asList(USER_NAME));
+ queryParams.forEach((key, value) -> queryParameters.put(key, Arrays.asList(value)));
final UriInfo uriInfo = EasyMock.createNiceMock(UriInfo.class);
EasyMock.expect(uriInfo.getQueryParameters()).andReturn(queryParameters).anyTimes();
EasyMock.replay(uriInfo);
- return tokenResource.getUserTokens(uriInfo);
+ return Subject.doAs(caller, (PrivilegedAction<Response>) () -> tokenResource.getUserTokens(uriInfo));
+ }
+
+ private TokenResource createTokenResourceWithTokensFor(String... users) throws Exception {
+ configureCommonExpectations(new HashMap<>(), Boolean.TRUE);
+ final TokenResource tr = new TokenResource();
+ tr.request = request;
+ tr.context = context;
+ tr.init();
+ for (String user : users) {
+ Subject.doAs(createTestSubject(user), (PrivilegedAction<Response>) () -> tr.doGet());
+ }
+ return tr;
+ }
+
+ @SuppressWarnings("unchecked")
+ private int tokenCount(Response response) {
+ final Collection<Object> tokens = ((Map<String, Collection<Object>>) JsonUtils.getObjectFromJsonString(response.getEntity().toString()))
+ .get("tokens");
+ return tokens.size();
+ }
+
+ @Test
+ public void testGetUserTokensOwnerCanSeeOwnTokens() throws Exception {
+ final TokenResource tr = createTokenResourceWithTokensFor(USER_NAME);
+ final Response response = getUserTokensResponse(tr, createTestSubject(USER_NAME), Collections.singletonMap("userName", USER_NAME));
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(1, tokenCount(response));
+ }
+
+ @Test
+ public void testGetUserTokensUnauthorizedForOtherUser() throws Exception {
+ final TokenResource tr = createTokenResourceWithTokensFor(USER_NAME);
+ final Response response = getUserTokensResponse(tr, createTestSubject("bob"), Collections.singletonMap("userName", USER_NAME));
+ assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
+ assertTrue(response.getEntity().toString().contains("not authorized"));
+ }
+
+ @Test
+ public void testGetUserTokensUnauthorizedForUserNameOrCreatedByOfOtherUser() throws Exception {
+ final TokenResource tr = createTokenResourceWithTokensFor(USER_NAME);
+ final Response response = getUserTokensResponse(tr, createTestSubject("bob"), Collections.singletonMap("userNameOrCreatedBy", USER_NAME));
+ assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
+ assertTrue(response.getEntity().toString().contains("not authorized"));
+ }
+
+ @Test
+ public void testGetUserTokensAllTokensDeniedForOrdinaryUser() throws Exception {
+ final TokenResource tr = createTokenResourceWithTokensFor(USER_NAME);
+ final Response response = getUserTokensResponse(tr, createTestSubject("bob"), Collections.singletonMap("allTokens", "true"));
+ assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
+ assertTrue(response.getEntity().toString().contains("not authorized"));
+ }
+
+ @Test
+ public void testGetUserTokensAdminCanSeeAllTokens() throws Exception {
+ final TokenResource tr = createTokenResourceWithTokensFor(USER_NAME, "bob");
+ usersCanSeeAllTokens.add("admin");
+ final Response response = getUserTokensResponse(tr, createTestSubject("admin"), Collections.singletonMap("allTokens", "true"));
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(2, tokenCount(response));
+ }
+
+ @Test
+ public void testGetUserTokensAdminCanSeeOtherUsersTokens() throws Exception {
+ final TokenResource tr = createTokenResourceWithTokensFor(USER_NAME);
+ usersCanSeeAllTokens.add("admin");
+ final Response response = getUserTokensResponse(tr, createTestSubject("admin"), Collections.singletonMap("userName", USER_NAME));
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(1, tokenCount(response));
}
@Test