Skip to content

Commit 4c9f6fe

Browse files
committed
Add OAuth implementation for testing
1 parent b86918e commit 4c9f6fe

7 files changed

Lines changed: 959 additions & 32 deletions

File tree

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<artifactId>cms</artifactId>
88
<packaging>jar</packaging>
99
<name>contentstack-management-java</name>
10-
<version>1.7.1</version>
10+
<version>1.7.1-SNAPSHOT</version>
1111
<description>Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an
1212
API-first approach
1313
</description>
@@ -363,7 +363,7 @@
363363
<plugin>
364364
<groupId>org.jacoco</groupId>
365365
<artifactId>jacoco-maven-plugin</artifactId>
366-
<version>0.8.7</version>
366+
<version>${jococo-plugin.version}</version>
367367
<executions>
368368
<execution>
369369
<id>prepare-agent</id>

src/main/java/com/contentstack/cms/Contentstack.java

Lines changed: 201 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,43 @@
11
package com.contentstack.cms;
22

3+
import java.io.IOException;
4+
import java.net.Proxy;
5+
import java.time.Duration;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import java.util.Objects;
9+
import java.util.concurrent.CompletableFuture;
10+
import java.util.concurrent.TimeUnit;
11+
import java.util.logging.Logger;
12+
13+
import org.jetbrains.annotations.NotNull;
14+
315
import com.contentstack.cms.core.AuthInterceptor;
416
import com.contentstack.cms.core.Util;
17+
import static com.contentstack.cms.core.Util.API_KEY;
18+
import static com.contentstack.cms.core.Util.AUTHORIZATION;
19+
import static com.contentstack.cms.core.Util.BRANCH;
20+
import static com.contentstack.cms.core.Util.ILLEGAL_USER;
21+
import static com.contentstack.cms.core.Util.PLEASE_LOGIN;
522
import com.contentstack.cms.models.Error;
623
import com.contentstack.cms.models.LoginDetails;
24+
import com.contentstack.cms.models.OAuthConfig;
25+
import com.contentstack.cms.models.OAuthTokens;
26+
import com.contentstack.cms.oauth.OAuthHandler;
27+
import com.contentstack.cms.oauth.OAuthInterceptor;
728
import com.contentstack.cms.organization.Organization;
829
import com.contentstack.cms.stack.Stack;
930
import com.contentstack.cms.user.User;
1031
import com.google.gson.Gson;
32+
1133
import okhttp3.ConnectionPool;
1234
import okhttp3.OkHttpClient;
1335
import okhttp3.ResponseBody;
1436
import okhttp3.logging.HttpLoggingInterceptor;
15-
import org.jetbrains.annotations.NotNull;
1637
import retrofit2.Response;
1738
import retrofit2.Retrofit;
1839
import retrofit2.converter.gson.GsonConverterFactory;
1940

20-
import java.io.IOException;
21-
import java.net.Proxy;
22-
import java.time.Duration;
23-
import java.util.HashMap;
24-
import java.util.Map;
25-
import java.util.Objects;
26-
import java.util.concurrent.TimeUnit;
27-
import java.util.logging.Logger;
28-
29-
import static com.contentstack.cms.core.Util.*;
30-
3141
/**
3242
* <b>Contentstack Java Management SDK</b>
3343
* <br>
@@ -41,7 +51,7 @@
4151
*/
4252
public class Contentstack {
4353

44-
public final Logger logger = Logger.getLogger(Contentstack.class.getName());
54+
public static final Logger logger = Logger.getLogger(Contentstack.class.getName());
4555
protected final String host;
4656
protected final String port;
4757
protected final String version;
@@ -51,6 +61,8 @@ public class Contentstack {
5161
protected final Boolean retryOnFailure;
5262
protected final Proxy proxy;
5363
protected AuthInterceptor interceptor;
64+
protected OAuthInterceptor oauthInterceptor;
65+
protected OAuthHandler oauthHandler;
5466
protected String[] earlyAccess;
5567
protected User user;
5668

@@ -201,15 +213,20 @@ public Response<LoginDetails> login(String emailId, String password, String tfaT
201213

202214
private void setupLoginCredentials(Response<LoginDetails> loginResponse) throws IOException {
203215
if (loginResponse.isSuccessful()) {
204-
assert loginResponse.body() != null;
205-
// logger.info(loginResponse.body().getNotice());
206-
this.authtoken = loginResponse.body().getUser().getAuthtoken();
207-
this.interceptor.setAuthtoken(this.authtoken);
216+
LoginDetails loginDetails = loginResponse.body();
217+
if (loginDetails != null && loginDetails.getUser() != null) {
218+
this.authtoken = loginDetails.getUser().getAuthtoken();
219+
if (this.interceptor != null) {
220+
this.interceptor.setAuthtoken(this.authtoken);
221+
}
222+
}
208223
} else {
209-
assert loginResponse.errorBody() != null;
210-
String errorJsonString = loginResponse.errorBody().string();
211-
logger.info(errorJsonString);
212-
new Gson().fromJson(errorJsonString, Error.class);
224+
ResponseBody errorBody = loginResponse.errorBody();
225+
if (errorBody != null) {
226+
String errorJsonString = errorBody.string();
227+
logger.info(errorJsonString);
228+
new Gson().fromJson(errorJsonString, Error.class);
229+
}
213230
}
214231
}
215232

@@ -435,10 +452,94 @@ public Stack stack(@NotNull String apiKey, @NotNull String managementToken, @Not
435452
}
436453

437454
/**
438-
* Instantiates a new Contentstack.
439-
*
440-
* @param builder the builder
455+
* Get the OAuth authorization URL for the user to visit
456+
* @return Authorization URL string
457+
* @throws IllegalStateException if OAuth is not configured
458+
*/
459+
public String getOAuthAuthorizationUrl() {
460+
if (!isOAuthConfigured()) {
461+
throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()");
462+
}
463+
return oauthHandler.authorize();
464+
}
465+
466+
/**
467+
* Exchange OAuth authorization code for tokens
468+
* @param code Authorization code from OAuth callback
469+
* @return CompletableFuture containing OAuth tokens
470+
* @throws IllegalStateException if OAuth is not configured
471+
*/
472+
public CompletableFuture<OAuthTokens> exchangeOAuthCode(String code) {
473+
if (!isOAuthConfigured()) {
474+
throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()");
475+
}
476+
return oauthHandler.exchangeCodeForToken(code);
477+
}
478+
479+
/**
480+
* Refresh the OAuth access token
481+
* @return CompletableFuture containing new OAuth tokens
482+
* @throws IllegalStateException if OAuth is not configured or no refresh token available
483+
*/
484+
public CompletableFuture<OAuthTokens> refreshOAuthToken() {
485+
if (!isOAuthConfigured()) {
486+
throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()");
487+
}
488+
return oauthHandler.refreshAccessToken();
489+
}
490+
491+
/**
492+
* Get the current OAuth tokens
493+
* @return Current OAuth tokens or null if not available
494+
*/
495+
public OAuthTokens getOAuthTokens() {
496+
return oauthHandler != null ? oauthHandler.getTokens() : null;
497+
}
498+
499+
/**
500+
* Check if we have valid OAuth tokens
501+
* @return true if we have valid tokens
502+
*/
503+
public boolean hasValidOAuthTokens() {
504+
return oauthInterceptor != null && oauthInterceptor.hasValidTokens();
505+
}
506+
507+
/**
508+
* Check if OAuth is configured
509+
* @return true if OAuth is configured
510+
*/
511+
public boolean isOAuthConfigured() {
512+
return oauthInterceptor != null && oauthInterceptor.isOAuthConfigured();
513+
}
514+
515+
/**
516+
* Get the OAuth handler instance
517+
* @return OAuth handler or null if not configured
518+
*/
519+
public OAuthHandler getOAuthHandler() {
520+
return oauthHandler;
521+
}
522+
523+
/**
524+
* Logout from OAuth session and optionally revoke authorization
525+
* @param revokeAuthorization If true, revokes the OAuth authorization
526+
* @return CompletableFuture that completes when logout is done
527+
*/
528+
public CompletableFuture<Void> oauthLogout(boolean revokeAuthorization) {
529+
if (!isOAuthConfigured()) {
530+
throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()");
531+
}
532+
return oauthHandler.logout(revokeAuthorization);
533+
}
534+
535+
/**
536+
* Logout from OAuth session without revoking authorization
537+
* @return CompletableFuture that completes when logout is done
441538
*/
539+
public CompletableFuture<Void> oauthLogout() {
540+
return oauthLogout(false);
541+
}
542+
442543
public Contentstack(Builder builder) {
443544
this.host = builder.hostname;
444545
this.port = builder.port;
@@ -449,6 +550,8 @@ public Contentstack(Builder builder) {
449550
this.retryOnFailure = builder.retry;
450551
this.proxy = builder.proxy;
451552
this.interceptor = builder.authInterceptor;
553+
this.oauthInterceptor = builder.oauthInterceptor;
554+
this.oauthHandler = builder.oauthHandler;
452555
this.earlyAccess = builder.earlyAccess;
453556
}
454557

@@ -462,6 +565,9 @@ public static class Builder {
462565
*/
463566
protected Proxy proxy;
464567
private AuthInterceptor authInterceptor;
568+
private OAuthInterceptor oauthInterceptor;
569+
private OAuthConfig oauthConfig;
570+
private OAuthHandler oauthHandler;
465571

466572
private String authtoken; // authtoken for client
467573
private String[] earlyAccess;
@@ -608,6 +714,49 @@ public Builder setAuthtoken(String authtoken) {
608714
return this;
609715
}
610716

717+
/**
718+
* Sets OAuth configuration for the client
719+
* @param config OAuth configuration
720+
* @return Builder instance
721+
*/
722+
public Builder setOAuthConfig(OAuthConfig config) {
723+
this.oauthConfig = config;
724+
return this;
725+
}
726+
727+
/**
728+
* Configures OAuth with client credentials (traditional flow)
729+
* @param appId Application ID
730+
* @param clientId Client ID
731+
* @param clientSecret Client secret
732+
* @param redirectUri Redirect URI
733+
* @return Builder instance
734+
*/
735+
public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri) {
736+
this.oauthConfig = OAuthConfig.builder()
737+
.appId(appId)
738+
.clientId(clientId)
739+
.clientSecret(clientSecret)
740+
.redirectUri(redirectUri)
741+
.build();
742+
return this;
743+
}
744+
745+
/**
746+
* Configures OAuth with PKCE (no client secret)
747+
* @param appId Application ID
748+
* @param clientId Client ID
749+
* @param redirectUri Redirect URI
750+
* @return Builder instance
751+
*/
752+
public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri) {
753+
this.oauthConfig = OAuthConfig.builder()
754+
.appId(appId)
755+
.clientId(clientId)
756+
.redirectUri(redirectUri)
757+
.build();
758+
return this;
759+
}
611760

612761
public Builder earlyAccess(String[] earlyAccess) {
613762
this.earlyAccess = earlyAccess;
@@ -631,18 +780,41 @@ private void validateClient(Contentstack contentstack) {
631780
.addConverterFactory(GsonConverterFactory.create())
632781
.client(httpClient(contentstack, this.retry)).build();
633782
contentstack.instance = this.instance;
783+
784+
// Initialize OAuth if configured
785+
if (this.oauthConfig != null) {
786+
this.oauthHandler = contentstack.oauthHandler = new OAuthHandler(httpClient(contentstack, this.retry), this.oauthConfig);
787+
this.oauthInterceptor = contentstack.oauthInterceptor = new OAuthInterceptor(this.oauthHandler);
788+
if (this.earlyAccess != null) {
789+
this.oauthInterceptor.setEarlyAccess(this.earlyAccess);
790+
}
791+
}
634792
}
635793

636794
private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailure) {
637-
this.authInterceptor = contentstack.interceptor = new AuthInterceptor();
638-
return new OkHttpClient.Builder()
795+
OkHttpClient.Builder builder = new OkHttpClient.Builder()
639796
.connectionPool(this.connectionPool)
640-
.addInterceptor(this.authInterceptor)
641797
.addInterceptor(logger())
642798
.proxy(this.proxy)
643799
.connectTimeout(Duration.ofSeconds(this.timeout))
644-
.retryOnConnectionFailure(retryOnFailure)
645-
.build();
800+
.retryOnConnectionFailure(retryOnFailure);
801+
802+
// Add either OAuth or traditional auth interceptor
803+
if (this.oauthConfig != null) {
804+
if (this.oauthInterceptor == null) {
805+
this.oauthHandler = new OAuthHandler(builder.build(), this.oauthConfig);
806+
this.oauthInterceptor = new OAuthInterceptor(this.oauthHandler);
807+
if (this.earlyAccess != null) {
808+
this.oauthInterceptor.setEarlyAccess(this.earlyAccess);
809+
}
810+
}
811+
builder.addInterceptor(this.oauthInterceptor);
812+
} else {
813+
this.authInterceptor = contentstack.interceptor = new AuthInterceptor();
814+
builder.addInterceptor(this.authInterceptor);
815+
}
816+
817+
return builder.build();
646818
}
647819

648820
private HttpLoggingInterceptor logger() {

src/main/java/com/contentstack/cms/core/Util.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public class Util {
7373
* string value "Java" concatenated
7474
* with the Java version and the operating system name.
7575
*/
76-
protected static String defaultUserAgent() {
76+
public static String defaultUserAgent() {
7777
String agent = System.getProperty("http.agent");
7878
String operatingSystem = System.getProperty("os.name").toUpperCase();
7979
return agent != null ? agent : ("Java" + System.getProperty("java.version") + " OS: " + operatingSystem);

0 commit comments

Comments
 (0)