You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2021/03/22 21:25:21 UTC
[knox] branch master updated: KNOX-2557 - Persisting token related
metadata using Knox's alias service (#420)
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 9e2b831 KNOX-2557 - Persisting token related metadata using Knox's alias service (#420)
9e2b831 is described below
commit 9e2b831343974670057c24ea590cb942f2433be3
Author: Sandor Molnar <sm...@cloudera.com>
AuthorDate: Mon Mar 22 22:25:14 2021 +0100
KNOX-2557 - Persisting token related metadata using Knox's alias service (#420)
---
.../token/impl/AliasBasedTokenStateService.java | 77 +++++++++++++++++++-
.../token/impl/DefaultTokenStateService.java | 13 ++++
.../token/impl/JournalBasedTokenStateService.java | 20 +++++-
.../token/impl/ZookeeperTokenStateService.java | 3 +
.../token/impl/state/FileTokenStateJournal.java | 46 +++++++++---
.../impl/state/MultiFileTokenStateJournal.java | 5 +-
.../gateway/services/token/state/JournalEntry.java | 8 +++
.../services/token/state/TokenStateJournal.java | 5 +-
.../impl/AliasBasedTokenStateServiceTest.java | 28 ++++++--
.../token/impl/DefaultTokenStateServiceTest.java | 62 +++++++++++-----
.../impl/JournalBasedTokenStateServiceTest.java | 3 +-
.../token/impl/ZookeeperTokenStateServiceTest.java | 8 +++
.../state/AbstractFileTokenStateJournalTest.java | 6 +-
.../impl/state/FileTokenStateJournalTest.java | 78 +++++++++++---------
.../gateway/service/knoxtoken/TokenResource.java | 2 +
.../knoxtoken/TokenServiceResourceTest.java | 10 +++
.../services/security/token/TokenMetadata.java | 82 ++++++++++++++++++++++
.../services/security/token/TokenStateService.java | 18 +++++
18 files changed, 392 insertions(+), 82 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 545fa96..4ac128a 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
@@ -42,6 +42,7 @@ import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.impl.DefaultKeystoreService;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
import org.apache.knox.gateway.services.token.TokenStateServiceStatistics;
import org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
@@ -55,6 +56,7 @@ import org.apache.knox.gateway.util.ExecutorServiceUtils;
public class AliasBasedTokenStateService extends DefaultTokenStateService implements TokenStatePeristerMonitorListener {
static final String TOKEN_MAX_LIFETIME_POSTFIX = "--max";
+ static final String TOKEN_META_POSTFIX = "--meta";
protected AliasService aliasService;
@@ -157,6 +159,9 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService implem
super.updateExpiration(tokenId, expiration);
super.setMaxLifetime(tokenId, maxLifeTime);
count+=2;
+ } else if (alias.endsWith(TOKEN_META_POSTFIX)) {
+ tokenId = alias.substring(0, alias.indexOf(TOKEN_META_POSTFIX));
+ super.addMetadata(tokenId, TokenMetadata.fromJSON(new String(passwordAliasMapEntry.getValue())));
}
// log some progress (it's very useful in case a huge amount of token related aliases in __gateway-credentials.jceks)
@@ -265,7 +270,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService implem
}
try {
- journal.add(tokenId, issueTime, expiration, maxLifetimeDuration);
+ journal.add(tokenId, issueTime, expiration, maxLifetimeDuration, null);
} catch (IOException e) {
log.failedToAddJournalEntry(tokenId, e);
}
@@ -419,8 +424,45 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService implem
super.updateExpiration(tokenId, expiration);
}
+ @Override
+ public void addMetadata(String tokenId, TokenMetadata metadata) {
+ addMetadataInMemory(tokenId, metadata);
+ try {
+ final JournalEntry entry = journal.get(tokenId);
+ if (entry != null) {
+ journal.add(entry.getTokenId(), Long.parseLong(entry.getIssueTime()), Long.parseLong(entry.getExpiration()), Long.parseLong(entry.getMaxLifetime()), metadata);
+ }
+ } catch (IOException e) {
+ log.failedToAddJournalEntry(tokenId, e);
+ }
+
+ synchronized (unpersistedState) {
+ unpersistedState.add(new TokenMetadataState(tokenId, metadata));
+ }
+ }
+
+ protected void addMetadataInMemory(String tokenId, TokenMetadata metadata) {
+ super.addMetadata(tokenId, metadata);
+ }
+
+ @Override
+ public TokenMetadata getTokenMetadata(String tokenId) {
+ TokenMetadata tokenMetadata = super.getTokenMetadata(tokenId);
+ if (tokenMetadata == null) {
+ try {
+ final char[] tokenMetadataAliasValue = getPasswordUsingAliasService(tokenId + TOKEN_META_POSTFIX);
+ if (tokenMetadataAliasValue != null) {
+ tokenMetadata = TokenMetadata.fromJSON(new String(tokenMetadataAliasValue));
+ }
+ } catch (AliasServiceException e) {
+ log.errorAccessingTokenState(tokenId, e);
+ }
+ }
+ return tokenMetadata;
+ }
+
enum TokenStateType {
- EXP(1), MAX(2);
+ EXP(1), MAX(2), META(3);
private final int id;
@@ -538,4 +580,35 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService implem
}
}
+ private static final class TokenMetadataState implements TokenState {
+
+ private final String tokenId;
+ private final TokenMetadata metadata;
+
+ TokenMetadataState(String tokenId, TokenMetadata metadata) {
+ this.tokenId = tokenId;
+ this.metadata = metadata;
+ }
+
+ @Override
+ public String getTokenId() {
+ return tokenId;
+ }
+
+ @Override
+ public String getAlias() {
+ return tokenId + TOKEN_META_POSTFIX;
+ }
+
+ @Override
+ public String getAliasValue() {
+ return metadata.toJSON();
+ }
+
+ @Override
+ public TokenStateType getType() {
+ return TokenStateType.META;
+ }
+ }
+
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
index 93d8560..f08412d 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
@@ -38,6 +38,7 @@ import javax.management.ObjectName;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.security.token.TokenStateService;
import org.apache.knox.gateway.services.security.token.TokenUtils;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
@@ -62,6 +63,8 @@ public class DefaultTokenStateService implements TokenStateService {
private final Map<String, Long> maxTokenLifetimes = new ConcurrentHashMap<>();
+ private final Map<String, TokenMetadata> metadataMap = new ConcurrentHashMap<>();
+
// Token eviction interval (in seconds)
private long tokenEvictionInterval;
@@ -282,6 +285,7 @@ public class DefaultTokenStateService implements TokenStateService {
private void removeTokenState(final Set<String> tokenIds) {
tokenExpirations.keySet().removeAll(tokenIds);
maxTokenLifetimes.keySet().removeAll(tokenIds);
+ metadataMap.keySet().removeAll(tokenIds);
log.removedTokenState(String.join(", ", tokenIds));
}
@@ -375,4 +379,13 @@ public class DefaultTokenStateService implements TokenStateService {
return tokenExpirations.keySet().stream().collect(Collectors.toList());
}
+ @Override
+ public void addMetadata(String tokenId, TokenMetadata metadata) {
+ metadataMap.put(tokenId, metadata);
+ }
+
+ @Override
+ public TokenMetadata getTokenMetadata(String tokenId) {
+ return metadataMap.get(tokenId);
+ }
}
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
index abee0e5..25597e2 100644
--- 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
@@ -20,6 +20,7 @@ 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.TokenMetadata;
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;
@@ -68,7 +69,7 @@ public class JournalBasedTokenStateService extends DefaultTokenStateService {
super.addToken(tokenId, issueTime, expiration, maxLifetimeDuration);
try {
- journal.add(tokenId, issueTime, expiration, maxLifetimeDuration);
+ journal.add(tokenId, issueTime, expiration, maxLifetimeDuration, null);
} catch (IOException e) {
log.failedToAddJournalEntry(tokenId, e);
}
@@ -151,7 +152,8 @@ public class JournalBasedTokenStateService extends DefaultTokenStateService {
journal.add(entry.getTokenId(),
Long.parseLong(entry.getIssueTime()),
expiration,
- Long.parseLong(entry.getMaxLifetime()));
+ Long.parseLong(entry.getMaxLifetime()),
+ entry.getTokenMetadata());
}
} catch (IOException e) {
log.errorAccessingTokenState(e);
@@ -170,4 +172,18 @@ public class JournalBasedTokenStateService extends DefaultTokenStateService {
return (entry == null);
}
+ @Override
+ public void addMetadata(String tokenId, TokenMetadata metadata) {
+ super.addMetadata(tokenId, metadata);
+ try {
+ JournalEntry entry = journal.get(tokenId);
+ if (entry == null) {
+ log.journalEntryNotFound(tokenId);
+ } else {
+ journal.add(entry.getTokenId(), Long.parseLong(entry.getIssueTime()), Long.parseLong(entry.getExpiration()), Long.parseLong(entry.getMaxLifetime()), metadata);
+ }
+ } catch (IOException e) {
+ log.errorAccessingTokenState(e);
+ }
+ }
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateService.java
index 161bfc3..60d0300 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/ZookeeperTokenStateService.java
@@ -30,6 +30,7 @@ import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.factory.AliasServiceFactory;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.impl.ZookeeperRemoteAliasService;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.token.RemoteTokenStateChangeListener;
/**
@@ -133,6 +134,8 @@ public class ZookeeperTokenStateService extends AliasBasedTokenStateService impl
if (alias.endsWith(TOKEN_MAX_LIFETIME_POSTFIX)) {
final long maxLifeTime = Long.parseLong(value);
setMaxLifetime(tokenId, maxLifeTime);
+ } else if (alias.endsWith(TOKEN_META_POSTFIX)) {
+ addMetadataInMemory(tokenId, TokenMetadata.fromJSON(value));
} else {
final long expiration = Long.parseLong(value);
updateExpirationInMemory(tokenId, expiration);
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 11dd702..966766d 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
@@ -22,6 +22,7 @@ import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.token.state.JournalEntry;
import org.apache.knox.gateway.services.token.state.TokenStateJournal;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.token.impl.TokenStateServiceMessages;
import java.io.BufferedReader;
@@ -49,6 +50,8 @@ 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 TokenStateServiceMessages log = MessagesFactory.get(TokenStateServiceMessages.class);
@@ -68,7 +71,7 @@ abstract class FileTokenStateJournal implements TokenStateJournal {
}
@Override
- public abstract void add(String tokenId, long issueTime, long expiration, long maxLifetime) throws IOException;
+ public abstract void add(String tokenId, long issueTime, long expiration, long maxLifetime, TokenMetadata tokenMetadata) throws IOException;
@Override
public void add(JournalEntry entry) throws IOException {
@@ -133,24 +136,31 @@ abstract class FileTokenStateJournal implements TokenStateJournal {
/**
* A JournalEntry implementation for File-based TokenStateJournal implementations
*/
- static final class FileJournalEntry implements JournalEntry {
+ public static final class FileJournalEntry implements JournalEntry {
private final String tokenId;
private final String issueTime;
private final String expiration;
private final String maxLifetime;
+ private final TokenMetadata tokenMetadata;
FileJournalEntry(final String tokenId, long issueTime, long expiration, long maxLifetime) {
- this(tokenId, String.valueOf(issueTime), String.valueOf(expiration), String.valueOf(maxLifetime));
+ this(tokenId, String.valueOf(issueTime), String.valueOf(expiration), String.valueOf(maxLifetime), null);
+ }
+
+ FileJournalEntry(final String tokenId, long issueTime, long expiration, long maxLifetime, TokenMetadata tokenMetadata) {
+ this(tokenId, String.valueOf(issueTime), String.valueOf(expiration), String.valueOf(maxLifetime), tokenMetadata);
}
FileJournalEntry(final String tokenId,
final String issueTime,
final String expiration,
- final String maxLifetime) {
+ final String maxLifetime,
+ final TokenMetadata tokenMetadata) {
this.tokenId = tokenId;
this.issueTime = issueTime;
this.expiration = expiration;
this.maxLifetime = maxLifetime;
+ this.tokenMetadata = tokenMetadata;
}
@Override
@@ -174,8 +184,13 @@ abstract class FileTokenStateJournal implements TokenStateJournal {
}
@Override
+ public TokenMetadata getTokenMetadata() {
+ return tokenMetadata;
+ }
+
+ @Override
public String toString() {
- String[] elements = new String[4];
+ String[] elements = new String[6];
elements[INDEX_TOKEN_ID] = getTokenId();
@@ -188,12 +203,20 @@ abstract class FileTokenStateJournal implements TokenStateJournal {
String maxLifetime = getMaxLifetime();
elements[INDEX_MAX_LIFETIME] = (maxLifetime != null) ? maxLifetime : "";
+ String userName = getTokenMetadata() == null ? "" : (getTokenMetadata().getUserName() == null ? "" : getTokenMetadata().getUserName());
+ elements[INDEX_USERNAME] = userName;
+
+ String comment = getTokenMetadata() == null ? "" : (getTokenMetadata().getComment() == null ? "" : getTokenMetadata().getComment());
+ elements[INDEX_COMMENT] = comment;
+
return String.format(Locale.ROOT,
- "%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_MAX_LIFETIME],
+ elements[INDEX_USERNAME],
+ elements[INDEX_COMMENT]);
}
/**
@@ -204,8 +227,8 @@ abstract class FileTokenStateJournal implements TokenStateJournal {
* @return A FileJournalEntry object created from the specified entry.
*/
static FileJournalEntry parse(final String entry) {
- String[] elements = entry.split(",");
- if (elements.length < 4) {
+ String[] elements = entry.split(",", -1);
+ if (elements.length < 6) {
throw new IllegalArgumentException("Invalid journal entry: " + entry);
}
@@ -213,11 +236,14 @@ 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 userName = elements[INDEX_USERNAME].trim();
+ String comment = elements[INDEX_COMMENT].trim();
return new FileJournalEntry(tokenId.isEmpty() ? null : tokenId,
issueTime.isEmpty() ? null : issueTime,
expiration.isEmpty() ? null : expiration,
- maxLifetime.isEmpty() ? null : maxLifetime);
+ maxLifetime.isEmpty() ? null : maxLifetime,
+ new TokenMetadata(userName.isEmpty() ? null : userName, comment.isEmpty() ? null : comment));
}
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/MultiFileTokenStateJournal.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/MultiFileTokenStateJournal.java
index dfdd1e1..6fccd60 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/MultiFileTokenStateJournal.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/state/MultiFileTokenStateJournal.java
@@ -19,6 +19,7 @@
package org.apache.knox.gateway.services.token.impl.state;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.token.state.JournalEntry;
import java.io.BufferedWriter;
@@ -53,8 +54,8 @@ class MultiFileTokenStateJournal extends FileTokenStateJournal {
}
@Override
- public void add(final String tokenId, long issueTime, long expiration, long maxLifetime) throws IOException {
- add(Collections.singletonList(new FileJournalEntry(tokenId, issueTime, expiration, maxLifetime)));
+ public void add(final String tokenId, long issueTime, long expiration, long maxLifetime, TokenMetadata tokenMetadata) throws IOException {
+ add(Collections.singletonList(new FileJournalEntry(tokenId, issueTime, expiration, maxLifetime, tokenMetadata)));
}
@Override
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/JournalEntry.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/JournalEntry.java
index d520f45..ccd7833 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/JournalEntry.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/JournalEntry.java
@@ -18,6 +18,8 @@
*/
package org.apache.knox.gateway.services.token.state;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
+
/**
* An entry in the TokenStateJournal
*/
@@ -48,4 +50,10 @@ public interface JournalEntry {
* @return The token's maximum allowed lifetime
*/
String getMaxLifetime();
+
+ /**
+ * @return The metadata belongs to this token
+ */
+ TokenMetadata getTokenMetadata();
+
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/TokenStateJournal.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/TokenStateJournal.java
index a51d162..f29378c 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/TokenStateJournal.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/state/TokenStateJournal.java
@@ -22,6 +22,8 @@ import java.io.IOException;
import java.util.Collection;
import java.util.List;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
+
/**
*
*/
@@ -34,10 +36,11 @@ public interface TokenStateJournal {
* @param issueTime The issue timestamp
* @param expiration The expiration time
* @param maxLifetime The maximum allowed lifetime
+ * @param tokenMetafata The associated token metadata
*
* @throws IOException exception on error
*/
- void add(String tokenId, long issueTime, long expiration, long maxLifetime)
+ void add(String tokenId, long issueTime, long expiration, long maxLifetime, TokenMetadata tokenMetadata)
throws IOException;
/**
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 5da2f5d..fc9f316 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
@@ -21,6 +21,7 @@ import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.AbstractAliasService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
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.state.JournalEntry;
@@ -511,7 +512,8 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
journal.add(token.getClaim(JWTToken.KNOX_ID_CLAIM),
System.currentTimeMillis(),
token.getExpiresDate().getTime(),
- System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24));
+ System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24),
+ null);
}
AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
@@ -563,32 +565,37 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
journal.add(token.getClaim(JWTToken.KNOX_ID_CLAIM),
System.currentTimeMillis(),
token.getExpiresDate().getTime(),
- System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24));
+ System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24),
+ null);
}
// Add an entry with an invalid token identifier
journal.add(" ",
System.currentTimeMillis(),
System.currentTimeMillis(),
- System.currentTimeMillis());
+ System.currentTimeMillis(),
+ null);
// Add an entry with an invalid issue time
journal.add(new TestJournalEntry(UUID.randomUUID().toString(),
"invalidLongValue",
String.valueOf(System.currentTimeMillis()),
- String.valueOf(System.currentTimeMillis())));
+ String.valueOf(System.currentTimeMillis()),
+ new TokenMetadata("testUser")));
// Add an entry with an invalid expiration time
journal.add(new TestJournalEntry(UUID.randomUUID().toString(),
String.valueOf(System.currentTimeMillis()),
"invalidLongValue",
- String.valueOf(System.currentTimeMillis())));
+ String.valueOf(System.currentTimeMillis()),
+ new TokenMetadata("testUser")));
// Add an entry with an invalid max lifetime
journal.add(new TestJournalEntry(UUID.randomUUID().toString(),
String.valueOf(System.currentTimeMillis()),
String.valueOf(System.currentTimeMillis()),
- "invalidLongValue"));
+ "invalidLongValue",
+ new TokenMetadata("testUser")));
AliasBasedTokenStateService tss = new NoEvictionAliasBasedTokenStateService();
tss.setAliasService(aliasService);
@@ -844,12 +851,14 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
private String issueTime;
private String expiration;
private String maxLifetime;
+ private TokenMetadata tokenMetadata;
- TestJournalEntry(String tokenId, String issueTime, String expiration, String maxLifetime) {
+ TestJournalEntry(String tokenId, String issueTime, String expiration, String maxLifetime, TokenMetadata tokenMetadata) {
this.tokenId = tokenId;
this.issueTime = issueTime;
this.expiration = expiration;
this.maxLifetime = maxLifetime;
+ this.tokenMetadata = tokenMetadata;
}
@Override
@@ -873,6 +882,11 @@ public class AliasBasedTokenStateServiceTest extends DefaultTokenStateServiceTes
}
@Override
+ public TokenMetadata getTokenMetadata() {
+ return tokenMetadata;
+ }
+
+ @Override
public String toString() {
return tokenId + "," + issueTime + "," + expiration + "," + maxLifetime;
}
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 6eaff90..1ed01ec 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
@@ -16,20 +16,13 @@
*/
package org.apache.knox.gateway.services.token.impl;
-import com.nimbusds.jose.JWSSigner;
-import com.nimbusds.jose.crypto.RSASSASigner;
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.token.TokenStateService;
-import org.apache.knox.gateway.services.security.token.TokenUtils;
-import org.apache.knox.gateway.services.security.token.impl.JWT;
-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 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;
import java.io.IOException;
import java.nio.file.Files;
@@ -42,11 +35,22 @@ import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
+import org.apache.knox.gateway.services.security.token.TokenStateService;
+import org.apache.knox.gateway.services.security.token.TokenUtils;
+import org.apache.knox.gateway.services.security.token.UnknownTokenException;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
+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 com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
public class DefaultTokenStateServiceTest {
@@ -262,6 +266,26 @@ public class DefaultTokenStateServiceTest {
tss.getTokenExpiration(token);
}
+ @Test
+ public void testAddTokenMetadata() throws Exception {
+ final JWT token = getJWTToken(System.currentTimeMillis());
+ final String tokenId = token.getClaim(JWTToken.KNOX_ID_CLAIM);
+ final TokenStateService tss = new DefaultTokenStateService();
+ tss.addToken((JWTToken) token, System.currentTimeMillis());
+ assertNull(tss.getTokenMetadata(tokenId));
+
+ final String userName = "testUser";
+ 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());
+
+ final String comment = "this is my test comment";
+ tss.addMetadata(token.getClaim(JWTToken.KNOX_ID_CLAIM), new TokenMetadata(userName, comment));
+ assertNotNull(tss.getTokenMetadata(tokenId));
+ assertEquals(tss.getTokenMetadata(tokenId).getComment(), comment);
+ }
+
protected static JWTToken createMockToken(final long expiration) {
return createMockToken("abcD1234eFGHIJKLmnoPQRSTUVwXYz", expiration);
}
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
index bcfaec3..bb5c99f 100644
--- 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
@@ -157,7 +157,8 @@ public class JournalBasedTokenStateServiceTest extends DefaultTokenStateServiceT
testJournal.add(uncachedTokenId,
System.currentTimeMillis(),
uncachedToken.getExpiresDate().getTime(),
- maxTokenLifetime);
+ maxTokenLifetime,
+ null);
assertEquals("Expected the uncached journal entry", 1, testJournal.get().size());
// Create and initialize the TokenStateService
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 443d409..a31aa47 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
@@ -48,6 +48,7 @@ import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.KeystoreService;
import org.apache.knox.gateway.services.security.KeystoreServiceException;
import org.apache.knox.gateway.services.security.MasterService;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.easymock.EasyMock;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -115,6 +116,13 @@ public class ZookeeperTokenStateServiceTest {
final long expiration = zktokenStateServiceNode2.getTokenExpiration("node1Token");
Thread.sleep(LONG_TOKEN_STATE_ALIAS_PERSISTENCE_INTERVAL * 1000);
assertEquals(2000L, expiration);
+
+ final String userName = "testUser";
+ final String comment = "This is my test comment";
+ zktokenStateServiceNode1.addMetadata("node1Token", new TokenMetadata(userName, comment));
+ Thread.sleep(LONG_TOKEN_STATE_ALIAS_PERSISTENCE_INTERVAL * 1000);
+ assertEquals(userName, zktokenStateServiceNode2.getTokenMetadata("node1Token").getUserName());
+ assertEquals(comment, zktokenStateServiceNode2.getTokenMetadata("node1Token").getComment());
}
@Test
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 5d64f71..1c5e65f 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
@@ -79,7 +79,7 @@ public abstract class AbstractFileTokenStateJournalTest {
long issueTime = System.currentTimeMillis();
long expiration = issueTime + TimeUnit.MINUTES.toMillis(5);
long maxLifetime = issueTime + (5 * TimeUnit.MINUTES.toMillis(5));
- journal.add(tokenId, issueTime, expiration, maxLifetime);
+ journal.add(tokenId, issueTime, expiration, maxLifetime, null);
// Get the token state from the journal, and validate its contents
JournalEntry entry = journal.get(tokenId);
@@ -109,7 +109,7 @@ public abstract class AbstractFileTokenStateJournalTest {
long issueTime = System.currentTimeMillis();
long expiration = issueTime + TimeUnit.MINUTES.toMillis(5);
long maxLifetime = issueTime + (5 * TimeUnit.MINUTES.toMillis(5));
- journal.add(tokenId, issueTime, expiration, maxLifetime);
+ journal.add(tokenId, issueTime, expiration, maxLifetime, null);
// Get the token state from the journal, and validate its contents
JournalEntry entry = journal.get(tokenId);
@@ -120,7 +120,7 @@ public abstract class AbstractFileTokenStateJournalTest {
assertEquals(maxLifetime, Long.parseLong(entry.getMaxLifetime()));
long updatedExpiration = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5);
- journal.add(tokenId, issueTime, updatedExpiration, maxLifetime);
+ journal.add(tokenId, issueTime, updatedExpiration, maxLifetime, null);
// Get and validate the updated token state
entry = journal.get(tokenId);
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 da79a4c..2fe601d 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
@@ -18,15 +18,15 @@
*/
package org.apache.knox.gateway.services.token.impl.state;
-import org.apache.knox.gateway.services.token.state.JournalEntry;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import org.apache.knox.gateway.services.token.state.JournalEntry;
+import org.junit.Test;
public class FileTokenStateJournalTest {
@@ -40,14 +40,14 @@ public class FileTokenStateJournalTest {
doTestParseJournalEntry(tokenId, issueTime, expiration, maxLifetime);
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void testParseJournalEntry_MissingMaxLifetime() {
final String tokenId = UUID.randomUUID().toString();
final Long issueTime = System.currentTimeMillis();
final Long expiration = issueTime + TimeUnit.HOURS.toMillis(1);
final Long maxLifetime = null;
- doTestParseJournalEntry(tokenId, issueTime, expiration, maxLifetime);
+ doTestParseJournalEntry(tokenId, issueTime, expiration, maxLifetime, "user", null);
}
@Test
@@ -76,57 +76,65 @@ public class FileTokenStateJournalTest {
}
@Test
+ public void tesParseTokenMetadata() throws Exception {
+ doTestParseJournalEntry("", "", "", "", "userName", "");
+ doTestParseJournalEntry("", "", "", "", "", "comment");
+ }
+
+ @Test
public void testParseJournalEntry_AllMissing() {
- doTestParseJournalEntry(null, null, null, " ");
+ doTestParseJournalEntry(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);
}
private void doTestParseJournalEntry(final String tokenId,
final Long issueTime,
final Long expiration,
- final Long maxLifetime) {
+ final Long maxLifetime,
+ final String userName,
+ final String comment) {
doTestParseJournalEntry(tokenId,
(issueTime != null ? issueTime.toString() : null),
(expiration != null ? expiration.toString() : null),
- (maxLifetime != null ? maxLifetime.toString() : null));
+ (maxLifetime != null ? maxLifetime.toString() : null),
+ userName, comment);
}
private void doTestParseJournalEntry(final String tokenId,
final String issueTime,
final String expiration,
- final String maxLifetime) {
+ final String maxLifetime,
+ final String userName,
+ final String comment) {
StringBuilder entryStringBuilder =
new StringBuilder(tokenId != null ? tokenId : "").append(',')
.append(issueTime != null ? issueTime : "")
.append(',')
.append(expiration != null ? expiration : "")
.append(',')
- .append(maxLifetime != null ? maxLifetime : "");
+ .append(maxLifetime != null ? maxLifetime : "")
+ .append(",").append(userName == null ? "" : userName)
+ .append(",").append(comment == null ? "" : comment);
JournalEntry entry = FileTokenStateJournal.FileJournalEntry.parse(entryStringBuilder.toString());
assertNotNull(entry);
- if (tokenId != null && !tokenId.trim().isEmpty()) {
- assertEquals(tokenId, entry.getTokenId());
- } else {
- assertNull(entry.getTokenId());
- }
-
- if (issueTime != null && !issueTime.trim().isEmpty()) {
- assertEquals(issueTime, entry.getIssueTime());
- } else {
- assertNull(entry.getIssueTime());
- }
-
- if (expiration != null && !expiration.trim().isEmpty()) {
- assertEquals(expiration, entry.getExpiration());
- } else {
- assertNull(entry.getExpiration());
- }
-
- if (maxLifetime != null && !maxLifetime.trim().isEmpty()) {
- assertEquals(maxLifetime, entry.getMaxLifetime());
- } else {
- assertNull(entry.getMaxLifetime());
- }
+ assertJournalEntryField(tokenId, entry.getTokenId());
+ assertJournalEntryField(issueTime, entry.getIssueTime());
+ assertJournalEntryField(expiration, entry.getExpiration());
+ assertJournalEntryField(maxLifetime, entry.getMaxLifetime());
+ assertJournalEntryField(userName, entry.getTokenMetadata().getUserName());
+ assertJournalEntryField(comment, entry.getTokenMetadata().getComment());
+ }
+
+ private void assertJournalEntryField(String received, String parsed) {
+ if (received != null && !received.trim().isEmpty()) {
+ assertEquals(received, parsed);
+ } else {
+ assertNull(parsed);
+ }
}
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 d260f58..9f285ee 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
@@ -53,6 +53,7 @@ import org.apache.knox.gateway.services.security.KeystoreServiceException;
import org.apache.knox.gateway.services.security.token.JWTokenAttributes;
import org.apache.knox.gateway.services.security.token.JWTokenAttributesBuilder;
import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.security.token.TokenServiceException;
import org.apache.knox.gateway.services.security.token.TokenStateService;
import org.apache.knox.gateway.services.security.token.TokenUtils;
@@ -441,6 +442,7 @@ public class TokenResource {
System.currentTimeMillis(),
expires,
maxTokenLifetime.orElse(tokenStateService.getDefaultMaxLifetimeDuration()));
+ tokenStateService.addMetadata(tokenId, new TokenMetadata(p.getName()));
log.storedToken(getTopologyName(), Tokens.getTokenDisplayText(accessToken), tokenId);
}
diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 54e92a4..b9d57b0 100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -33,6 +33,7 @@ import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.token.JWTokenAttributes;
import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
+import org.apache.knox.gateway.services.security.token.TokenMetadata;
import org.apache.knox.gateway.services.security.token.TokenStateService;
import org.apache.knox.gateway.services.security.token.TokenUtils;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
@@ -1130,6 +1131,15 @@ public class TokenServiceResourceTest {
}
@Override
+ public void addMetadata(String tokenId, TokenMetadata metadata) {
+ }
+
+ @Override
+ public TokenMetadata getTokenMetadata(String tokenId) {
+ return null;
+ }
+
+ @Override
public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
}
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
new file mode 100644
index 0000000..f8a6bd3
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
@@ -0,0 +1,82 @@
+/*
+ * 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.security.token;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+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 = "";
+
+ private final String userName;
+ private final String comment;
+
+ public TokenMetadata(String userName) {
+ this(userName, EMPTY_COMMENT);
+ }
+
+ public TokenMetadata(String userName, String comment) {
+ this.userName = userName;
+ this.comment = comment;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ 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));
+ }
+ throw new IllegalArgumentException("Invalid metadata JSON: " + json);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java
index 007c2de..736a272 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java
@@ -166,4 +166,22 @@ public interface TokenStateService extends Service {
*/
long getTokenExpiration(String tokenId, boolean validate) throws UnknownTokenException;
+ /**
+ * Adds metadata to the token identified by the given ID
+ *
+ * @param tokenId
+ * The token's unique identifier.
+ * @param metadata
+ * The metadata to be added
+ */
+ void addMetadata(String tokenId, TokenMetadata metadata);
+
+ /**
+ *
+ * @param tokenId
+ * The token's unique identifier.
+ * @return The associated token metadata
+ */
+ TokenMetadata getTokenMetadata(String tokenId);
+
}