net.snowflake.client.core.SessionUtil Maven / Gradle / Ivy
/*
* Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
*/
package net.snowflake.client.core;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import net.snowflake.client.jdbc.ErrorCode;
import net.snowflake.client.jdbc.SnowflakeDriver;
import net.snowflake.client.jdbc.SnowflakeReauthenticationRequest;
import net.snowflake.client.jdbc.SnowflakeSQLException;
import net.snowflake.client.jdbc.SnowflakeType;
import net.snowflake.client.jdbc.SnowflakeUtil;
import net.snowflake.client.log.ArgSupplier;
import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;
import net.snowflake.common.core.ClientAuthnDTO;
import net.snowflake.common.core.ClientAuthnParameter;
import net.snowflake.common.core.SqlState;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.SystemDefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.HeaderGroup;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import static net.snowflake.client.core.SFTrustManager.resetOCSPResponseCacherServerURL;
/**
* Low level session util
*/
public class SessionUtil
{
public static final String SF_QUERY_DATABASE = "databaseName";
public static final String SF_QUERY_SCHEMA = "schemaName";
public static final String SF_QUERY_WAREHOUSE = "warehouse";
public static final String SF_QUERY_ROLE = "roleName";
public static final String SF_QUERY_REQUEST_ID = "requestId";
public static final String SF_PATH_AUTHENTICATOR_REQUEST
= "/session/authenticator-request";
public static final String SF_QUERY_SESSION_DELETE = "delete";
public static final String SF_HEADER_AUTHORIZATION = HttpHeaders.AUTHORIZATION;
public static final String SF_HEADER_BASIC_AUTHTYPE = "Basic";
public static final String SF_HEADER_SNOWFLAKE_AUTHTYPE = "Snowflake";
public static final String SF_HEADER_TOKEN_TAG = "Token";
public static final String CLIENT_STORE_TEMPORARY_CREDENTIAL = "CLIENT_STORE_TEMPORARY_CREDENTIAL";
public static final String SERVICE_NAME = "SERVICE_NAME";
public static final String CLIENT_RESULT_COLUMN_CASE_INSENSITIVE = "CLIENT_RESULT_COLUMN_CASE_INSENSITIVE";
public static final String JDBC_RS_COLUMN_CASE_INSENSITIVE = "JDBC_RS_COLUMN_CASE_INSENSITIVE";
public static final String CLIENT_RESULT_CHUNK_SIZE_JVM = "net.snowflake.jdbc.clientResultChunkSize";
public static final String CLIENT_RESULT_CHUNK_SIZE = "CLIENT_RESULT_CHUNK_SIZE";
public static final String CLIENT_MEMORY_LIMIT_JVM = "net.snowflake.jdbc.clientMemoryLimit";
public static final String CLIENT_MEMORY_LIMIT = "CLIENT_MEMORY_LIMIT";
public static final String CLIENT_PREFETCH_THREADS_JVM = "net.snowflake.jdbc.clientPrefetchThreads";
public static final String CLIENT_PREFETCH_THREADS = "CLIENT_PREFETCH_THREADS";
public static final String CACHE_FILE_NAME = "temporary_credential.json";
public static final String OCSP_FAIL_OPEN_JVM = "net.snowflake.jdbc.ocspFailOpen";
public static final String OCSP_FAIL_OPEN = "ocspFailOpen";
protected static final FileCacheManager fileCacheManager;
static final String SF_HEADER_SERVICE_NAME = "X-Snowflake-Service";
static final
SFLogger logger = SFLoggerFactory.getLogger(SessionUtil.class);
private static final String SF_PATH_LOGIN_REQUEST =
"/session/v1/login-request";
private static final String SF_PATH_TOKEN_REQUEST = "/session/token-request";
private static final String SF_PATH_SESSION = "/session";
private static final String CACHE_DIR_PROP = "net.snowflake.jdbc.temporaryCredentialCacheDir";
private static final String CACHE_DIR_ENV = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR";
private static final long CACHE_EXPIRATION_IN_SECONDS = 86400L;
private static final long CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS = 60L;
private final static Map> ID_TOKEN_CACHE = new HashMap<>();
private final static Object ID_TOKEN_CACHE_LOCK = new Object();
public static long DEFAULT_CLIENT_MEMORY_LIMIT = 1536; // MB
public static Map JVM_PARAMS_TO_PARAMS = new HashMap<>();
private static ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();
private static int DEFAULT_HTTP_CLIENT_CONNECTION_TIMEOUT = 60000; // millisec
private static int DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT = 300000; // millisec
private static int DEFAULT_HEALTH_CHECK_INTERVAL = 45; // sec
private static Set STRING_PARAMS = new HashSet<>(Arrays.asList(
"TIMEZONE",
"TIMESTAMP_OUTPUT_FORMAT",
"TIMESTAMP_NTZ_OUTPUT_FORMAT",
"TIMESTAMP_LTZ_OUTPUT_FORMAT",
"TIMESTAMP_TZ_OUTPUT_FORMAT",
"DATE_OUTPUT_FORMAT",
"TIME_OUTPUT_FORMAT",
"BINARY_OUTPUT_FORMAT",
"CLIENT_TIMESTAMP_TYPE_MAPPING",
SERVICE_NAME));
private static Set INT_PARAMS = new HashSet<>(Arrays.asList(
"CLIENT_RESULT_PREFETCH_SLOTS",
"CLIENT_RESULT_PREFETCH_THREADS",
CLIENT_PREFETCH_THREADS,
CLIENT_MEMORY_LIMIT,
CLIENT_RESULT_CHUNK_SIZE,
"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD",
"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY"));
private static Set BOOLEAN_PARAMS = new HashSet<>(Arrays.asList(
"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ",
"JDBC_EXECUTE_RETURN_COUNT_FOR_DML",
"CLIENT_DISABLE_INCIDENTS",
"CLIENT_SESSION_KEEP_ALIVE",
"CLIENT_TELEMETRY_ENABLED",
CLIENT_STORE_TEMPORARY_CREDENTIAL,
"JDBC_USE_JSON_PARSER",
"AUTOCOMMIT",
"JDBC_EFFICIENT_CHUNK_STORAGE",
JDBC_RS_COLUMN_CASE_INSENSITIVE,
CLIENT_RESULT_COLUMN_CASE_INSENSITIVE,
"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX",
"JDBC_TREAT_DECIMAL_AS_INT",
"JDBC_ENABLE_COMBINED_DESCRIBE"));
static
{
JVM_PARAMS_TO_PARAMS.put(
CLIENT_RESULT_CHUNK_SIZE_JVM, CLIENT_RESULT_CHUNK_SIZE);
JVM_PARAMS_TO_PARAMS.put(
CLIENT_MEMORY_LIMIT_JVM, CLIENT_MEMORY_LIMIT);
JVM_PARAMS_TO_PARAMS.put(
CLIENT_PREFETCH_THREADS_JVM, CLIENT_PREFETCH_THREADS);
JVM_PARAMS_TO_PARAMS.put(
OCSP_FAIL_OPEN_JVM, OCSP_FAIL_OPEN);
}
static
{
fileCacheManager = FileCacheManager
.builder()
.setCacheDirectorySystemProperty(CACHE_DIR_PROP)
.setCacheDirectoryEnvironmentVariable(CACHE_DIR_ENV)
.setBaseCacheFileName(CACHE_FILE_NAME)
.setCacheExpirationInSeconds(CACHE_EXPIRATION_IN_SECONDS)
.setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS).build();
}
/**
* Returns Authenticator type
*
* @param loginInput login information
* @return Authenticator type
*/
static private ClientAuthnDTO.AuthenticatorType getAuthenticator(
LoginInput loginInput)
{
if (loginInput.getAuthenticator() != null)
{
if (loginInput.getAuthenticator().equalsIgnoreCase(
ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name()))
{
// SAML 2.0 compliant service/application
return ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER;
}
else if (loginInput.getAuthenticator().equalsIgnoreCase(
ClientAuthnDTO.AuthenticatorType.OAUTH.name()))
{
// OAuth Authentication
return ClientAuthnDTO.AuthenticatorType.OAUTH;
}
else if (loginInput.getAuthenticator().equalsIgnoreCase(
ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT.name()))
{
return ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT;
}
else if (!loginInput.getAuthenticator().equalsIgnoreCase(
ClientAuthnDTO.AuthenticatorType.SNOWFLAKE.name()))
{
// OKTA authenticator v1. This will be deprecated once externalbrowser
// is in production.
return ClientAuthnDTO.AuthenticatorType.OKTA;
}
}
// authenticator is null, then jdbc will decide authenticator depends on
// if privateKey is specified or not. If yes, authenticator type will be
// SNOWFLAKE_JWT, otherwise it will use SNOWFLAKE.
return loginInput.getPrivateKey() != null ?
ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT :
ClientAuthnDTO.AuthenticatorType.SNOWFLAKE;
}
/**
* Open a new session
*
* @param loginInput login information
* @return information get after login such as token information
* @throws SFException if unexpected uri syntax
* @throws SnowflakeSQLException if failed to establish connection with snowflake
*/
static public LoginOutput openSession(LoginInput loginInput)
throws SFException, SnowflakeSQLException
{
AssertUtil.assertTrue(loginInput.getServerUrl() != null,
"missing server URL for opening session");
AssertUtil.assertTrue(loginInput.getAppId() != null,
"missing app id for opening session");
AssertUtil.assertTrue(loginInput.getLoginTimeout() >= 0,
"negative login timeout for opening session");
final ClientAuthnDTO.AuthenticatorType authenticator = getAuthenticator(
loginInput);
if (!authenticator.equals(ClientAuthnDTO.AuthenticatorType.OAUTH))
{
// OAuth does not require a username
AssertUtil.assertTrue(loginInput.getUserName() != null,
"missing user name for opening session");
}
if (authenticator.equals(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER))
{
// force to set the flag.
loginInput.sessionParameters.put(CLIENT_STORE_TEMPORARY_CREDENTIAL, true);
}
else
{
// TODO: patch for now. We should update mergeProperteis
// to normalize all parameters using STRING_PARAMS, INT_PARAMS and
// BOOLEAN_PARAMS.
Object value = loginInput.sessionParameters.get(
CLIENT_STORE_TEMPORARY_CREDENTIAL);
if (value != null)
{
loginInput.sessionParameters.put(
CLIENT_STORE_TEMPORARY_CREDENTIAL, asBoolean(value));
}
}
boolean isClientStoreTemporaryCredential = asBoolean(
loginInput.sessionParameters.get(CLIENT_STORE_TEMPORARY_CREDENTIAL));
LoginOutput loginOutput;
if (isClientStoreTemporaryCredential &&
(loginOutput = readTemporaryCredential(loginInput)) != null)
{
return loginOutput;
}
return newSession(loginInput);
}
static private LoginOutput readTemporaryCredential(LoginInput loginInput)
throws SFException, SnowflakeSQLException
{
String idToken;
synchronized (ID_TOKEN_CACHE_LOCK)
{
JsonNode res = fileCacheManager.readCacheFile();
readJsonStoreCache(res);
Map userMap = ID_TOKEN_CACHE.get(
loginInput.getAccountName().toUpperCase());
if (userMap == null)
{
return null;
}
idToken = userMap.get(loginInput.getUserName().toUpperCase());
}
if (idToken == null)
{
return null;
}
loginInput.setIdToken(idToken);
try
{
return issueSession(loginInput);
}
catch (SnowflakeReauthenticationRequest ex)
{
logger.debug("The token expired. errorCode. Reauthenticating...");
}
return null;
}
static private void readJsonStoreCache(JsonNode m)
{
if (m == null || !m.getNodeType().equals(JsonNodeType.OBJECT))
{
logger.debug("Invalid cache file format.");
return;
}
for (Iterator> itr = m.fields(); itr.hasNext(); )
{
Map.Entry accountMap = itr.next();
String account = accountMap.getKey();
if (!ID_TOKEN_CACHE.containsKey(account))
{
ID_TOKEN_CACHE.put(account, new HashMap());
}
JsonNode userJsonNode = accountMap.getValue();
for (Iterator> itr0 = userJsonNode.fields(); itr0.hasNext(); )
{
Map.Entry userMap = itr0.next();
ID_TOKEN_CACHE.get(account).put(
userMap.getKey(), userMap.getValue().asText());
}
}
}
static private void writeTemporaryCredential(
LoginInput loginInput, LoginOutput loginOutput)
{
if (Strings.isNullOrEmpty(loginOutput.getIdToken()))
{
return; // no idToken
}
synchronized (ID_TOKEN_CACHE_LOCK)
{
String currentAccount = loginInput.getAccountName().toUpperCase();
Map currentUserMap = ID_TOKEN_CACHE.get(currentAccount);
if (currentUserMap == null)
{
currentUserMap = new HashMap<>();
ID_TOKEN_CACHE.put(currentAccount, currentUserMap);
}
currentUserMap.put(loginInput.getUserName().toUpperCase(), loginOutput.getIdToken());
ObjectNode out = mapper.createObjectNode();
for (Map.Entry> elem : ID_TOKEN_CACHE.entrySet())
{
String account = elem.getKey();
Map userMap = elem.getValue();
ObjectNode userNode = mapper.createObjectNode();
for (Map.Entry elem0 : userMap.entrySet())
{
userNode.put(elem0.getKey(), elem0.getValue());
}
out.set(account, userNode);
}
fileCacheManager.writeCacheFile(out);
}
}
static private boolean asBoolean(Object value)
{
if (value == null)
{
return false;
}
switch (value.getClass().getName())
{
case "java.lang.Boolean":
return (Boolean) value;
case "java.lang.String":
return Boolean.valueOf((String) value);
}
return false;
}
static private LoginOutput newSession(LoginInput loginInput)
throws SFException, SnowflakeSQLException
{
// build URL for login request
URIBuilder uriBuilder;
URI loginURI;
String tokenOrSamlResponse = null;
String samlProofKey = null;
boolean consentCacheIdToken = true;
String sessionToken;
String masterToken;
String sessionDatabase;
String sessionSchema;
String sessionRole;
String sessionWarehouse;
long masterTokenValidityInSeconds;
String remMeToken;
String idToken;
String databaseVersion = null;
int databaseMajorVersion = 0;
int databaseMinorVersion = 0;
String newClientForUpgrade = null;
int healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL;
int httpClientSocketTimeout = loginInput.getSocketTimeout();
final ClientAuthnDTO.AuthenticatorType authenticator = getAuthenticator(
loginInput);
Map commonParams;
try
{
uriBuilder = new URIBuilder(loginInput.getServerUrl());
// add database name and schema name as query parameters
if (loginInput.getDatabaseName() != null)
{
uriBuilder.addParameter(SF_QUERY_DATABASE, loginInput.getDatabaseName());
}
if (loginInput.getSchemaName() != null)
{
uriBuilder.addParameter(SF_QUERY_SCHEMA, loginInput.getSchemaName());
}
if (loginInput.getWarehouse() != null)
{
uriBuilder.addParameter(SF_QUERY_WAREHOUSE, loginInput.getWarehouse());
}
if (loginInput.getRole() != null)
{
uriBuilder.addParameter(SF_QUERY_ROLE, loginInput.getRole());
}
if (authenticator == ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER)
{
// SAML 2.0 compliant service/application
SessionUtilExternalBrowser s =
SessionUtilExternalBrowser.createInstance(loginInput);
s.authenticate();
tokenOrSamlResponse = s.getToken();
samlProofKey = s.getProofKey();
consentCacheIdToken = s.isConsentCacheIdToken();
}
else if (authenticator == ClientAuthnDTO.AuthenticatorType.OKTA)
{
// okta authenticator v1
tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput);
}
else if (authenticator == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT)
{
SessionUtilKeyPair s = new SessionUtilKeyPair(loginInput.getPrivateKey(),
loginInput.getAccountName(),
loginInput.getUserName());
loginInput.setToken(s.issueJwtToken());
}
uriBuilder.addParameter(SF_QUERY_REQUEST_ID, UUID.randomUUID().toString());
uriBuilder.setPath(SF_PATH_LOGIN_REQUEST);
loginURI = uriBuilder.build();
}
catch (URISyntaxException ex)
{
logger.error("Exception when building URL", ex);
throw new SFException(ex, ErrorCode.INTERNAL_ERROR,
"unexpected URI syntax exception:1");
}
if (loginInput.getServerUrl().indexOf(".privatelink.snowflakecomputing.com") > 0)
{
// Privatelink uses special OCSP Cache server
try
{
URL url = new URL(loginInput.getServerUrl());
String host = url.getHost();
logger.debug("HOST: {}", host);
String ocspCacheServerUrl = String.format(
"http://ocsp%s/%s",
host.substring(host.indexOf('.')),
SFTrustManager.CACHE_FILE_NAME);
logger.debug("OCSP Cache Server for Privatelink: {}",
ocspCacheServerUrl);
resetOCSPResponseCacherServerURL(ocspCacheServerUrl);
}
catch (IOException ex)
{
throw new SFException(ex, ErrorCode.INTERNAL_ERROR,
"unexpected URL syntax exception");
}
}
HttpPost postRequest = null;
try
{
ClientAuthnDTO authnData = new ClientAuthnDTO();
Map data = new HashMap<>();
data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId());
/*
* username is always included regardless of authenticator to identify
* the user.
*/
data.put(ClientAuthnParameter.LOGIN_NAME.name(),
loginInput.getUserName());
/*
* only include password information in the request to GS if federated
* authentication method is not specified.
* When specified, this password information is really to be used to
* authenticate with the IDP provider only, and GS should not have any
* trace for this information.
*/
if (authenticator == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE)
{
data.put(ClientAuthnParameter.PASSWORD.name(), loginInput.getPassword());
}
else if (authenticator == ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER)
{
data.put(ClientAuthnParameter.AUTHENTICATOR.name(),
ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name());
data.put(ClientAuthnParameter.PROOF_KEY.name(), samlProofKey);
data.put(ClientAuthnParameter.TOKEN.name(), tokenOrSamlResponse);
}
else if (authenticator == ClientAuthnDTO.AuthenticatorType.OKTA)
{
data.put(ClientAuthnParameter.RAW_SAML_RESPONSE.name(), tokenOrSamlResponse);
}
else if (authenticator == ClientAuthnDTO.AuthenticatorType.OAUTH ||
authenticator == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT)
{
data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticator.name());
data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getToken());
}
Map clientEnv = new HashMap();
clientEnv.put("OS", System.getProperty("os.name"));
clientEnv.put("OS_VERSION", System.getProperty("os.version"));
clientEnv.put("JAVA_VERSION", System.getProperty("java.version"));
clientEnv.put("JAVA_RUNTIME", System.getProperty("java.runtime.name"));
clientEnv.put("JAVA_VM", System.getProperty("java.vm.name"));
clientEnv.put("OCSP_MODE", loginInput.getOCSPMode().name());
if (loginInput.getApplication() != null)
{
clientEnv.put("APPLICATION", loginInput.getApplication());
}
else
{
// When you add new client environment info, please add new keys to
// messages_en_US.src.json so that they can be displayed properly in UI
// detect app name
String appName = System.getProperty("sun.java.command");
// remove the arguments
if (appName != null)
{
if (appName.indexOf(" ") > 0)
{
appName = appName.substring(0, appName.indexOf(" "));
}
clientEnv.put("APPLICATION", appName);
}
}
// add properties from client info
Properties clientInfo = loginInput.getClientInfo();
if (clientInfo != null)
{
for (Map.Entry property : clientInfo.entrySet())
{
if (property != null && property.getKey() != null &&
property.getValue() != null)
{
clientEnv.put(property.getKey().toString(),
property.getValue().toString());
}
}
}
// SNOW-20103: track additional client info in session
String clientInfoJSONStr = System.getProperty("snowflake.client.info");
if (clientInfoJSONStr != null)
{
JsonNode clientInfoJSON = null;
try
{
clientInfoJSON = mapper.readTree(clientInfoJSONStr);
}
catch (Throwable ex)
{
logger.debug(
"failed to process snowflake.client.info property as JSON: {}"
, clientInfoJSONStr, ex);
}
if (clientInfoJSON != null)
{
Iterator> fields = clientInfoJSON.fields();
while (fields.hasNext())
{
Map.Entry field = fields.next();
clientEnv.put(field.getKey(), field.getValue().asText());
}
}
}
data.put(ClientAuthnParameter.CLIENT_ENVIRONMENT.name(), clientEnv);
// Initialize the session parameters
Map sessionParameter = loginInput.getSessionParameters();
if (sessionParameter != null)
{
data.put(ClientAuthnParameter.SESSION_PARAMETERS.name(), loginInput
.getSessionParameters());
}
if (loginInput.getAccountName() != null)
{
data.put(ClientAuthnParameter.ACCOUNT_NAME.name(),
loginInput.getAccountName());
}
// Second Factor Authentication
if (loginInput.isPasscodeInPassword())
{
data.put(ClientAuthnParameter.EXT_AUTHN_DUO_METHOD.name(), "passcode");
}
else if (loginInput.getPasscode() != null)
{
data.put(ClientAuthnParameter.EXT_AUTHN_DUO_METHOD.name(), "passcode");
data.put(ClientAuthnParameter.PASSCODE.name(),
loginInput.getPasscode());
}
else
{
data.put(ClientAuthnParameter.EXT_AUTHN_DUO_METHOD.name(), "push");
}
data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(),
loginInput.getAppVersion());
authnData.setData(data);
String json = mapper.writeValueAsString(authnData);
postRequest = new HttpPost(loginURI);
// attach the login info json body to the post request
StringEntity input = new StringEntity(json, Charset.forName("UTF-8"));
input.setContentType("application/json");
postRequest.setEntity(input);
postRequest.addHeader("accept", "application/json");
/*
* HttpClient should take authorization header from char[] instead of
* String.
*/
postRequest.setHeader(SF_HEADER_AUTHORIZATION,
SF_HEADER_BASIC_AUTHTYPE);
setServiceNameHeader(loginInput, postRequest);
String theString = HttpUtil.executeRequest(postRequest,
loginInput.getLoginTimeout(),
0, null);
// general method, same as with data binding
JsonNode jsonNode = mapper.readTree(theString);
// check the success field first
if (!jsonNode.path("success").asBoolean())
{
logger.debug("response = {}", theString);
String errorCode = jsonNode.path("code").asText();
throw new SnowflakeSQLException(
SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
ErrorCode.CONNECTION_ERROR.getMessageCode(),
errorCode, jsonNode.path("message").asText());
}
// session token is in the data field of the returned json response
sessionToken = jsonNode.path("data").path("token").asText();
masterToken = jsonNode.path("data").path("masterToken").asText();
remMeToken = jsonNode.path("data").path("remMeToken").asText();
idToken = nullStringAsEmptyString(
jsonNode.path("data").path("idToken").asText());
masterTokenValidityInSeconds = jsonNode.path("data").
path("masterValidityInSeconds").asLong();
String serverVersion =
jsonNode.path("data").path("serverVersion").asText();
JsonNode dbNode = jsonNode.path("data").path("sessionInfo").path("databaseName");
sessionDatabase = dbNode.isNull() ? null : dbNode.asText();
JsonNode schemaNode = jsonNode.path("data").path("sessionInfo").path("schemaName");
sessionSchema = schemaNode.isNull() ? null : schemaNode.asText();
JsonNode roleNode = jsonNode.path("data").path("sessionInfo").path("roleName");
sessionRole = roleNode.isNull() ? null : roleNode.asText();
JsonNode warehouseNode = jsonNode.path("data").path("sessionInfo").path("warehouseName");
sessionWarehouse = warehouseNode.isNull() ? null : warehouseNode.asText();
commonParams =
SessionUtil.getCommonParams(jsonNode.path("data").path("parameters"));
if (serverVersion != null)
{
logger.debug("server version = {}", serverVersion);
if (serverVersion.indexOf(" ") > 0)
{
databaseVersion = serverVersion.substring(0,
serverVersion.indexOf(" "));
}
else
{
databaseVersion = serverVersion;
}
}
else
{
logger.debug("server version is null");
}
if (databaseVersion != null)
{
String[] components = databaseVersion.split("\\.");
if (components != null && components.length >= 2)
{
try
{
databaseMajorVersion = Integer.parseInt(components[0]);
databaseMinorVersion = Integer.parseInt(components[1]);
}
catch (Exception ex)
{
logger.error("Exception encountered when parsing server "
+ "version: {} Exception: {}",
databaseVersion, ex.getMessage());
}
}
}
else
{
logger.debug("database version is null");
}
if (!jsonNode.path("data").path("newClientForUpgrade").isNull())
{
newClientForUpgrade =
jsonNode.path("data").path("newClientForUpgrade").asText();
logger.debug("new client: {}", newClientForUpgrade);
}
// get health check interval and adjust network timeouts if different
int healthCheckIntervalFromGS =
jsonNode.path("data").path("healthCheckInterval").asInt();
logger.debug(
"health check interval = {}", healthCheckIntervalFromGS);
if (healthCheckIntervalFromGS > 0 &&
healthCheckIntervalFromGS != healthCheckInterval)
{
// add health check interval to socket timeout
httpClientSocketTimeout = loginInput.getSocketTimeout() +
(healthCheckIntervalFromGS * 1000);
final HttpParams httpParams = new BasicHttpParams();
// set timeout so that we don't wait forever
HttpConnectionParams.setConnectionTimeout(httpParams,
loginInput.getConnectionTimeout());
HttpConnectionParams.setSoTimeout(httpParams,
httpClientSocketTimeout);
((SystemDefaultHttpClient) HttpUtil.getHttpClient()).setParams(httpParams);
logger.debug(
"adjusted connection timeout to = {}",
loginInput.getConnectionTimeout());
logger.debug(
"adjusted socket timeout to = {}", httpClientSocketTimeout);
}
}
catch (SnowflakeSQLException ex)
{
throw ex; // must catch here to avoid Throwable to get the exception
}
catch (IOException ex)
{
logger.error("IOException when creating session: " +
postRequest, ex);
throw new SnowflakeSQLException(ex, SqlState.IO_ERROR,
ErrorCode.NETWORK_ERROR.getMessageCode(),
"Exception encountered when opening connection: " +
ex.getMessage());
}
catch (Throwable ex)
{
logger.error("Exception when creating session: " +
postRequest, ex);
throw new SnowflakeSQLException(ex,
SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
ErrorCode.CONNECTION_ERROR.getMessageCode(),
ErrorCode.CONNECTION_ERROR.getMessageCode(), ex.getMessage());
}
LoginOutput ret = new LoginOutput(sessionToken,
masterToken,
masterTokenValidityInSeconds,
remMeToken,
idToken,
databaseVersion,
databaseMajorVersion,
databaseMinorVersion,
httpClientSocketTimeout,
sessionDatabase,
sessionSchema,
sessionRole,
sessionWarehouse,
commonParams);
ret.setUpdatedByTokenRequest(false);
if (consentCacheIdToken)
{
writeTemporaryCredential(loginInput, ret);
}
return ret;
}
private static void setServiceNameHeader(LoginInput loginInput, HttpPost postRequest)
{
if (!Strings.isNullOrEmpty(loginInput.getServiceName()))
{
// service name is used to route a request to appropriate cluster.
postRequest.setHeader(SF_HEADER_SERVICE_NAME,
loginInput.getServiceName());
}
}
static private String nullStringAsEmptyString(String value)
{
if (Strings.isNullOrEmpty(value) || "null".equals(value))
{
return "";
}
return value;
}
/**
* Delete the id token cache
*/
static public void deleteIdTokenCache()
{
fileCacheManager.deleteCacheFile();
}
/**
* Renew a session.
*
* Use cases:
* - Session and Master tokens are provided. No Id token:
* - succeed in getting a new Session token.
* - fail and raise SnowflakeReauthenticationRequest because Master
* token expires. Since no id token exists, the exception is thrown
* to the upstream.
* - Session and Id tokens are provided. No Master token:
* - fail and raise SnowflakeReauthenticationRequest and
* issue a new Session token
* - fail and raise SnowflakeReauthenticationRequest and fail
* to issue a new Session token as the
*
* @param loginInput login information
* @return login output
* @throws SFException if unexpected uri information
* @throws SnowflakeSQLException if failed to renew the session
*/
static public LoginOutput renewSession(LoginInput loginInput)
throws SFException, SnowflakeSQLException
{
try
{
return tokenRequest(loginInput, TokenRequestType.RENEW);
}
catch (SnowflakeReauthenticationRequest ex)
{
if (Strings.isNullOrEmpty(loginInput.getIdToken()))
{
throw ex;
}
return tokenRequest(loginInput, TokenRequestType.ISSUE);
}
}
/**
* Issue a session
*
* @param loginInput login information
* @return login output
* @throws SFException if unexpected uri information
* @throws SnowflakeSQLException if failed to renew the session
*/
static public LoginOutput issueSession(LoginInput loginInput)
throws SFException, SnowflakeSQLException
{
return tokenRequest(loginInput, TokenRequestType.ISSUE);
}
static private LoginOutput tokenRequest(
LoginInput loginInput, TokenRequestType requestType)
throws SFException, SnowflakeSQLException
{
AssertUtil.assertTrue(loginInput.getServerUrl() != null,
"missing server URL for tokenRequest");
if (requestType == TokenRequestType.RENEW)
{
AssertUtil.assertTrue(loginInput.getMasterToken() != null,
"missing master token for tokenRequest");
AssertUtil.assertTrue(loginInput.getSessionToken() != null,
"missing session token for tokenRequest");
}
else if (requestType == TokenRequestType.ISSUE)
{
AssertUtil.assertTrue(loginInput.getIdToken() != null,
"missing id token for tokenRequest");
}
AssertUtil.assertTrue(loginInput.getLoginTimeout() >= 0,
"negative login timeout for tokenRequest");
// build URL for login request
URIBuilder uriBuilder;
HttpPost postRequest;
String sessionToken;
String masterToken;
try
{
uriBuilder = new URIBuilder(loginInput.getServerUrl());
uriBuilder.setPath(SF_PATH_TOKEN_REQUEST);
uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID,
UUID.randomUUID().toString());
postRequest = new HttpPost(uriBuilder.build());
}
catch (URISyntaxException ex)
{
logger.error("Exception when creating http request", ex);
throw new SFException(ex, ErrorCode.INTERNAL_ERROR,
"unexpected URI syntax exception:3");
}
try
{
// input json with old session token and request type, notice the
// session token needs to be quoted.
Map payload = new HashMap<>();
String headerToken;
if (requestType == TokenRequestType.RENEW)
{
headerToken = loginInput.getMasterToken();
payload.put("oldSessionToken", loginInput.getSessionToken());
}
else
{
headerToken = loginInput.getIdToken();
payload.put("idToken", loginInput.getIdToken());
}
payload.put("requestType", requestType.value);
String json = mapper.writeValueAsString(payload);
// attach the login info json body to the post request
StringEntity input = new StringEntity(json, Charset.forName("UTF-8"));
input.setContentType("application/json");
postRequest.setEntity(input);
postRequest.addHeader("accept", "application/json");
postRequest.setHeader(
SF_HEADER_AUTHORIZATION,
SF_HEADER_SNOWFLAKE_AUTHTYPE + " " +
SF_HEADER_TOKEN_TAG + "=\"" + headerToken + "\"");
setServiceNameHeader(loginInput, postRequest);
logger.debug(
"request type: {}, old session token: {}, " +
"master token: {}, id token: {}",
requestType.value,
(ArgSupplier) () -> loginInput.getSessionToken() != null ? "******" : null,
(ArgSupplier) () -> loginInput.getMasterToken() != null ? "******" : null,
(ArgSupplier) () -> loginInput.getIdToken() != null ? "******" : null);
String theString = HttpUtil.executeRequest(postRequest,
loginInput.getLoginTimeout(), 0, null);
// general method, same as with data binding
JsonNode jsonNode = mapper.readTree(theString);
// check the success field first
if (!jsonNode.path("success").asBoolean())
{
logger.debug("response = {}", theString);
String errorCode = jsonNode.path("code").asText();
String message = jsonNode.path("message").asText();
EventUtil.triggerBasicEvent(
Event.EventType.NETWORK_ERROR,
"SessionUtil:renewSession failure, error code="
+ errorCode + ", message=" + message,
true);
SnowflakeUtil.checkErrorAndThrowExceptionIncludingReauth(jsonNode);
}
// session token is in the data field of the returned json response
sessionToken = jsonNode.path("data").path("sessionToken").asText();
masterToken = jsonNode.path("data").path("masterToken").asText();
}
catch (IOException ex)
{
logger.error("IOException when renewing session: " +
postRequest, ex);
// Any EventType.NETWORK_ERRORs should have been triggered before
// exception was thrown.
throw new SFException(ex, ErrorCode.NETWORK_ERROR, ex.getMessage());
}
LoginOutput loginOutput = new LoginOutput();
loginOutput
.setSessionToken(sessionToken)
.setMasterToken(masterToken)
.setUpdatedByTokenRequest(true)
.setUpdatedByTokenRequestIssue(requestType == TokenRequestType.ISSUE);
return loginOutput;
}
/**
* Close a session
*
* @param loginInput login information
* @throws SnowflakeSQLException if failed to close session
* @throws SFException if failed to close session
*/
static public void closeSession(LoginInput loginInput)
throws SFException, SnowflakeSQLException
{
logger.debug(" public void close() throws SFException");
// assert the following inputs are valid
AssertUtil.assertTrue(loginInput.getServerUrl() != null,
"missing server URL for closing session");
AssertUtil.assertTrue(loginInput.getSessionToken() != null,
"missing session token for closing session");
AssertUtil.assertTrue(loginInput.getLoginTimeout() >= 0,
"missing login timeout for closing session");
HttpPost postRequest = null;
try
{
URIBuilder uriBuilder;
uriBuilder = new URIBuilder(loginInput.getServerUrl());
uriBuilder.addParameter(SF_QUERY_SESSION_DELETE, "true");
uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID,
UUID.randomUUID().toString());
uriBuilder.setPath(SF_PATH_SESSION);
postRequest = new HttpPost(uriBuilder.build());
postRequest.setHeader(SF_HEADER_AUTHORIZATION,
SF_HEADER_SNOWFLAKE_AUTHTYPE + " "
+ SF_HEADER_TOKEN_TAG + "=\""
+ loginInput.getSessionToken() + "\"");
setServiceNameHeader(loginInput, postRequest);
String theString = HttpUtil.executeRequest(postRequest,
loginInput.getLoginTimeout(),
0, null);
JsonNode rootNode;
logger.debug(
"connection close response: {}",
theString);
rootNode = mapper.readTree(theString);
SnowflakeUtil.checkErrorAndThrowException(rootNode);
}
catch (URISyntaxException ex)
{
throw new RuntimeException("unexpected URI syntax exception", ex);
}
catch (IOException ex)
{
logger.error("unexpected IO exception for: " + postRequest,
ex);
}
catch (SnowflakeSQLException ex)
{
// ignore session expiration exception
if (ex.getErrorCode() != Constants.SESSION_EXPIRED_GS_CODE)
{
throw ex;
}
}
}
/**
* Given access token, query IDP URL snowflake app to get SAML response
* We also need to perform important client side validation:
* validate the post back url come back with the SAML response
* contains the same prefix as the Snowflake's server url, which is the
* intended destination url to Snowflake.
* Explanation:
* This emulates the behavior of IDP initiated login flow in the user
* browser where the IDP instructs the browser to POST the SAML
* assertion to the specific SP endpoint. This is critical in
* preventing a SAML assertion issued to one SP from being sent to
* another SP.
*
* @param loginInput
* @param ssoUrl
* @param oneTimeToken
* @return
* @throws SnowflakeSQLException
*/
private static String federatedFlowStep4(
LoginInput loginInput,
String ssoUrl,
String oneTimeToken) throws SnowflakeSQLException
{
String responseHtml = "";
try
{
final URL url = new URL(ssoUrl);
URI oktaGetUri = new URIBuilder()
.setScheme(url.getProtocol())
.setHost(url.getHost())
.setPath(url.getPath())
.setParameter("RelayState", "%2Fsome%2Fdeep%2Flink")
.setParameter("onetimetoken", oneTimeToken).build();
HttpGet httpGet = new HttpGet(oktaGetUri);
HeaderGroup headers = new HeaderGroup();
headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, "*/*"));
httpGet.setHeaders(headers.getAllHeaders());
responseHtml = HttpUtil.executeRequest(httpGet,
loginInput.getLoginTimeout(), 0, null);
// step 5
String postBackUrl = getPostBackUrlFromHTML(responseHtml);
if (!isPrefixEqual(postBackUrl, loginInput.getServerUrl()))
{
logger.debug("The specified authenticator {} and the destination URL " +
"in the SAML assertion {} do not match.",
loginInput.getAuthenticator(), postBackUrl);
throw new SnowflakeSQLException(
SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
ErrorCode.IDP_INCORRECT_DESTINATION.getMessageCode());
}
}
catch (IOException | URISyntaxException ex)
{
handleFederatedFlowError(loginInput, ex);
}
return responseHtml;
}
/**
* Query IDP token url to authenticate and retrieve access token
*
* @param loginInput
* @param tokenUrl
* @return
* @throws SnowflakeSQLException
*/
private static String federatedFlowStep3(LoginInput loginInput, String tokenUrl)
throws SnowflakeSQLException
{
String oneTimeToken = "";
try
{
URL url = new URL(tokenUrl);
URI tokenUri = url.toURI();
final HttpPost postRequest = new HttpPost(tokenUri);
StringEntity params = new StringEntity("{\"username\":\"" +
loginInput.getUserName() + "\",\"password\":\"" +
loginInput.getPassword() + "\"}");
postRequest.setEntity(params);
HeaderGroup headers = new HeaderGroup();
headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, "application/json"));
headers.addHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"));
postRequest.setHeaders(headers.getAllHeaders());
final String idpResponse = HttpUtil.executeRequestWithoutCookies(postRequest,
loginInput.getLoginTimeout(), 0, null);
logger.debug("user is authenticated against {}.",
loginInput.getAuthenticator());
// session token is in the data field of the returned json response
final JsonNode jsonNode = mapper.readTree(idpResponse);
oneTimeToken = jsonNode.get("cookieToken").asText();
}
catch (IOException | URISyntaxException ex)
{
handleFederatedFlowError(loginInput, ex);
}
return oneTimeToken;
}
/**
* Perform important client side validation:
* validate both token url and sso url contains same prefix
* (protocol + host + port) as the given authenticator url.
* Explanation:
* This provides a way for the user to 'authenticate' the IDP it is
* sending his/her credentials to. Without such a check, the user could
* be coerced to provide credentials to an IDP impersonator.
*
* @param loginInput
* @param tokenUrl
* @param ssoUrl
* @throws SnowflakeSQLException
*/
private static void federatedFlowStep2(
LoginInput loginInput,
String tokenUrl,
String ssoUrl) throws SnowflakeSQLException
{
try
{
if (!isPrefixEqual(loginInput.getAuthenticator(), tokenUrl) ||
!isPrefixEqual(loginInput.getAuthenticator(), ssoUrl))
{
logger.debug("The specified authenticator {} is not supported.",
loginInput.getAuthenticator());
throw new SnowflakeSQLException(
SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
ErrorCode.IDP_CONNECTION_ERROR.getMessageCode());
}
}
catch (MalformedURLException ex)
{
handleFederatedFlowError(loginInput, ex);
}
}
/**
* Query Snowflake to obtain IDP token url and IDP SSO url
*
* @param loginInput
* @throws SnowflakeSQLException
*/
private static JsonNode federatedFlowStep1(LoginInput loginInput)
throws SnowflakeSQLException
{
JsonNode dataNode = null;
try
{
URIBuilder fedUriBuilder = new URIBuilder(loginInput.getServerUrl());
fedUriBuilder.setPath(SF_PATH_AUTHENTICATOR_REQUEST);
URI fedUrlUri = fedUriBuilder.build();
Map data = new HashMap<>();
data.put(ClientAuthnParameter.ACCOUNT_NAME.name(),
loginInput.getAccountName());
data.put(ClientAuthnParameter.AUTHENTICATOR.name(),
loginInput.getAuthenticator());
data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId());
data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(),
loginInput.getAppVersion());
ClientAuthnDTO authnData = new ClientAuthnDTO();
authnData.setData(data);
String json = mapper.writeValueAsString(authnData);
// attach the login info json body to the post request
StringEntity input = new StringEntity(json, Charset.forName("UTF-8"));
input.setContentType("application/json");
HttpPost postRequest = new HttpPost(fedUrlUri);
postRequest.setEntity(input);
postRequest.addHeader("accept", "application/json");
final String gsResponse = HttpUtil.executeRequest(postRequest,
loginInput.getLoginTimeout(), 0, null);
logger.debug("authenticator-request response: {}", gsResponse);
JsonNode jsonNode = mapper.readTree(gsResponse);
// check the success field first
if (!jsonNode.path("success").asBoolean())
{
logger.debug("response = {}", gsResponse);
String errorCode = jsonNode.path("code").asText();
throw new SnowflakeSQLException(
SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
ErrorCode.CONNECTION_ERROR.getMessageCode(),
errorCode, jsonNode.path("message").asText());
}
// session token is in the data field of the returned json response
dataNode = jsonNode.path("data");
}
catch (IOException | URISyntaxException ex)
{
handleFederatedFlowError(loginInput, ex);
}
return dataNode;
}
/**
* Logs an error generated during the federated authentication flow and
* re-throws it as a SnowflakeSQLException.
* Note that we seperate IOExceptions since those tend to be network related.
*
* @param loginInput
* @param ex
* @throws SnowflakeSQLException
*/
private static void handleFederatedFlowError(LoginInput loginInput, Exception ex)
throws SnowflakeSQLException
{
if (ex instanceof IOException)
{
logger.error("IOException when authenticating with " +
loginInput.getAuthenticator(), ex);
throw new SnowflakeSQLException(ex, SqlState.IO_ERROR,
ErrorCode.NETWORK_ERROR.getMessageCode(),
"Exception encountered when opening connection: " +
ex.getMessage());
}
logger.error("Exception when authenticating with " +
loginInput.getAuthenticator(), ex);
throw new SnowflakeSQLException(ex,
SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
ErrorCode.CONNECTION_ERROR.getMessageCode(),
ErrorCode.CONNECTION_ERROR.getMessageCode(), ex.getMessage());
}
/**
* FEDERATED FLOW
* See SNOW-27798 for additional details.
*
* @return saml response
* @throws SnowflakeSQLException
*/
static private String getSamlResponseUsingOkta(LoginInput loginInput)
throws SnowflakeSQLException
{
JsonNode dataNode = federatedFlowStep1(loginInput);
String tokenUrl = dataNode.path("tokenUrl").asText();
String ssoUrl = dataNode.path("ssoUrl").asText();
federatedFlowStep2(loginInput, tokenUrl, ssoUrl);
final String oneTimeToken = federatedFlowStep3(loginInput, tokenUrl);
final String responseHtml = federatedFlowStep4(
loginInput, ssoUrl, oneTimeToken);
return responseHtml;
}
/**
* Verify if two input urls have the same protocol, host, and port.
*
* @param aUrlStr a source URL string
* @param bUrlStr a target URL string
* @return true if matched otherwise false
* @throws MalformedURLException raises if a URL string is not valid.
*/
static boolean isPrefixEqual(String aUrlStr, String bUrlStr)
throws MalformedURLException
{
URL aUrl = new URL(aUrlStr);
URL bUrl = new URL(bUrlStr);
int aPort = aUrl.getPort();
int bPort = bUrl.getPort();
if (aPort == -1 && "https".equals(aUrl.getProtocol()))
{
// default port number for HTTPS
aPort = 443;
}
if (bPort == -1 && "https".equals(bUrl.getProtocol()))
{
// default port number for HTTPS
bPort = 443;
}
// no default port number for HTTP is supported.
return aUrl.getHost().equalsIgnoreCase(bUrl.getHost()) &&
aUrl.getProtocol().equalsIgnoreCase(bUrl.getProtocol()) &&
aPort == bPort;
}
/**
* Extracts post back url from the HTML returned by the IDP
*
* @param html
* @return
*/
static private String getPostBackUrlFromHTML(String html)
{
Document doc = Jsoup.parse(html);
Elements e1 = doc.getElementsByTag("body");
Elements e2 = e1.get(0).getElementsByTag("form");
String postBackUrl = e2.first().attr("action");
return postBackUrl;
}
/**
* Helper function to parse a JsonNode from a GS response
* containing CommonParameters, emitting an EnumMap of parameters
*
* @param paramsNode parameters in JSON form
* @return map object including key and value pairs
*/
public static Map getCommonParams(JsonNode paramsNode)
{
Map parameters = new HashMap<>();
for (JsonNode child : paramsNode)
{
// If there isn't a name then the response from GS must be erroneous.
if (!child.hasNonNull("name"))
{
logger.error("Common Parameter JsonNode encountered with "
+ "no parameter name!");
continue;
}
// Look up the parameter based on the "name" attribute of the node.
String paramName = child.path("name").asText();
// What type of value is it and what's the value?
if (!child.hasNonNull("value"))
{
logger.debug("No value found for Common Parameter {}",
child.path("name").asText());
continue;
}
if (STRING_PARAMS.contains(paramName.toUpperCase()))
{
parameters.put(paramName, child.path("value").asText());
}
else if (INT_PARAMS.contains(paramName.toUpperCase()))
{
parameters.put(paramName, child.path("value").asInt());
}
else if (BOOLEAN_PARAMS.contains(paramName.toUpperCase()))
{
parameters.put(paramName, child.path("value").asBoolean());
}
else
{
logger.debug("Unknown Common Parameter: {}", paramName);
}
logger.debug("Parameter {}: {}",
paramName, child.path("value").asText());
}
return parameters;
}
public static void updateSfDriverParamValues(
Map parameters,
SFSession session)
{
for (Map.Entry entry : parameters.entrySet())
{
logger.debug("processing parameter {}", entry.getKey());
if ("CLIENT_DISABLE_INCIDENTS".equalsIgnoreCase(entry.getKey()))
{
SnowflakeDriver.setDisableIncidents((Boolean) entry.getValue());
}
else if (
"JDBC_EXECUTE_RETURN_COUNT_FOR_DML".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setExecuteReturnCountForDML((Boolean) entry.getValue());
}
}
else if (
"CLIENT_SESSION_KEEP_ALIVE".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setEnableHeartbeat((Boolean) entry.getValue());
}
}
else if (
"AUTOCOMMIT".equalsIgnoreCase(entry.getKey()))
{
boolean autoCommit = (Boolean) entry.getValue();
if (session != null && session.getAutoCommit() != autoCommit)
{
session.setAutoCommit(autoCommit);
}
}
else if (
JDBC_RS_COLUMN_CASE_INSENSITIVE.equalsIgnoreCase(entry.getKey()) ||
CLIENT_RESULT_COLUMN_CASE_INSENSITIVE.equalsIgnoreCase(entry.getKey()))
{
if (session != null && !session.isResultColumnCaseInsensitive())
{
session.setResultColumnCaseInsensitive((boolean) entry.getValue());
}
}
else if ("CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setMetadataRequestUseConnectionCtx((boolean) entry.getValue());
}
}
else if ("CLIENT_TIMESTAMP_TYPE_MAPPING".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setTimestampMappedType(SnowflakeType.valueOf(
((String) entry.getValue()).toUpperCase()));
}
}
else if ("JDBC_TREAT_DECIMAL_AS_INT".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setJdbcTreatDecimalAsInt((boolean) entry.getValue());
}
}
else if ("JDBC_ENABLE_COMBINED_DESCRIBE".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setEnableCombineDescribe((boolean) entry.getValue());
}
}
else if ("CLIENT_TELEMETRY_ENABLED".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setClientTelemetryEnabled((boolean) entry.getValue());
}
}
else if ("CLIENT_STAGE_ARRAY_BINDING_THRESHOLD".equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setArrayBindStageThreshold((int) entry.getValue());
}
}
else if (CLIENT_STORE_TEMPORARY_CREDENTIAL.equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setStoreTemporaryCredential((boolean) entry.getValue());
}
}
else if (SERVICE_NAME.equalsIgnoreCase(entry.getKey()))
{
if (session != null)
{
session.setServiceName((String) entry.getValue());
}
}
}
}
enum TokenRequestType
{
RENEW("RENEW"),
CLONE("CLONE"),
ISSUE("ISSUE");
private String value;
TokenRequestType(String value)
{
this.value = value;
}
}
/**
* A class for holding all information required for login
*/
public static class LoginInput
{
private String serverUrl;
private String databaseName;
private String schemaName;
private String warehouse;
private String role;
private String authenticator;
private String accountName;
private int loginTimeout = -1; // default is invalid
private String userName;
private String password;
private Properties clientInfo;
private boolean passcodeInPassword;
private String passcode;
private String token;
private int connectionTimeout = DEFAULT_HTTP_CLIENT_CONNECTION_TIMEOUT;
private int socketTimeout = DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT;
private String appId;
private String appVersion;
private String sessionToken;
private String masterToken;
private Map sessionParameters;
private PrivateKey privateKey;
private String application;
private String idToken;
private String serviceName;
private OCSPMode ocspMode;
public LoginInput()
{
}
public String getServerUrl()
{
return serverUrl;
}
public LoginInput setServerUrl(String serverUrl)
{
this.serverUrl = serverUrl;
return this;
}
public String getDatabaseName()
{
return databaseName;
}
public LoginInput setDatabaseName(String databaseName)
{
this.databaseName = databaseName;
return this;
}
public String getSchemaName()
{
return schemaName;
}
public LoginInput setSchemaName(String schemaName)
{
this.schemaName = schemaName;
return this;
}
public String getWarehouse()
{
return warehouse;
}
public LoginInput setWarehouse(String warehouse)
{
this.warehouse = warehouse;
return this;
}
public String getRole()
{
return role;
}
public LoginInput setRole(String role)
{
this.role = role;
return this;
}
public String getAuthenticator()
{
return authenticator;
}
public LoginInput setAuthenticator(String authenticator)
{
this.authenticator = authenticator;
return this;
}
public String getAccountName()
{
return accountName;
}
public LoginInput setAccountName(String accountName)
{
this.accountName = accountName;
return this;
}
public int getLoginTimeout()
{
return loginTimeout;
}
public LoginInput setLoginTimeout(int loginTimeout)
{
this.loginTimeout = loginTimeout;
return this;
}
public String getUserName()
{
return userName;
}
public LoginInput setUserName(String userName)
{
this.userName = userName;
return this;
}
public String getPassword()
{
return password;
}
public LoginInput setPassword(String password)
{
this.password = password;
return this;
}
public Properties getClientInfo()
{
return clientInfo;
}
public LoginInput setClientInfo(Properties clientInfo)
{
this.clientInfo = clientInfo;
return this;
}
public String getPasscode()
{
return passcode;
}
public LoginInput setPasscode(String passcode)
{
this.passcode = passcode;
return this;
}
public String getToken()
{
return token;
}
public LoginInput setToken(String token)
{
this.token = token;
return this;
}
public int getConnectionTimeout()
{
return connectionTimeout;
}
public LoginInput setConnectionTimeout(int connectionTimeout)
{
this.connectionTimeout = connectionTimeout;
return this;
}
public int getSocketTimeout()
{
return socketTimeout;
}
public LoginInput setSocketTimeout(int socketTimeout)
{
this.socketTimeout = socketTimeout;
return this;
}
public boolean isPasscodeInPassword()
{
return passcodeInPassword;
}
public LoginInput setPasscodeInPassword(boolean passcodeInPassword)
{
this.passcodeInPassword = passcodeInPassword;
return this;
}
public String getAppId()
{
return appId;
}
public LoginInput setAppId(String appId)
{
this.appId = appId;
return this;
}
public String getAppVersion()
{
return appVersion;
}
public LoginInput setAppVersion(String appVersion)
{
this.appVersion = appVersion;
return this;
}
public String getSessionToken()
{
return sessionToken;
}
public LoginInput setSessionToken(String sessionToken)
{
this.sessionToken = sessionToken;
return this;
}
public String getMasterToken()
{
return masterToken;
}
public LoginInput setMasterToken(String masterToken)
{
this.masterToken = masterToken;
return this;
}
public String getIdToken()
{
return idToken;
}
public LoginInput setIdToken(String idToken)
{
this.idToken = idToken;
return this;
}
public Map getSessionParameters()
{
return sessionParameters;
}
public LoginInput setSessionParameters(Map sessionParameters)
{
this.sessionParameters = sessionParameters;
return this;
}
public PrivateKey getPrivateKey()
{
return privateKey;
}
public LoginInput setPrivateKey(PrivateKey privateKey)
{
this.privateKey = privateKey;
return this;
}
public String getApplication()
{
return application;
}
public LoginInput setApplication(String application)
{
this.application = application;
return this;
}
public String getServiceName()
{
return serviceName;
}
public LoginInput setServiceName(String serviceName)
{
this.serviceName = serviceName;
return this;
}
public OCSPMode getOCSPMode()
{
return ocspMode;
}
public LoginInput setOCSPMode(OCSPMode ocspMode)
{
this.ocspMode = ocspMode;
return this;
}
}
/**
* Login output information including session tokens, database versions
*/
public static class LoginOutput
{
String sessionToken;
String masterToken;
long masterTokenValidityInSeconds;
String remMeToken;
String idToken;
String databaseVersion;
int databaseMajorVersion;
int databaseMinorVersion;
int httpClientSocketTimeout;
String sessionDatabase;
String sessionSchema;
String sessionRole;
String sessionWarehouse;
Map commonParams;
boolean updatedByTokenRequest;
boolean updatedByTokenRequestIssue;
public LoginOutput()
{
}
public LoginOutput(String sessionToken, String masterToken,
long masterTokenValidityInSeconds,
String remMeToken,
String idToken,
String databaseVersion,
int databaseMajorVersion, int databaseMinorVersion,
int httpClientSocketTimeout,
String sessionDatabase,
String sessionSchema,
String sessionRole,
String sessionWarehouse,
Map commonParams)
{
this.sessionToken = sessionToken;
this.masterToken = masterToken;
this.remMeToken = remMeToken;
this.idToken = idToken;
this.databaseVersion = databaseVersion;
this.databaseMajorVersion = databaseMajorVersion;
this.databaseMinorVersion = databaseMinorVersion;
this.httpClientSocketTimeout = httpClientSocketTimeout;
this.sessionDatabase = sessionDatabase;
this.sessionSchema = sessionSchema;
this.sessionRole = sessionRole;
this.sessionWarehouse = sessionWarehouse;
this.commonParams = commonParams;
this.masterTokenValidityInSeconds = masterTokenValidityInSeconds;
}
public String getSessionToken()
{
return sessionToken;
}
public LoginOutput setSessionToken(String sessionToken)
{
this.sessionToken = sessionToken;
return this;
}
public String getMasterToken()
{
return masterToken;
}
public LoginOutput setMasterToken(String masterToken)
{
this.masterToken = masterToken;
return this;
}
public String getIdToken()
{
return idToken;
}
public LoginOutput setIdToken(String idToken)
{
this.idToken = idToken;
return this;
}
public String getDatabaseVersion()
{
return databaseVersion;
}
public int getDatabaseMajorVersion()
{
return databaseMajorVersion;
}
public int getDatabaseMinorVersion()
{
return databaseMinorVersion;
}
public int getHttpClientSocketTimeout()
{
return httpClientSocketTimeout;
}
public Map getCommonParams()
{
return commonParams;
}
public LoginOutput setCommonParams(Map commonParams)
{
this.commonParams = commonParams;
return this;
}
public String getSessionDatabase()
{
return sessionDatabase;
}
public void setSessionDatabase(String sessionDatabase)
{
this.sessionDatabase = sessionDatabase;
}
public String getSessionSchema()
{
return sessionSchema;
}
public void setSessionSchema(String sessionSchema)
{
this.sessionSchema = sessionSchema;
}
public String getSessionRole()
{
return sessionRole;
}
void setSessionRole(String sessionRole)
{
this.sessionRole = sessionRole;
}
String getSessionWarehouse()
{
return sessionWarehouse;
}
void setSessionWarehouse(String sessionWarehouse)
{
this.sessionWarehouse = sessionWarehouse;
}
public long getMasterTokenValidityInSeconds()
{
return masterTokenValidityInSeconds;
}
boolean isUpdatedByTokenRequest()
{
return updatedByTokenRequest;
}
LoginOutput setUpdatedByTokenRequest(boolean updatedByTokenRequest)
{
this.updatedByTokenRequest = updatedByTokenRequest;
return this;
}
boolean isUpdatedByTokenRequestIssue()
{
return updatedByTokenRequestIssue;
}
LoginOutput setUpdatedByTokenRequestIssue(boolean updatedByTokenRequestIssue)
{
this.updatedByTokenRequestIssue = updatedByTokenRequestIssue;
return this;
}
}
}