Token management (#1201)

* http-rest-starter w/ Stargate data gateway
* Token management w/ Token() binding function
* Expansion of Token usage to scenarios
* Version requirements updated for http-rest scenarios
This commit is contained in:
Jeff Banks
2023-04-13 13:57:20 -05:00
committed by GitHub
parent 8fef3f4b88
commit 11613268a6
21 changed files with 668 additions and 522 deletions

View File

@@ -35,60 +35,75 @@ import java.util.function.Function;
@ThreadSafeMapper
@Categories({Category.general})
public class StargateToken implements Function<Object, String> {
public class Token implements Function<String, String> {
private static final Logger logger = LogManager.getLogger(StargateToken.class);
private final Credentials credentials;
private final String url;
private final HttpClient httpClient;
private static final Logger logger = LogManager.getLogger(Token.class);
private Credentials credentials;
private URI uri;
private String providedToken;
public StargateToken(String url) throws SecurityException {
this(url, Credentials.defaultCredentials());
}
public Token(String token, String uri, String uid, String password) {
public StargateToken(String url, Credentials credentials) throws SecurityException {
this(url, credentials, HttpClient.newBuilder().build());
}
if (token != null && !token.trim().isEmpty()) {
this.providedToken = token.trim();
return;
}
if (uri == null || uri.trim().isEmpty()) {
throw new IllegalArgumentException("Expected uri to be specified for obtaining Token.");
}
this.uri = URI.create(uri.trim());
if (uid == null || uid.trim().isEmpty()) {
throw new IllegalArgumentException("Expected uid to be specified for obtaining Token.");
}
if (password == null || password.trim().isEmpty()) {
throw new IllegalArgumentException("Expected password to be specified for obtaining Token.");
}
this.credentials = Credentials.create(uid.trim(), password.trim());
public StargateToken(String url, Credentials credentials, HttpClient client) throws SecurityException {
this.url = url;
this.credentials = credentials;
this.httpClient = client;
if (TokenKeeper.isExpired()) {
authTokenStargate(url, credentials);
authTokenStargate(this.uri, this.credentials);
}
}
@Override
public String apply(Object value) throws SecurityException {
if (TokenKeeper.isExpired()) {
authTokenStargate(url, credentials);
public String apply(String p1) {
if (this.providedToken != null) {
return this.providedToken;
}
return TokenKeeper.token;
if (TokenKeeper.isExpired() || TokenKeeper.token == null || TokenKeeper.token.isEmpty()) {
authTokenStargate(this.uri, this.credentials);
}
return TokenKeeper.getToken();
}
public static void setExpired() {
TokenKeeper.isExpiredRequested = true;
}
private void authTokenStargate(String url, Credentials credentials) throws SecurityException {
private static void authTokenStargate(URI uri, Credentials credentials) throws SecurityException {
if (credentials == null || url == null) {
if (credentials == null || uri == null) {
throw new BasicError("Must provide url and credentials to obtain authTokenStargate");
}
logger.debug("Received url for Stargate auth token request: {} ", url);
logger.debug(() -> "Received uri for auth token request: " + uri);
try {
final Gson gson = new Gson();
HttpRequest.Builder builder = HttpRequest.newBuilder();
builder = builder.uri(URI.create(url));
builder = builder.uri(uri);
builder = builder.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(credentials)));
builder.setHeader("Content-Type", "application/json");
HttpRequest request = builder.build();
HttpClient httpClient = HttpClient.newBuilder().build();
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
logger.debug(() -> "Stargate response status code: " + resp.statusCode());
if (resp.statusCode() != 201) {
@@ -98,8 +113,8 @@ public class StargateToken implements Function<Object, String> {
throw new BasicError(errorMessage);
}
Credentials retrievedToken = gson.fromJson(resp.body(), Credentials.class);
TokenKeeper.setToken(retrievedToken.getAuthToken());
final Credentials cred = gson.fromJson(resp.body(), Credentials.class);
TokenKeeper.setToken(cred.getAuthToken());
} catch (Exception e) {
throw new SecurityException("Auth Token error, stargate-token retrieval failure", e);
@@ -121,28 +136,31 @@ public class StargateToken implements Function<Object, String> {
lastTokenInstant = Instant.now();
}
public static void setToken(String input) {
token = input;
}
public static String getToken() {
return token;
}
public static Instant lastTokenInstant() {
return lastTokenInstant;
}
public static boolean isExpired() {
if (isExpiredRequested || Duration.between(Instant.now(),
if (token == null || isExpiredRequested || Duration.between(Instant.now(),
lastTokenInstant).toMinutes() > TOKEN_EXPIRE_MIN) {
logger.trace("Token expiry detected.");
logger.debug("Token expiry detected.");
lastTokenInstant = Instant.now();
isExpiredRequested = false;
token = null;
return true;
}
logger.debug(() -> "Token not expired, reusing as: " + token);
return false;
}
public static synchronized void setToken(String value) {
token = value;
}
public static synchronized String getToken() {
return token;
}
}
}

View File

@@ -18,23 +18,17 @@ package io.nosqlbench.virtdata.library.basics.shared.util;
public class Credentials {
private static final String DEFAULT_IDENTITY = "cassandra";
private String username;
private String password;
private final String username;
private final String password;
private String authToken;
public static Credentials defaultCredentials() {
return new Credentials(DEFAULT_IDENTITY, DEFAULT_IDENTITY);
}
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
// Added for support of auth tokens not obtained via defaultCredentials
public Credentials(String authToken) {
this.authToken = authToken;
public static Credentials create(String username, String password) {
return new Credentials(username, password);
}
public String getUsername() {
@@ -48,4 +42,5 @@ public class Credentials {
public String getAuthToken() {
return authToken;
}
}

View File

@@ -1,148 +0,0 @@
/*
* Copyright (c) 2023 nosqlbench
*
* Licensed 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 io.nosqlbench.virtdata.library.basics.shared.unary_string;
import io.nosqlbench.virtdata.library.basics.shared.util.Credentials;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.internal.util.reflection.FieldReader;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Instant;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class StargateTokenTest extends TokenTest {
private static final String TEST_AUTH_TOKEN = "8675309";
private static final String VALID_TEST_URL = "http://foobar.com:8675";
private static final String VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON =
"{ 'authToken': " + "\"" + TEST_AUTH_TOKEN + "\"" + "}";
private static final Credentials VALID_TEST_CREDS = new Credentials("username", "password");
private static final Object TOKEN_APPLY_PLACEHOLDER = new Object();
@Mock
private static HttpResponse<String> httpResponse;
@Mock
private static HttpClient httpClient;
@BeforeAll
public static void init() {
httpResponse = mock(HttpResponse.class);
httpClient = mock(HttpClient.class);
}
@BeforeEach
public void setup() {
StargateToken.TokenKeeper.reset();
}
@Test
void applyTokenSuccess() throws Exception {
when(httpResponse.body()).thenReturn(VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON);
when(httpResponse.statusCode()).thenReturn(201);
when(httpClient.send(Mockito.any(HttpRequest.class),
Mockito.any(HttpResponse.BodyHandlers.ofString().getClass())))
.thenReturn(httpResponse);
final StargateToken stargateToken = new StargateToken(VALID_TEST_URL,
VALID_TEST_CREDS, httpClient);
final String result = stargateToken.apply(TOKEN_APPLY_PLACEHOLDER);
assertThat(result).isEqualTo(TEST_AUTH_TOKEN);
}
@Test
void receivedResponse500() throws Exception {
when(httpResponse.body()).thenReturn(VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON);
when(httpResponse.statusCode()).thenReturn(500);
when(httpClient.send(Mockito.any(HttpRequest.class), Mockito.any(HttpResponse.BodyHandlers.ofString()
.getClass())))
.thenReturn(httpResponse);
assertThatExceptionOfType(SecurityException.class).isThrownBy(() -> new StargateToken(VALID_TEST_URL,
VALID_TEST_CREDS, httpClient));
}
@Test
void applyTokenSuccessWithRefreshTokenRequested() throws Exception {
// --- Initial check
StargateToken.TokenKeeper.reset();
when(httpResponse.body()).thenReturn(VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON);
when(httpResponse.statusCode()).thenReturn(201);
when(httpClient.send(Mockito.any(HttpRequest.class),
Mockito.any(HttpResponse.BodyHandlers.ofString()
.getClass())))
.thenReturn(httpResponse);
final StargateToken stargateToken = new StargateToken(VALID_TEST_URL, VALID_TEST_CREDS, httpClient);
final String resultFirstCheck = stargateToken.apply(TOKEN_APPLY_PLACEHOLDER);
final Instant tokenInstantFirstCheck = StargateToken.TokenKeeper.lastTokenInstant();
assertThat(resultFirstCheck).isEqualTo(TEST_AUTH_TOKEN);
assertThat(tokenInstantFirstCheck).isNotNull();
// --- Subtest 2 - When NOT having an expired token, expect that the lastTokenInstant does NOT change.
when(httpResponse.body()).thenReturn("{ 'authToken': " + "\"" + "refreshed-token" + "\"" + "}");
when(httpResponse.statusCode()).thenReturn(201);
when(httpClient.send(Mockito.any(HttpRequest.class), Mockito.any(HttpResponse.BodyHandlers.ofString()
.getClass())))
.thenReturn(httpResponse);
final String resultSecondCheck = stargateToken.apply(TOKEN_APPLY_PLACEHOLDER);
final Instant tokenInstantSecondCheck = StargateToken.TokenKeeper.lastTokenInstant();
assertThat(resultSecondCheck).isEqualTo(resultFirstCheck);
assertThat(tokenInstantSecondCheck).isEqualTo(tokenInstantFirstCheck);
// --- Subtest 3 - When having an expired token, expect that the lastTokenInstant changes and
// tokens are different.
// Note: Explicit token expiry, default is 30 minutes
StargateToken.setExpired();
final String resultThirdCheck = stargateToken.apply(TOKEN_APPLY_PLACEHOLDER);
final FieldReader fileReaderLastCheck = new FieldReader(stargateToken,
FieldUtils.getDeclaredField(StargateToken.class,
"lastTokenInstant", true));
final Instant tokenInstantThirdCheck = StargateToken.TokenKeeper.lastTokenInstant();
assertThat(tokenInstantThirdCheck.isAfter(tokenInstantFirstCheck)).isTrue();
assertThat(resultSecondCheck).isNotEqualTo(resultThirdCheck);
}
}

View File

@@ -13,8 +13,163 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.virtdata.library.basics.shared.unary_string;
public abstract class TokenTest {
// Intent is to expand for generic (non-Stargate) http-rest test conditions and utils.
}
import io.nosqlbench.virtdata.library.basics.shared.util.Credentials;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.internal.util.reflection.FieldReader;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Instant;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class TokenTest {
private static final String TEST_AUTH_TOKEN = "8675309";
private static final String VALID_TEST_URL = "http://foobar.com:8675";
private static final String VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON =
"{ 'authToken': " + "\"" + TEST_AUTH_TOKEN + "\"" + "}";
private static final Credentials VALID_TEST_CREDS = new Credentials("username", "password");
@Mock
private static HttpResponse<String> httpResponse;
@Mock
private static HttpClient httpClient;
private static MockedStatic<HttpClient> HttpCli;
@Mock
private static HttpClient.Builder httpBuilder;
@BeforeAll
public static void init() {
httpResponse = mock(HttpResponse.class);
httpClient = mock(HttpClient.class);
httpBuilder = mock(HttpClient.Builder.class);
HttpCli = Mockito.mockStatic(HttpClient.class);
}
@BeforeEach
public void setup() {
Token.TokenKeeper.reset();
}
@Test
void applyTokenSuccess() throws Exception {
when(httpResponse.body()).thenReturn(VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON);
when(httpResponse.statusCode()).thenReturn(201);
mockResponse();
final Token token = new Token(null, VALID_TEST_URL, VALID_TEST_CREDS.getUsername(),
VALID_TEST_CREDS.getPassword());
// Since constructor handles state management, the inputs aren't utilized in the apply function.
final String result = token.apply("p1");
assertThat(result).isEqualTo(TEST_AUTH_TOKEN);
}
@Test
void receivedResponse500() throws Exception {
when(httpResponse.body()).thenReturn(VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON);
when(httpResponse.statusCode()).thenReturn(500);
mockResponse();
assertThatExceptionOfType(SecurityException.class).isThrownBy(() -> new Token(null, VALID_TEST_URL,
VALID_TEST_CREDS.getUsername(), VALID_TEST_CREDS.getPassword()));
}
@Test
void applyTokenSuccessWithRefreshTokenRequested() throws Exception {
Token.TokenKeeper.reset();
when(httpResponse.body()).thenReturn(VALID_STARGATE_AUTH_TOKEN_RESPONSE_JSON);
when(httpResponse.statusCode()).thenReturn(201);
mockResponse();
final Token token = new Token(null, VALID_TEST_URL, VALID_TEST_CREDS.getUsername(),
VALID_TEST_CREDS.getPassword());
final String resultFirstCheck = token.apply("p1");
final Instant tokenInstantFirstCheck = Token.TokenKeeper.lastTokenInstant();
assertThat(resultFirstCheck).isEqualTo(TEST_AUTH_TOKEN);
assertThat(tokenInstantFirstCheck).isNotNull();
// --- Subtest 2 - NOT having an expired token, expect that the lastTokenInstant does NOT change.
when(httpResponse.body()).thenReturn("{ 'authToken': " + "\"" + "refreshed-token" + "\"" + "}");
when(httpResponse.statusCode()).thenReturn(201);
mockResponse();
final String resultSecondCheck = token.apply("p1");
final Instant tokenInstantSecondCheck = Token.TokenKeeper.lastTokenInstant();
assertThat(resultSecondCheck).isEqualTo(resultFirstCheck);
assertThat(tokenInstantSecondCheck).isEqualTo(tokenInstantFirstCheck);
// --- Subtest 3 - Having expired token, expect that the lastTokenInstant changes and
// tokens are different.
// Note: Explicit token expiry, default is 30m
Token.setExpired();
final String resultThirdCheck = token.apply("p1");
final FieldReader fileReaderLastCheck = new FieldReader(token,
FieldUtils.getDeclaredField(Token.class,
"lastTokenInstant", true));
final Instant tokenInstantThirdCheck = Token.TokenKeeper.lastTokenInstant();
assertThat(tokenInstantThirdCheck.isAfter(tokenInstantFirstCheck)).isTrue();
assertThat(resultSecondCheck).isNotEqualTo(resultThirdCheck);
}
@Test
void provideToken() {
final Token token = new Token(TEST_AUTH_TOKEN, VALID_TEST_URL, VALID_TEST_CREDS.getUsername(),
VALID_TEST_CREDS.getPassword());
final String result = token.apply("p1");
assertThat(result).isEqualTo(TEST_AUTH_TOKEN);
final Token token2 = new Token(TEST_AUTH_TOKEN, null, null, null);
final String result2 = token2.apply("p1");
assertThat(result2).isEqualTo(TEST_AUTH_TOKEN);
}
private void mockResponse() throws Exception {
HttpCli.when(HttpClient::newBuilder).thenReturn(httpBuilder);
when(httpBuilder.build()).thenReturn(httpClient);
when(httpClient.send(Mockito.any(HttpRequest.class),
Mockito.any(HttpResponse.BodyHandlers.ofString().getClass())))
.thenReturn(httpResponse);
}
}