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 <[email protected]>
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