You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2022/03/10 20:27:55 UTC
[knox] branch master updated: KNOX-2712 - Managing custom Knox Token metadata (#542)
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 5b4040f KNOX-2712 - Managing custom Knox Token metadata (#542)
5b4040f is described below
commit 5b4040ffeb6c0fee7a69b87ae83b54077b0c634e
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Thu Mar 10 21:27:47 2022 +0100
KNOX-2712 - Managing custom Knox Token metadata (#542)
---
.../gateway/service/knoxtoken/TokenResource.java | 54 ++++++++++++++++++++--
.../knoxtoken/TokenServiceResourceTest.java | 17 ++++++-
.../gateway/services/security/token/KnoxToken.java | 8 ++++
.../services/security/token/TokenMetadata.java | 18 ++++++++
.../token-management/app/metadata.ts | 1 +
.../app/token.management.component.html | 10 +++-
.../app/token.management.component.ts | 8 ++++
7 files changed, 110 insertions(+), 6 deletions(-)
diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 96c189e..ea03439 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
@@ -29,11 +29,13 @@ import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
+import java.util.TreeSet;
import java.util.UUID;
import javax.annotation.PostConstruct;
@@ -46,9 +48,9 @@ import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.KeyLengthException;
@@ -119,6 +121,7 @@ public class TokenResource {
private static final String LIFESPAN_INPUT_ENABLED_PARAM = "knox.token.lifespan.input.enabled";
private static final String LIFESPAN_INPUT_ENABLED_TEXT = "lifespanInputEnabled";
static final String KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION = "knox.token.user.limit.exceeded.action";
+ private static final String METADATA_QUERY_PARAM_PREFIX = "md_";
private static final long TOKEN_TTL_DEFAULT = 30000L;
static final String TOKEN_API_PATH = "knoxtoken/api/v1";
static final String RESOURCE_PATH = TOKEN_API_PATH + "/token";
@@ -405,11 +408,43 @@ public class TokenResource {
@GET
@Path(GET_USER_TOKENS)
@Produces({APPLICATION_JSON, APPLICATION_XML})
- public Response getUserTokens(@QueryParam("userName") String userName) {
+ public Response getUserTokens(@Context UriInfo uriInfo) {
if (tokenStateService == null) {
return Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("{\n \"error\": \"Token management is not configured\"\n}\n").build();
} else {
- final Collection<KnoxToken> tokens = tokenStateService.getTokens(userName);
+ if (uriInfo == null) {
+ throw new IllegalArgumentException("URI info cannot be NULL.");
+ }
+ final Map<String, String> metadataMap = new HashMap<>();
+ uriInfo.getQueryParameters().entrySet().forEach(entry -> {
+ if (entry.getKey().startsWith(METADATA_QUERY_PARAM_PREFIX)) {
+ String metadataName = entry.getKey().substring(METADATA_QUERY_PARAM_PREFIX.length());
+ metadataMap.put(metadataName, entry.getValue().get(0));
+ }
+ });
+
+ final String userName = uriInfo.getQueryParameters().getFirst("userName");
+ final Collection<KnoxToken> userTokens = tokenStateService.getTokens(userName);
+ final Collection<KnoxToken> tokens = new TreeSet<>();
+ if (metadataMap.isEmpty()) {
+ tokens.addAll(userTokens);
+ } else {
+ userTokens.forEach(knoxToken -> {
+ for (Map.Entry<String, String> entry : metadataMap.entrySet()) {
+ if (StringUtils.isBlank(entry.getValue()) || "*".equals(entry.getValue())) {
+ // we should only filter tokens by metadata name
+ if (knoxToken.hasMetadata(entry.getKey())) {
+ tokens.add(knoxToken);
+ }
+ } else {
+ // metadata value should also match
+ if (entry.getValue().equals(knoxToken.getMetadataValue(entry.getKey()))) {
+ tokens.add(knoxToken);
+ }
+ }
+ }
+ });
+ }
return Response.status(Response.Status.OK).entity(JsonUtils.renderAsJsonString(Collections.singletonMap("tokens", tokens))).build();
}
}
@@ -733,6 +768,7 @@ public class TokenResource {
final String comment = request.getParameter(COMMENT);
final TokenMetadata tokenMetadata = new TokenMetadata(p.getName(), StringUtils.isBlank(comment) ? null : comment);
tokenMetadata.setPasscode(tokenMAC.hash(tokenId, issueTime, p.getName(), passcode));
+ addArbitraryTokenMetadata(tokenMetadata);
tokenStateService.addMetadata(tokenId, tokenMetadata);
log.storedToken(getTopologyName(), Tokens.getTokenDisplayText(accessToken), Tokens.getTokenIDDisplayText(tokenId));
}
@@ -747,6 +783,18 @@ public class TokenResource {
return Response.ok().entity("{ \"Unable to acquire token.\" }").build();
}
+ private void addArbitraryTokenMetadata(TokenMetadata tokenMetadata) {
+ final Enumeration<String> paramNames = request.getParameterNames();
+ while (paramNames.hasMoreElements()) {
+ final String paramName = paramNames.nextElement();
+ if (paramName.startsWith(METADATA_QUERY_PARAM_PREFIX)) {
+ final String metadataName = paramName.substring(METADATA_QUERY_PARAM_PREFIX.length());
+ final String metadataValue = request.getParameter(paramName);
+ tokenMetadata.add(metadataName, metadataValue);
+ }
+ }
+ }
+
private String generatePasscodeField(String tokenId, String passcode) {
final String base64TokenIdPasscode = Base64.encodeBase64String(tokenId.getBytes(StandardCharsets.UTF_8)) + "::" + Base64.encodeBase64String(passcode.getBytes(StandardCharsets.UTF_8));
return Base64.encodeBase64String(base64TokenIdPasscode.getBytes(StandardCharsets.UTF_8));
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 e74f2e2..eaf514f 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
@@ -65,7 +65,10 @@ import org.junit.Test;
import javax.security.auth.Subject;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.security.KeyPair;
@@ -153,6 +156,7 @@ public class TokenServiceResourceTest {
if (contextExpectations.containsKey(TokenResource.LIFESPAN)) {
EasyMock.expect(request.getParameter(TokenResource.LIFESPAN)).andReturn(contextExpectations.get(TokenResource.LIFESPAN)).anyTimes();
}
+ EasyMock.expect(request.getParameterNames()).andReturn(Collections.emptyEnumeration()).anyTimes();
GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services).anyTimes();
@@ -1007,7 +1011,7 @@ public class TokenServiceResourceTest {
for (int i = 0; i < numberOfPreExistingTokens; i++) {
tr.doGet();
}
- Response getKnoxTokensResponse = tr.getUserTokens(USER_NAME);
+ Response getKnoxTokensResponse = getUserTokensResponse(tr);
Collection<String> tokens = ((Map<String, Collection<String>>) JsonUtils.getObjectFromJsonString(getKnoxTokensResponse.getEntity().toString()))
.get("tokens");
assertEquals(tokens.size(), numberOfPreExistingTokens);
@@ -1021,6 +1025,15 @@ public class TokenServiceResourceTest {
assertTrue(response.getEntity().toString().contains("Unable to get token - token limit exceeded."));
}
+ private Response getUserTokensResponse(TokenResource tokenResource) {
+ final MultivaluedMap<String, String> queryParameters = new MultivaluedHashMap<>();
+ queryParameters.put("userName", Arrays.asList(USER_NAME));
+ final UriInfo uriInfo = EasyMock.createNiceMock(UriInfo.class);
+ EasyMock.expect(uriInfo.getQueryParameters()).andReturn(queryParameters).anyTimes();
+ EasyMock.replay(uriInfo);
+ return tokenResource.getUserTokens(uriInfo);
+ }
+
@Test
public void testTokenLimitPerUserExceeded() throws Exception {
try {
@@ -1063,7 +1076,7 @@ public class TokenServiceResourceTest {
throw new Exception(getTokenResponse.getEntity().toString());
}
}
- final Response getKnoxTokensResponse = tr.getUserTokens(USER_NAME);
+ final Response getKnoxTokensResponse = getUserTokensResponse(tr);
final Collection<String> tokens = ((Map<String, Collection<String>>) JsonUtils.getObjectFromJsonString(getKnoxTokensResponse.getEntity().toString()))
.get("tokens");
assertEquals(tokens.size(), revokeOldestToken ? configuredLimit : numberOfTokens);
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/KnoxToken.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/KnoxToken.java
index 220c093..b34397b 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/KnoxToken.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/KnoxToken.java
@@ -83,6 +83,14 @@ public class KnoxToken implements Comparable<KnoxToken>{
this.metadata = metadata;
}
+ public String getMetadataValue(String key) {
+ return this.metadata == null ? null : metadata.getMetadata(key);
+ }
+
+ public boolean hasMetadata(String key) {
+ return getMetadataValue(key) != null;
+ }
+
@Override
public int compareTo(KnoxToken other) {
return Long.compare(this.issueTime, other.issueTime);
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
index 1f612c5..8fa49cd 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
@@ -16,7 +16,9 @@
*/
package org.apache.knox.gateway.services.security.token;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
@@ -33,6 +35,7 @@ public class TokenMetadata {
public static final String COMMENT = "comment";
public static final String ENABLED = "enabled";
public static final String PASSCODE = "passcode";
+ private static final List<String> KNOWN_MD_NAMES = Arrays.asList(USER_NAME, COMMENT, ENABLED, PASSCODE);
private final Map<String, String> metadataMap = new HashMap<>();
@@ -66,6 +69,21 @@ public class TokenMetadata {
return new HashMap<String, String>(this.metadataMap);
}
+ @JsonIgnore
+ public String getMetadata(String key) {
+ return this.metadataMap.get(key);
+ }
+
+ public Map<String, String> getCustomMetadataMap() {
+ final Map<String, String> customMetadataMap = new HashMap<>();
+ this.metadataMap.forEach((key, value) -> {
+ if (!KNOWN_MD_NAMES.contains(key)) {
+ customMetadataMap.put(key, value);
+ }
+ });
+ return customMetadataMap;
+ }
+
public String getUserName() {
return metadataMap.get(USER_NAME);
}
diff --git a/knox-token-management-ui/token-management/app/metadata.ts b/knox-token-management-ui/token-management/app/metadata.ts
index 26c6884..da5ff3f 100644
--- a/knox-token-management-ui/token-management/app/metadata.ts
+++ b/knox-token-management-ui/token-management/app/metadata.ts
@@ -19,4 +19,5 @@ export class Metadata {
enabled: boolean;
userName: string;
comment: string;
+ customMetadataMap: Map<string, string>;
}
diff --git a/knox-token-management-ui/token-management/app/token.management.component.html b/knox-token-management-ui/token-management/app/token.management.component.html
index d08cdf4..4e5c6fd 100644
--- a/knox-token-management-ui/token-management/app/token.management.component.html
+++ b/knox-token-management-ui/token-management/app/token.management.component.html
@@ -28,6 +28,7 @@
<th>Issued</th>
<th>Expires</th>
<th>Comment</th>
+ <th>Additional Metadata</th>
<th>Actions</th>
</tr>
</thead>
@@ -39,6 +40,13 @@
<td *ngIf="isTokenExpired(knoxToken.expirationLong)" style="color: red">{{formatDateTime(knoxToken.expirationLong)}}</td>
<td>{{knoxToken.metadata.comment}}</td>
<td>
+ <ul>
+ <li *ngFor="let metadata of getCustomMetadataArray(knoxToken)">
+ {{metadata[0]}} = {{metadata[1]}}
+ </li>
+ </ul>
+ </td>
+ <td>
<button *ngIf="knoxToken.metadata.enabled && !isTokenExpired(knoxToken.expirationLong)" (click)="disableToken(knoxToken.tokenId);">Disable</button>
<button *ngIf="!knoxToken.metadata.enabled && !isTokenExpired(knoxToken.expirationLong)" (click)="enableToken(knoxToken.tokenId);">Enable</button>
<button (click)="revokeToken(knoxToken.tokenId);">Revoke</button>
@@ -47,7 +55,7 @@
</tbody>
<tfoot>
<tr>
- <td colspan="5">
+ <td colspan="6">
<mfBootstrapPaginator [rowsOnPageSet]="[5,10,15]"></mfBootstrapPaginator>
</td>
</tr>
diff --git a/knox-token-management-ui/token-management/app/token.management.component.ts b/knox-token-management-ui/token-management/app/token.management.component.ts
index fb926fc..15a0e8c 100644
--- a/knox-token-management-ui/token-management/app/token.management.component.ts
+++ b/knox-token-management-ui/token-management/app/token.management.component.ts
@@ -80,4 +80,12 @@ export class TokenManagementComponent implements OnInit {
return Date.now() > expiration;
}
+ getCustomMetadataArray(knoxToken: KnoxToken): [string, string][] {
+ let mdMap = new Map();
+ if (knoxToken.metadata.customMetadataMap) {
+ mdMap = knoxToken.metadata.customMetadataMap;
+ }
+ return Array.from(Object.entries(mdMap));
+ }
+
}