You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2022/03/07 10:10:11 UTC

[knox] branch master updated: KNOX-2713 - Allowing end-users to customize 'user limit exceeded' action when creating Knox tokens (#543)

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

smolnar 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 848689b  KNOX-2713 - Allowing end-users to customize 'user limit exceeded' action when creating Knox tokens (#543)
848689b is described below

commit 848689b0b0e8c6a969dd5bdd2f85d40cf4047a73
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Mon Mar 7 11:10:05 2022 +0100

    KNOX-2713 - Allowing end-users to customize 'user limit exceeded' action when creating Knox tokens (#543)
---
 .../gateway/service/knoxtoken/TokenResource.java   | 22 ++++++++++++--
 .../service/knoxtoken/TokenServiceMessages.java    |  3 ++
 .../knoxtoken/TokenServiceResourceTest.java        | 35 +++++++++++++++++-----
 3 files changed, 51 insertions(+), 9 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 d083c8e..96c189e 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
@@ -32,6 +32,7 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Optional;
 import java.util.UUID;
 
@@ -117,6 +118,7 @@ public class TokenResource {
   private static final String TSS_MAXIMUM_LIFETIME_TEXT = "maximumLifetimeText";
   private static final String LIFESPAN_INPUT_ENABLED_PARAM = "knox.token.lifespan.input.enabled";
   private static final String LIFESPAN_INPUT_ENABLED_TEXT = "lifespanInputEnabled";
+  static final String KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION = "knox.token.user.limit.exceeded.action";
   private static final long TOKEN_TTL_DEFAULT = 30000L;
   static final String TOKEN_API_PATH = "knoxtoken/api/v1";
   static final String RESOURCE_PATH = TOKEN_API_PATH + "/token";
@@ -150,6 +152,9 @@ public class TokenResource {
 
   private int tokenLimitPerUser;
 
+  enum UserLimitExceededAction {REMOVE_OLDEST, RETURN_ERROR};
+  private UserLimitExceededAction userLimitExceededAction = UserLimitExceededAction.RETURN_ERROR;
+
   private List<String> allowedRenewers;
 
   @Context
@@ -246,6 +251,11 @@ public class TokenResource {
       tokenMAC = new TokenMAC(gatewayConfig.getKnoxTokenHashAlgorithm(), aliasService.getPasswordFromAliasForGateway(TokenMAC.KNOX_TOKEN_HASH_KEY_ALIAS_NAME));
 
       tokenLimitPerUser = gatewayConfig.getMaximumNumberOfTokensPerUser();
+      final String userLimitExceededActionParam = context.getInitParameter(KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION);
+      if (userLimitExceededActionParam != null) {
+        userLimitExceededAction = UserLimitExceededAction.valueOf(userLimitExceededActionParam);
+        log.generalInfoMessage("Configured Knox Token user limit exceeded action = " + userLimitExceededAction.name());
+      }
 
       String renewIntervalValue = context.getInitParameter(TOKEN_EXP_RENEWAL_INTERVAL);
       if (renewIntervalValue != null && !renewIntervalValue.isEmpty()) {
@@ -654,9 +664,17 @@ public class TokenResource {
 
     if (tokenStateService != null) {
       if (tokenLimitPerUser != -1) { // if -1 => unlimited tokens for all users
-        if (tokenStateService.getTokens(p.getName()).size() >= tokenLimitPerUser) {
+        final Collection<KnoxToken> userTokens = tokenStateService.getTokens(p.getName());
+        if (userTokens.size() >= tokenLimitPerUser) {
           log.tokenLimitExceeded(p.getName());
-          return Response.status(Response.Status.FORBIDDEN).entity("{ \"Unable to get token - token limit exceeded.\" }").build();
+          if (UserLimitExceededAction.RETURN_ERROR == userLimitExceededAction) {
+            return Response.status(Response.Status.FORBIDDEN).entity("{ \"Unable to get token - token limit exceeded.\" }").build();
+          } else {
+            // userTokens is an ordered collection (by issue time) -> the first element is the oldest one
+            final String oldestTokenId = userTokens.iterator().next().getTokenId();
+            log.generalInfoMessage(String.format(Locale.getDefault(), "Revoking %s's oldest token %s ...", p.getName(), Tokens.getTokenIDDisplayText(oldestTokenId)));
+            revoke(oldestTokenId);
+           }
         }
       }
     }
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 8cae4b6..6208cbc 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
@@ -86,4 +86,7 @@ public interface TokenServiceMessages {
 
   @Message( level = MessageLevel.ERROR, text = "Unable to get token for user {0}: token limit exceeded")
   void tokenLimitExceeded(String userName);
+
+  @Message( level = MessageLevel.INFO, text = "{0}")
+  void generalInfoMessage(String message);
 }
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 d895a53..e74f2e2 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
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.service.knoxtoken;
 
 import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.KNOX_TOKEN_USER_LIMIT;
 import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.KNOX_TOKEN_USER_LIMIT_DEFAULT;
+import static org.apache.knox.gateway.service.knoxtoken.TokenResource.KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -984,12 +985,12 @@ public class TokenServiceResourceTest {
 
   @Test
   public void testConfiguredTokenLimitPerUser() throws Exception {
-    testLimitingTokensPerUser(String.valueOf(KNOX_TOKEN_USER_LIMIT_DEFAULT), KNOX_TOKEN_USER_LIMIT_DEFAULT);
+    testLimitingTokensPerUser(KNOX_TOKEN_USER_LIMIT_DEFAULT, KNOX_TOKEN_USER_LIMIT_DEFAULT);
   }
 
   @Test
   public void testUnlimitedTokensPerUser() throws Exception {
-    testLimitingTokensPerUser(String.valueOf("-1"), 100);
+    testLimitingTokensPerUser(-1, 100);
   }
 
   @Test
@@ -1023,16 +1024,32 @@ public class TokenServiceResourceTest {
   @Test
   public void testTokenLimitPerUserExceeded() throws Exception {
     try {
-      testLimitingTokensPerUser(String.valueOf("10"), 11);
+      testLimitingTokensPerUser(10, 11);
       fail("Exception should have been thrown");
     } catch (Exception e) {
       assertTrue(e.getMessage().contains("Unable to get token - token limit exceeded."));
     }
   }
 
-  private void testLimitingTokensPerUser(String configuredLimit, int numberOfTokens) throws Exception {
+  @Test
+  public void testTokenLimitPerUserExceededShouldRevokeOldestToken() throws Exception {
+    try {
+      testLimitingTokensPerUser(10, 11, true);
+    } catch (Exception e) {
+      fail("Exception should NOT have been thrown");
+    }
+  }
+
+  private void testLimitingTokensPerUser(int configuredLimit, int numberOfTokens) throws Exception {
+    testLimitingTokensPerUser(configuredLimit, numberOfTokens, false);
+  }
+
+  private void testLimitingTokensPerUser(int configuredLimit, int numberOfTokens, boolean revokeOldestToken) throws Exception {
     final Map<String, String> contextExpectations = new HashMap<>();
-    contextExpectations.put(KNOX_TOKEN_USER_LIMIT, configuredLimit);
+    contextExpectations.put(KNOX_TOKEN_USER_LIMIT, String.valueOf(configuredLimit));
+    if (revokeOldestToken) {
+      contextExpectations.put(KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION, TokenResource.UserLimitExceededAction.REMOVE_OLDEST.name());
+    }
     configureCommonExpectations(contextExpectations, Boolean.TRUE);
 
     final TokenResource tr = new TokenResource();
@@ -1041,7 +1058,7 @@ public class TokenServiceResourceTest {
     tr.init();
 
     for (int i = 0; i < numberOfTokens; i++) {
-      final Response getTokenResponse = tr.doGet();
+      final Response getTokenResponse = Subject.doAs(createTestSubject(USER_NAME), (PrivilegedAction<Response>) () -> tr.doGet());
       if (getTokenResponse.getStatus() != Response.Status.OK.getStatusCode()) {
         throw new Exception(getTokenResponse.getEntity().toString());
       }
@@ -1049,7 +1066,7 @@ public class TokenServiceResourceTest {
     final Response getKnoxTokensResponse = tr.getUserTokens(USER_NAME);
     final Collection<String> tokens = ((Map<String, Collection<String>>) JsonUtils.getObjectFromJsonString(getKnoxTokensResponse.getEntity().toString()))
         .get("tokens");
-    assertEquals(tokens.size(), numberOfTokens);
+    assertEquals(tokens.size(), revokeOldestToken ? configuredLimit : numberOfTokens);
   }
 
   /**
@@ -1395,6 +1412,10 @@ public class TokenServiceResourceTest {
 
     @Override
     public void revokeToken(String tokenId) {
+      issueTimes.remove(tokenId);
+      expirationData.remove(tokenId);
+      maxLifetimes.remove(tokenId);
+      tokenMetadata.remove(tokenId);
     }
 
     @Override