Skip to content

Commit e41aa86

Browse files
authored
Merge pull request #1589 from marklogic/feature/491-cloud-token
DEVEXP-491 Can now configure token duration
2 parents 6cf0b16 + 4339746 commit e41aa86

File tree

7 files changed

+174
-43
lines changed

7 files changed

+174
-43
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientBuilder.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ public DatabaseClientBuilder withCloudAuth(String apiKey, String basePath) {
147147
.withBasePath(basePath);
148148
}
149149

150+
/**
151+
*
152+
* @param apiKey
153+
* @param basePath
154+
* @param tokenDuration length in minutes until the generated access token expires
155+
* @return
156+
* @since 6.3.0
157+
*/
158+
public DatabaseClientBuilder withCloudAuth(String apiKey, String basePath, Integer tokenDuration) {
159+
return withAuthType(AUTH_TYPE_MARKLOGIC_CLOUD)
160+
.withCloudApiKey(apiKey)
161+
.withBasePath(basePath)
162+
.withCloudTokenDuration(tokenDuration != null ? tokenDuration.toString() : null);
163+
}
164+
150165
public DatabaseClientBuilder withKerberosAuth(String principal) {
151166
return withAuthType(AUTH_TYPE_KERBEROS)
152167
.withKerberosPrincipal(principal);
@@ -186,6 +201,16 @@ public DatabaseClientBuilder withCloudApiKey(String cloudApiKey) {
186201
return this;
187202
}
188203

204+
/**
205+
* @param tokenDuration length in minutes until the generated access token expires
206+
* @return
207+
* @since 6.3.0
208+
*/
209+
public DatabaseClientBuilder withCloudTokenDuration(String tokenDuration) {
210+
props.put(PREFIX + "cloud.tokenDuration", tokenDuration);
211+
return this;
212+
}
213+
189214
public DatabaseClientBuilder withCertificateFile(String file) {
190215
props.put(PREFIX + "certificate.file", file);
191216
return this;

marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientFactory.java

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -450,45 +450,89 @@ public SecurityContext withSSLContext(SSLContext context, X509TrustManager trust
450450
* @since 6.1.0
451451
*/
452452
public static class MarkLogicCloudAuthContext extends AuthContext {
453-
private String tokenEndpoint;
454-
private String grantType;
455-
private String apiKey;
453+
private String tokenEndpoint;
454+
private String grantType;
455+
private String apiKey;
456+
private Integer tokenDuration;
456457

457-
public MarkLogicCloudAuthContext(String apiKey) {
458-
this(apiKey, "/token", "apikey");
459-
}
458+
/**
459+
* @param apiKey user's API key for accessing MarkLogic Cloud
460+
*/
461+
public MarkLogicCloudAuthContext(String apiKey) {
462+
this(apiKey, null);
463+
}
460464

461-
public MarkLogicCloudAuthContext(String apiKey, String tokenEndpoint, String grantType) {
462-
this.apiKey = apiKey;
463-
this.tokenEndpoint = tokenEndpoint;
464-
this.grantType = grantType;
465-
}
465+
/**
466+
* @param apiKey user's API key for accessing MarkLogic Cloud
467+
* @param tokenDuration length in minutes until the generated access token expires
468+
* @since 6.3.0
469+
*/
470+
public MarkLogicCloudAuthContext(String apiKey, Integer tokenDuration) {
471+
this(apiKey, "/token", "apikey", tokenDuration);
472+
}
466473

467-
public String getTokenEndpoint() {
468-
return tokenEndpoint;
469-
}
474+
/**
475+
* Only intended to be used in the scenario that the token endpoint of "/token" and the grant type of "apikey"
476+
* are not the intended values.
477+
*
478+
* @param apiKey user's API key for accessing MarkLogic Cloud
479+
* @param tokenEndpoint for overriding the default token endpoint if necessary
480+
* @param grantType for overriding the default grant type if necessary
481+
*/
482+
public MarkLogicCloudAuthContext(String apiKey, String tokenEndpoint, String grantType) {
483+
this(apiKey, tokenEndpoint, grantType, null);
484+
}
470485

471-
public String getGrantType() {
472-
return grantType;
473-
}
486+
/**
487+
* Only intended to be used in the scenario that the token endpoint of "/token" and the grant type of "apikey"
488+
* are not the intended values.
489+
*
490+
* @param apiKey user's API key for accessing MarkLogic Cloud
491+
* @param tokenEndpoint for overriding the default token endpoint if necessary
492+
* @param grantType for overriding the default grant type if necessary
493+
* @param tokenDuration length in minutes until the generated access token expires
494+
* @since 6.3.0
495+
*/
496+
public MarkLogicCloudAuthContext(String apiKey, String tokenEndpoint, String grantType, Integer tokenDuration) {
497+
this.apiKey = apiKey;
498+
this.tokenEndpoint = tokenEndpoint;
499+
this.grantType = grantType;
500+
this.tokenDuration = tokenDuration;
501+
}
474502

475-
public String getApiKey() {
476-
return apiKey;
477-
}
503+
public String getTokenEndpoint() {
504+
return tokenEndpoint;
505+
}
478506

479-
@Override
480-
public MarkLogicCloudAuthContext withSSLContext(SSLContext context, X509TrustManager trustManager) {
481-
this.sslContext = context;
482-
this.trustManager = trustManager;
483-
return this;
484-
}
507+
public String getGrantType() {
508+
return grantType;
509+
}
485510

486-
@Override
487-
public MarkLogicCloudAuthContext withSSLHostnameVerifier(SSLHostnameVerifier verifier) {
488-
this.sslVerifier = verifier;
489-
return this;
490-
}
491-
}
511+
public String getApiKey() {
512+
return apiKey;
513+
}
514+
515+
/**
516+
* @return
517+
* @since 6.3.0
518+
*/
519+
public Integer getTokenDuration() {
520+
return tokenDuration;
521+
}
522+
523+
@Override
524+
public MarkLogicCloudAuthContext withSSLContext(SSLContext context, X509TrustManager trustManager) {
525+
this.sslContext = context;
526+
this.trustManager = trustManager;
527+
return this;
528+
}
529+
530+
@Override
531+
public MarkLogicCloudAuthContext withSSLHostnameVerifier(SSLHostnameVerifier verifier) {
532+
this.sslVerifier = verifier;
533+
return this;
534+
}
535+
}
492536

493537
public static class BasicAuthContext extends AuthContext {
494538
String user;

marklogic-client-api/src/main/java/com/marklogic/client/impl/DatabaseClientPropertySource.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,17 @@ private DatabaseClientFactory.SecurityContext newDigestAuthContext() {
206206
}
207207

208208
private DatabaseClientFactory.SecurityContext newCloudAuthContext() {
209-
return new DatabaseClientFactory.MarkLogicCloudAuthContext(getRequiredStringValue("cloud.apiKey"));
209+
String apiKey = getRequiredStringValue("cloud.apiKey");
210+
String val = getNullableStringValue("cloud.tokenDuration");
211+
Integer duration = null;
212+
if (val != null) {
213+
try {
214+
duration = Integer.parseInt(val);
215+
} catch (NumberFormatException e) {
216+
throw new IllegalArgumentException("Cloud token duration must be numeric");
217+
}
218+
}
219+
return new DatabaseClientFactory.MarkLogicCloudAuthContext(apiKey, duration);
210220
}
211221

212222
private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLInputs sslInputs) {

marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/MarkLogicCloudAuthenticationConfigurer.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,24 @@ private Response callTokenEndpoint() {
109109
protected HttpUrl buildTokenUrl() {
110110
// For the near future, it's guaranteed that https and 443 will be required for connecting to MarkLogic Cloud,
111111
// so providing the ability to customize this would be misleading.
112-
return new HttpUrl.Builder()
113-
.scheme("https")
114-
.host(host)
115-
.port(443)
116-
.build()
117-
.resolve(securityContext.getTokenEndpoint()).newBuilder().build();
112+
HttpUrl.Builder builder = new HttpUrl.Builder()
113+
.scheme("https")
114+
.host(host)
115+
.port(443)
116+
.build()
117+
.resolve(securityContext.getTokenEndpoint()).newBuilder();
118+
119+
Integer duration = securityContext.getTokenDuration();
120+
return duration != null ?
121+
builder.addQueryParameter("duration", duration.toString()).build() :
122+
builder.build();
118123
}
119124

120125
protected FormBody newFormBody() {
121126
return new FormBody.Builder()
122-
.add("grant_type", securityContext.getGrantType())
123-
.add("key", securityContext.getApiKey()).build();
127+
.add("grant_type", securityContext.getGrantType())
128+
.add("key", securityContext.getApiKey())
129+
.build();
124130
}
125131

126132
private String getAccessTokenFromResponse(Response response) {
@@ -191,8 +197,8 @@ private synchronized void generateNewTokenIfNecessary(String currentToken) {
191197

192198
private Request addTokenToRequest(Chain chain) {
193199
return chain.request().newBuilder()
194-
.header("Authorization", "Bearer " + token)
195-
.build();
200+
.header("Authorization", "Bearer " + token)
201+
.build();
196202
}
197203
}
198204
}

marklogic-client-api/src/test/java/com/marklogic/client/impl/DatabaseClientPropertySourceTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import static org.junit.jupiter.api.Assertions.assertEquals;
1313
import static org.junit.jupiter.api.Assertions.assertNotNull;
14+
import static org.junit.jupiter.api.Assertions.assertThrows;
1415
import static org.junit.jupiter.api.Assertions.assertTrue;
1516

1617
/**
@@ -96,6 +97,17 @@ void cloudAuthWithNoSslInputs() {
9697
"trust manager should be used");
9798
}
9899

100+
@Test
101+
void cloudWithNonNumericDuration() {
102+
props.put(PREFIX + "authType", "cloud");
103+
props.put(PREFIX + "cloud.apiKey", "abc123");
104+
props.put(PREFIX + "basePath", "/my/path");
105+
props.put(PREFIX + "cloud.tokenDuration", "abc");
106+
107+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> buildBean());
108+
assertEquals("Cloud token duration must be numeric", ex.getMessage());
109+
}
110+
99111
private DatabaseClientFactory.Bean buildBean() {
100112
DatabaseClientPropertySource source = new DatabaseClientPropertySource(propertyName -> props.get(propertyName));
101113
return source.newClientBean();

marklogic-client-api/src/test/java/com/marklogic/client/impl/okhttp/MarkLogicCloudAuthenticationConfigurerTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,30 @@ void buildTokenUrlWithCustomTokenPath() throws Exception {
4141
assertEquals("https://otherhost/customToken", tokenUrl.toString());
4242
}
4343

44+
@Test
45+
void buildTokenUrlWithDuration() throws Exception {
46+
Integer duration = 10;
47+
MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator client = new MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator("somehost",
48+
new DatabaseClientFactory.MarkLogicCloudAuthContext("doesnt-matter", duration)
49+
.withSSLContext(SSLContext.getDefault(), null)
50+
);
51+
52+
HttpUrl tokenUrl = client.buildTokenUrl();
53+
assertEquals("https://somehost/token?duration=10", tokenUrl.toString());
54+
}
55+
56+
@Test
57+
void buildTokenUrlWithDurationAndCustomPath() throws Exception {
58+
Integer duration = 10;
59+
MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator client = new MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator("somehost",
60+
new DatabaseClientFactory.MarkLogicCloudAuthContext("doesnt-matter", "/customToken", "doesnt-matter", duration)
61+
.withSSLContext(SSLContext.getDefault(), null)
62+
);
63+
64+
HttpUrl tokenUrl = client.buildTokenUrl();
65+
assertEquals("https://somehost/customToken?duration=10", tokenUrl.toString());
66+
}
67+
4468
@Test
4569
void newFormBody() {
4670
FormBody body = new MarkLogicCloudAuthenticationConfigurer.DefaultTokenGenerator("host-doesnt-matter",

marklogic-client-api/src/test/java/com/marklogic/client/test/DatabaseClientBuilderTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ void cloudWithBasePath() {
123123
"trust manager should be used as well if the user doesn't provide their own");
124124
}
125125

126+
@Test
127+
void cloudWithDuration() {
128+
bean = Common.newClientBuilder().withCloudAuth("abc123", "/my/path", 10).buildBean();
129+
DatabaseClientFactory.MarkLogicCloudAuthContext context =
130+
(DatabaseClientFactory.MarkLogicCloudAuthContext) bean.getSecurityContext();
131+
assertEquals("abc123", context.getApiKey());
132+
assertEquals("/my/path", bean.getBasePath());
133+
assertEquals(10, context.getTokenDuration());
134+
}
135+
126136
@Test
127137
void cloudNoApiKey() {
128138
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> Common.newClientBuilder()

0 commit comments

Comments
 (0)