Skip to content
Open
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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,28 @@ Run the command:
BasicTableExample https://localhost:443 -useKVProxy -user driver \
-password Driver.User@01

##### Run using OAuth 2.0 authorization

The existing examples support exchanging an OAuth access token for a NoSQL
login token through a secure on-premises proxy by using the `-useOAuth` flag.
The store and proxy must already be configured for OAuth, and the OAuth
principal must have the privileges required by the selected example.

The example reads a single access token and its remaining lifetime from
environment variables. Supplying the token this way keeps the example
independent of the identity provider and avoids placing the bearer token in
the command line. A production application should obtain fresh tokens in
`OAuthAccessTokenProvider.getAccessTokenInfo()` and leave automatic renewal
enabled.

Run the example using an OAuth token that is valid for another 300 seconds:

$ export NOSQL_OAUTH_ACCESS_TOKEN='<access-token>'
$ export NOSQL_OAUTH_EXPIRES_IN_SECONDS=300
$ java -Djavax.net.ssl.trustStorePassword=123456 \
-Djavax.net.ssl.trustStore=driver.trust -cp .:../lib/nosqldriver.jar \
BasicTableExample https://localhost:443 -useKVProxy -useOAuth

#### Run using the Oracle NoSQL Database Cloud Simulator

Run against the Oracle NoSQL Cloud Simulator using its default endpoint
Expand Down
3 changes: 2 additions & 1 deletion driver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@
<serverType>none</serverType>
<included.tests>
StoreAccessTokenProviderTest.java, ResourcePrincipalProviderTest.java,
ConfigFileTest.java, SignatureProviderTest.java, AuthRetryTest.java,
OAuthAccessTokenProviderTest.java, ConfigFileTest.java,
SignatureProviderTest.java, AuthRetryTest.java,
UserProfileProviderTest.java, InstancePrincipalsProviderTest.java,
HandleConfigTest.java, JsonTest.java, ValueTest.java,
SessionTokenProviderTest.java, RequestTest.java
Expand Down
62 changes: 48 additions & 14 deletions driver/src/main/java/oracle/nosql/driver/http/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import oracle.nosql.driver.httpclient.HttpClient;
import oracle.nosql.driver.httpclient.ResponseHandler;
import oracle.nosql.driver.kv.AuthenticationException;
import oracle.nosql.driver.kv.OAuthAccessTokenProvider;
import oracle.nosql.driver.kv.StoreAccessTokenProvider;
import oracle.nosql.driver.ops.AddReplicaRequest;
import oracle.nosql.driver.ops.DeleteRequest;
Expand Down Expand Up @@ -285,9 +286,9 @@ public Client(Logger logger,
"Must configure AuthorizationProvider to use HttpClient");
}

/* StoreAccessTokenProvider == onprem */
/* StoreAccessTokenProvider/OAuthAccessTokenProvider == onprem */
if (config.getRateLimitingEnabled() &&
!(authProvider instanceof StoreAccessTokenProvider)) {
!isOnPremAuthProvider()) {
logFine(logger, "Starting client with rate limiting enabled");
rateLimiterMap = new RateLimiterMap();
tableLimitUpdateMap = new ConcurrentHashMap<String, AtomicLong>();
Expand Down Expand Up @@ -374,6 +375,11 @@ public int getFreeChannelCount() {
return httpClient.getFreeChannelCount();
}

private boolean isOnPremAuthProvider() {
return authProvider instanceof StoreAccessTokenProvider ||
authProvider instanceof OAuthAccessTokenProvider;
}

/**
* Get the next client-scoped request id. It needs to be combined with the
* client id to obtain a globally unique scope.
Expand Down Expand Up @@ -675,12 +681,10 @@ public Result execute(Request kvRequest) {
kvRequest.setTimeoutInternal(timeoutMs);

/*
* If on-premises the authProvider will always be a
* StoreAccessTokenProvider. If so, check against
* configurable limit. Otherwise check against internal
* hardcoded cloud limit.
* If on-premises, check against configurable limit.
* Otherwise check against internal hardcoded cloud limit.
*/
if (authProvider instanceof StoreAccessTokenProvider) {
if (isOnPremAuthProvider()) {
if (buffer.readableBytes() >
httpClient.getMaxContentLength()) {
throw new RequestSizeLimitException("The request " +
Expand Down Expand Up @@ -844,6 +848,32 @@ public Result execute(Request kvRequest) {
"Client re-auth on AuthenticationException: " +
rae.getMessage());
continue;
} else if (authProvider instanceof OAuthAccessTokenProvider) {
/*
* OAuthAccessTokenProvider obtains a new NoSQL login
* token lazily after the cache is flushed. Retry this
* path only once so repeated RETRY_AUTHENTICATION
* responses are surfaced as authentication failures
* instead of eventually timing out the request.
*/
if (retriedException(kvRequest,
AuthenticationException.class)) {
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
statsControl.observeError(kvRequest);
logFine(logger,
"Client OAuth re-auth failed: " +
rae.getMessage());
throw rae;
}
authProvider.flushCache();
kvRequest.addRetryException(rae.getClass());
kvRequest.incrementRetries();
exception = rae;
logFine(logger,
"Client retrying OAuth re-auth on " +
"AuthenticationException: " +
rae.getMessage());
continue;
}
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
statsControl.observeError(kvRequest);
Expand All @@ -859,7 +889,8 @@ public Result execute(Request kvRequest) {
* failures. This does not include permissions-related errors,
* which would be a UnauthorizedException.
*/
if (retriedInvalidAuthorizationException(kvRequest)) {
if (retriedException(kvRequest,
InvalidAuthorizationException.class)) {
/* same as NoSQLException below */
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
statsControl.observeError(kvRequest);
Expand Down Expand Up @@ -1584,20 +1615,23 @@ private void updateTableLimiters(String tableName, String compartmentId) {
}

/**
* Returns whether an {@link InvalidAuthorizationException} has been
* retried for the given request.
* Returns whether an exception type has been retried for the given
* request.
*
* @param request the request to check
* @return true if an {@link InvalidAuthorizationException} has been
* retried for the request, false otherwise
* @param exceptionClass the exception class to check
* @return true if the exception type has been retried for the request
*/
private boolean retriedInvalidAuthorizationException(Request request) {
private boolean retriedException(
Request request,
Class<? extends Throwable> exceptionClass) {

final RetryStats rs = request.getRetryStats();
if (rs == null || rs.getRetries() <= 0) {
return false;
}

return rs.getNumExceptions(InvalidAuthorizationException.class) > 0;
return rs.getNumExceptions(exceptionClass) > 0;
}

private void throwIfTransportRetryNotAllowed(Request request,
Expand Down
27 changes: 22 additions & 5 deletions driver/src/main/java/oracle/nosql/driver/http/NoSQLHandleImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import oracle.nosql.driver.StatsControl;
import oracle.nosql.driver.UserInfo;
import oracle.nosql.driver.iam.SignatureProvider;
import oracle.nosql.driver.kv.OAuthAccessTokenProvider;
import oracle.nosql.driver.kv.StoreAccessTokenProvider;
import oracle.nosql.driver.ops.AddReplicaRequest;
import oracle.nosql.driver.ops.DeleteRequest;
Expand Down Expand Up @@ -154,15 +155,23 @@ private void configAuthProvider(Logger logger, NoSQLHandleConfig config) {
}
if (stProvider.isSecure() &&
stProvider.getEndpoint() == null) {
String endpoint = config.getServiceURL().toString();
if (endpoint.endsWith("/")) {
endpoint = endpoint.substring(0, endpoint.length() - 1);
}
stProvider.setEndpoint(endpoint)
stProvider.setEndpoint(getAuthEndpoint(config))
.setSslContext(config.getSslContext())
.setSslHandshakeTimeout(
config.getSSLHandshakeTimeout());
}
} else if (ap instanceof OAuthAccessTokenProvider) {
final OAuthAccessTokenProvider oatProvider =
(OAuthAccessTokenProvider) ap;
if (oatProvider.getLogger() == null) {
oatProvider.setLogger(logger);
}
if (oatProvider.getEndpoint() == null) {
oatProvider.setEndpoint(getAuthEndpoint(config))
.setSslContext(config.getSslContext())
.setSslHandshakeTimeout(
config.getSSLHandshakeTimeout());
}
} else if (ap instanceof SignatureProvider) {
SignatureProvider sigProvider = (SignatureProvider) ap;
if (sigProvider.getLogger() == null) {
Expand All @@ -176,6 +185,14 @@ private void configAuthProvider(Logger logger, NoSQLHandleConfig config) {
}
}

private String getAuthEndpoint(NoSQLHandleConfig config) {
String endpoint = config.getServiceURL().toString();
if (endpoint.endsWith("/")) {
endpoint = endpoint.substring(0, endpoint.length() - 1);
}
return endpoint;
}

@Override
public DeleteResult delete(DeleteRequest request) {
checkClient();
Expand Down
Loading