Skip to content

Commit

Permalink
Changes to support user provided OAuth2 access token (#841)
Browse files Browse the repository at this point in the history
* Changes to support user provided OAuth2 access token
  • Loading branch information
arvindkrishnakumar-okta authored Mar 24, 2023
1 parent 8c2f12d commit f06eaba
Show file tree
Hide file tree
Showing 17 changed files with 577 additions and 29 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ ApiClient client = Clients.builder()
// (or) .setPrivateKey(Paths.get("/path/to/yourPrivateKey.pem"))
// (or) .setPrivateKey(inputStream)
// (or) .setPrivateKey(privateKey)
// (or) .setOAuth2AccessToken("access token string") // if set, private key (if supplied) will be ignored
.build();
```
[//]: # (end: createOAuth2Client)
Expand Down
2 changes: 1 addition & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.okta.sdk</groupId>
<artifactId>okta-sdk-root</artifactId>
<version>10.2.3-SNAPSHOT</version>
<version>10.3.1-SNAPSHOT</version>
</parent>

<artifactId>okta-sdk-api</artifactId>
Expand Down
15 changes: 14 additions & 1 deletion api/src/main/java/com/okta/sdk/client/ClientBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public interface ClientBuilder {
String DEFAULT_CLIENT_ID_PROPERTY_NAME = "okta.client.clientId";
String DEFAULT_CLIENT_SCOPES_PROPERTY_NAME = "okta.client.scopes";
String DEFAULT_CLIENT_PRIVATE_KEY_PROPERTY_NAME = "okta.client.privateKey";
String DEFAULT_CLIENT_OAUTH2_ACCESS_TOKEN_PROPERTY_NAME = "okta.client.oauth2.accessToken";
String DEFAULT_CLIENT_KID_PROPERTY_NAME = "okta.client.kid";
String DEFAULT_CLIENT_REQUEST_TIMEOUT_PROPERTY_NAME = "okta.client.requestTimeout";
String DEFAULT_CLIENT_RETRY_MAX_ATTEMPTS_PROPERTY_NAME = "okta.client.rateLimit.maxRetries";
Expand All @@ -89,7 +90,7 @@ public interface ClientBuilder {
* Allows specifying an {@code ApiKey} instance directly instead of relying on the
* default location + override/fallback behavior defined in the {@link ClientBuilder documentation above}.
*
* Currently you should use a com.okta.sdk.impl.api.TokenClientCredentials (if you are NOT using an okta.yaml file)
* Currently, you should use a com.okta.sdk.impl.api.TokenClientCredentials (if you are NOT using an okta.yaml file)
*
* @param clientCredentials the token to use to authenticate requests to the Okta API server.
* @return the ClientBuilder instance for method chaining.
Expand Down Expand Up @@ -242,6 +243,18 @@ public interface ClientBuilder {
*/
ClientBuilder setPrivateKey(PrivateKey privateKey);

/**
* Allows specifying the user obtained OAuth2 access token to be used by the SDK.
* The SDK will NOT obtain access token automatically (using the supplied private key)
* when this is set.
*
* @param oAuth2AccessToken the token string.
* @return the ClientBuilder instance for method chaining.
*
* @since 10.2.x
*/
ClientBuilder setOAuth2AccessToken(String oAuth2AccessToken);

/**
* Allows specifying the client ID instead of relying on the default location + override/fallback behavior defined
* in the {@link ClientBuilder documentation above}.
Expand Down
6 changes: 5 additions & 1 deletion api/src/main/java/com/okta/sdk/error/ErrorHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JacksonException;

import com.okta.commons.lang.Strings;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.FileCopyUtils;
Expand Down Expand Up @@ -47,9 +48,12 @@ public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
public void handleError(ClientHttpResponse httpResponse) throws IOException, ResourceException {

final int statusCode = httpResponse.getRawStatusCode();
final String message = new String(FileCopyUtils.copyToByteArray(httpResponse.getBody()));
String message = new String(FileCopyUtils.copyToByteArray(httpResponse.getBody()));

if (!isValid(message)) {
if (!Strings.hasText(message)) {
message = httpResponse.getStatusText();
}
throw new ResourceException(new NonJsonError(statusCode, message));
}

Expand Down
2 changes: 1 addition & 1 deletion coverage/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.okta.sdk</groupId>
<artifactId>okta-sdk-root</artifactId>
<version>10.2.3-SNAPSHOT</version>
<version>10.3.1-SNAPSHOT</version>
</parent>

<artifactId>okta-sdk-coverage</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.okta.sdk</groupId>
<artifactId>okta-sdk-root</artifactId>
<version>10.2.3-SNAPSHOT</version>
<version>10.3.1-SNAPSHOT</version>
</parent>

<artifactId>okta-sdk-examples</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion examples/quickstart/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<parent>
<groupId>com.okta.sdk</groupId>
<artifactId>okta-sdk-examples</artifactId>
<version>10.2.3-SNAPSHOT</version>
<version>10.3.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static void main(String[] args) {

final String email = "joe.coder+" + UUID.randomUUID() + "@example.com";
final String groupName = "java-sdk-quickstart-" + UUID.randomUUID();
final char[] password = {'P','a','s','s','w','o','r','d','1'};
final char[] password = {'$','D','o','l','l','a','r','d','i','m','e','1','2','3','*'};

ClientBuilder builder;
ApiClient client;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ private void createOAuth2Client() {
// (or) .setPrivateKey(Paths.get("/path/to/yourPrivateKey.pem"))
// (or) .setPrivateKey(inputStream)
// (or) .setPrivateKey(privateKey)
// (or) .setOAuth2AccessToken("access token string") // if set, private key (if supplied) will be ignored
.build();
}

Expand Down
2 changes: 1 addition & 1 deletion impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.okta.sdk</groupId>
<artifactId>okta-sdk-root</artifactId>
<version>10.2.3-SNAPSHOT</version>
<version>10.3.1-SNAPSHOT</version>
</parent>

<artifactId>okta-sdk-impl</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,9 @@

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
Expand All @@ -100,6 +98,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -267,6 +266,10 @@ else if (SYSPROPS_TOKEN.equalsIgnoreCase(location)) {
clientConfig.setPrivateKey(props.get(DEFAULT_CLIENT_PRIVATE_KEY_PROPERTY_NAME));
}

if (Strings.hasText(props.get(DEFAULT_CLIENT_OAUTH2_ACCESS_TOKEN_PROPERTY_NAME))) {
clientConfig.setOAuth2AccessToken(props.get(DEFAULT_CLIENT_OAUTH2_ACCESS_TOKEN_PROPERTY_NAME));
}

if (Strings.hasText(props.get(DEFAULT_CLIENT_KID_PROPERTY_NAME))) {
clientConfig.setKid(props.get(DEFAULT_CLIENT_KID_PROPERTY_NAME));
}
Expand Down Expand Up @@ -346,7 +349,7 @@ public ApiClient build() {
}

if (this.clientConfig.getBaseUrlResolver() == null) {
ConfigurationValidator.validateOrgUrl(this.clientConfig.getBaseUrl(), allowNonHttpsForTesting);
ConfigurationValidator.assertOrgUrl(this.clientConfig.getBaseUrl(), allowNonHttpsForTesting);
this.clientConfig.setBaseUrlResolver(new DefaultBaseUrlResolver(this.clientConfig.getBaseUrl()));
}

Expand All @@ -367,12 +370,18 @@ public ApiClient build() {

validateOAuth2ClientConfig(this.clientConfig);

accessTokenRetrieverService = new AccessTokenRetrieverServiceImpl(clientConfig, apiClient);
if (Strings.hasText(this.clientConfig.getOAuth2AccessToken())) {
log.debug("Will use client provided Access token for OAuth2 authentication (private key, if supplied would be ignored)");
apiClient.setAccessToken(this.clientConfig.getOAuth2AccessToken());
} else {
log.debug("Will retrieve Access Token automatically from Okta for OAuth2 authentication");
accessTokenRetrieverService = new AccessTokenRetrieverServiceImpl(clientConfig, apiClient);

OAuth2ClientCredentials oAuth2ClientCredentials =
new OAuth2ClientCredentials(accessTokenRetrieverService);
OAuth2ClientCredentials oAuth2ClientCredentials =
new OAuth2ClientCredentials(accessTokenRetrieverService);

this.clientConfig.setClientCredentialsResolver(new DefaultClientCredentialsResolver(oAuth2ClientCredentials));
this.clientConfig.setClientCredentialsResolver(new DefaultClientCredentialsResolver(oAuth2ClientCredentials));
}
}

return apiClient;
Expand All @@ -386,9 +395,11 @@ private void validateOAuth2ClientConfig(ClientConfiguration clientConfiguration)
Assert.isTrue(clientConfiguration.getScopes() != null && !clientConfiguration.getScopes().isEmpty(),
"At least one scope is required");
String privateKey = clientConfiguration.getPrivateKey();
Assert.hasText(privateKey, "privateKey cannot be null (either PEM file path (or) full PEM content must be supplied)");
String oAuth2AccessToken = clientConfiguration.getOAuth2AccessToken();
Assert.isTrue(Objects.nonNull(privateKey) || Objects.nonNull(oAuth2AccessToken),
"Either Private Key (or) Access Token must be supplied for OAuth2 Authentication mode");

if (!ConfigUtil.hasPrivateKeyContentWrapper(privateKey)) {
if (Strings.hasText(privateKey) && !ConfigUtil.hasPrivateKeyContentWrapper(privateKey)) {
// privateKey is a file path, check if the file exists
Path privateKeyPemFilePath;
try {
Expand Down Expand Up @@ -533,7 +544,7 @@ public ClientBuilder setPrivateKey(PrivateKey privateKey) {
}

private String getFileContent(File file) {
try (InputStream inputStream = new FileInputStream(file)) {
try (InputStream inputStream = Files.newInputStream(file.toPath())) {
return readFromInputStream(inputStream);
} catch (IOException e) {
throw new IllegalArgumentException("Could not read from supplied private key file");
Expand All @@ -557,7 +568,7 @@ private String readFromInputStream(InputStream inputStream) throws IOException {
Assert.notNull(inputStream, "InputStream cannot be null.");
StringBuilder resultStringBuilder = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(
inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
resultStringBuilder.append(line).append("\n");
Expand All @@ -573,6 +584,13 @@ public ClientBuilder setClientId(String clientId) {
return this;
}

@Override
public ClientBuilder setOAuth2AccessToken(String oAuth2AccessToken) {
Assert.notNull(oAuth2AccessToken, "oAuth2AccessToken cannot be null.");
this.clientConfig.setOAuth2AccessToken(oAuth2AccessToken);
return this;
}

@Override
public ClientBuilder setKid(String kid) {
Assert.notNull(kid, "kid cannot be null.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class ClientConfiguration extends HttpClientConfiguration {
private String clientId;
private Set<String> scopes = new HashSet<>();
private String privateKey;
private String oAuth2AccessToken;
private String kid;

public String getApiToken() {
Expand Down Expand Up @@ -118,6 +119,14 @@ public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}

public String getOAuth2AccessToken() {
return oAuth2AccessToken;
}

public void setOAuth2AccessToken(String oAuth2AccessToken) {
this.oAuth2AccessToken = oAuth2AccessToken;
}

public boolean isCacheManagerEnabled() {
return cacheManagerEnabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public DefaultEnvVarNameConverter() {
ClientBuilder.DEFAULT_CLIENT_AUTHORIZATION_MODE_PROPERTY_NAME,
ClientBuilder.DEFAULT_CLIENT_ID_PROPERTY_NAME,
ClientBuilder.DEFAULT_CLIENT_SCOPES_PROPERTY_NAME,
ClientBuilder.DEFAULT_CLIENT_PRIVATE_KEY_PROPERTY_NAME);
ClientBuilder.DEFAULT_CLIENT_PRIVATE_KEY_PROPERTY_NAME,
ClientBuilder.DEFAULT_CLIENT_OAUTH2_ACCESS_TOKEN_PROPERTY_NAME);
}

private Map<String, String> buildReverseLookupToMap(String... dottedPropertyNames) {
Expand Down
6 changes: 3 additions & 3 deletions impl/src/main/java/com/okta/sdk/impl/util/ConfigUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ public class ConfigUtil {
public static final String EC_PRIVATE_KEY_FOOTER = "-----END EC PRIVATE KEY-----";

/**
* Check if the PEM key has BEGIN content wrapper.
* Check if the private key PEM has BEGIN content wrapper.
*
* @param key the supplied key has BEGIN wrapper/header
* @return
* @param key the supplied private key PEM string.
* @return true if the supplied private key has the BEGIN content wrapper, false otherwise.
*/
public static boolean hasPrivateKeyContentWrapper(String key) {
return key.startsWith("-----BEGIN");
Expand Down
Loading

0 comments on commit f06eaba

Please sign in to comment.