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:48:19 UTC

(knox) branch v3.0.0 updated (7f9b68670 -> cc04bd01d)

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

hanicz pushed a change to branch v3.0.0
in repository https://gitbox.apache.org/repos/asf/knox.git


    from 7f9b68670 KNOX-3386: LDAP Proxy pages backends (#1331)
     new 8b86afb11 KNOX-3410: KnoxToken enable/disable endpoints perform no caller autho… (#1343)
     new cc04bd01d KNOX-3411: KnoxToken getUserTokens returns every user's token metadat… (#1344)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../gateway/service/knoxtoken/TokenResource.java   |  52 +++-
 .../service/knoxtoken/TokenServiceMessages.java    |   3 +
 .../knoxtoken/TokenServiceResourceTest.java        | 270 +++++++++++++++++++--
 3 files changed, 302 insertions(+), 23 deletions(-)


(knox) 01/02: KNOX-3410: KnoxToken enable/disable endpoints perform no caller autho… (#1343)

Posted by ha...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hanicz pushed a commit to branch v3.0.0
in repository https://gitbox.apache.org/repos/asf/knox.git

commit 8b86afb111386f58399a16a30f07b10a90902641
Author: hanicz <ha...@users.noreply.github.com>
AuthorDate: Tue Aug 11 16:44:15 2026 +0200

    KNOX-3410: KnoxToken enable/disable endpoints perform no caller autho… (#1343)
    
    * KNOX-3410: KnoxToken enable/disable endpoints perform no caller authorization
    
    * KNOX-3410: Address review comments
    
    (cherry picked from commit f2126008ab764003e9831e48d30302886be93496)
---
 .../gateway/service/knoxtoken/TokenResource.java   |  21 ++-
 .../knoxtoken/TokenServiceResourceTest.java        | 183 +++++++++++++++++++--
 2 files changed, 183 insertions(+), 21 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 6d876648b..5a4429d85 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
@@ -584,7 +584,7 @@ public class TokenResource {
     } else {
       final String renewer = SubjectUtils.getCurrentEffectivePrincipalName();
 
-      if (tokenRenewalOrRevocationAuthorized(renewer)) {
+      if (tokenStateChangeAuthorized(renewer)) {
         try {
           JWTToken jwt = new JWTToken(token);
           if (tokenStateService.isExpired(jwt)) {
@@ -624,7 +624,7 @@ public class TokenResource {
     return resp;
   }
 
-  private boolean tokenRenewalOrRevocationAuthorized(final String principalName) {
+  private boolean tokenStateChangeAuthorized(final String principalName) {
     final boolean userAllowed = allowedRenewers.contains(principalName);
     final boolean groupAllowed = SubjectUtils.getCurrentGroupPrincipals().stream()
             .map(GroupPrincipal::getName)
@@ -675,7 +675,7 @@ public class TokenResource {
           errorStatus = Response.Status.FORBIDDEN;
           error = "SSO cookie (" + Tokens.getTokenIDDisplayText(tokenId) + ") cannot not be revoked.";
           errorCode = ErrorCode.UNAUTHORIZED;
-        } else if (triesToRevokeOwnToken(tokenId, revoker) || tokenRenewalOrRevocationAuthorized(revoker)) {
+        } else if (triesToChangeOwnToken(tokenId, revoker) || tokenStateChangeAuthorized(revoker)) {
           tokenStateService.revokeToken(tokenId);
           log.revokedToken(getTopologyName(),
                   Tokens.getTokenDisplayText(token),
@@ -715,7 +715,7 @@ public class TokenResource {
     return metadata == null ? false : metadata.isKnoxSsoCookie();
   }
 
-  private boolean triesToRevokeOwnToken(String tokenId, String revoker) throws UnknownTokenException {
+  private boolean triesToChangeOwnToken(String tokenId, String revoker) throws UnknownTokenException {
     final TokenMetadata metadata = tokenStateService.getTokenMetadata(tokenId);
     final String tokenUserName = metadata == null ? "" : metadata.getUserName();
     final String tokenCreatedBy = metadata == null ? "" : metadata.getCreatedBy();
@@ -785,13 +785,19 @@ public class TokenResource {
   private Response setTokenEnabledFlag(String tokenId, boolean enable, boolean batch) {
     String error = "";
     ErrorCode errorCode = ErrorCode.UNKNOWN;
+    Response.Status responseStatus = Response.Status.BAD_REQUEST;
     if (tokenStateService == null) {
       error = "Unable to " + (enable ? "enable" : "disable") + " tokens because token management is not configured";
       errorCode = ErrorCode.CONFIGURATION_ERROR;
     } else {
       try {
         final TokenMetadata tokenMetadata = tokenStateService.getTokenMetadata(tokenId);
-        if (!batch && enable && tokenMetadata.isEnabled()) {
+        final String caller = SubjectUtils.getCurrentEffectivePrincipalName();
+        if (!(triesToChangeOwnToken(tokenId, caller) || tokenStateChangeAuthorized(caller))) {
+          responseStatus = Response.Status.FORBIDDEN;
+          error = "Caller (" + caller + ") not authorized to " + (enable ? "enable" : "disable") + " tokens.";
+          errorCode = ErrorCode.UNAUTHORIZED;
+        } else if (!batch && enable && tokenMetadata.isEnabled()) {
           error = "Token is already enabled";
           errorCode = ErrorCode.ALREADY_ENABLED;
         } else if (!batch && !enable && !tokenMetadata.isEnabled()) {
@@ -811,11 +817,12 @@ public class TokenResource {
     }
 
     if (error.isEmpty()) {
+      responseStatus = Response.Status.OK;
       log.setEnabledFlag(getTopologyName(), enable, Tokens.getTokenIDDisplayText(tokenId));
-      return Response.status(Response.Status.OK).entity("{\n  \"setEnabledFlag\": \"true\",\n  \"isEnabled\": \"" + enable + "\"\n}\n").build();
+      return Response.status(responseStatus).entity("{\n  \"setEnabledFlag\": \"true\",\n  \"isEnabled\": \"" + enable + "\"\n}\n").build();
     } else {
       log.badSetEnabledFlagRequest(getTopologyName(), Tokens.getTokenIDDisplayText(tokenId), error);
-      return Response.status(Response.Status.BAD_REQUEST).entity("{\n  \"setEnabledFlag\": \"false\",\n  \"error\": \"" + error + "\",\n  \"code\": " + errorCode.toInt() + "\n}\n").build();
+      return Response.status(responseStatus).entity("{\n  \"setEnabledFlag\": \"false\",\n  \"error\": \"" + error + "\",\n  \"code\": " + errorCode.toInt() + "\n}\n").build();
     }
   }
 
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 8398e168b..80a17fdce 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
@@ -138,7 +138,9 @@ public class TokenServiceResourceTest {
 
   private enum TokenLifecycleOperation {
     Renew,
-    Revoke
+    Revoke,
+    Enable,
+    Disable
   }
 
   @BeforeClass
@@ -1045,6 +1047,104 @@ public class TokenServiceResourceTest {
             "Caller (" + caller + ") not authorized to revoke tokens.", TokenResource.ErrorCode.UNAUTHORIZED);
   }
 
+  @Test
+  public void testTokenDisable_Enabled_NoSubject() throws Exception {
+    final TokenRenewalTestConfigs configs = TokenRenewalTestConfigs.builder().serviceLevelConfig(true).build();
+    final Response response = doTestSetTokenEnabledFlag(configs, false);
+    validateSetEnabledFlagResponse(response, 403, false,
+            "Caller (null) not authorized to disable tokens.", TokenResource.ErrorCode.UNAUTHORIZED);
+  }
+
+  @Test
+  public void testTokenDisable_Enabled_UnauthorizedCaller() throws Exception {
+    final String caller = "scott";
+    final TokenRenewalTestConfigs configs = TokenRenewalTestConfigs.builder()
+            .serviceLevelConfig(true)
+            .caller(createTestSubject(caller))
+            .build();
+    final Response response = doTestSetTokenEnabledFlag(configs, false);
+    validateSetEnabledFlagResponse(response, 403, false,
+            "Caller (" + caller + ") not authorized to disable tokens.", TokenResource.ErrorCode.UNAUTHORIZED);
+  }
+
+  @Test
+  public void testTokenDisable_Enabled_OwnToken() throws Exception {
+    final TokenRenewalTestConfigs configs = TokenRenewalTestConfigs.builder()
+            .serviceLevelConfig(true)
+            .caller(createTestSubject(USER_NAME))
+            .build();
+    final Response response = doTestSetTokenEnabledFlag(configs, false);
+    validateSuccessfulSetEnabledFlagResponse(response, false);
+  }
+
+  @Test
+  public void testTokenDisable_Enabled_WithRenewerWhitelist() throws Exception {
+    final String caller = "scott";
+    final TokenRenewalTestConfigs configs = TokenRenewalTestConfigs.builder()
+            .serviceLevelConfig(true)
+            .renewers("tony, dany,  steve ," + caller)
+            .caller(createTestSubject(caller))
+            .build();
+    final Response response = doTestSetTokenEnabledFlag(configs, false);
+    validateSuccessfulSetEnabledFlagResponse(response, false);
+  }
+
+  @Test
+  public void testTokenDisable_Enabled_WithGroupRenewerWhitelist() throws Exception {
+    final String caller = "scott";
+    final String group = "devOps";
+    final TokenRenewalTestConfigs configs = TokenRenewalTestConfigs.builder()
+            .serviceLevelConfig(true)
+            .groupRenewers(group)
+            .caller(createTestSubject(caller, group))
+            .build();
+    final Response response = doTestSetTokenEnabledFlag(configs, false);
+    validateSuccessfulSetEnabledFlagResponse(response, false);
+  }
+
+  @Test
+  public void testTokenEnable_UnauthorizedCallerRejectedBeforeStateCheck() throws Exception {
+    final String caller = "scott";
+    final TokenRenewalTestConfigs configs = TokenRenewalTestConfigs.builder()
+            .serviceLevelConfig(true)
+            .caller(createTestSubject(caller))
+            .build();
+    final Response response = doTestSetTokenEnabledFlag(configs, true);
+    validateSetEnabledFlagResponse(response, 403, false,
+            "Caller (" + caller + ") not authorized to enable tokens.", TokenResource.ErrorCode.UNAUTHORIZED);
+  }
+
+  @Test
+  public void testTokenEnable_AlreadyEnabled_OwnerGetsStateCheck() throws Exception {
+    final TokenRenewalTestConfigs configs = TokenRenewalTestConfigs.builder()
+            .serviceLevelConfig(true)
+            .caller(createTestSubject(USER_NAME))
+            .build();
+    final Response response = doTestSetTokenEnabledFlag(configs, true);
+    validateSetEnabledFlagResponse(response, 400, false,
+            "Token is already enabled", TokenResource.ErrorCode.ALREADY_ENABLED);
+  }
+
+  @Test
+  public void testTokenDisable_Enabled_ImpersonatorCanDisableCreatedToken() throws Exception {
+    final Response response = doTestImpersonatedTokenSetEnabledFlag(createTestSubject(USER_NAME), false);
+    validateSuccessfulSetEnabledFlagResponse(response, false);
+  }
+
+  @Test
+  public void testTokenDisable_Enabled_ImpersonatedUserCanDisableOwnToken() throws Exception {
+    final Response response = doTestImpersonatedTokenSetEnabledFlag(createTestSubject("impersonatedUserName"), false);
+    validateSuccessfulSetEnabledFlagResponse(response, false);
+  }
+
+  @Test
+  public void testTokenDisable_Enabled_UnauthorizedCallerCannotDisableImpersonatedToken() throws Exception {
+    final String caller = "scott";
+    final Response response = doTestImpersonatedTokenSetEnabledFlag(createTestSubject(caller), false);
+    validateSetEnabledFlagResponse(response, 403, false,
+            "Caller (" + caller + ") not authorized to disable tokens.", TokenResource.ErrorCode.UNAUTHORIZED);
+  }
+
   @Test
   public void testKidJkuClaims() throws Exception {
     final Map<String, String> contextExpectations = new HashMap<>();
@@ -1700,6 +1800,42 @@ public class TokenServiceResourceTest {
     return doTestTokenLifecyle(TokenLifecycleOperation.Revoke, configs.isTokenStateServerManaged(), null, configs.getRenewers(), configs.getGroupRenewers(), null, configs.getCaller(), impersonatedUser).getValue();
   }
 
+  private Response doTestSetTokenEnabledFlag(final TokenRenewalTestConfigs configs, final boolean enable) throws Exception {
+    final TokenLifecycleOperation operation = enable ? TokenLifecycleOperation.Enable : TokenLifecycleOperation.Disable;
+    return doTestTokenLifecyle(operation, configs.isTokenStateServerManaged(), null, configs.getRenewers(), configs.getGroupRenewers(), null, configs.getCaller(), null).getValue();
+  }
+
+  /**
+   * Issues a token under an impersonating subject (so the persisted metadata has
+   * userName=impersonated user and createdBy=impersonator) and then attempts to flip its
+   * enabled flag as {@code caller}. Mirrors the impersonated-token creation flow in
+   * {@link #testCreateImpersonatedToken(boolean)}.
+   */
+  private Response doTestImpersonatedTokenSetEnabledFlag(final Subject caller, final boolean enable) throws Exception {
+    final String impersonatedUser = "impersonatedUserName";
+    final Map<String, String> contextExpectations = new HashMap<>();
+    contextExpectations.put("knox.token.exp.server-managed", Boolean.TRUE.toString());
+    contextExpectations.put(TokenResource.QUERY_PARAMETER_DOAS, impersonatedUser);
+    contextExpectations.put(AuthFilterUtils.PROXYUSER_PREFIX + "." + USER_NAME + ".users", impersonatedUser);
+    contextExpectations.put(AuthFilterUtils.PROXYUSER_PREFIX + "." + USER_NAME + ".hosts", "*");
+    contextExpectations.put(ContextAttributes.IMPERSONATION_ENABLED_ATTRIBUTE, Boolean.TRUE.toString());
+    configureCommonExpectations(contextExpectations, Boolean.TRUE);
+
+    final TokenResource tr = new TokenResource();
+    tr.request = request;
+    tr.context = context;
+    tr.init();
+
+    final Subject issuer = createTestSubject(USER_NAME);
+    issuer.getPrincipals().add(new ImpersonatedPrincipal(impersonatedUser));
+    final Response issueResponse = Subject.doAs(issuer, (PrivilegedAction<Response>) () -> tr.doGet());
+    assertEquals(200, issueResponse.getStatus());
+    final String accessToken = getTagValue(issueResponse.getEntity().toString(), "access_token");
+    final String tokenId = TokenUtils.getTokenId(new JWTToken(accessToken));
+
+    return requestSetTokenEnabledFlag(tr, tokenId, enable, caller);
+  }
+
   /**
    * @param operation          A TokenLifecycleOperation
    * @param serviceLevelConfig true, if server-side token state management should be enabled at the service level;
@@ -1751,6 +1887,8 @@ public class TokenServiceResourceTest {
     Response response = switch (operation) {
         case Renew -> requestTokenRenewal(tr, accessToken, caller);
         case Revoke -> requestTokenRevocation(tr, accessToken, caller);
+        case Enable -> requestSetTokenEnabledFlag(tr, TokenUtils.getTokenId(new JWTToken(accessToken)), true, caller);
+        case Disable -> requestSetTokenEnabledFlag(tr, TokenUtils.getTokenId(new JWTToken(accessToken)), false, caller);
     };
 
       return new AbstractMap.SimpleEntry<>(tss, response);
@@ -1792,6 +1930,11 @@ public class TokenServiceResourceTest {
     return response;
   }
 
+  private static Response requestSetTokenEnabledFlag(final TokenResource tr, final String tokenId, final boolean enable, final Subject caller) {
+    final PrivilegedAction<Response> action = () -> enable ? tr.enable(tokenId) : tr.disable(tokenId);
+    return caller != null ? Subject.doAs(caller, action) : action.run();
+  }
+
   private static void validateSuccessfulRenewalResponse(final Response response) throws IOException {
     validateRenewalResponse(response, 200, true, null, null);
   }
@@ -1801,13 +1944,22 @@ public class TokenServiceResourceTest {
                                               final boolean  expectedResult,
                                               final String   expectedMessage,
                                               final TokenResource.ErrorCode expectedCode) throws IOException {
+    validateLifecycleResponse(response, "renewed", expectedStatusCode, expectedResult, expectedMessage, expectedCode);
+  }
+
+  private static void validateLifecycleResponse(final Response response,
+                                                final String   resultField,
+                                                final int      expectedStatusCode,
+                                                final boolean  expectedResult,
+                                                final String   expectedMessage,
+                                                final TokenResource.ErrorCode expectedCode) throws IOException {
     assertEquals(expectedStatusCode, response.getStatus());
     assertTrue(response.hasEntity());
     String responseContent = (String) response.getEntity();
     assertNotNull(responseContent);
     assertFalse(responseContent.isEmpty());
     Map<String, Object> json = parseJSONResponse(responseContent);
-    boolean result = Boolean.valueOf((String)json.get("renewed"));
+    boolean result = Boolean.parseBoolean((String) json.get(resultField));
     assertEquals(expectedResult, result);
     assertEquals(expectedMessage, json.get("error"));
     if (expectedCode != null) {
@@ -1824,18 +1976,21 @@ public class TokenServiceResourceTest {
                                                  final boolean  expectedResult,
                                                  final String   expectedMessage,
                                                  final TokenResource.ErrorCode expectedCode) throws IOException {
-    assertEquals(expectedStatusCode, response.getStatus());
-    assertTrue(response.hasEntity());
-    String responseContent = (String) response.getEntity();
-    assertNotNull(responseContent);
-    assertFalse(responseContent.isEmpty());
-    Map<String, Object> json = parseJSONResponse(responseContent);
-    boolean result = Boolean.valueOf((String)json.get("revoked"));
-    assertEquals(expectedResult, result);
-    assertEquals(expectedMessage, json.get("error"));
-    if (expectedCode != null) {
-      assertEquals(expectedCode.toInt(), json.get("code"));
-    }
+    validateLifecycleResponse(response, "revoked", expectedStatusCode, expectedResult, expectedMessage, expectedCode);
+  }
+
+  private static void validateSuccessfulSetEnabledFlagResponse(final Response response, final boolean enable) throws IOException {
+    validateSetEnabledFlagResponse(response, 200, true, null, null);
+    final Map<String, Object> json = parseJSONResponse((String) response.getEntity());
+    assertEquals(String.valueOf(enable), json.get("isEnabled"));
+  }
+
+  private static void validateSetEnabledFlagResponse(final Response response,
+                                                     final int      expectedStatusCode,
+                                                     final boolean  expectedResult,
+                                                     final String   expectedMessage,
+                                                     final TokenResource.ErrorCode expectedCode) throws IOException {
+    validateLifecycleResponse(response, "setEnabledFlag", expectedStatusCode, expectedResult, expectedMessage, expectedCode);
   }
 
 


(knox) 02/02: KNOX-3411: KnoxToken getUserTokens returns every user's token metadat… (#1344)

Posted by ha...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hanicz pushed a commit to branch v3.0.0
in repository https://gitbox.apache.org/repos/asf/knox.git

commit cc04bd01d690c41d485b1400021a2e2c5f075e1e
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
    
    (cherry picked from commit 049dc0cb894f907efa920a1a2c10fb6ed56fb109)
---
 .../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