Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to support user provided OAuth2 access token #841

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
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 @@ -78,11 +78,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 @@ -99,6 +97,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 @@ -266,6 +265,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 @@ -345,7 +348,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 @@ -366,12 +369,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 @@ -385,9 +394,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 @@ -528,7 +539,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 @@ -552,7 +563,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 @@ -568,6 +579,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