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()));
+ }
}