All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.nosql.driver.kv.StoreAccessTokenProvider Maven / Gradle / Ivy

/*-
 * Copyright (c) 2011, 2020 Oracle and/or its affiliates.  All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 *  https://oss.oracle.com/licenses/upl/
 */

package oracle.nosql.driver.kv;

import static oracle.nosql.driver.util.HttpConstants.AUTHORIZATION;
import static oracle.nosql.driver.util.HttpConstants.KV_SECURITY_PATH;

import java.net.URL;
import java.util.Arrays;
import java.util.Base64;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

import oracle.nosql.driver.AuthorizationProvider;
import oracle.nosql.driver.InvalidAuthorizationException;
import oracle.nosql.driver.NoSQLException;
import oracle.nosql.driver.NoSQLHandleConfig;
import oracle.nosql.driver.httpclient.HttpClient;
import oracle.nosql.driver.ops.Request;
import oracle.nosql.driver.util.HttpRequestUtil;
import oracle.nosql.driver.util.HttpRequestUtil.HttpResponse;
import oracle.nosql.driver.values.JsonUtils;
import oracle.nosql.driver.values.MapValue;

import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.ssl.SslContext;

/**
 * On-premise only.
 * 

* StoreAccessTokenProvider is an {@link AuthorizationProvider} that performs * the following functions: *

    *
  • Initial (bootstrap) login to store, using credentials provided
  • *
  • Storage of bootstrap login token for re-use
  • *
  • Optionally renews the login token before it expires
  • *
  • Logs out of the store when closed
  • *
*

* If accessing an insecure instance of Oracle NoSQL Database the default * constructor is used, with no arguments. *

* If accessing a secure instance of Oracle NoSQL Database a user name and * password must be provided. That user must already exist in the NoSQL * Database and have sufficient permission to perform table operations. * That user's identity is used to authorize all database operations. */ public class StoreAccessTokenProvider implements AuthorizationProvider { /* * This is used when we send user:password pair. */ private static final String BASIC_PREFIX = "Basic "; /* * This is the general prefix for the login token. */ private static final String BEARER_PREFIX = "Bearer "; /* * login service end point name. */ private static final String LOGIN_SERVICE = "/login"; /* * login token renew service end point name. */ private static final String RENEW_SERVICE = "/renew"; /* * logout service end point name. */ private static final String LOGOUT_SERVICE = "/logout"; /* * Default timeout when sending http request to server */ private static final int HTTP_TIMEOUT_MS = 30000; /* * Authentication string which contain the Bearer prefix and login token's * binary representation in hex format. */ private AtomicReference authString = new AtomicReference(); /* * Login token expiration time. */ private long expirationTime; /* * A timer task used to periodically renew the login token. */ private volatile Timer timer; /* * logger */ private Logger logger; /* * Whether to renew the login token automatically */ private boolean autoRenew = true; /* * Whether this is a secure store token provider. */ private final boolean isSecure; /* * Name of the user to login */ private final String userName; /* * Password of the user to login */ private final char[] password; /* * Host name of the proxy machine which host the login service */ private String loginHost; /* * Port number of the proxy machine which host the login service */ private int loginPort; /* * Endpoint to reach the authenticating entity (Proxy) */ private String endpoint; /* * Base path for security related services */ private final static String basePath = KV_SECURITY_PATH; /* * Whether this provider is closed */ private boolean isClosed = false; /* * SslContext used by http client */ private SslContext sslContext; /** * @hidden * This is only used for unit test */ public static boolean disableSSLHook; /** * This method is used for access to a store without security enabled. */ public StoreAccessTokenProvider() { isSecure = false; userName = null; password = null; loginHost = null; endpoint = null; loginPort = 0; } /** * This constructor is used when accessing a secure store. *

* This constructor requires a valid user name and password to access * the target store. The user must exist and have sufficient permission * to perform table operations required by the application. The user * identity is used to authorize all operations performed by the * application. * * @param userName the user name to use for the store. This user must * exist in the NoSQL Database and is the identity that is used for * authorizing all database operations. * @param password the password for the user. * * @throws IllegalArgumentException if one or more of the parameters is * malformed or a required parameter is missing. */ public StoreAccessTokenProvider(String userName, char[] password) { isSecure = true; this.userName = userName; this.password = Arrays.copyOf(password, password.length); this.logger = null; /* * Check null */ if (this.userName == null || this.userName.isEmpty() || this.password == null) { throw new IllegalArgumentException( "Invalid input arguments"); } } /** * @hidden * * Bootstrap login using the provided credentials */ public synchronized void bootstrapLogin() { if (!isSecure || isClosed) { return; } try { /* * Convert the user:password pair in base 64 format with * Basic prefix */ final String encoded = Base64.getEncoder(). encodeToString(( userName + ":" + String.valueOf(password)).getBytes()); /* * Send request to server for login token */ HttpResponse response = sendRequest(BASIC_PREFIX + encoded, LOGIN_SERVICE); /* * login fail */ if (response.getStatusCode() != HttpResponseStatus.OK.code()) { throw new InvalidAuthorizationException( "Fail to login to service: " + response.getOutput()); } if (isClosed) { return; } /* * Generate the authentication string using login token */ authString.set(BEARER_PREFIX + parseJsonResult(response.getOutput())); /* * Schedule login token renew thread */ scheduleRefresh(); } catch (InvalidAuthorizationException iae) { throw iae; } catch (Exception e) { throw new NoSQLException("Bootstrap login fail", e); } } /** * @hidden */ @Override public String getAuthorizationString(Request request) { if (!isSecure) { return null; } /* * Already close */ if (isClosed) { return null; } /* * If there is no cached auth string, re-authentication to retrieve * the login token and generate the auth string. */ if (authString.get() == null) { bootstrapLogin(); } return authString.get(); } /** * @hidden */ @Override public void validateAuthString(String input) { if (isSecure() && input == null) { throw new IllegalArgumentException( "Secured StoreAccessProvider acquired an unexpected null " + "authorization string"); } } /** * Closes the provider, releasing resources such as a stored login * token. */ @Override public synchronized void close() { /* * Don't do anything for non-secure case */ if (!isSecure || isClosed) { return; } /* * Send request for logout */ try { final HttpResponse response = sendRequest(authString.get(), LOGOUT_SERVICE); if (response.getStatusCode() != HttpResponseStatus.OK.code()) { if (logger != null) { logger.info("Failed to logout user " + userName + ": " + response.getOutput()); } } } catch (Exception e) { if (logger != null) { logger.info("Failed to logout user " + userName + ": " + e); } } /* * Clean up */ isClosed = true; authString = null; expirationTime = 0; Arrays.fill(password, ' '); if (timer != null) { timer.cancel(); timer = null; } } /** * Returns the logger, or null if not set. * * @return the logger */ public Logger getLogger() { return logger; } /** * Sets a Logger instance for this provider. * @param logger the logger * @return this */ public StoreAccessTokenProvider setLogger(Logger logger) { this.logger = logger; return this; } public String getEndpoint() { return endpoint; } public StoreAccessTokenProvider setEndpoint(String endpoint) { this.endpoint = endpoint; URL url = NoSQLHandleConfig.createURL(endpoint, ""); if (!url.getProtocol().toLowerCase().equals("https") && isSecure) { throw new IllegalArgumentException( "StoreAccessTokenProvider requires use of https"); } this.loginHost = url.getHost(); this.loginPort = url.getPort(); return this; } public StoreAccessTokenProvider setSslContext(SslContext sslCtx) { this.sslContext = sslCtx; return this; } /** * Returns whether the provider is accessing a secured store * * @return true if accessing a secure store */ public boolean isSecure() { return isSecure; } /** * Schedule a login token renew when half of the token life time is * reached. */ private void scheduleRefresh() { /* * Only run when autoRenew is set */ if (!isSecure || !autoRenew) { return; } /* * Clean up any existing timer */ if (timer != null) { timer.cancel(); timer = null; } final long acquireTime = System.currentTimeMillis(); if (expirationTime <= 0) { return; } /* * If it is 10 seconds before expiration, don't do further renew to * avoid to many renew request in the last few seconds. */ if (expirationTime > acquireTime + 10000) { final long renewTime = acquireTime + (expirationTime - acquireTime) / 2; timer = new Timer(true /* isDaemon */); /* Attempt a renew at the token half-life */ timer.schedule(new RefreshTask(), (renewTime - acquireTime)); } } /** * Retrieve login token from JSON string */ private String parseJsonResult(String jsonResult) { final MapValue mapValue = JsonUtils.createValueFromJson(jsonResult, null).asMap(); /* * Extract expiration time from JSON result */ expirationTime = mapValue.getLong("expireAt"); /* * Extract login token from JSON result */ return mapValue.getString("token"); } /** * Send HTTPS request to login/renew/logout service location with proper * authentication information. */ private HttpResponse sendRequest(String authHeader, String serviceName) throws Exception { HttpClient client = null; try { final HttpHeaders headers = new DefaultHttpHeaders(); headers.set(AUTHORIZATION, authHeader); client = new HttpClient( loginHost, loginPort, 0, 0, 0, (isSecure && !disableSSLHook) ? sslContext : null, serviceName, logger); return HttpRequestUtil.doGetRequest( client, NoSQLHandleConfig.createURL(endpoint, basePath + serviceName) .toString(), headers, HTTP_TIMEOUT_MS, logger); } finally { if (client != null) { client.shutdown(); } } } /** * Returns whether the login token is to be automatically renewed. * * @return true if auto-renew is set */ public boolean isAutoRenew() { return autoRenew; } /** * Sets the auto-renew state. If true, automatic renewal of the login * token is enabled. * * @param autoRenew set to true to enable auto-renew * * @return this */ public StoreAccessTokenProvider setAutoRenew(boolean autoRenew) { this.autoRenew = autoRenew; return this; } /** * This task sends a request to the server for login session extension. * Depending on the server policy, a new login token with new expiration * time may or may not be granted. */ private class RefreshTask extends TimerTask { @Override public void run() { if (!isSecure || !autoRenew || isClosed) { return; } try { final String oldAuth = authString.get(); HttpResponse response = sendRequest(oldAuth, RENEW_SERVICE); final String token = parseJsonResult(response.getOutput()); if (response.getStatusCode() != HttpResponseStatus.OK.code()) { throw new InvalidAuthorizationException(token); } if (isClosed) { return; } authString.compareAndSet(oldAuth, BEARER_PREFIX + token); scheduleRefresh(); } catch (Exception e) { if (logger != null) { logger.info("Failed to renew login token: " + e); } if (timer != null) { timer.cancel(); timer = null; } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy