Skip to content
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
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Database. In addition, a running proxy service is required. See
[Information about the proxy](https://docs.oracle.com/en/database/other-databases/nosql-database/24.3/admin/proxy.html)
for proxy configuration information.

On-premise authorization requires use of [StoreAccessTokenProvider](https://oracle.github.io/nosql-java-sdk/oracle/nosql/driver/kv/StoreAccessTokenProvider.html)
On-premise authorization supports [StoreAccessTokenProvider](https://oracle.github.io/nosql-java-sdk/oracle/nosql/driver/kv/StoreAccessTokenProvider.html) for store credentials and [OAuthAccessTokenProvider](https://oracle.github.io/nosql-java-sdk/oracle/nosql/driver/kv/OAuthAccessTokenProvider.html) for OAuth 2.0 access-token authentication.
See the Quickstart example below for code details for connecting on-premise.

### Connecting to the Oracle NoSQL Database Cloud Simulator
Expand Down 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 from an environment variable.
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 a usable token in
`OAuthAccessTokenProvider.getAccessToken()` and leave automatic renewal
enabled. The SDK schedules re-login from the NoSQL login-token expiration
returned by the server.

Run the example using an OAuth access token:

$ export NOSQL_OAUTH_ACCESS_TOKEN='<access-token>'
$ 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
12 changes: 8 additions & 4 deletions driver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@
<serverType>cloudsim</serverType>
<!-- exclude non-server tests and on-premises tests -->
<excluded.tests>
StoreAccessTokenProviderTest.java, ResourcePrincipalProviderTest.java,
StoreAccessTokenProviderTest.java, OAuthAccessTokenProviderTest.java,
ResourcePrincipalProviderTest.java,
ConfigFileTest.java, SignatureProviderTest.java, AuthRetryTest.java,
UserProfileProviderTest.java, InstancePrincipalsProviderTest.java,
HandleConfigTest.java, JsonTest.java, ValueTest.java,
Expand All @@ -106,7 +107,8 @@
<serverType>onprem</serverType>
<!-- exclude non-server tests -->
<excluded.tests>
StoreAccessTokenProviderTest.java, ResourcePrincipalProviderTest.java,
StoreAccessTokenProviderTest.java, OAuthAccessTokenProviderTest.java,
ResourcePrincipalProviderTest.java,
ConfigFileTest.java, SignatureProviderTest.java, AuthRetryTest.java,
UserProfileProviderTest.java, InstancePrincipalsProviderTest.java,
HandleConfigTest.java, JsonTest.java, ValueTest.java,
Expand All @@ -124,7 +126,8 @@
<serverType>onprem</serverType>
<!-- exclude non-server tests -->
<excluded.tests>
StoreAccessTokenProviderTest.java, ResourcePrincipalProviderTest.java,
StoreAccessTokenProviderTest.java, OAuthAccessTokenProviderTest.java,
ResourcePrincipalProviderTest.java,
ConfigFileTest.java, SignatureProviderTest.java, AuthRetryTest.java,
UserProfileProviderTest.java, InstancePrincipalsProviderTest.java,
HandleConfigTest.java, JsonTest.java, ValueTest.java,
Expand All @@ -143,7 +146,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
97 changes: 79 additions & 18 deletions driver/src/main/java/oracle/nosql/driver/http/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,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 @@ -292,9 +293,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 @@ -384,6 +385,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 @@ -691,12 +697,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 @@ -860,6 +864,32 @@ public Result execute(Request kvRequest) {
logFine(logger,
"Client re-auth on AuthenticationException");
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 (retriedOAuthAuthentication(kvRequest)) {
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
statsControl.observeError(kvRequest);
logFine(logger,
"Client OAuth re-auth failed with " +
rae.getClass().getName());
throw rae;
}
((OAuthAccessTokenProvider) authProvider)
.invalidateAuthorizationString(authString);
kvRequest.addRetryException(
AuthenticationException.class);
kvRequest.incrementRetries();
exception = rae;
logFine(logger,
"Client retrying OAuth re-auth on " +
rae.getClass().getName());
continue;
}
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
statsControl.observeError(kvRequest);
Expand All @@ -875,16 +905,31 @@ public Result execute(Request kvRequest) {
* failures. This does not include permissions-related errors,
* which would be a UnauthorizedException.
*/
if (retriedInvalidAuthorizationException(kvRequest)) {
final boolean oauthProvider =
authProvider instanceof OAuthAccessTokenProvider;
if ((oauthProvider &&
retriedOAuthAuthentication(kvRequest)) ||
(!oauthProvider &&
retriedException(
kvRequest,
InvalidAuthorizationException.class))) {
/* same as NoSQLException below */
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
statsControl.observeError(kvRequest);
logFine(logger, "Client execute InvalidAuthorizationException");
logFine(logger,
"Client execute InvalidAuthorizationException");
throw iae;
}
/* flush auth cache and do one retry */
authProvider.flushCache();
kvRequest.addRetryException(iae.getClass());
if (oauthProvider) {
((OAuthAccessTokenProvider) authProvider)
.invalidateAuthorizationString(authString);
} else {
authProvider.flushCache();
}
kvRequest.addRetryException(
oauthProvider ? InvalidAuthorizationException.class :
iae.getClass());
kvRequest.incrementRetries();
exception = iae;
logFine(logger,
Expand Down Expand Up @@ -1597,20 +1642,29 @@ 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 boolean retriedOAuthAuthentication(Request request) {
return retriedException(request, AuthenticationException.class) ||
retriedException(
request, InvalidAuthorizationException.class);
}

private void throwIfTransportRetryNotAllowed(Request request,
Expand Down Expand Up @@ -1644,8 +1698,15 @@ private void handleRetry(RetryableException re,
private void logRetries(int numRetries, Throwable exception) {
Level level = Level.FINE;
if (logger != null) {
final String exceptionDetail =
exception == null ? "" :
", exception: " +
(authProvider instanceof OAuthAccessTokenProvider &&
(exception instanceof AuthenticationException ||
exception instanceof InvalidAuthorizationException) ?
exception.getClass().getName() : exception);
logger.log(level, "Client, doing retry: " + numRetries +
(exception != null ? ", exception: " + exception : ""));
exceptionDetail);
}
}

Expand Down
22 changes: 17 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,18 @@ 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);
}
oatProvider.prepare(config);
} else if (ap instanceof SignatureProvider) {
SignatureProvider sigProvider = (SignatureProvider) ap;
if (sigProvider.getLogger() == null) {
Expand All @@ -176,6 +180,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