Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,21 @@ public DatabaseClientBuilder withCloudAuth(String apiKey, String basePath) {
.withBasePath(basePath);
}

/**
*
* @param apiKey
* @param basePath
* @param tokenDuration length in minutes until the generated access token expires
* @return
* @since 6.3.0
*/
public DatabaseClientBuilder withCloudAuth(String apiKey, String basePath, Integer tokenDuration) {
return withAuthType(AUTH_TYPE_MARKLOGIC_CLOUD)
.withCloudApiKey(apiKey)
.withBasePath(basePath)
.withCloudTokenDuration(tokenDuration != null ? tokenDuration.toString() : null);
}

public DatabaseClientBuilder withKerberosAuth(String principal) {
return withAuthType(AUTH_TYPE_KERBEROS)
.withKerberosPrincipal(principal);
Expand Down Expand Up @@ -186,6 +201,16 @@ public DatabaseClientBuilder withCloudApiKey(String cloudApiKey) {
return this;
}

/**
* @param tokenDuration length in minutes until the generated access token expires
* @return
* @since 6.3.0
*/
public DatabaseClientBuilder withCloudTokenDuration(String tokenDuration) {
props.put(PREFIX + "cloud.tokenDuration", tokenDuration);
return this;
}

public DatabaseClientBuilder withCertificateFile(String file) {
props.put(PREFIX + "certificate.file", file);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,45 +450,89 @@ public SecurityContext withSSLContext(SSLContext context, X509TrustManager trust
* @since 6.1.0
*/
public static class MarkLogicCloudAuthContext extends AuthContext {
private String tokenEndpoint;
private String grantType;
private String apiKey;
private String tokenEndpoint;
private String grantType;
private String apiKey;
private Integer tokenDuration;

public MarkLogicCloudAuthContext(String apiKey) {
this(apiKey, "/token", "apikey");
}
/**
* @param apiKey user's API key for accessing MarkLogic Cloud
*/
public MarkLogicCloudAuthContext(String apiKey) {
this(apiKey, null);
}

public MarkLogicCloudAuthContext(String apiKey, String tokenEndpoint, String grantType) {
this.apiKey = apiKey;
this.tokenEndpoint = tokenEndpoint;
this.grantType = grantType;
}
/**
* @param apiKey user's API key for accessing MarkLogic Cloud
* @param tokenDuration length in minutes until the generated access token expires
* @since 6.3.0
*/
public MarkLogicCloudAuthContext(String apiKey, Integer tokenDuration) {
this(apiKey, "/token", "apikey", tokenDuration);
}

public String getTokenEndpoint() {
return tokenEndpoint;
}
/**
* Only intended to be used in the scenario that the token endpoint of "/token" and the grant type of "apikey"
* are not the intended values.
*
* @param apiKey user's API key for accessing MarkLogic Cloud
* @param tokenEndpoint for overriding the default token endpoint if necessary
* @param grantType for overriding the default grant type if necessary
*/
public MarkLogicCloudAuthContext(String apiKey, String tokenEndpoint, String grantType) {
this(apiKey, tokenEndpoint, grantType, null);
}

public String getGrantType() {
return grantType;
}
/**
* Only intended to be used in the scenario that the token endpoint of "/token" and the grant type of "apikey"
* are not the intended values.
*
* @param apiKey user's API key for accessing MarkLogic Cloud
* @param tokenEndpoint for overriding the default token endpoint if necessary
* @param grantType for overriding the default grant type if necessary
* @param tokenDuration length in minutes until the generated access token expires
* @since 6.3.0
*/
public MarkLogicCloudAuthContext(String apiKey, String tokenEndpoint, String grantType, Integer tokenDuration) {
this.apiKey = apiKey;
this.tokenEndpoint = tokenEndpoint;
this.grantType = grantType;
this.tokenDuration = tokenDuration;
}

public String getApiKey() {
return apiKey;
}
public String getTokenEndpoint() {
return tokenEndpoint;
}

@Override
public MarkLogicCloudAuthContext withSSLContext(SSLContext context, X509TrustManager trustManager) {
this.sslContext = context;
this.trustManager = trustManager;
return this;
}
public String getGrantType() {
return grantType;
}

@Override
public MarkLogicCloudAuthContext withSSLHostnameVerifier(SSLHostnameVerifier verifier) {
this.sslVerifier = verifier;
return this;
}
}
public String getApiKey() {
return apiKey;
}

/**
* @return
* @since 6.3.0
*/
public Integer getTokenDuration() {
return tokenDuration;
}

@Override
public MarkLogicCloudAuthContext withSSLContext(SSLContext context, X509TrustManager trustManager) {
this.sslContext = context;
this.trustManager = trustManager;
return this;
}

@Override
public MarkLogicCloudAuthContext withSSLHostnameVerifier(SSLHostnameVerifier verifier) {
this.sslVerifier = verifier;
return this;
}
}

public static class BasicAuthContext extends AuthContext {
String user;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,17 @@ private DatabaseClientFactory.SecurityContext newDigestAuthContext() {
}

private DatabaseClientFactory.SecurityContext newCloudAuthContext() {
return new DatabaseClientFactory.MarkLogicCloudAuthContext(getRequiredStringValue("cloud.apiKey"));
String apiKey = getRequiredStringValue("cloud.apiKey");
String val = getNullableStringValue("cloud.tokenDuration");
Integer duration = null;
if (val != null) {
try {
duration = Integer.parseInt(val);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Cloud token duration must be numeric");
}
}
return new DatabaseClientFactory.MarkLogicCloudAuthContext(apiKey, duration);
}

private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLInputs sslInputs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,24 @@ private Response callTokenEndpoint() {
protected HttpUrl buildTokenUrl() {
// For the near future, it's guaranteed that https and 443 will be required for connecting to MarkLogic Cloud,
// so providing the ability to customize this would be misleading.
return new HttpUrl.Builder()
.scheme("https")
.host(host)
.port(443)
.build()
.resolve(securityContext.getTokenEndpoint()).newBuilder().build();
HttpUrl.Builder builder = new HttpUrl.Builder()
.scheme("https")
.host(host)
.port(443)
.build()
.resolve(securityContext.getTokenEndpoint()).newBuilder();

Integer duration = securityContext.getTokenDuration();
return duration != null ?
builder.addQueryParameter("duration", duration.toString()).build() :
builder.build();
}

protected FormBody newFormBody() {
return new FormBody.Builder()
.add("grant_type", securityContext.getGrantType())
.add("key", securityContext.getApiKey()).build();
.add("grant_type", securityContext.getGrantType())
.add("key", securityContext.getApiKey())
.build();
}

private String getAccessTokenFromResponse(Response response) {
Expand Down Expand Up @@ -191,8 +197,8 @@ private synchronized void generateNewTokenIfNecessary(String currentToken) {

private Request addTokenToRequest(Chain chain) {
return chain.request().newBuilder()
.header("Authorization", "Bearer " + token)
.build();
.header("Authorization", "Bearer " + token)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
Expand Down Expand Up @@ -96,6 +97,17 @@ void cloudAuthWithNoSslInputs() {
"trust manager should be used");
}

@Test
void cloudWithNonNumericDuration() {
props.put(PREFIX + "authType", "cloud");
props.put(PREFIX + "cloud.apiKey", "abc123");
props.put(PREFIX + "basePath", "/my/path");
props.put(PREFIX + "cloud.tokenDuration", "abc");

IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> buildBean());
assertEquals("Cloud token duration must be numeric", ex.getMessage());
}

private DatabaseClientFactory.Bean buildBean() {
DatabaseClientPropertySource source = new DatabaseClientPropertySource(propertyName -> props.get(propertyName));
return source.newClientBean();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ void buildTokenUrlWithCustomTokenPath() throws Exception {
assertEquals("https://otherhost/customToken", tokenUrl.toString());
}

@Test
void buildTokenUrlWithDuration() throws Exception {
Integer duration = 10;
MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator client = new MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator("somehost",
new DatabaseClientFactory.MarkLogicCloudAuthContext("doesnt-matter", duration)
.withSSLContext(SSLContext.getDefault(), null)
);

HttpUrl tokenUrl = client.buildTokenUrl();
assertEquals("https://somehost/token?duration=10", tokenUrl.toString());
}

@Test
void buildTokenUrlWithDurationAndCustomPath() throws Exception {
Integer duration = 10;
MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator client = new MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator("somehost",
new DatabaseClientFactory.MarkLogicCloudAuthContext("doesnt-matter", "/customToken", "doesnt-matter", duration)
.withSSLContext(SSLContext.getDefault(), null)
);

HttpUrl tokenUrl = client.buildTokenUrl();
assertEquals("https://somehost/customToken?duration=10", tokenUrl.toString());
}

@Test
void newFormBody() {
FormBody body = new MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator("host-doesnt-matter",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ void cloudWithBasePath() {
"trust manager should be used as well if the user doesn't provide their own");
}

@Test
void cloudWithDuration() {
bean = Common.newClientBuilder().withCloudAuth("abc123", "/my/path", 10).buildBean();
DatabaseClientFactory.MarkLogicCloudAuthContext context =
(DatabaseClientFactory.MarkLogicCloudAuthContext) bean.getSecurityContext();
assertEquals("abc123", context.getApiKey());
assertEquals("/my/path", bean.getBasePath());
assertEquals(10, context.getTokenDuration());
}

@Test
void cloudNoApiKey() {
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Common.newClientBuilder()
Expand Down