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

pzampino 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 71f676b  KNOX-2392 - Simple file-based TokenStateService 
implementation (#350)
71f676b is described below

commit 71f676bf895d97ea6d95b82addd22b8598fdee9b
Author: Phil Zampino <[email protected]>
AuthorDate: Fri Jun 19 14:38:01 2020 -0400

    KNOX-2392 - Simple file-based TokenStateService implementation (#350)
---
 .../token/impl/AliasBasedTokenStateService.java    |  11 +-
 .../token/impl/JournalBasedTokenStateService.java  | 173 +++++++++++
 .../token/impl/TokenStateServiceMessages.java      |   9 +-
 .../impl/AliasBasedTokenStateServiceTest.java      |  91 ++----
 .../token/impl/DefaultTokenStateServiceTest.java   |  23 +-
 .../impl/JournalBasedTokenStateServiceTest.java    | 330 +++++++++++++++++++++
 .../state/AbstractFileTokenStateJournalTest.java   |   6 +
 7 files changed, 576 insertions(+), 67 deletions(-)

diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
index 28ca807..8b44336 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
@@ -234,11 +234,14 @@ public class AliasBasedTokenStateService extends 
DefaultTokenStateService {
     long expiration = 0;
     try {
       char[] expStr = 
aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, 
tokenId);
-      if (expStr != null) {
-        expiration = Long.parseLong(new String(expStr));
-        // Update the in-memory cache to avoid subsequent keystore look-ups 
for the same state
-        super.updateExpiration(tokenId, expiration);
+      if (expStr == null) {
+        throw new UnknownTokenException(tokenId);
       }
+      expiration = Long.parseLong(new String(expStr));
+      // Update the in-memory cache to avoid subsequent keystore look-ups for 
the same state
+      super.updateExpiration(tokenId, expiration);
+    } catch (UnknownTokenException e) {
+      throw e;
     } catch (Exception e) {
       log.errorAccessingTokenState(tokenId, e);
     }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateService.java
new file mode 100644
index 0000000..39feb7e
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateService.java
@@ -0,0 +1,173 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ *
+ */
+package org.apache.knox.gateway.services.token.impl;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.token.UnknownTokenException;
+import 
org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
+import org.apache.knox.gateway.services.token.state.JournalEntry;
+import org.apache.knox.gateway.services.token.state.TokenStateJournal;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class JournalBasedTokenStateService extends DefaultTokenStateService {
+
+    private TokenStateJournal journal;
+
+    @Override
+    public void init(final GatewayConfig config, final Map<String, String> 
options) throws ServiceLifecycleException {
+        super.init(config, options);
+
+        try {
+            // Initialize the token state journal
+            journal = TokenStateJournalFactory.create(config);
+
+            // Load any persisted journal entries, and add them to the 
unpersisted state collection
+            List<JournalEntry> entries = journal.get();
+            for (JournalEntry entry : entries) {
+                String id = entry.getTokenId();
+                try {
+                    long issueTime   = Long.parseLong(entry.getIssueTime());
+                    long expiration  = Long.parseLong(entry.getExpiration());
+                    long maxLifetime = Long.parseLong(entry.getMaxLifetime());
+
+                    // Add the token state to memory
+                    super.addToken(id, issueTime, expiration, maxLifetime);
+
+                } catch (Exception e) {
+                    log.failedToLoadJournalEntry(id, e);
+                }
+            }
+        } catch (IOException e) {
+            throw new ServiceLifecycleException("Failed to load persisted 
state from the token state journal", e);
+        }
+    }
+
+    @Override
+    public void addToken(final String tokenId, long issueTime, long 
expiration, long maxLifetimeDuration) {
+        super.addToken(tokenId, issueTime, expiration, maxLifetimeDuration);
+
+        try {
+            journal.add(tokenId, issueTime, expiration, maxLifetimeDuration);
+        } catch (IOException e) {
+            log.failedToAddJournalEntry(tokenId, e);
+        }
+    }
+
+    @Override
+    public long getTokenExpiration(final String tokenId, boolean validate) 
throws UnknownTokenException {
+        // Check the in-memory collection first, to avoid file access when 
possible
+        try {
+            // If the token identifier is valid, and the associated state is 
available from the in-memory cache, then
+            // return the expiration from there.
+            return super.getTokenExpiration(tokenId, validate);
+        } catch (UnknownTokenException e) {
+            // It's not in memory
+        }
+
+        if (validate) {
+            validateToken(tokenId);
+        }
+
+        // If there is no associated state in the in-memory cache, proceed to 
check the journal
+        long expiration = 0;
+        try {
+            JournalEntry entry = journal.get(tokenId);
+            if (entry == null) {
+                throw new UnknownTokenException(tokenId);
+            }
+
+            expiration = Long.parseLong(entry.getExpiration());
+            super.addToken(tokenId,
+                           Long.parseLong(entry.getIssueTime()),
+                           expiration,
+                           Long.parseLong(entry.getMaxLifetime()));
+        } catch (IOException e) {
+            log.failedToLoadJournalEntry(e);
+        }
+
+        return expiration;
+    }
+
+    @Override
+    protected long getMaxLifetime(final String tokenId) {
+        long result = super.getMaxLifetime(tokenId);
+
+        // If there is no result from the in-memory collection, proceed to 
check the journal
+        if (result < 1L) {
+            try {
+                JournalEntry entry = journal.get(tokenId);
+                if (entry == null) {
+                    throw new UnknownTokenException(tokenId);
+                }
+                result = Long.parseLong(entry.getMaxLifetime());
+                super.setMaxLifetime(tokenId, 
Long.parseLong(entry.getIssueTime()), result);
+            } catch (Exception e) {
+                log.failedToLoadJournalEntry(e);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected void removeTokens(final Set<String> tokenIds) throws 
UnknownTokenException {
+        super.removeTokens(tokenIds);
+        try {
+            journal.remove(tokenIds);
+        } catch (IOException e) {
+            log.failedToRemoveJournalEntries(e);
+        }
+    }
+
+    @Override
+    protected void updateExpiration(final String tokenId, long expiration) {
+        super.updateExpiration(tokenId, expiration);
+        try {
+            JournalEntry entry = journal.get(tokenId);
+            if (entry == null) {
+                log.journalEntryNotFound(tokenId);
+            } else {
+                // Adding will overwrite the existing journal entry, thus 
updating it with the new expiration
+                journal.add(entry.getTokenId(),
+                            Long.parseLong(entry.getIssueTime()),
+                            expiration,
+                            Long.parseLong(entry.getMaxLifetime()));
+            }
+        } catch (IOException e) {
+            log.errorAccessingTokenState(e);
+        }
+    }
+
+    @Override
+    protected boolean isUnknown(final String tokenId) {
+        JournalEntry entry = null;
+        try {
+            entry = journal.get(tokenId);
+        } catch (IOException e) {
+            log.errorAccessingTokenState(e);
+        }
+
+        return (entry == null);
+    }
+
+}
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 735abc9..686d4bd 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
@@ -97,7 +97,7 @@ public interface TokenStateServiceMessages {
   @Message(level = MessageLevel.INFO, text = "Removed token state aliases for 
{0}")
   void removedTokenStateAliases(String tokenId);
 
-  @Message(level = MessageLevel.INFO, text = "Loading peristed token state 
journal entries")
+  @Message(level = MessageLevel.DEBUG, text = "Loading peristed token state 
journal entries")
   void loadingPersistedJournalEntries();
 
   @Message(level = MessageLevel.DEBUG, text = "Loaded peristed token state 
journal entry for {0}")
@@ -106,10 +106,10 @@ public interface TokenStateServiceMessages {
   @Message(level = MessageLevel.ERROR, text = "The peristed token state 
journal entry {0} is empty")
   void emptyJournalEntry(String journalEntryName);
 
-  @Message(level = MessageLevel.INFO, text = "Added token state journal entry 
for {0}")
+  @Message(level = MessageLevel.DEBUG, text = "Added token state journal entry 
for {0}")
   void addedJournalEntry(String tokenId);
 
-  @Message(level = MessageLevel.INFO, text = "Removed token state journal 
entry for {0}")
+  @Message(level = MessageLevel.DEBUG, text = "Removed token state journal 
entry for {0}")
   void removedJournalEntry(String tokenId);
 
   @Message(level = MessageLevel.INFO, text = "Token state journal entry not 
found for {0}")
@@ -133,4 +133,7 @@ public interface TokenStateServiceMessages {
   @Message(level = MessageLevel.ERROR, text = "Failed to remove the token 
state journal entry for {0} : {1}")
   void failedToRemoveJournalEntry(String tokenId, @StackTrace(level = 
MessageLevel.DEBUG) Exception e);
 
+  @Message(level = MessageLevel.ERROR, text = "Failed to remove the token 
state journal entries : {0}")
+  void failedToRemoveJournalEntries(@StackTrace(level = MessageLevel.DEBUG) 
Exception e);
+
 }
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java
index 606b3aa..2b589fb 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateServiceTest.java
@@ -26,11 +26,8 @@ import 
org.apache.knox.gateway.services.token.state.JournalEntry;
 import org.apache.knox.gateway.services.token.state.TokenStateJournal;
 import 
org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
 import org.easymock.EasyMock;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
 
-import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.nio.file.Files;
@@ -56,23 +53,9 @@ import static org.junit.Assert.assertTrue;
 
 public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTest {
 
-  @Rule
-  public final TemporaryFolder testFolder = new TemporaryFolder();
-
-  private Path gatewaySecurityDir;
-
   private Long tokenStatePersistenceInterval = TimeUnit.SECONDS.toMillis(15);
 
   @Override
-  protected String getGatewaySecurityDir() throws IOException {
-    if (gatewaySecurityDir == null) {
-      gatewaySecurityDir = testFolder.newFolder().toPath();
-      Files.createDirectories(gatewaySecurityDir);
-    }
-    return gatewaySecurityDir.toString();
-  }
-
-  @Override
   protected long getTokenStatePersistenceInterval() {
     return (tokenStatePersistenceInterval != null) ? 
tokenStatePersistenceInterval : super.getTokenStatePersistenceInterval();
   }
@@ -168,13 +151,8 @@ public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTes
     tss.setAliasService(aliasService);
     initTokenStateService(tss);
 
-    Field tokenExpirationsField = 
tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
-    tokenExpirationsField.setAccessible(true);
-    Map<String, Long> tokenExpirations = (Map<String, Long>) 
tokenExpirationsField.get(tss);
-
-    Field maxTokenLifetimesField = 
tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
-    maxTokenLifetimesField.setAccessible(true);
-    Map<String, Long> maxTokenLifetimes = (Map<String, Long>) 
maxTokenLifetimesField.get(tss);
+    Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
+    Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
 
     final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
     final long maxTokenLifetime = evictionInterval * 3;
@@ -263,13 +241,8 @@ public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTes
     tss.setAliasService(aliasService);
     initTokenStateService(tss);
 
-    Field tokenExpirationsField = 
tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
-    tokenExpirationsField.setAccessible(true);
-    Map<String, Long> tokenExpirations = (Map<String, Long>) 
tokenExpirationsField.get(tss);
-
-    Field maxTokenLifetimesField = 
tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
-    maxTokenLifetimesField.setAccessible(true);
-    Map<String, Long> maxTokenLifetimes = (Map<String, Long>) 
maxTokenLifetimesField.get(tss);
+    Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
+    Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
 
     try {
       tss.start();
@@ -316,9 +289,7 @@ public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTes
     tss.setAliasService(aliasService);
     initTokenStateService(tss);
 
-    Field maxTokenLifetimesField = 
tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
-    maxTokenLifetimesField.setAccessible(true);
-    Map<String, Long> maxTokenLifetimes = (Map<String, Long>) 
maxTokenLifetimesField.get(tss);
+    Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
 
     final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
     final long maxTokenLifetime = evictionInterval * 3;
@@ -380,9 +351,7 @@ public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTes
     tss.setAliasService(aliasService);
     initTokenStateService(tss);
 
-    Field tokenExpirationsField = 
tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
-    tokenExpirationsField.setAccessible(true);
-    Map<String, Long> tokenExpirations = (Map<String, Long>) 
tokenExpirationsField.get(tss);
+    Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
 
     final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
     final long maxTokenLifetime = evictionInterval * 3;
@@ -537,18 +506,10 @@ public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTes
     // Initialize the service, and presumably load the previously-persisted 
journal entries
     initTokenStateService(tss);
 
-    Field tokenExpirationsField = 
tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
-    tokenExpirationsField.setAccessible(true);
-    Map<String, Long> tokenExpirations = (Map<String, Long>) 
tokenExpirationsField.get(tss);
-
-    Field maxTokenLifetimesField = 
tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
-    maxTokenLifetimesField.setAccessible(true);
-    Map<String, Long> maxTokenLifetimes = (Map<String, Long>) 
maxTokenLifetimesField.get(tss);
+    Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
+    Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
 
-    Field unpersistedStateField = 
tss.getClass().getDeclaredField("unpersistedState");
-    unpersistedStateField.setAccessible(true);
-    List<AliasBasedTokenStateService.TokenState> unpersistedState =
-            (List<AliasBasedTokenStateService.TokenState>) 
unpersistedStateField.get(tss);
+    List<AliasBasedTokenStateService.TokenState> unpersistedState = 
getUnpersistedStateField(tss);
 
     assertEquals("Expected the tokens expirations to have been added in the 
base class cache.",
                  TOKEN_COUNT,
@@ -621,18 +582,10 @@ public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTes
     // Initialize the service, and presumably load the previously-persisted 
journal entries
     initTokenStateService(tss);
 
-    Field tokenExpirationsField = 
tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
-    tokenExpirationsField.setAccessible(true);
-    Map<String, Long> tokenExpirations = (Map<String, Long>) 
tokenExpirationsField.get(tss);
+    Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
+    Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
 
-    Field maxTokenLifetimesField = 
tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
-    maxTokenLifetimesField.setAccessible(true);
-    Map<String, Long> maxTokenLifetimes = (Map<String, Long>) 
maxTokenLifetimesField.get(tss);
-
-    Field unpersistedStateField = 
tss.getClass().getDeclaredField("unpersistedState");
-    unpersistedStateField.setAccessible(true);
-    List<AliasBasedTokenStateService.TokenState> unpersistedState =
-            (List<AliasBasedTokenStateService.TokenState>) 
unpersistedStateField.get(tss);
+    List<AliasBasedTokenStateService.TokenState> unpersistedState = 
getUnpersistedStateField(tss);
 
     assertEquals("Expected the tokens expirations to have been added in the 
base class cache.",
                  TOKEN_COUNT,
@@ -805,6 +758,26 @@ public class AliasBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTes
     }
   }
 
+  private static Map<String, Long> getTokenExpirationsField(TokenStateService 
tss) throws Exception {
+    Field tokenExpirationsField = 
tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
+    tokenExpirationsField.setAccessible(true);
+    return (Map<String, Long>) tokenExpirationsField.get(tss);
+  }
+
+  private static Map<String, Long> getMaxTokenLifetimesField(TokenStateService 
tss) throws Exception {
+    Field maxTokenLifetimesField = 
tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
+    maxTokenLifetimesField.setAccessible(true);
+    return (Map<String, Long>) maxTokenLifetimesField.get(tss);
+  }
+
+  private static List<AliasBasedTokenStateService.TokenState> 
getUnpersistedStateField(TokenStateService tss)
+          throws Exception {
+    Field unpersistedStateField = 
tss.getClass().getDeclaredField("unpersistedState");
+    unpersistedStateField.setAccessible(true);
+    return (List<AliasBasedTokenStateService.TokenState>) 
unpersistedStateField.get(tss);
+
+  }
+
   private static class TestJournalEntry implements JournalEntry {
 
     private String tokenId;
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 190dd70..6eaff90 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
@@ -27,9 +27,13 @@ import 
org.apache.knox.gateway.services.security.token.UnknownTokenException;
 import org.apache.knox.gateway.services.security.token.impl.JWTToken;
 import org.easymock.EasyMock;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.interfaces.RSAPrivateKey;
@@ -48,6 +52,11 @@ public class DefaultTokenStateServiceTest {
 
   private static RSAPrivateKey privateKey;
 
+  @Rule
+  public final TemporaryFolder testFolder = new TemporaryFolder();
+
+  private Path gatewaySecurityDir;
+
   @BeforeClass
   public static void setUpBeforeClass() throws Exception {
     KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
@@ -87,6 +96,14 @@ public class DefaultTokenStateServiceTest {
     createTokenStateService().getTokenExpiration(TokenUtils.getTokenId(token));
   }
 
+  @Test(expected = UnknownTokenException.class)
+  public void testGetExpiration_InvalidToken_WithoutValidation() throws 
Exception {
+    final JWTToken token = createMockToken(System.currentTimeMillis() + 
TimeUnit.SECONDS.toMillis(60));
+
+    // Expecting an UnknownTokenException because the token is not known to 
the TokenStateService
+    createTokenStateService().getTokenExpiration(TokenUtils.getTokenId(token), 
false);
+  }
+
   @Test
   public void testGetExpiration_AfterRenewal() throws Exception {
     final JWTToken token = createMockToken(System.currentTimeMillis() + 
TimeUnit.SECONDS.toMillis(60));
@@ -290,7 +307,11 @@ public class DefaultTokenStateServiceTest {
   }
 
   protected String getGatewaySecurityDir() throws IOException {
-    return null;
+    if (gatewaySecurityDir == null) {
+      gatewaySecurityDir = testFolder.newFolder().toPath();
+      Files.createDirectories(gatewaySecurityDir);
+    }
+    return gatewaySecurityDir.toString();
   }
 
   protected TokenStateService createTokenStateService() throws Exception {
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateServiceTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateServiceTest.java
new file mode 100644
index 0000000..4f6ceda
--- /dev/null
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JournalBasedTokenStateServiceTest.java
@@ -0,0 +1,330 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ *
+ */
+package org.apache.knox.gateway.services.token.impl;
+
+import org.apache.knox.gateway.services.security.token.TokenStateService;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
+import 
org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
+import org.apache.knox.gateway.services.token.state.TokenStateJournal;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class JournalBasedTokenStateServiceTest extends 
DefaultTokenStateServiceTest {
+
+    @Override
+    protected TokenStateService createTokenStateService() throws Exception {
+        TokenStateService tss =  new JournalBasedTokenStateService();
+        initTokenStateService(tss);
+        return tss;
+    }
+
+
+    @Test
+    public void testBulkTokenStateEviction() throws Exception {
+        final int TOKEN_COUNT = 5;
+        final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
+        final long maxTokenLifetime = evictionInterval * 3;
+
+        final Set<JWTToken> testTokens = new HashSet<>();
+        for (int i = 0; i < TOKEN_COUNT ; i++) {
+            testTokens.add(createMockToken(System.currentTimeMillis() - 
TimeUnit.SECONDS.toMillis(60)));
+        }
+
+        TokenStateService tss = createTokenStateService();
+
+        TokenStateJournal journal = getJournalField(tss);
+
+        try {
+            tss.start();
+
+            // Add the expired tokens
+            for (JWTToken token : testTokens) {
+                tss.addToken(token.getClaim(JWTToken.KNOX_ID_CLAIM),
+                             System.currentTimeMillis(),
+                             token.getExpiresDate().getTime(),
+                             maxTokenLifetime);
+                assertTrue("Expected the token to have expired.", 
tss.isExpired(token));
+            }
+
+            assertEquals(TOKEN_COUNT, journal.get().size());
+
+            // Sleep to allow the eviction evaluation to be performed
+            Thread.sleep(evictionInterval + (evictionInterval / 2));
+        } finally {
+            tss.stop();
+        }
+
+        assertEquals(0, journal.get().size());
+    }
+
+    @Test
+    public void testAddAndRemoveTokenIncludesCache() throws Exception {
+        final int TOKEN_COUNT = 5;
+
+        final Set<JWTToken> testTokens = new HashSet<>();
+        for (int i = 0; i < TOKEN_COUNT ; i++) {
+            testTokens.add(createMockToken(System.currentTimeMillis() - 
TimeUnit.SECONDS.toMillis(60)));
+        }
+
+        TokenStateService tss = createTokenStateService();
+
+        Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
+        Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
+
+        final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
+        final long maxTokenLifetime = evictionInterval * 3;
+
+        try {
+            tss.start();
+
+            // Add the expired tokens
+            for (JWTToken token : testTokens) {
+                tss.addToken(token.getClaim(JWTToken.KNOX_ID_CLAIM),
+                             System.currentTimeMillis(),
+                             token.getExpiresDate().getTime(),
+                             maxTokenLifetime);
+            }
+
+            assertEquals("Expected the tokens to have been added in the base 
class cache.",
+                         TOKEN_COUNT,
+                         tokenExpirations.size());
+            assertEquals("Expected the tokens lifetimes to have been added in 
the base class cache.",
+                         TOKEN_COUNT,
+                         maxTokenLifetimes.size());
+
+            // Sleep to allow the eviction evaluation to be performed
+            Thread.sleep(evictionInterval + (evictionInterval / 4));
+
+        } finally {
+            tss.stop();
+        }
+
+        assertEquals("Expected the tokens to have been removed from the base 
class cache as a result of eviction.",
+                     0,
+                     tokenExpirations.size());
+        assertEquals("Expected the tokens lifetimes to have been removed from 
the base class cache as a result of eviction.",
+                     0,
+                     maxTokenLifetimes.size());
+    }
+
+    /**
+     * Verify that the token state reaper includes previously-persisted token 
state, so it's not left in the file
+     * system forever.
+     */
+    @Test
+    public void testTokenEvictionIncludesPreviouslyPersistedJournalEntries() 
throws Exception {
+        final int TOKEN_COUNT = 5;
+        final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
+        final long maxTokenLifetime = evictionInterval * 3;
+
+        final Set<JWTToken> testTokens = new HashSet<>();
+        for (int i = 0; i < TOKEN_COUNT ; i++) {
+            testTokens.add(createMockToken(System.currentTimeMillis() - 
TimeUnit.SECONDS.toMillis(60)));
+        }
+
+        TokenStateJournal testJournal =
+                    
TokenStateJournalFactory.create(createMockGatewayConfig(false,
+                                                                            
getGatewaySecurityDir(),
+                                                                            
getTokenStatePersistenceInterval()));
+
+        // Add a journal entry prior to initializing the TokenStateService
+        final JWTToken uncachedToken = 
createMockToken(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(60));
+        final String uncachedTokenId = 
uncachedToken.getClaim(JWTToken.KNOX_ID_CLAIM);
+        testJournal.add(uncachedTokenId,
+                        System.currentTimeMillis(),
+                        uncachedToken.getExpiresDate().getTime(),
+                        maxTokenLifetime);
+        assertEquals("Expected the uncached journal entry", 1, 
testJournal.get().size());
+
+        // Create and initialize the TokenStateService
+        TokenStateService tss = createTokenStateService();
+        TokenStateJournal journal = getJournalField(tss);
+
+        Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
+        Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
+
+        assertEquals("Expected the previously-persisted journal entry to have 
been loaded into the cache.",
+                     1,
+                     tokenExpirations.size());
+        assertEquals("Expected the previously-persisted journal entry to have 
been loaded into the cache.",
+                     1,
+                     maxTokenLifetimes.size());
+
+        try {
+            tss.start();
+
+            // Add the expired tokens
+            for (JWTToken token : testTokens) {
+                tss.addToken(token.getClaim(JWTToken.KNOX_ID_CLAIM),
+                             System.currentTimeMillis(),
+                             token.getExpiresDate().getTime(),
+                             maxTokenLifetime);
+            }
+
+            assertEquals("Expected the tokens to have been added in the base 
class cache.",
+                         TOKEN_COUNT + 1,
+                         tokenExpirations.size());
+            assertEquals("Expected the tokens lifetimes to have been added in 
the base class cache.",
+                         TOKEN_COUNT + 1,
+                         maxTokenLifetimes.size());
+            assertEquals("Expected the uncached journal entry in addition to 
the cached tokens",
+                         TOKEN_COUNT + 1,
+                         journal.get().size());
+
+
+            // Sleep to allow the eviction evaluation to be performed, but 
only one iteration
+            Thread.sleep(evictionInterval + (evictionInterval / 4));
+        } finally {
+            tss.stop();
+        }
+
+        assertEquals("Expected the tokens to have been removed from the base 
class cache as a result of eviction.",
+                     0,
+                     tokenExpirations.size());
+        assertEquals("Expected the tokens lifetimes to have been removed from 
the base class cache as a result of eviction.",
+                     0,
+                     maxTokenLifetimes.size());
+        assertEquals("Expected the journal entries to have been removed as a 
result of the eviction",
+                     0,
+                     journal.get().size());
+    }
+
+    @Test
+    public void testGetMaxLifetimeUsesCache() throws Exception {
+        final int TOKEN_COUNT = 10;
+        TokenStateService tss = createTokenStateService();
+
+        Map<String, Long> maxTokenLifetimes = getMaxTokenLifetimesField(tss);
+
+        final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
+        final long maxTokenLifetime = evictionInterval * 3;
+
+        final Set<JWTToken> testTokens = new HashSet<>();
+        for (int i = 0; i < TOKEN_COUNT ; i++) {
+            testTokens.add(createMockToken(System.currentTimeMillis() - 
TimeUnit.SECONDS.toMillis(60)));
+        }
+
+        try {
+            tss.start();
+
+            // Add the expired tokens
+            for (JWTToken token : testTokens) {
+                tss.addToken(token.getClaim(JWTToken.KNOX_ID_CLAIM),
+                             System.currentTimeMillis(),
+                             token.getExpiresDate().getTime(),
+                             maxTokenLifetime);
+
+            }
+
+            assertEquals("Expected the tokens lifetimes to have been added in 
the base class cache.",
+                         TOKEN_COUNT,
+                         maxTokenLifetimes.size());
+
+            // Set the cache values to be different from the underlying 
journal entry value
+            final long updatedMaxLifetime = evictionInterval * 5;
+            for (Map.Entry<String, Long> entry : maxTokenLifetimes.entrySet()) 
{
+                entry.setValue(updatedMaxLifetime);
+            }
+
+            // Verify that we get the cache value back
+            for (String tokenId : maxTokenLifetimes.keySet()) {
+                assertEquals("Expected the cached max lifetime, rather than 
the journal entry value",
+                             updatedMaxLifetime,
+                             ((JournalBasedTokenStateService) 
tss).getMaxLifetime(tokenId));
+            }
+        } finally {
+            tss.stop();
+        }
+    }
+
+    @Test
+    public void testUpdateExpirationUsesCache() throws Exception {
+        final int TOKEN_COUNT = 10;
+        TokenStateService tss = createTokenStateService();
+
+        Map<String, Long> tokenExpirations = getTokenExpirationsField(tss);
+
+        final long evictionInterval = TimeUnit.SECONDS.toMillis(3);
+        final long maxTokenLifetime = evictionInterval * 3;
+
+        final Set<JWTToken> testTokens = new HashSet<>();
+        for (int i = 0; i < TOKEN_COUNT ; i++) {
+            testTokens.add(createMockToken(System.currentTimeMillis() - 
TimeUnit.SECONDS.toMillis(60)));
+        }
+
+        try {
+            tss.start();
+
+            // Add the expired tokens
+            for (JWTToken token : testTokens) {
+                tss.addToken(token.getClaim(JWTToken.KNOX_ID_CLAIM),
+                             System.currentTimeMillis(),
+                             token.getExpiresDate().getTime(),
+                             maxTokenLifetime);
+            }
+
+            assertEquals("Expected the tokens expirations to have been added 
in the base class cache.",
+                         TOKEN_COUNT,
+                         tokenExpirations.size());
+
+            // Set the cache values to be different from the underlying 
journal entry value
+            final long updatedExpiration = System.currentTimeMillis();
+            for (String tokenId : tokenExpirations.keySet()) {
+                ((JournalBasedTokenStateService) 
tss).updateExpiration(tokenId, updatedExpiration);
+            }
+
+            // Invoking with true/false validation flags as it should not 
affect if values are coming from the cache
+            int count = 0;
+            for (String tokenId : tokenExpirations.keySet()) {
+                assertEquals("Expected the cached expiration to have been 
updated.",
+                             updatedExpiration,
+                             tss.getTokenExpiration(tokenId, count++ % 2 == 
0));
+            }
+
+        } finally {
+            tss.stop();
+        }
+    }
+
+    private static TokenStateJournal getJournalField(TokenStateService tss) 
throws Exception {
+        Field journalField = 
JournalBasedTokenStateService.class.getDeclaredField("journal");
+        journalField.setAccessible(true);
+        return (TokenStateJournal) journalField.get(tss);
+    }
+
+    private static Map<String, Long> 
getTokenExpirationsField(TokenStateService tss) throws Exception {
+        Field tokenExpirationsField = 
tss.getClass().getSuperclass().getDeclaredField("tokenExpirations");
+        tokenExpirationsField.setAccessible(true);
+        return (Map<String, Long>) tokenExpirationsField.get(tss);
+    }
+
+    private static Map<String, Long> 
getMaxTokenLifetimesField(TokenStateService tss) throws Exception {
+        Field maxTokenLifetimesField = 
tss.getClass().getSuperclass().getDeclaredField("maxTokenLifetimes");
+        maxTokenLifetimesField.setAccessible(true);
+        return (Map<String, Long>) maxTokenLifetimesField.get(tss);
+    }
+}
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/AbstractFileTokenStateJournalTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/AbstractFileTokenStateJournalTest.java
index d0a385c..5d64f71 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/AbstractFileTokenStateJournalTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/state/AbstractFileTokenStateJournalTest.java
@@ -227,4 +227,10 @@ public abstract class AbstractFileTokenStateJournalTest {
         }
     }
 
+    @Test
+    public void testGetUnknownToken() throws Exception {
+        GatewayConfig config = getGatewayConfig();
+        TokenStateJournal journal = createTokenStateJournal(config);
+        assertNull(journal.get(UUID.randomUUID().toString()));
+    }
 }

Reply via email to