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 dd716ea  KNOX-2602 - Added the enabled flag and related token service 
API into the tokens' metadata. (#447)
dd716ea is described below

commit dd716eafc5da45ae39f85300ab62c63ef15ee55a
Author: Sandor Molnar <[email protected]>
AuthorDate: Wed May 5 15:49:02 2021 +0200

    KNOX-2602 - Added the enabled flag and related token service API into the 
tokens' metadata. (#447)
    
    Instead of adding another column in KNOX_TABLES, a brand new DB table was 
introduced where an arbitrary number of token metadata can be stored 
(identified by the given token's ID and the metadata name).
---
 .../services/token/impl/JDBCTokenStateService.java | 30 +++++++++++-
 .../services/token/impl/TokenStateDatabase.java    | 50 ++++++++++++++------
 .../token/impl/TokenStateServiceMessages.java      |  2 +-
 .../token/impl/state/FileTokenStateJournal.java    | 18 +++++---
 .../resources/createKnoxTokenDatabaseTable.sql     |  2 -
 ...ql => createKnoxTokenMetadataDatabaseTable.sql} | 12 ++---
 .../token/impl/DefaultTokenStateServiceTest.java   |  6 ++-
 .../token/impl/JDBCTokenStateServiceTest.java      | 30 +++++++++---
 .../token/impl/ZookeeperTokenStateServiceTest.java |  3 +-
 .../impl/state/FileTokenStateJournalTest.java      | 17 +++++--
 .../gateway/service/knoxtoken/TokenResource.java   | 43 ++++++++++++++++++
 .../service/knoxtoken/TokenServiceMessages.java    |  3 ++
 .../services/security/token/TokenMetadata.java     | 53 ++++++++++++++++------
 13 files changed, 209 insertions(+), 60 deletions(-)

diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java
index 472200d..4907188 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.ServiceLifecycleException;
 import org.apache.knox.gateway.services.security.AliasService;
@@ -38,6 +39,7 @@ public class JDBCTokenStateService extends 
DefaultTokenStateService {
   private TokenStateDatabase tokenDatabase;
   private AtomicBoolean initialized = new AtomicBoolean(false);
   private Lock initLock = new ReentrantLock(true);
+  private Lock addMetadataLock = new ReentrantLock(true);
 
   public void setAliasService(AliasService aliasService) {
     this.aliasService = aliasService;
@@ -194,7 +196,8 @@ public class JDBCTokenStateService extends 
DefaultTokenStateService {
   @Override
   public void addMetadata(String tokenId, TokenMetadata metadata) {
     try {
-      final boolean added = tokenDatabase.addMetadata(tokenId, metadata);
+      boolean added = saveMetadataMapInDatabase(tokenId, 
metadata.getMetadataMap());
+
       if (added) {
         log.updatedMetadataInDatabase(Tokens.getTokenIDDisplayText(tokenId));
 
@@ -210,6 +213,31 @@ public class JDBCTokenStateService extends 
DefaultTokenStateService {
     }
   }
 
+  private boolean saveMetadataMapInDatabase(String tokenId, Map<String, 
String> metadataMap) throws SQLException {
+    addMetadataLock.lock();
+    try {
+      boolean saved = false;
+      for (Map.Entry<String, String> metadataMapEntry : 
metadataMap.entrySet()) {
+        if (StringUtils.isNotBlank(metadataMapEntry.getValue())) {
+          if (upsertTokenMetadata(tokenId, metadataMapEntry.getKey(), 
metadataMapEntry.getValue())) {
+            saved = true;
+          }
+        }
+      }
+      return saved;
+    } finally {
+      addMetadataLock.unlock();
+    }
+  }
+
+  private boolean upsertTokenMetadata(String tokenId, String metadataName, 
String metadataValue) throws SQLException {
+    if (!tokenDatabase.updateMetadata(tokenId, metadataName, metadataValue)) {
+      return tokenDatabase.addMetadata(tokenId, metadataName, metadataValue);
+    } else {
+      return true; //successfully updated
+    }
+  }
+
   @Override
   public TokenMetadata getTokenMetadata(String tokenId) throws 
UnknownTokenException {
     TokenMetadata tokenMetadata = null;
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
index 2693093..88bd68d 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
@@ -18,6 +18,7 @@
 package org.apache.knox.gateway.services.token.impl;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.InputStream;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
@@ -25,7 +26,9 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 
 import javax.sql.DataSource;
 
@@ -34,34 +37,38 @@ import 
org.apache.knox.gateway.services.security.token.TokenMetadata;
 
 public class TokenStateDatabase {
   private static final String TOKENS_TABLE_CREATE_SQL_FILE_NAME = 
"createKnoxTokenDatabaseTable.sql";
+  private static final String TOKEN_METADATA_TABLE_CREATE_SQL_FILE_NAME = 
"createKnoxTokenMetadataDatabaseTable.sql";
   static final String TOKENS_TABLE_NAME = "KNOX_TOKENS";
+  static final String TOKEN_METADATA_TABLE_NAME = "KNOX_TOKEN_METADATA";
   private static final String ADD_TOKEN_SQL = "INSERT INTO " + 
TOKENS_TABLE_NAME + "(token_id, issue_time, expiration, max_lifetime) VALUES(?, 
?, ?, ?)";
   private static final String REMOVE_TOKEN_SQL = "DELETE FROM " + 
TOKENS_TABLE_NAME + " WHERE token_id = ?";
   private static final String REMOVE_EXPIRED_TOKENS_SQL = "DELETE FROM " + 
TOKENS_TABLE_NAME + " WHERE expiration < ?";
   static final String GET_TOKEN_EXPIRATION_SQL = "SELECT expiration FROM " + 
TOKENS_TABLE_NAME + " WHERE token_id = ?";
   private static final String UPDATE_TOKEN_EXPIRATION_SQL = "UPDATE " + 
TOKENS_TABLE_NAME + " SET expiration = ? WHERE token_id = ?";
   static final String GET_MAX_LIFETIME_SQL = "SELECT max_lifetime FROM " + 
TOKENS_TABLE_NAME + " WHERE token_id = ?";
-  private static final String ADD_METADATA_SQL = "UPDATE " + TOKENS_TABLE_NAME 
+ " SET username = ?, comment = ? WHERE token_id = ?";
-  private static final String GET_METADATA_SQL = "SELECT username, comment 
FROM " + TOKENS_TABLE_NAME + " WHERE token_id = ?";
+  private static final String ADD_METADATA_SQL = "INSERT INTO " + 
TOKEN_METADATA_TABLE_NAME + "(token_id, md_name, md_value) VALUES(?, ?, ?)";
+  private static final String UPDATE_METADATA_SQL = "UPDATE " + 
TOKEN_METADATA_TABLE_NAME + " SET md_value = ? WHERE token_id = ? AND md_name = 
?";
+  private static final String GET_METADATA_SQL = "SELECT md_name, md_value 
FROM " + TOKEN_METADATA_TABLE_NAME + " WHERE token_id = ?";
 
   private final DataSource dataSource;
 
   TokenStateDatabase(DataSource dataSource) throws Exception {
     this.dataSource = dataSource;
-    createKnoxTokensTableIfNotExists();
+    createTableIfNotExists(TOKENS_TABLE_NAME, 
TOKENS_TABLE_CREATE_SQL_FILE_NAME);
+    createTableIfNotExists(TOKEN_METADATA_TABLE_NAME, 
TOKEN_METADATA_TABLE_CREATE_SQL_FILE_NAME);
   }
 
-  private void createKnoxTokensTableIfNotExists() throws Exception {
-    if (!isKnoxTokensTableExist()) {
-      createKnoxTokensTable();
+  private void createTableIfNotExists(String tableName, String 
createSqlFileName) throws Exception {
+    if (!isTableExists(tableName)) {
+      createTable(createSqlFileName);
     }
   }
 
-  private boolean isKnoxTokensTableExist() throws SQLException {
+  private boolean isTableExists(String tableName) throws SQLException {
     boolean exists = false;
     try (Connection connection = dataSource.getConnection()) {
       final DatabaseMetaData dbMetadata = connection.getMetaData();
-      final String tableNameToCheck = dbMetadata.storesUpperCaseIdentifiers() 
? TOKENS_TABLE_NAME : TOKENS_TABLE_NAME.toLowerCase(Locale.ROOT);
+      final String tableNameToCheck = dbMetadata.storesUpperCaseIdentifiers() 
? tableName : tableName.toLowerCase(Locale.ROOT);
       try (ResultSet tables = dbMetadata.getTables(connection.getCatalog(), 
null, tableNameToCheck, null)) {
         exists = tables.next();
       }
@@ -69,8 +76,8 @@ public class TokenStateDatabase {
     return exists;
   }
 
-  private void createKnoxTokensTable() throws Exception {
-    final InputStream is = 
TokenStateDatabase.class.getClassLoader().getResourceAsStream(TOKENS_TABLE_CREATE_SQL_FILE_NAME);
+  private void createTable(String createSqlFileName) throws Exception {
+    final InputStream is = 
TokenStateDatabase.class.getClassLoader().getResourceAsStream(createSqlFileName);
     final String createTableSql = IOUtils.toString(is, UTF_8);
     try (Connection connection = dataSource.getConnection(); Statement 
createTableStatment = connection.createStatement();) {
       createTableStatment.execute(createTableSql);
@@ -127,11 +134,20 @@ public class TokenStateDatabase {
     }
   }
 
-  boolean addMetadata(String tokenId, TokenMetadata metadata) throws 
SQLException {
+  boolean updateMetadata(String tokenId, String metadataName, String 
metadataValue) throws SQLException {
+    try (Connection connection = dataSource.getConnection(); PreparedStatement 
updateMetadataStatement = connection.prepareStatement(UPDATE_METADATA_SQL)) {
+      updateMetadataStatement.setString(1, metadataValue);
+      updateMetadataStatement.setString(2, tokenId);
+      updateMetadataStatement.setString(3, metadataName);
+      return updateMetadataStatement.executeUpdate() == 1;
+    }
+  }
+
+  boolean addMetadata(String tokenId, String metadataName, String 
metadataValue) throws SQLException {
     try (Connection connection = dataSource.getConnection(); PreparedStatement 
addMetadataStatement = connection.prepareStatement(ADD_METADATA_SQL)) {
-      addMetadataStatement.setString(1, metadata.getUserName());
-      addMetadataStatement.setString(2, metadata.getComment());
-      addMetadataStatement.setString(3, tokenId);
+      addMetadataStatement.setString(1, tokenId);
+      addMetadataStatement.setString(2, metadataName);
+      addMetadataStatement.setString(3, metadataValue);
       return addMetadataStatement.executeUpdate() == 1;
     }
   }
@@ -140,7 +156,11 @@ public class TokenStateDatabase {
     try (Connection connection = dataSource.getConnection(); PreparedStatement 
getMaxLifetimeStatement = connection.prepareStatement(GET_METADATA_SQL)) {
       getMaxLifetimeStatement.setString(1, tokenId);
       try (ResultSet rs = getMaxLifetimeStatement.executeQuery()) {
-        return rs.next() ? new TokenMetadata(rs.getString(1), rs.getString(2)) 
: null;
+        final Map<String, String> metadataMap = new HashMap<>();
+        while (rs.next()) {
+          metadataMap.put(rs.getString(1), rs.getString(2));
+        }
+        return metadataMap.isEmpty() ? null : new TokenMetadata(metadataMap);
       }
     }
   }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
index a5eeece..101c3cc 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
@@ -223,7 +223,7 @@ public interface TokenStateServiceMessages {
   @Message(level = MessageLevel.DEBUG, text = "Updated metadata for {0} in the 
database")
   void updatedMetadataInDatabase(String tokenId);
 
-  @Message(level = MessageLevel.DEBUG, text = "Failed to update metadata for 
{0} in the database")
+  @Message(level = MessageLevel.DEBUG, text = "Failed to save/update metadata 
for {0} in the database")
   void failedToUpdateMetadataInDatabase(String tokenId);
 
   @Message(level = MessageLevel.ERROR, text = "An error occurred while 
updating metadata for {0} in the database : {1}")
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournal.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournal.java
index 966766d..cdccad6 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournal.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournal.java
@@ -50,8 +50,9 @@ abstract class FileTokenStateJournal implements 
TokenStateJournal {
     protected static final int INDEX_ISSUE_TIME   = 1;
     protected static final int INDEX_EXPIRATION   = 2;
     protected static final int INDEX_MAX_LIFETIME = 3;
-    protected static final int INDEX_USERNAME     = 4;
-    protected static final int INDEX_COMMENT      = 5;
+    protected static final int INDEX_ENABLED      = 4;
+    protected static final int INDEX_USERNAME     = 5;
+    protected static final int INDEX_COMMENT      = 6;
 
     protected static final TokenStateServiceMessages log = 
MessagesFactory.get(TokenStateServiceMessages.class);
 
@@ -190,7 +191,7 @@ abstract class FileTokenStateJournal implements 
TokenStateJournal {
 
         @Override
         public String toString() {
-            String[] elements = new String[6];
+            String[] elements = new String[7];
 
             elements[INDEX_TOKEN_ID] = getTokenId();
 
@@ -203,6 +204,9 @@ abstract class FileTokenStateJournal implements 
TokenStateJournal {
             String maxLifetime = getMaxLifetime();
             elements[INDEX_MAX_LIFETIME] = (maxLifetime != null) ? maxLifetime 
: "";
 
+            String enabled = getTokenMetadata() == null ? "" : 
String.valueOf(getTokenMetadata().isEnabled());
+            elements[INDEX_ENABLED] = enabled;
+
             String userName = getTokenMetadata() == null ? "" : 
(getTokenMetadata().getUserName() == null ? "" : 
getTokenMetadata().getUserName());
             elements[INDEX_USERNAME] = userName;
 
@@ -210,11 +214,12 @@ abstract class FileTokenStateJournal implements 
TokenStateJournal {
             elements[INDEX_COMMENT] = comment;
 
             return String.format(Locale.ROOT,
-                                 "%s,%s,%s,%s,%s,%s",
+                                 "%s,%s,%s,%s,%s,%s,%s",
                                  elements[INDEX_TOKEN_ID],
                                  elements[INDEX_ISSUE_TIME],
                                  elements[INDEX_EXPIRATION],
                                  elements[INDEX_MAX_LIFETIME],
+                                 elements[INDEX_ENABLED],
                                  elements[INDEX_USERNAME],
                                  elements[INDEX_COMMENT]);
         }
@@ -228,7 +233,7 @@ abstract class FileTokenStateJournal implements 
TokenStateJournal {
           */
         static FileJournalEntry parse(final String entry) {
             String[] elements = entry.split(",", -1);
-            if (elements.length < 6) {
+            if (elements.length < 7) {
                 throw new IllegalArgumentException("Invalid journal entry: " + 
entry);
             }
 
@@ -236,6 +241,7 @@ abstract class FileTokenStateJournal implements 
TokenStateJournal {
             String issueTime   = elements[INDEX_ISSUE_TIME].trim();
             String expiration  = elements[INDEX_EXPIRATION].trim();
             String maxLifetime = elements[INDEX_MAX_LIFETIME].trim();
+            String enabled     = elements[INDEX_ENABLED].trim();
             String userName    = elements[INDEX_USERNAME].trim();
             String comment     = elements[INDEX_COMMENT].trim();
 
@@ -243,7 +249,7 @@ abstract class FileTokenStateJournal implements 
TokenStateJournal {
                                         issueTime.isEmpty() ? null : issueTime,
                                         expiration.isEmpty() ? null : 
expiration,
                                         maxLifetime.isEmpty() ? null : 
maxLifetime,
-                                        new TokenMetadata(userName.isEmpty() ? 
null : userName, comment.isEmpty() ? null : comment));
+                                        new TokenMetadata(userName.isEmpty() ? 
null : userName, comment.isEmpty() ? null : comment, 
Boolean.parseBoolean(enabled)));
         }
 
     }
diff --git a/gateway-server/src/main/resources/createKnoxTokenDatabaseTable.sql 
b/gateway-server/src/main/resources/createKnoxTokenDatabaseTable.sql
index faeca79..598ed7e 100644
--- a/gateway-server/src/main/resources/createKnoxTokenDatabaseTable.sql
+++ b/gateway-server/src/main/resources/createKnoxTokenDatabaseTable.sql
@@ -18,7 +18,5 @@ CREATE TABLE KNOX_TOKENS (
    issue_time bigint NOT NULL,
    expiration bigint NOT NULL,
    max_lifetime bigint NOT NULL,
-   username varchar(128),
-   comment varchar(256),
    PRIMARY KEY (token_id)
 )
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/createKnoxTokenDatabaseTable.sql 
b/gateway-server/src/main/resources/createKnoxTokenMetadataDatabaseTable.sql
similarity index 77%
copy from gateway-server/src/main/resources/createKnoxTokenDatabaseTable.sql
copy to 
gateway-server/src/main/resources/createKnoxTokenMetadataDatabaseTable.sql
index faeca79..9a11380 100644
--- a/gateway-server/src/main/resources/createKnoxTokenDatabaseTable.sql
+++ b/gateway-server/src/main/resources/createKnoxTokenMetadataDatabaseTable.sql
@@ -13,12 +13,10 @@
 --  License for the specific language governing permissions and limitations 
under
 --  the License.
 
-CREATE TABLE KNOX_TOKENS (
+CREATE TABLE KNOX_TOKEN_METADATA (
    token_id varchar(128) NOT NULL,
-   issue_time bigint NOT NULL,
-   expiration bigint NOT NULL,
-   max_lifetime bigint NOT NULL,
-   username varchar(128),
-   comment varchar(256),
-   PRIMARY KEY (token_id)
+   md_name varchar(32) NOT NULL,
+   md_value varchar(256) NOT NULL,
+   PRIMARY KEY (token_id, md_name),
+   CONSTRAINT fk_token_id FOREIGN KEY(token_id) REFERENCES 
KNOX_TOKENS(token_id) ON DELETE CASCADE
 )
\ No newline at end of file
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
index f11aa81..8d9836d 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.services.token.impl;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -292,12 +293,13 @@ public class DefaultTokenStateServiceTest {
     tss.addMetadata(token.getClaim(JWTToken.KNOX_ID_CLAIM), new 
TokenMetadata(userName));
     assertNotNull(tss.getTokenMetadata(tokenId));
     assertEquals(tss.getTokenMetadata(tokenId).getUserName(), userName);
-    assertTrue(tss.getTokenMetadata(tokenId).getComment().isEmpty());
+    assertNull(tss.getTokenMetadata(tokenId).getComment());
 
     final String comment = "this is my test comment";
-    tss.addMetadata(token.getClaim(JWTToken.KNOX_ID_CLAIM), new 
TokenMetadata(userName, comment));
+    tss.addMetadata(token.getClaim(JWTToken.KNOX_ID_CLAIM), new 
TokenMetadata(userName, comment, true));
     assertNotNull(tss.getTokenMetadata(tokenId));
     assertEquals(tss.getTokenMetadata(tokenId).getComment(), comment);
+    assertTrue(tss.getTokenMetadata(tokenId).isEnabled());
   }
 
   protected static JWTToken createMockToken(final long expiration) {
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
index b9ef2b7..7658e4f 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
@@ -18,6 +18,7 @@
 package org.apache.knox.gateway.services.token.impl;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.nio.file.Path;
@@ -47,9 +48,8 @@ import org.junit.rules.TemporaryFolder;
 public class JDBCTokenStateServiceTest {
 
   private static final String GET_TOKENS_COUNT_SQL = "SELECT count(*) FROM " + 
TokenStateDatabase.TOKENS_TABLE_NAME;
-  private static final String GET_USERNAME_SQL = "SELECT username FROM " + 
TokenStateDatabase.TOKENS_TABLE_NAME + " WHERE token_id = ?";
-  private static final String GET_COMMENT_SQL = "SELECT comment FROM " + 
TokenStateDatabase.TOKENS_TABLE_NAME + " WHERE token_id = ?";
-  private static final String TRUNCATE_KNOX_TOKENS_SQL = "TRUNCATE TABLE " + 
TokenStateDatabase.TOKENS_TABLE_NAME;
+  private static final String TRUNCATE_KNOX_TOKENS_SQL = "DELETE FROM " + 
TokenStateDatabase.TOKENS_TABLE_NAME;
+  private static final String TRUNCATE_KNOX_TOKEN_METADATA_SQL = "DELETE FROM 
" + TokenStateDatabase.TOKEN_METADATA_TABLE_NAME;
 
   @ClassRule
   public static final TemporaryFolder testFolder = new TemporaryFolder();
@@ -143,15 +143,25 @@ public class JDBCTokenStateServiceTest {
   @Test(expected = UnknownTokenException.class)
   public void testAddMetadata() throws Exception {
     final String tokenId = UUID.randomUUID().toString();
+    final TokenMetadata tokenMetadata = new TokenMetadata("sampleUser", "my 
test comment", false);
     jdbcTokenStateService.addToken(tokenId, 1, 1, 1);
-    jdbcTokenStateService.addMetadata(tokenId, new TokenMetadata("sampleUser", 
"my test comment"));
+    jdbcTokenStateService.addMetadata(tokenId, tokenMetadata);
 
     assertEquals("sampleUser", 
jdbcTokenStateService.getTokenMetadata(tokenId).getUserName());
     assertEquals("my test comment", 
jdbcTokenStateService.getTokenMetadata(tokenId).getComment());
+    assertFalse(jdbcTokenStateService.getTokenMetadata(tokenId).isEnabled());
 
-    assertEquals("sampleUser", getStringTokenAttributeFromDatabase(tokenId, 
GET_USERNAME_SQL));
-    assertEquals("my test comment", 
getStringTokenAttributeFromDatabase(tokenId, GET_COMMENT_SQL));
+    assertEquals("sampleUser", getStringTokenAttributeFromDatabase(tokenId, 
getSelectMetadataSql(TokenMetadata.USER_NAME)));
+    assertEquals("my test comment", 
getStringTokenAttributeFromDatabase(tokenId, 
getSelectMetadataSql(TokenMetadata.COMMENT)));
+    assertEquals("false", getStringTokenAttributeFromDatabase(tokenId, 
getSelectMetadataSql(TokenMetadata.ENABLED)));
 
+    //enable the token (it was disabled)
+    tokenMetadata.setEnabled(true);
+    jdbcTokenStateService.addMetadata(tokenId, tokenMetadata);
+    assertTrue(jdbcTokenStateService.getTokenMetadata(tokenId).isEnabled());
+    assertEquals("true", getStringTokenAttributeFromDatabase(tokenId, 
getSelectMetadataSql(TokenMetadata.ENABLED)));
+
+    //remove and get -> expect UnknownTokenException
     jdbcTokenStateService.removeToken(tokenId);
     jdbcTokenStateService.getTokenMetadata(tokenId);
   }
@@ -190,9 +200,17 @@ public class JDBCTokenStateServiceTest {
   }
 
   private void truncateDatabase() throws SQLException {
+    try (Connection conn = derbyDatabase.getConnection(); PreparedStatement 
stmt = conn.prepareStatement(TRUNCATE_KNOX_TOKEN_METADATA_SQL)) {
+      stmt.executeUpdate();
+    }
+
     try (Connection conn = derbyDatabase.getConnection(); PreparedStatement 
stmt = conn.prepareStatement(TRUNCATE_KNOX_TOKENS_SQL)) {
       stmt.executeUpdate();
     }
   }
 
+  private String getSelectMetadataSql(String metadataName) {
+    return "SELECT md_value FROM " + 
TokenStateDatabase.TOKEN_METADATA_TABLE_NAME + " WHERE token_id = ? AND md_name 
= '" + metadataName + "'";
+  }
+
 }
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateServiceTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateServiceTest.java
index b446651..ee89293 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateServiceTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateServiceTest.java
@@ -122,10 +122,11 @@ public class ZookeeperTokenStateServiceTest {
 
     final String userName = "testUser";
     final String comment = "This is my test comment";
-    zktokenStateServiceNode1.addMetadata(tokenId, new TokenMetadata(userName, 
comment));
+    zktokenStateServiceNode1.addMetadata(tokenId, new TokenMetadata(userName, 
comment, true));
     Thread.sleep(LONG_TOKEN_STATE_ALIAS_PERSISTENCE_INTERVAL * 1000);
     assertEquals(userName, 
zktokenStateServiceNode2.getTokenMetadata(tokenId).getUserName());
     assertEquals(comment, 
zktokenStateServiceNode2.getTokenMetadata(tokenId).getComment());
+    assertTrue(zktokenStateServiceNode2.getTokenMetadata(tokenId).isEnabled());
   }
 
   @Test
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournalTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournalTest.java
index 2fe601d..9d99ff2 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournalTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/FileTokenStateJournalTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertNull;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.knox.gateway.services.token.state.JournalEntry;
 import org.junit.Test;
 
@@ -47,7 +48,7 @@ public class FileTokenStateJournalTest {
         final Long   expiration  = issueTime + TimeUnit.HOURS.toMillis(1);
         final Long   maxLifetime = null;
 
-        doTestParseJournalEntry(tokenId, issueTime, expiration, maxLifetime, 
"user", null);
+        doTestParseJournalEntry(tokenId, issueTime, expiration, maxLifetime, 
Boolean.TRUE, "user", null);
     }
 
     @Test
@@ -77,29 +78,32 @@ public class FileTokenStateJournalTest {
 
     @Test
     public void tesParseTokenMetadata() throws Exception {
-      doTestParseJournalEntry("", "", "", "", "userName", "");
-      doTestParseJournalEntry("", "", "", "", "", "comment");
+      doTestParseJournalEntry("", "", "", "", "", "userName", "");
+      doTestParseJournalEntry("", "", "", "", "", "", "comment");
+      doTestParseJournalEntry("", "", "", "", "false", "", "");
     }
 
     @Test
     public void testParseJournalEntry_AllMissing() {
-        doTestParseJournalEntry(null, null, null, " ", null, null);
+        doTestParseJournalEntry(null, null, null, " ", null, null, null);
     }
 
     private void doTestParseJournalEntry(final String tokenId, final Long 
issueTime, final Long expiration, final Long maxLifetime) {
-      doTestParseJournalEntry(tokenId, issueTime, expiration, maxLifetime, 
null, null);
+      doTestParseJournalEntry(tokenId, issueTime, expiration, maxLifetime, 
null, null, null);
     }
 
     private void doTestParseJournalEntry(final String tokenId,
                                          final Long   issueTime,
                                          final Long   expiration,
                                          final Long   maxLifetime,
+                                         final Boolean enabled,
                                          final String userName,
                                          final String comment) {
         doTestParseJournalEntry(tokenId,
                                 (issueTime != null ? issueTime.toString() : 
null),
                                 (expiration != null ? expiration.toString() : 
null),
                                 (maxLifetime != null ? maxLifetime.toString() 
: null),
+                                (enabled != null ? enabled.toString() : null),
                                 userName, comment);
     }
 
@@ -107,6 +111,7 @@ public class FileTokenStateJournalTest {
                                          final String issueTime,
                                          final String expiration,
                                          final String maxLifetime,
+                                         final String enabled,
                                          final String userName,
                                          final String comment) {
         StringBuilder entryStringBuilder =
@@ -116,6 +121,7 @@ public class FileTokenStateJournalTest {
                                                              
.append(expiration != null ? expiration : "")
                                                              .append(',')
                                                              
.append(maxLifetime != null ? maxLifetime : "")
+                                                             
.append(",").append(enabled != null ? enabled : "")
                                                              
.append(",").append(userName == null ? "" : userName)
                                                              
.append(",").append(comment == null ? "" : comment);
 
@@ -125,6 +131,7 @@ public class FileTokenStateJournalTest {
         assertJournalEntryField(issueTime, entry.getIssueTime());
         assertJournalEntryField(expiration, entry.getExpiration());
         assertJournalEntryField(maxLifetime, entry.getMaxLifetime());
+        assertJournalEntryField(StringUtils.isBlank(enabled) ? "false" : 
enabled, String.valueOf(entry.getTokenMetadata().isEnabled()));
         assertJournalEntryField(userName, 
entry.getTokenMetadata().getUserName());
         assertJournalEntryField(comment, 
entry.getTokenMetadata().getComment());
     }
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 3c78c6b..e484f56 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
@@ -103,6 +103,8 @@ public class TokenResource {
   static final String GET_TSS_STATUS_PATH = "/getTssStatus";
   static final String RENEW_PATH = "/renew";
   static final String REVOKE_PATH = "/revoke";
+  static final String ENABLE_PATH = "/enable";
+  static final String DISABLE_PATH = "/disable";
   private static final String TARGET_ENDPOINT_PULIC_CERT_PEM = 
"knox.token.target.endpoint.cert.pem";
   private static TokenServiceMessages log = 
MessagesFactory.get(TokenServiceMessages.class);
   private long tokenTTL = TOKEN_TTL_DEFAULT;
@@ -410,6 +412,47 @@ public class TokenResource {
     return resp;
   }
 
+  @POST
+  @Path(ENABLE_PATH)
+  @Produces({ APPLICATION_JSON })
+  public Response enable(String tokenId) {
+    return setTokenEnabledFlag(tokenId, true);
+  }
+
+  @POST
+  @Path(DISABLE_PATH)
+  @Produces({ APPLICATION_JSON })
+  public Response disable(String tokenId) {
+    return setTokenEnabledFlag(tokenId, false);
+  }
+
+  private Response setTokenEnabledFlag(String tokenId, boolean enabled) {
+    String error = "";
+    if (tokenStateService == null) {
+      error = "Unable to " + (enabled ? "enable" : "disable") + " tokens 
because token management is not configured";
+    } else {
+      try {
+        final TokenMetadata tokenMetadata = 
tokenStateService.getTokenMetadata(tokenId);
+        if (enabled && tokenMetadata.isEnabled()) {
+          error = "Token is already enabled";
+        } else if (!enabled && !tokenMetadata.isEnabled()) {
+          error = "Token is already disabled";
+        } else {
+          tokenMetadata.setEnabled(enabled);
+          tokenStateService.addMetadata(tokenId, tokenMetadata);
+        }
+      } catch (UnknownTokenException e) {
+        error = safeGetMessage(e);
+      }
+    }
+    if (error.isEmpty()) {
+      return Response.status(Response.Status.OK).entity("{\n  
\"setEnabledFlag\": \"true\",\n  \"isEnabled\": \"" + enabled + 
"\"\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}\n").build();
+    }
+  }
+
   private X509Certificate extractCertificate(HttpServletRequest req) {
     X509Certificate[] certs = (X509Certificate[]) 
req.getAttribute("javax.servlet.request.X509Certificate");
     if (null != certs && certs.length > 0) {
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 14ef1dc..f2a65e5 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
@@ -71,6 +71,9 @@ public interface TokenServiceMessages {
   @Message( level = MessageLevel.ERROR, text = "Knox Token service ({0}) 
rejected a bad revocation request for token {1}: {2}")
   void badRevocationRequest(String topologyName, String tokenDisplayText, 
String error);
 
+  @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.DEBUG, text = "Knox Token service ({0}) 
stored state for token {1} ({2})")
   void storedToken(String topologyName, String tokenDisplayText, String 
tokenId);
 
diff --git 
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
 
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
index f8a6bd3..97455a5 100644
--- 
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
+++ 
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
@@ -19,6 +19,7 @@ package org.apache.knox.gateway.services.security.token;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -26,41 +27,65 @@ import org.apache.commons.lang3.builder.ToStringStyle;
 import org.apache.knox.gateway.util.JsonUtils;
 
 public class TokenMetadata {
-  private static final String JSON_ELEMENT_USER_NAME = "userName";
-  private static final String JSON_ELEMENT_COMMENT = "comment";
-  private static final String EMPTY_COMMENT = "";
+  public static final String USER_NAME = "userName";
+  public static final String COMMENT = "comment";
+  public static final String ENABLED = "enabled";
 
-  private final String userName;
-  private final String comment;
+  private final Map<String, String> metadataMap = new HashMap<>();
 
   public TokenMetadata(String userName) {
-    this(userName, EMPTY_COMMENT);
+    this(userName, null);
   }
 
   public TokenMetadata(String userName, String comment) {
-    this.userName = userName;
-    this.comment = comment;
+    this(userName, comment, true);
+  }
+
+  public TokenMetadata(String userName, String comment, boolean enabled) {
+    saveMetadata(USER_NAME, userName);
+    saveMetadata(COMMENT, comment);
+    setEnabled(enabled);
+  }
+
+  private void saveMetadata(String key, String value) {
+    if (StringUtils.isNotBlank(value)) {
+      this.metadataMap.put(key, value);
+    }
+  }
+
+  public TokenMetadata(Map<String, String> metadataMap) {
+    this.metadataMap.clear();
+    this.metadataMap.putAll(metadataMap);
+  }
+
+  public Map<String, String> getMetadataMap() {
+    return new HashMap<String, String>(this.metadataMap);
   }
 
   public String getUserName() {
-    return userName;
+    return metadataMap.get(USER_NAME);
   }
 
   public String getComment() {
-    return comment;
+    return metadataMap.get(COMMENT);
+  }
+
+  public void setEnabled(boolean enabled) {
+    saveMetadata(ENABLED, String.valueOf(enabled));
+  }
+
+  public boolean isEnabled() {
+    return Boolean.parseBoolean(metadataMap.get(ENABLED));
   }
 
   public String toJSON() {
-    final Map<String, String> metadataMap = new HashMap<>();
-    metadataMap.put(JSON_ELEMENT_USER_NAME, getUserName());
-    metadataMap.put(JSON_ELEMENT_COMMENT, getComment() == null ? EMPTY_COMMENT 
: getComment());
     return JsonUtils.renderAsJsonString(metadataMap);
   }
 
   public static TokenMetadata fromJSON(String json) {
     final Map<String, String> metadataMap = 
JsonUtils.getMapFromJsonString(json);
     if (metadataMap != null) {
-      return new TokenMetadata(metadataMap.get(JSON_ELEMENT_USER_NAME), 
metadataMap.get(JSON_ELEMENT_COMMENT));
+      return new TokenMetadata(metadataMap);
     }
     throw new IllegalArgumentException("Invalid metadata JSON: " + json);
   }

Reply via email to