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

net.snowflake.client.core.SessionUtil Maven / Gradle / Ivy

There is a newer version: 3.19.0
Show newest version
/*
 * 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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy