snowflake-sdk
Advanced tools
Comparing version 1.11.0 to 1.12.0
214
index.d.ts
@@ -10,48 +10,4 @@ /* | ||
declare module 'snowflake-sdk' { | ||
export const enum RowMode { | ||
ARRAY = 'array', | ||
OBJECT = 'object', | ||
OBJECT_WITH_RENAMED_DUPLICATED_COLUMNS = 'object_with_renamed_duplicated_columns', | ||
} | ||
export const enum LogLevel { | ||
ERROR = 'ERROR', | ||
WARN = 'WARN', | ||
INFO = 'INFO', | ||
DEBUG = 'DEBUG', | ||
TRACE = 'TRACE', | ||
} | ||
export const enum DataType { | ||
String = 'String', | ||
Boolean = 'Boolean', | ||
Number = 'Number', | ||
Date = 'Date', | ||
JSON = 'JSON', | ||
Buffer = 'Buffer', | ||
} | ||
const enum StatementStatus { | ||
Fetching = "fetching", | ||
Complete = "complete", | ||
} | ||
const enum QueryStatus { | ||
RUNNING = 'RUNNING', | ||
ABORTING = 'ABORTING', | ||
SUCCESS = 'SUCCESS', | ||
FAILED_WITH_ERROR = 'FAILED_WITH_ERROR', | ||
ABORTED = 'ABORTED', | ||
QUEUED = 'QUEUED', | ||
FAILED_WITH_INCIDENT = 'FAILED_WITH_INCIDENT', | ||
DISCONNECTED = 'DISCONNECTED', | ||
RESUMING_WAREHOUSE = 'RESUMING_WAREHOUSE', | ||
// purposeful typo.Is present in QueryDTO.java | ||
QUEUED_REPAIRING_WAREHOUSE = 'QUEUED_REPARING_WAREHOUSE', | ||
RESTARTED = 'RESTARTED', | ||
BLOCKED = 'BLOCKED', | ||
NO_DATA = 'NO_DATA', | ||
} | ||
const enum ErrorCode { | ||
enum ErrorCode { | ||
// 400001 | ||
@@ -127,2 +83,5 @@ ERR_INTERNAL_ASSERT_FAILED = 400001, | ||
ERR_CONN_CREATE_INVALID_DISABLE_CONSOLE_LOGIN = 404047, | ||
ERR_CONN_CREATE_INVALID_FORCE_GCP_USE_DOWNSCOPED_CREDENTIAL = 404048, | ||
ERR_CONN_CREATE_INVALID_REPRESENT_NULL_AS_STRING_NULL = 404050, | ||
ERR_CONN_CREATE_INVALID_DISABLE_SAML_URL_CHECK = 404051, | ||
@@ -133,5 +92,5 @@ // 405001 | ||
// 405501 | ||
ERR_CONN_CONNECT_STATUS_CONNECTING = 405501, // sql state: 08002 | ||
ERR_CONN_CONNECT_STATUS_CONNECTED = 405502, // sql state: 08002 | ||
ERR_CONN_CONNECT_STATUS_DISCONNECTED = 405503, // sql state: 08002 | ||
ERR_CONN_CONNECT_STATUS_CONNECTING = 405501, // sql state= 08002 | ||
ERR_CONN_CONNECT_STATUS_CONNECTED = 405502, // sql state= 08002 | ||
ERR_CONN_CONNECT_STATUS_DISCONNECTED = 405503, // sql state= 08002 | ||
ERR_CONN_CREATE_INVALID_AUTH_CONNECT = 405504, | ||
@@ -148,4 +107,4 @@ ERR_CONN_CONNECT_INVALID_CLIENT_CONFIG = 405505, | ||
// 407001 | ||
ERR_CONN_REQUEST_STATUS_PRISTINE = 407001, // sql state: 08003 | ||
ERR_CONN_REQUEST_STATUS_DISCONNECTED = 407002, // sql state: 08003 | ||
ERR_CONN_REQUEST_STATUS_PRISTINE = 407001, // sql state= 08003 | ||
ERR_CONN_REQUEST_STATUS_DISCONNECTED = 407002, // sql state= 08003 | ||
@@ -219,3 +178,3 @@ // 408001 | ||
ERR_GET_RESULTS_QUERY_ID_NO_DATA = 460002, | ||
ERR_GET_RESULTS_QUERY_ID_NOT_SUCCESS_STATUS = 460003, | ||
ERR_GET_RESULTS_QUERY_ID_NOT_SUCCESS_STATUS = 460003 | ||
} | ||
@@ -228,3 +187,9 @@ | ||
export type StatementCallback = (err: SnowflakeError | undefined, stmt: RowStatement | FileAndStageBindStatement, rows?: Array<any> | undefined) => void; | ||
export type ConnectionCallback = (err: SnowflakeError | undefined, conn: Connection) => void | ||
export type ConnectionCallback = (err: SnowflakeError | undefined, conn: Connection) => void; | ||
export type RowMode = "object" | "array" | "object_with_renamed_duplicated_columns"; | ||
export type LogLevel = "ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE"; | ||
export type DataType = "String" | "Boolean" | "Number" | "Date" | "JSON" | "Buffer"; | ||
export type QueryStatus = "RUNNING" | "ABORTING" | "SUCCESS" | "FAILED_WITH_ERROR" | "ABORTED" | "QUEUED" | "FAILED_WITH_INCIDENT" | "DISCONNECTED" | "RESUMING_WAREHOUSE" | "QUEUED_REPARING_WAREHOUSE" | "RESTARTED" | "BLOCKED" | "NO_DATA"; | ||
export type StatementStatus = "fetching" | "complete"; | ||
type PoolOptions = import('generic-pool').Options; | ||
@@ -234,2 +199,9 @@ type Readable = import('stream').Readable; | ||
export interface XMlParserConfigOption { | ||
ignoreAttributes?: boolean; | ||
alwaysCreateTextNode?: boolean; | ||
attributeNamePrefix?: string; | ||
attributesGroupName?: false | null | string; | ||
} | ||
export interface ConfigureOptions { | ||
@@ -240,9 +212,14 @@ /** | ||
*/ | ||
logLevel?: LogLevel | undefined; | ||
logFilePath?: string | undefined; | ||
logLevel?: LogLevel; | ||
logFilePath?: string; | ||
/** | ||
* additionalLogToConsole is a Boolean value that indicates whether to send log messages also to the console when a filePath is specified. | ||
*/ | ||
additionalLogToConsole?: boolean | null; | ||
/** | ||
* Check the ocsp checking is off. | ||
*/ | ||
insecureConnect?: boolean | undefined; | ||
insecureConnect?: boolean; | ||
@@ -253,3 +230,3 @@ /** | ||
*/ | ||
ocspFailOpen?: boolean | undefined; | ||
ocspFailOpen?: boolean; | ||
@@ -263,6 +240,8 @@ /** | ||
xmlParserConfig?: XMlParserConfigOption; | ||
/** | ||
* Specifies whether to enable keep-alive functionality on the socket immediately after receiving a new connection request. | ||
*/ | ||
keepAlive?: boolean, | ||
keepAlive?: boolean; | ||
} | ||
@@ -315,2 +294,13 @@ | ||
/** | ||
* Specifies the timeout, in milliseconds, for browser activities related to SSO authentication. The default value is 120000 (milliseconds). | ||
*/ | ||
browserActionTimeout?: number; | ||
/** | ||
* Specifies the lists of hosts that the driver should connect to directly, bypassing the proxy server (e.g. *.amazonaws.com to bypass Amazon S3 access). For multiple hosts, separate the hostnames with a pipe symbol (|). | ||
* You can also use an asterisk as a wild card. For example: noProxy: "*.amazonaws.com|*.my_company.com" | ||
*/ | ||
noProxy?: string; | ||
/** | ||
* Specifies the hostname of an authenticated proxy server. | ||
@@ -321,7 +311,22 @@ */ | ||
/** | ||
* Specifies the username used to connect to an authenticated proxy server. | ||
*/ | ||
proxyUser?: string; | ||
/** | ||
* Specifies the password for the user specified by proxyUser. | ||
*/ | ||
proxyPassword?: string; | ||
/** | ||
* Specifies the port of an authenticated proxy server. | ||
*/ | ||
proxyPort?: number; | ||
/** | ||
* Specifies the protocol used to connect to the authenticated proxy server. Use this property to specify the HTTP protocol: http or https. | ||
*/ | ||
proxyProtocol?: string; | ||
/** | ||
* Specifies the serviceName. | ||
@@ -367,2 +372,7 @@ */ | ||
/** | ||
* Number of milliseconds to keep the connection alive with no response. Default: 90000 (1 minute 30 seconds). | ||
*/ | ||
timeout?: number; | ||
/** | ||
* The default security role to use for the session after connecting. | ||
@@ -385,3 +395,3 @@ */ | ||
*/ | ||
fetchAsString?: DataType[] | undefined; | ||
fetchAsString?: DataType[]; | ||
@@ -414,2 +424,23 @@ /** | ||
/** | ||
* The max login timeout value. This value is either 0 or over 300. | ||
*/ | ||
retryTimeout?: number; | ||
/** | ||
* The option to skip the SAML URL check in the Okta authentication | ||
*/ | ||
disableSamlUrlCheck?: boolean; | ||
/** | ||
* The option to fetch all the null values in the columns as the string null. | ||
*/ | ||
representNullAsStringNull?: boolean; | ||
/** | ||
* Number of threads for clients to use to prefetch large result sets. Valid values: 1-10. | ||
*/ | ||
resultPrefetch?: number; | ||
//Connection options Options but not on the web document. | ||
/** | ||
* Set whether the retry reason is included or not in the retry url. | ||
@@ -420,11 +451,36 @@ */ | ||
/** | ||
* The max login timeout value. This value is either 0 or over 300. | ||
* Number of retries for the login request. | ||
*/ | ||
retryTimeout?: number; | ||
sfRetryMaxLoginRetries?: number; | ||
/** | ||
* The option to throw an error on the bind stage if this is enabled. | ||
*/ | ||
forceStageBindError?: number; | ||
/** | ||
* The option to disable the query context cache. | ||
*/ | ||
disableQueryContextCache?: boolean; | ||
/** | ||
* The option to disable GCS_USE_DOWNSCOPED_CREDENTIAL session parameter | ||
*/ | ||
gcsUseDownscopedCredential?: boolean; | ||
/** | ||
* The option to use https request only for the snowflake server if other GCP metadata or configuration is already set on the machine. | ||
* The default value is false. | ||
*/ | ||
forceGCPUseDownscopedCredential?: boolean | ||
forceGCPUseDownscopedCredential?: boolean; | ||
/** | ||
* The option to disable the web authentication console login. | ||
*/ | ||
disableConsoleLogin?: boolean; | ||
/** | ||
* Turn on the validation function which checks whether all the connection configuration from users are valid or not. | ||
*/ | ||
validateDefaultParameters?: boolean; | ||
} | ||
@@ -506,2 +562,7 @@ | ||
isAnError(): boolean; | ||
/* | ||
* Returns a serialized version of this connection. | ||
*/ | ||
serialize(): String; | ||
} | ||
@@ -511,5 +572,10 @@ | ||
sqlText: string; | ||
complete: StatementCallback; | ||
complete?: StatementCallback; | ||
/** | ||
* Enable asynchronous queries by including asyncExec: true in the connection.execute method. | ||
*/ | ||
asyncExec?: boolean; | ||
/** | ||
* The requestId is for resubmitting requests. | ||
@@ -547,2 +613,13 @@ * Detailed Information: https://docs.snowflake.com/en/developer-guide/node-js/nodejs-driver-execute. | ||
parameters?: Record<string, any>; | ||
/** | ||
* Returns the rowMode string value ('array', 'object' or 'object_with_renamed_duplicated_columns'). Could be null or undefined. | ||
*/ | ||
rowMode?: RowMode; | ||
/** | ||
* Current working directory to use for GET/PUT execution using relative paths from a client location | ||
* that is different from the connector directory. | ||
*/ | ||
cwd?: string; | ||
} | ||
@@ -614,6 +691,9 @@ | ||
/** | ||
* Cancels this statement if possible. | ||
*/ | ||
cancel(callback?: StatementCallback): void; | ||
/** | ||
* Streams the rows in this statement's result. If start and end values are | ||
* specified, only rows in the specified range are streamed. | ||
* | ||
* @param {Object} options | ||
*/ | ||
@@ -626,4 +706,2 @@ streamRows(options?: StreamOptions): Readable; | ||
* callback will only be invoked on rows in the specified range. | ||
* | ||
* @param {Object} options | ||
*/ | ||
@@ -783,3 +861,3 @@ fetchRows(options?: StreamOptions): Readable; | ||
end?: number; | ||
fetchAsString?: DataType[] | undefined; | ||
fetchAsString?: DataType[]; | ||
} | ||
@@ -816,2 +894,2 @@ | ||
export function createPool(options: ConnectionOptions, poolOptions?: PoolOptions): Pool<Connection>; | ||
} | ||
} |
@@ -14,3 +14,3 @@ /* | ||
const REGEX_SNOWFLAKE_ENDPOINT = /.snowflakecomputing.com$/; | ||
const REGEX_SNOWFLAKE_ENDPOINT = /.snowflakecomputing./; | ||
@@ -17,0 +17,0 @@ const ocspFailOpenWarning = |
@@ -20,6 +20,9 @@ /* | ||
*/ | ||
function AuthKeypair(privateKey, privateKeyPath, privateKeyPass, cryptomod, jwtmod, filesystem) { | ||
function AuthKeypair(connectionConfig, cryptomod, jwtmod, filesystem) { | ||
const crypto = typeof cryptomod !== 'undefined' ? cryptomod : require('crypto'); | ||
const jwt = typeof jwtmod !== 'undefined' ? jwtmod : require('jsonwebtoken'); | ||
const fs = typeof filesystem !== 'undefined' ? filesystem : require('fs'); | ||
let privateKey = connectionConfig.getPrivateKey(); | ||
const privateKeyPath = connectionConfig.getPrivateKeyPath(); | ||
const privateKeyPass = connectionConfig.getPrivateKeyPass(); | ||
@@ -138,3 +141,3 @@ let jwtToken; | ||
const currentTime = Date.now(); | ||
const jwtTokenExp = currentTime + LIFETIME; | ||
const jwtTokenExp = currentTime + (LIFETIME * 1000); | ||
@@ -152,4 +155,13 @@ // Create payload containing jwt token and lifetime span | ||
}; | ||
this.reauthenticate = async function (body) { | ||
this.authenticate(connectionConfig.getAuthenticator(), | ||
connectionConfig.getServiceName(), | ||
connectionConfig.account, | ||
connectionConfig.username); | ||
this.updateBody(body); | ||
}; | ||
} | ||
module.exports = AuthKeypair; |
@@ -54,2 +54,3 @@ /* | ||
body['data']['PROOF_KEY'] = proofKey; | ||
body['data']['AUTHENTICATOR'] = 'EXTERNALBROWSER'; | ||
}; | ||
@@ -56,0 +57,0 @@ |
@@ -10,3 +10,5 @@ /* | ||
const AuthOkta = require('./auth_okta'); | ||
const AuthIDToken = require('./auth_idtoken'); | ||
const Logger = require('../logger'); | ||
const AuthMFAToken = require('./auth_mfatoken'); | ||
@@ -21,2 +23,4 @@ let authenticator; | ||
OAUTH_AUTHENTICATOR: 'OAUTH', | ||
MFA_TOKEN_AUTHENTICATOR: 'USERNAME_PASSWORD_MFA', | ||
ID_TOKEN_AUTHENTICATOR: 'ID_TOKEN', | ||
}; | ||
@@ -81,7 +85,15 @@ | ||
} else if (authType === authenticationTypes.EXTERNAL_BROWSER_AUTHENTICATOR) { | ||
auth = new AuthWeb(connectionConfig, httpClient); | ||
if (connectionConfig.getClientStoreTemporaryCredential() && !!connectionConfig.idToken) { | ||
auth = new AuthIDToken(connectionConfig, httpClient); | ||
} else { | ||
auth = new AuthWeb(connectionConfig, httpClient); | ||
} | ||
} else if (authType === authenticationTypes.MFA_TOKEN_AUTHENTICATOR) { | ||
if (connectionConfig.getClientRequestMFAToken() && !!connectionConfig.mfaToken) { | ||
auth = new AuthMFAToken(connectionConfig); | ||
} else { | ||
auth = new AuthDefault(connectionConfig.password); | ||
} | ||
} else if (authType === authenticationTypes.KEY_PAIR_AUTHENTICATOR) { | ||
auth = new AuthKeypair(connectionConfig.getPrivateKey(), | ||
connectionConfig.getPrivateKeyPath(), | ||
connectionConfig.getPrivateKeyPass()); | ||
auth = new AuthKeypair(connectionConfig); | ||
} else if (authType === authenticationTypes.OAUTH_AUTHENTICATOR) { | ||
@@ -88,0 +100,0 @@ auth = new AuthOauth(connectionConfig.getToken()); |
@@ -9,2 +9,3 @@ /* | ||
const Errors = require('../errors'); | ||
const path = require('path'); | ||
const ErrorCodes = Errors.codes; | ||
@@ -17,2 +18,3 @@ const NativeTypes = require('./result/data_types').NativeTypes; | ||
const DataTypes = require('./result/data_types'); | ||
const Logger = require('../logger'); | ||
const WAIT_FOR_BROWSER_ACTION_TIMEOUT = 120000; | ||
@@ -58,9 +60,10 @@ const DEFAULT_PARAMS = | ||
'retryTimeout', | ||
'clientRequestMFAToken', | ||
'clientStoreTemporaryCredential', | ||
'disableConsoleLogin', | ||
'forceGCPUseDownscopedCredential', | ||
'disableSamlUrlCheck', | ||
'representNullAsStringNull', | ||
'disableSamlURLCheck' | ||
'disableSamlURLCheck', | ||
'credentialCacheDir', | ||
]; | ||
const Logger = require('../logger'); | ||
@@ -71,3 +74,6 @@ function consolidateHostAndAccount(options) { | ||
let realRegion = undefined; | ||
const protocol = options.protocol || 'https'; | ||
const port = Util.exists(options.port) ? Util.format(':%s', options.port) : ''; | ||
if (Util.exists(options.region)) { | ||
@@ -87,24 +93,13 @@ Errors.checkArgumentValid(Util.isCorrectSubdomain(options.region), ErrorCodes.ERR_CONN_CREATE_INVALID_REGION_REGEX); | ||
} | ||
if (!Util.exists(options.host)) { | ||
options.host = Util.constructHostname(realRegion, realAccount); | ||
} | ||
} | ||
if (!Util.isString(options.accessUrl) || !Util.exists(options.accessUrl)) { | ||
if (options.region === 'us-west-2') { | ||
options.region = ''; | ||
} | ||
if (Util.exists(options.host)) { | ||
options.accessUrl = Util.format('https://%s', options.host); | ||
} else if (dotPos < 0 && Util.isString(options.region) && options.region.length > 0) { | ||
options.accessUrl = Util.format('https://%s.%s.snowflakecomputing.com', options.account, options.region); | ||
} else { | ||
options.accessUrl = Util.format('https://%s.snowflakecomputing.com', options.account); | ||
} | ||
} else if (!Util.exists(options.account)) { | ||
if (Util.exists(options.accessUrl)) { //accessUrl is set in configuration | ||
try { | ||
const parsedUrl = url.parse(options.accessUrl); | ||
Errors.checkArgumentValid(Util.exists(parsedUrl.hostname), ErrorCodes.ERR_CONN_CREATE_INVALID_ACCESS_URL); | ||
if (!Util.exists(options.host)) { | ||
options.host = parsedUrl.hostname; | ||
} | ||
const dotPos = parsedUrl.hostname.indexOf('.'); | ||
if (dotPos > 0) { | ||
if (dotPos > 0 && !Util.exists(options.account)) { | ||
realAccount = parsedUrl.hostname.substring(0, dotPos); | ||
@@ -114,6 +109,21 @@ } | ||
Errors.checkArgumentValid( | ||
false, ErrorCodes.ERR_CONN_CREATE_INVALID_ACCESS_URL); | ||
false, ErrorCodes.ERR_CONN_CREATE_MISSING_ACCOUNT); | ||
} | ||
} else if (Util.exists(options.host)) { //host is set in configuration | ||
options.accessUrl = Util.format('%s://%s%s', protocol, options.host, port); | ||
const dotPos = options.host.indexOf('.'); | ||
if (dotPos > 0 && !Util.exists(options.account)) { | ||
realAccount = options.host.substring(0, dotPos); | ||
} else { | ||
realAccount = options.account; | ||
} | ||
} else if (Util.exists(options.account)) { //only account() is set in configuration | ||
if (options.region === 'us-west-2') { | ||
options.region = ''; | ||
} | ||
options.host = Util.constructHostname(realRegion, realAccount); | ||
options.accessUrl = Util.format('%s://%s%s', protocol, options.host, port); | ||
} | ||
if (Util.exists(realAccount) && options.accessUrl.endsWith('global.snowflakecomputing.com')) { | ||
if (Util.exists(realAccount) && options.accessUrl.includes('global.snowflakecomputing')) { | ||
const dashPos = realAccount.indexOf('-'); | ||
@@ -127,4 +137,7 @@ if (dashPos > 0) { | ||
options.region = realRegion; | ||
// check for missing password | ||
// check for missing accessURL | ||
Errors.checkArgumentExists(Util.exists(options.account), ErrorCodes.ERR_CONN_CREATE_MISSING_ACCOUNT); | ||
// check for missing account | ||
Errors.checkArgumentExists(Util.exists(options.accessUrl), ErrorCodes.ERR_CONN_CREATE_MISSING_ACCESS_URL); | ||
} | ||
@@ -161,4 +174,4 @@ | ||
if (!Util.exists(options.authenticator) || | ||
(options.authenticator !== authenticationTypes.OAUTH_AUTHENTICATOR && | ||
options.authenticator !== authenticationTypes.EXTERNAL_BROWSER_AUTHENTICATOR)) { | ||
(options.authenticator.toUpperCase() !== authenticationTypes.OAUTH_AUTHENTICATOR && | ||
options.authenticator.toUpperCase() !== authenticationTypes.EXTERNAL_BROWSER_AUTHENTICATOR)) { | ||
// check for missing username | ||
@@ -304,3 +317,3 @@ Errors.checkArgumentExists(Util.exists(options.username), | ||
const token = options.token; | ||
if (Util.exists(options.token)) { | ||
if (Util.exists(token)) { | ||
Errors.checkArgumentValid(Util.isString(token), | ||
@@ -491,2 +504,10 @@ ErrorCodes.ERR_CONN_CREATE_INVALID_OAUTH_TOKEN); | ||
let clientRequestMFAToken = false; | ||
if (Util.exists(options.clientRequestMFAToken)) { | ||
Errors.checkArgumentValid(Util.isBoolean(options.clientRequestMFAToken), | ||
ErrorCodes.ERR_CONN_CREATE_INVALID_CLIENT_REQUEST_MFA_TOKEN); | ||
clientRequestMFAToken = options.clientRequestMFAToken; | ||
} | ||
let disableConsoleLogin = true; | ||
@@ -515,3 +536,3 @@ if (Util.exists(options.disableConsoleLogin)) { | ||
} | ||
let disableSamlURLCheck = false; | ||
@@ -525,2 +546,19 @@ if (Util.exists(options.disableSamlURLCheck)) { | ||
let clientStoreTemporaryCredential = false; | ||
if (Util.exists(options.clientStoreTemporaryCredential)) { | ||
Errors.checkArgumentValid(Util.isBoolean(options.clientStoreTemporaryCredential), | ||
ErrorCodes.ERR_CONN_CREATE_INVALID_CLIENT_STORE_TEMPORARY_CREDENTIAL); | ||
clientStoreTemporaryCredential = options.clientStoreTemporaryCredential; | ||
} | ||
let credentialCacheDir = null; | ||
if (Util.exists(options.credentialCacheDir)) { | ||
const absolutePath = path.resolve(options.credentialCacheDir); | ||
Errors.checkArgumentValid(Util.validatePath(absolutePath), | ||
ErrorCodes.ERR_CONN_CREATE_INVALID_CREDENTIAL_CACHE_DIR); | ||
credentialCacheDir = absolutePath; | ||
} | ||
/** | ||
@@ -749,3 +787,3 @@ * Returns an object that contains information about the proxy hostname, port, | ||
/** | ||
* Returns the bind threshold | ||
* Returns the bind threshold | ||
* | ||
@@ -808,4 +846,4 @@ * @returns {string} | ||
/** | ||
* Returns whether the SAML URL check is enabled or not. | ||
* | ||
* Returns whether the SAML URL check is enabled or not. | ||
* | ||
* @returns {Boolean} | ||
@@ -817,2 +855,18 @@ */ | ||
this.getCredentialCacheDir = function () { | ||
return credentialCacheDir; | ||
}; | ||
this.getClientRequestMFAToken = function () { | ||
return clientRequestMFAToken; | ||
}; | ||
/** | ||
* Returns whether the auth token saves on the local machine or not. | ||
* | ||
* @returns {Boolean} | ||
*/ | ||
this.getClientStoreTemporaryCredential = function () { | ||
return clientStoreTemporaryCredential; | ||
}; | ||
// save config options | ||
@@ -819,0 +873,0 @@ this.username = options.username; |
@@ -20,4 +20,5 @@ /* | ||
const { init: initEasyLogging } = require('../logger/easy_logging_starter'); | ||
const GlobalConfig = require('../global_config'); | ||
const JsonCredentialManager = require('../authentication/secure_storage/json_credential_manager'); | ||
const PRIVATELINK_URL_SUFFIX = '.privatelink.snowflakecomputing.com'; | ||
@@ -165,3 +166,3 @@ /** | ||
this.setupOcspPrivateLink = function (host) { | ||
process.env.SF_OCSP_RESPONSE_CACHE_SERVER_URL = `http://ocsp.${host}/ocsp_response_cache.json`; | ||
process.env.SF_OCSP_RESPONSE_CACHE_SERVER_URL = Util.createOcspResponseCacheServerUrl(host); | ||
}; | ||
@@ -188,2 +189,4 @@ | ||
this.determineConnectionDomain = () => connectionConfig.accessUrl && connectionConfig.accessUrl.includes('snowflakecomputing.cn') ? 'CHINA' : 'GLOBAL'; | ||
/** | ||
@@ -197,2 +200,4 @@ * Establishes a connection if we aren't in a fatal state. | ||
this.connect = function (callback) { | ||
const connectionDomain = this.determineConnectionDomain(); | ||
Logger.getInstance().info(`Connecting to ${connectionDomain} Snowflake domain`); | ||
// invalid callback | ||
@@ -203,3 +208,3 @@ Errors.checkArgumentValid( | ||
if (connectionConfig.host.endsWith(PRIVATELINK_URL_SUFFIX)) { | ||
if (Util.exists(connectionConfig.host) && Util.isPrivateLink(connectionConfig.host)) { | ||
this.setupOcspPrivateLink(connectionConfig.host); | ||
@@ -271,2 +276,5 @@ } | ||
this.connectAsync = async function (callback) { | ||
const connectingDomain = this.determineConnectionDomain(); | ||
Logger.getInstance().info(`Connecting to ${connectingDomain} Snowflake domain`); | ||
// invalid callback | ||
@@ -277,3 +285,3 @@ Errors.checkArgumentValid( | ||
if (connectionConfig.host.endsWith(PRIVATELINK_URL_SUFFIX)) { | ||
if (Util.isPrivateLink(connectionConfig.host)) { | ||
this.setupOcspPrivateLink(connectionConfig.host); | ||
@@ -285,4 +293,24 @@ } | ||
// callback | ||
const self = this; | ||
const authType = Authenticator.authenticationTypes; | ||
if (connectionConfig.getClientStoreTemporaryCredential()) { | ||
const key = Util.buildCredentialCacheKey(connectionConfig.host, | ||
connectionConfig.username, Authenticator.authenticationTypes.ID_TOKEN_AUTHENTICATOR); | ||
if (GlobalConfig.getCredentialManager() === null) { | ||
GlobalConfig.setCustomCredentialManager(new JsonCredentialManager(connectionConfig.getCredentialCacheDir())); | ||
} | ||
connectionConfig.idToken = await GlobalConfig.getCredentialManager().read(key); | ||
} | ||
if (connectionConfig.getClientRequestMFAToken()) { | ||
const key = Util.buildCredentialCacheKey(connectionConfig.host, | ||
connectionConfig.username, authType.MFA_TOKEN_AUTHENTICATOR); | ||
if (GlobalConfig.getCredentialManager() === null) { | ||
GlobalConfig.setCustomCredentialManager(new JsonCredentialManager(connectionConfig.getCredentialCacheDir())); | ||
} | ||
connectionConfig.mfaToken = await GlobalConfig.getCredentialManager().read(key); | ||
} | ||
// Get authenticator to use | ||
@@ -301,25 +329,24 @@ const auth = Authenticator.getAuthenticator(connectionConfig, context.getHttpClient()); | ||
connectionConfig.account, | ||
connectionConfig.username) | ||
.then(() => { | ||
// JSON for connection | ||
const body = Authenticator.formAuthJSON(connectionConfig.getAuthenticator(), | ||
connectionConfig.account, | ||
connectionConfig.username, | ||
connectionConfig.getClientType(), | ||
connectionConfig.getClientVersion(), | ||
connectionConfig.getClientEnvironment()); | ||
connectionConfig.username); | ||
// JSON for connection | ||
const body = Authenticator.formAuthJSON(connectionConfig.getAuthenticator(), | ||
connectionConfig.account, | ||
connectionConfig.username, | ||
connectionConfig.getClientType(), | ||
connectionConfig.getClientVersion(), | ||
connectionConfig.getClientEnvironment()); | ||
// Update JSON body with the authentication values | ||
auth.updateBody(body); | ||
// Update JSON body with the authentication values | ||
auth.updateBody(body); | ||
// Request connection | ||
services.sf.connect({ | ||
callback: connectCallback(self, callback), | ||
json: body | ||
}); | ||
}); | ||
// Request connection | ||
services.sf.connect({ | ||
callback: connectCallback(self, callback), | ||
json: body, | ||
}); | ||
} catch (authErr) { | ||
callback(authErr); | ||
} | ||
// return the connection to facilitate chaining | ||
@@ -326,0 +353,0 @@ return this; |
@@ -185,2 +185,6 @@ /* | ||
} | ||
const cwd = statementOptions.cwd; | ||
if (Util.exists(cwd)) { | ||
Errors.checkArgumentValid(Util.isString(cwd), ErrorCodes.ERR_CONN_FETCH_RESULT_INVALID_CWD); | ||
} | ||
@@ -201,2 +205,3 @@ // validate non-user-specified arguments | ||
statementContext.rowMode = statementOptions.rowMode; | ||
statementContext.cwd = statementOptions.cwd; | ||
@@ -380,2 +385,6 @@ // set the statement type | ||
if (Util.exists(statementOptions.cwd)) { | ||
statementContext.cwd = statementOptions.cwd; | ||
} | ||
// validate non-user-specified arguments | ||
@@ -382,0 +391,0 @@ Errors.assertInternal(Util.isObject(services)); |
@@ -26,2 +26,3 @@ /* | ||
exports[403006] = 'Invalid keep alive value. The specified value must be a boolean.'; | ||
exports[403007] = 'Invalid custom credential manager value. The specified value must be an object, and it should have three methods: write, read, remove'; | ||
@@ -76,5 +77,10 @@ // 404001 | ||
exports[404047] = 'Invalid disableConsoleLogin. The specified value must be a boolean'; | ||
exports[404048] = 'Invalid disableGCPTokenUpload. The specified value must be a boolean'; | ||
exports[404048] = 'Invalid forceGCPUseDownscopedCredential. The specified value must be a boolean'; | ||
exports[404049] = 'Invalid clientStoreTemporaryCredential. The specified value must be a boolean.'; | ||
exports[404050] = 'Invalid representNullAsStringNull. The specified value must be a boolean'; | ||
exports[404051] = 'Invalid disableSamlURLCheck. The specified value must be a boolean'; | ||
exports[404052] = 'Invalid clientRequestMFAToken. The specified value must be a boolean.'; | ||
exports[404053] = 'A host must be specified.'; | ||
exports[404054] = 'Invalid host. The specified value must be a string.'; | ||
@@ -132,2 +138,3 @@ // 405001 | ||
exports[410008] = 'Invalid fetchAsString type: %s. The supported types are: String, Boolean, Number, Date, Buffer, and JSON.'; | ||
exports[410009] = 'Invalid cwd (current working directory) type: %s. The specified value must be a string.'; | ||
@@ -134,0 +141,0 @@ // 411001 |
@@ -12,3 +12,4 @@ /* | ||
code.MASTER_TOKEN_EXPIRED = '390114'; | ||
code.ID_TOKEN_INVALID = '390195'; | ||
exports.code = code; |
@@ -16,2 +16,3 @@ /* | ||
const GlobalConfig = require('./global_config'); | ||
const { loadConnectionConfiguration } = require('./configuration/connection_configuration'); | ||
@@ -67,2 +68,12 @@ /** | ||
if (connectionOptions == null) { | ||
try { | ||
connectionOptions = loadConnectionConfiguration(); | ||
} catch ( error ) { | ||
Logger.getInstance().debug(`Problem during reading connection configuration from file: ${error.message}`); | ||
Errors.checkArgumentExists(Util.exists(connectionOptions), | ||
ErrorCodes.ERR_CONN_CREATE_MISSING_OPTIONS); | ||
} | ||
} | ||
const validateCredentials = !config && (connectionOptions && !connectionOptions.sessionToken); | ||
@@ -211,2 +222,10 @@ const connectionConfig = | ||
} | ||
const customCredentialManager = options.customCredentialManager; | ||
if (Util.exists(customCredentialManager)) { | ||
Errors.checkArgumentValid(Util.isObject(customCredentialManager), | ||
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_CUSTOM_CREDENTIAL_MANAGER); | ||
GlobalConfig.setCustomCredentialManager(customCredentialManager); | ||
} | ||
} | ||
@@ -213,0 +232,0 @@ }; |
@@ -31,2 +31,3 @@ /* | ||
codes.ERR_GLOBAL_CONFIGURE_INVALID_KEEP_ALIVE = 403006; | ||
codes.ERR_GLOBAL_CONFIGURE_INVALID_CUSTOM_CREDENTIAL_MANAGER = 403007; | ||
@@ -82,5 +83,10 @@ // 404001 | ||
codes.ERR_CONN_CREATE_INVALID_FORCE_GCP_USE_DOWNSCOPED_CREDENTIAL = 404048; | ||
codes.ERR_CONN_CREATE_INVALID_CLIENT_STORE_TEMPORARY_CREDENTIAL = 404049; | ||
codes.ERR_CONN_CREATE_INVALID_REPRESENT_NULL_AS_STRING_NULL = 404050; | ||
codes.ERR_CONN_CREATE_INVALID_DISABLE_SAML_URL_CHECK = 404051; | ||
codes.ERR_CONN_CREATE_INVALID_CLIENT_REQUEST_MFA_TOKEN = 404052; | ||
codes.ERR_CONN_CREATE_MISSING_HOST = 404053; | ||
codes.ERR_CONN_CREATE_INVALID_HOST = 404054; | ||
// 405001 | ||
@@ -137,2 +143,3 @@ codes.ERR_CONN_CONNECT_INVALID_CALLBACK = 405001; | ||
codes.ERR_CONN_FETCH_RESULT_INVALID_FETCH_AS_STRING_VALUES = 410008; | ||
codes.ERR_CONN_FETCH_RESULT_INVALID_CWD = 410009; | ||
@@ -139,0 +146,0 @@ // 411001 |
@@ -10,4 +10,2 @@ /* | ||
const Logger = require('../logger'); | ||
const AES_CBC = 'aes-128-cbc'; | ||
const AES_ECB = 'aes-128-ecb'; | ||
const AES_BLOCK_SIZE = 128; | ||
@@ -37,2 +35,10 @@ const blockSize = parseInt(AES_BLOCK_SIZE / 8); // in bytes | ||
function aesCbc(keySizeInBytes) { | ||
return `aes-${keySizeInBytes * 8}-cbc`; | ||
} | ||
function aesEcb(keySizeInBytes) { | ||
return `aes-${keySizeInBytes * 8}-ecb`; | ||
} | ||
exports.EncryptionMetadata = EncryptionMetadata; | ||
@@ -141,6 +147,6 @@ | ||
const ivData = getSecureRandom(blockSize); | ||
const fileKey = getSecureRandom(blockSize); | ||
const fileKey = getSecureRandom(keySize); | ||
// Create cipher with file key, AES CBC, and iv data | ||
let cipher = crypto.createCipheriv(AES_CBC, fileKey, ivData); | ||
let cipher = crypto.createCipheriv(aesCbc(keySize), fileKey, ivData); | ||
const encrypted = cipher.update(fileStream); | ||
@@ -151,3 +157,3 @@ const final = cipher.final(); | ||
// Create key cipher with decoded key and AES ECB | ||
cipher = crypto.createCipheriv(AES_ECB, decodedKey, null); | ||
cipher = crypto.createCipheriv(aesEcb(keySize), decodedKey, null); | ||
@@ -194,6 +200,6 @@ // Encrypt with file key | ||
const ivData = getSecureRandom(blockSize); | ||
const fileKey = getSecureRandom(blockSize); | ||
const fileKey = getSecureRandom(keySize); | ||
// Create cipher with file key, AES CBC, and iv data | ||
let cipher = crypto.createCipheriv(AES_CBC, fileKey, ivData); | ||
let cipher = crypto.createCipheriv(aesCbc(keySize), fileKey, ivData); | ||
@@ -222,3 +228,3 @@ // Create temp file | ||
// Create key cipher with decoded key and AES ECB | ||
cipher = crypto.createCipheriv(AES_ECB, decodedKey, null); | ||
cipher = crypto.createCipheriv(aesEcb(keySize), decodedKey, null); | ||
@@ -270,2 +276,3 @@ // Encrypt with file key | ||
const decodedKey = Buffer.from(encryptionMaterial[QUERY_STAGE_MASTER_KEY], BASE64); | ||
const keySize = decodedKey.length; | ||
@@ -291,3 +298,3 @@ // Get key bytes and iv bytes from base64 encoded value | ||
// Create key decipher with decoded key and AES ECB | ||
let decipher = crypto.createDecipheriv(AES_ECB, decodedKey, null); | ||
let decipher = crypto.createDecipheriv(aesEcb(keySize), decodedKey, null); | ||
const fileKey = Buffer.concat([ | ||
@@ -299,3 +306,3 @@ decipher.update(keyBytes), | ||
// Create decipher with file key, iv bytes, and AES CBC | ||
decipher = crypto.createDecipheriv(AES_CBC, fileKey, ivBytes); | ||
decipher = crypto.createDecipheriv(aesCbc(keySize), fileKey, ivBytes); | ||
@@ -302,0 +309,0 @@ await new Promise(function (resolve) { |
@@ -67,2 +67,3 @@ /* | ||
const command = context.sqlText; | ||
const cwd = context.cwd; | ||
@@ -604,3 +605,11 @@ let commandType; | ||
// Get root directory of file path | ||
const root = path.dirname(src); | ||
let root = path.dirname(src); | ||
// If cwd exists and root is relative . then replace with context's cwd | ||
// Used for VS Code extension where extension cwd differs from user workspace dir | ||
if (cwd && !path.isAbsolute(src)) { | ||
const absolutePath = path.resolve(cwd, src); | ||
root = path.dirname(absolutePath); | ||
} | ||
let dir; | ||
@@ -671,2 +680,10 @@ | ||
localLocation = expandTilde(data['localLocation']); | ||
// If cwd exists and root is relative . then replace with context's cwd | ||
// Used for VS Code extension where extension cwd differs from user workspace dir | ||
if (cwd && !path.isAbsolute(localLocation)) { | ||
const absolutePath = path.resolve(cwd, localLocation); | ||
localLocation = absolutePath; | ||
} | ||
const dir = fs.statSync(localLocation); | ||
@@ -673,0 +690,0 @@ if (!dir.isDirectory()) { |
@@ -6,3 +6,3 @@ /* | ||
const crypto = require('crypto'); | ||
const fs = require('fs'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
@@ -13,2 +13,3 @@ const struct = require('python-struct'); | ||
const glob = require('glob'); | ||
const Logger = require('../logger'); | ||
@@ -149,2 +150,27 @@ const resultStatus = { | ||
function validateOnlyUserReadWritePermission(filePath) { | ||
if (os.platform() === 'win32') { | ||
return; | ||
} | ||
fs.accessSync(filePath, fs.constants.F_OK); | ||
const mode = (fs.statSync(filePath)).mode; | ||
const permission = (mode & 0o00777 | 0o600); | ||
//This should be 600 permission, which means the file permission has not been changed by others. | ||
if (permission === 0o600) { | ||
Logger.getInstance().debug(`Validated that the user has only read and write permission for file: ${filePath}, Permission: ${permission}`); | ||
} else { | ||
throw new Error(`File permissions different than read/write for user. File: ${filePath}`); | ||
} | ||
} | ||
function generateChecksum(str, algorithm, encoding) { | ||
return crypto | ||
.createHash(algorithm || 'sha256') | ||
.update(str, 'utf8') | ||
.digest(encoding || 'hex') | ||
.substring(0, 32); | ||
} | ||
exports.getMatchingFilePaths = getMatchingFilePaths; | ||
exports.validateOnlyUserReadWritePermission = validateOnlyUserReadWritePermission; | ||
exports.generateChecksum = generateChecksum; |
@@ -8,5 +8,5 @@ /* | ||
const mkdirp = require('mkdirp'); | ||
const Errors = require('./errors'); | ||
const ErrorCodes = Errors.codes; | ||
const Util = require('./util'); | ||
const Errors = require('./errors'); | ||
const Logger = require('./logger'); | ||
@@ -260,2 +260,14 @@ const { XMLParser, XMLValidator } = require('fast-xml-parser'); | ||
let credentialManager = null; | ||
exports.setCustomCredentialManager = function (customCredentialManager) { | ||
Errors.checkArgumentValid(Util.checkValidCustomCredentialManager(customCredentialManager), | ||
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_CUSTOM_CREDENTIAL_MANAGER); | ||
credentialManager = customCredentialManager; | ||
Logger.getInstance().info('Custom credential manager is set by a user.'); | ||
}; | ||
exports.getCredentialManager = function () { | ||
return credentialManager; | ||
}; |
@@ -27,4 +27,7 @@ /* | ||
* @param {Number} capacity Maximum capacity of the cache. | ||
* @param {Number} sessionId Session for which the cache is created. | ||
*/ | ||
function QueryContextCache(capacity) { | ||
function QueryContextCache(capacity, sessionId) { | ||
Logger.getInstance().debug(`Creating new QueryContextCache with capacity ${capacity} for session ${sessionId}`); | ||
this.sessionId = sessionId; | ||
this.capacity = capacity; | ||
@@ -45,2 +48,3 @@ this.idMap = new Map(); // Map for id and QCC | ||
this.sortTreeSet(); | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - Added QCE: ${JSON.stringify(qce)}`); | ||
}; | ||
@@ -57,2 +61,3 @@ | ||
this.treeSet.delete(qce); | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - Removed QCE: ${JSON.stringify(qce)}`); | ||
}; | ||
@@ -63,3 +68,3 @@ | ||
* and add a new element received. | ||
*{ | ||
* | ||
* @param {Object} oldQCE an element exist in the cache | ||
@@ -74,2 +79,4 @@ * @param {Object} newQCE a new element just received. | ||
this.addQCE(newQCE); | ||
Logger.getInstance().debug(`QCC session ${this.sessionId} - Replaced QCE: ${JSON.stringify(oldQCE)} with ${JSON.stringify(newQCE)}`); | ||
}; | ||
@@ -88,3 +95,5 @@ | ||
QueryContextCache.prototype.merge = function (newQCE) { | ||
Logger.getInstance().debug(`QCC session ${this.sessionId} - Merging QCE: ${JSON.stringify(newQCE)}`); | ||
if (this.idMap.has(newQCE.id)) { | ||
Logger.getInstance().debug(`QCC session ${this.sessionId} - Element id ${newQCE.id} found in cache`); | ||
@@ -94,4 +103,5 @@ // ID found in the cache | ||
if (newQCE.timestamp > qce.timestamp) { | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - New element is more recent. Current timestamp: ${qce.timestamp}, new timestamp: ${newQCE.timestamp}`); | ||
if (qce.priority === newQCE.priority) { | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - Element priority (${qce.priority}) is the same`); | ||
// Same priority, overwrite new data at same place | ||
@@ -101,3 +111,3 @@ qce.timestamp = newQCE.timestamp; | ||
} else { | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - Element priority changed. Current priority: ${qce.priority}, new priority: ${newQCE.priority}`); | ||
// Change in priority | ||
@@ -107,8 +117,10 @@ this.replaceQCE(qce, newQCE); | ||
} else if (newQCE.timestamp === qce.timestamp && qce.priority !== newQCE.priority) { | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - Element timestamp is the same, but priority changes. Current priority: ${qce.priority}, new priority: ${newQCE.priority}`); | ||
// Same read timestamp but change in priority | ||
this.replaceQCE(qce, newQCE); | ||
} else { | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - Element is the same. Doing nothing.`); | ||
} | ||
} else { | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - New element`); | ||
// new id | ||
@@ -120,6 +132,7 @@ if (this.priorityMap.has(newQCE.priority)) { | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - Element with the same priority found: ${JSON.stringify(qce)}. Replacing with new element: ${JSON.stringify(newQCE)}`); | ||
// Replace with new data | ||
this.replaceQCE(qce, newQCE); | ||
} else { | ||
Logger.getInstance().debug(`QCC session ${this.sessionId} - Adding new element to the cache: ${JSON.stringify(newQCE)}`); | ||
// new priority | ||
@@ -137,4 +150,4 @@ // Add new element in the cache | ||
QueryContextCache.prototype.checkCacheCapacity = function () { | ||
Logger.getInstance().debug( | ||
`checkCacheCapacity() called. treeSet size ${this.treeSet.size} cache capacity ${this.capacity}` ); | ||
Logger.getInstance().trace( | ||
`QCC session ${this.sessionId} - checkCacheCapacity() called. treeSet size ${this.treeSet.size}, cache capacity ${this.capacity}`); | ||
@@ -146,5 +159,4 @@ // remove elements based on priority | ||
} | ||
Logger.getInstance().debug( | ||
`checkCacheCapacity() returns. treeSet size ${this.treeSet.size} cache capacity ${this.capacity}`, | ||
); | ||
Logger.getInstance().trace( | ||
`QCC session ${this.sessionId} - checkCacheCapacity() returns. treeSet size ${this.treeSet.size}, cache capacity ${this.capacity}`); | ||
}; | ||
@@ -154,7 +166,7 @@ | ||
QueryContextCache.prototype.clearCache = function () { | ||
Logger.getInstance().debug('clearCache() called'); | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - clearCache() called`); | ||
this.idMap.clear(); | ||
this.priorityMap.clear(); | ||
this.treeSet.clear(); | ||
Logger.getInstance().debug(`clearCache() returns. Number of entries in cache now ${this.treeSet.size}`,); | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - clearCache() returns. Number of entries in cache now ${this.treeSet.size}`); | ||
}; | ||
@@ -171,7 +183,7 @@ | ||
const stringifyData = JSON.stringify(data); | ||
Logger.getInstance().debug(`deserializeQueryContext() called: data from server: ${stringifyData}`); | ||
Logger.getInstance().debug(`QCC session ${this.sessionId} - deserializeQueryContext() called: data from server: ${stringifyData}`); | ||
if (!data || stringifyData === '{}' || data.entries === null) { | ||
this.clearCache(); | ||
Logger.getInstance().debug('deserializeQueryContext() returns'); | ||
Logger.getInstance().trace(`QCC session ${this.sessionId} - deserializeQueryContext() returns`); | ||
this.logCacheEntries(); | ||
@@ -212,3 +224,3 @@ return; | ||
Logger.getInstance().warn( | ||
'deserializeQueryContextJson: deserializeQueryContextElement meets mismatch field type. Clear the QueryContextCache.'); | ||
`QCC session ${this.sessionId} - deserializeQueryContextJson: deserializeQueryContextElement meets mismatch field type. Clear the QueryContextCache.`); | ||
this.clearCache(); | ||
@@ -221,3 +233,3 @@ return; | ||
} catch (e) { | ||
Logger.getInstance().debug(`deserializeQueryContextJson: Exception = ${e.getMessage}`, ); | ||
Logger.getInstance().debug(`QCC session ${this.sessionId} - deserializeQueryContextJson: Exception = ${e.getMessage}`); | ||
@@ -240,5 +252,5 @@ // Not rethrowing. clear the cache as incomplete merge can lead to unexpected behavior. | ||
entry.context = null; | ||
Logger.getInstance().debug('deserializeQueryContextElement `context` field is empty'); | ||
Logger.getInstance().debug(`QCC session ${this.sessionId} - deserializeQueryContextElement \`context\` field is empty`); | ||
} else { | ||
Logger.getInstance().warn('deserializeQueryContextElement: `context` field is not String type'); | ||
Logger.getInstance().warn(`QCC session ${this.sessionId} - deserializeQueryContextElement: \`context\` field is not String type`); | ||
return null; | ||
@@ -251,8 +263,7 @@ } | ||
QueryContextCache.prototype.logCacheEntries = function () { | ||
this.treeSet.forEach(function (elem) { | ||
Logger.getInstance().debug( | ||
`Cache Entry: id: ${elem.id} timestamp: ${elem.timestamp} priority: ${elem.priority}`, | ||
); | ||
}); | ||
`QCC session ${this.sessionId} - Cache Entry: id: ${elem.id} timestamp: ${elem.timestamp} priority: ${elem.priority}`); | ||
}, this); | ||
}; | ||
@@ -289,2 +300,1 @@ | ||
module.exports = QueryContextCache; | ||
@@ -58,4 +58,6 @@ /* | ||
const Logger = require('../logger'); | ||
const { getCurrentAuth } = require('../authentication/authentication'); | ||
const GlobalConfig = require('../global_config'); | ||
const { authenticationTypes, getCurrentAuth } = require('../authentication/authentication'); | ||
const AuthOkta = require('../authentication/auth_okta'); | ||
const AuthKeypair = require('../authentication/auth_keypair'); | ||
@@ -157,2 +159,18 @@ function isRetryableNetworkError(err) { | ||
/** | ||
* Set the session id for the current SnowflakeService | ||
* @param sessionId | ||
*/ | ||
this.setSessionId = function (sessionId) { | ||
this.sessionId = sessionId; | ||
}; | ||
/** | ||
* Get the session id. | ||
* @returns {number} | ||
*/ | ||
this.getSessionId = function () { | ||
return this.sessionId; | ||
}; | ||
/** | ||
* Transitions to the Pristine state. | ||
@@ -522,3 +540,5 @@ * | ||
if (!connectionConfig.getDisableQueryContextCache()){ | ||
this.qcc = new QueryContextCache(size); | ||
this.qcc = new QueryContextCache(size, this.getSessionId()); | ||
} else { | ||
Logger.getInstance().debug(`QueryContextCache initialization skipped as it is disabled for connection with sessionId: ${this.sessionId}`); | ||
} | ||
@@ -629,2 +649,13 @@ }; | ||
const data = body.data; | ||
const auth = getCurrentAuth(); | ||
if (body.code === GSErrors.code.ID_TOKEN_INVALID && data.authnMethod === 'TOKEN') { | ||
Logger.getInstance().debug('ID Token being used has expired. Reauthenticating'); | ||
const key = Util.buildCredentialCacheKey(connectionConfig.host, | ||
connectionConfig.username, authenticationTypes.ID_TOKEN_AUTHENTICATOR); | ||
await GlobalConfig.getCredentialManager().remove(key); | ||
await auth.reauthenticate(requestOptions.json); | ||
return httpClient.request(realRequestOptions); | ||
} | ||
err = Errors.createOperationFailedError( | ||
@@ -1070,3 +1101,13 @@ body.code, data, body.message, | ||
} | ||
if (Util.exists(this.connectionConfig.getClientRequestMFAToken())) { | ||
sessionParameters.SESSION_PARAMETERS.CLIENT_REQUEST_MFA_TOKEN = | ||
this.connectionConfig.getClientRequestMFAToken(); | ||
} | ||
if (Util.exists(this.connectionConfig.getClientStoreTemporaryCredential())) { | ||
sessionParameters.SESSION_PARAMETERS.CLIENT_STORE_TEMPORARY_CREDENTIAL = | ||
this.connectionConfig.getClientStoreTemporaryCredential(); | ||
} | ||
Util.apply(json.data, clientInfo); | ||
@@ -1086,3 +1127,3 @@ Util.apply(json.data, sessionParameters); | ||
const parent = this; | ||
const requestCallback = function (err, body) { | ||
const requestCallback = async function (err, body) { | ||
// clear credential-related information | ||
@@ -1095,2 +1136,5 @@ connectionConfig.clearCredentials(); | ||
Errors.assertInternal(Util.exists(body.data)); | ||
parent.snowflakeService.setSessionId(body.data.sessionId); | ||
Logger.getInstance().debug(`New session with id ${parent.snowflakeService.getSessionId()} initialized`); | ||
@@ -1103,2 +1147,14 @@ // update the parameters | ||
if (connectionConfig.getClientRequestMFAToken() && body.data.mfaToken) { | ||
const key = Util.buildCredentialCacheKey(connectionConfig.host, | ||
connectionConfig.username, authenticationTypes.MFA_TOKEN_AUTHENTICATOR); | ||
await GlobalConfig.getCredentialManager().write(key, body.data.mfaToken); | ||
} | ||
if (connectionConfig.getClientStoreTemporaryCredential() && body.data.idToken) { | ||
const key = Util.buildCredentialCacheKey(connectionConfig.host, | ||
connectionConfig.username, authenticationTypes.ID_TOKEN_AUTHENTICATOR); | ||
await GlobalConfig.getCredentialManager().write(key, body.data.idToken); | ||
} | ||
// we're now connected | ||
@@ -1120,3 +1176,3 @@ parent.snowflakeService.transitionToConnected(); | ||
if (sleep <= 0) { | ||
Logger.getInstance().debug('Reached out to the Login Timeout'); | ||
Logger.getInstance().debug('Reached out to the max Login Timeout'); | ||
parent.snowflakeService.transitionToDisconnected(); | ||
@@ -1140,2 +1196,6 @@ } | ||
} else { | ||
if (auth instanceof AuthKeypair) { | ||
Logger.getInstance().debug('AuthKeyPair authentication requires token refresh.'); | ||
await auth.reauthenticate(context.options.json); | ||
} | ||
setTimeout(sendRequest, sleep * 1000); | ||
@@ -1594,2 +1654,2 @@ return; | ||
}; | ||
} | ||
} |
107
lib/util.js
@@ -8,2 +8,6 @@ /* | ||
const os = require('os'); | ||
const Logger = require('./logger'); | ||
const fs = require('fs'); | ||
const Errors = require('./errors'); | ||
const ErrorCodes = Errors.codes; | ||
@@ -516,3 +520,3 @@ /** | ||
if (region === 'us-west-2') { | ||
region = ''; | ||
host = account + '.snowflakecomputing.com'; | ||
} else if (region != null) { | ||
@@ -522,3 +526,8 @@ if (account.indexOf('.') > 0) { | ||
} | ||
host = account + '.' + region + '.snowflakecomputing.com'; | ||
if (region.startsWith('cn-') || region.startsWith('CN-')) { | ||
host = account + '.' + region + '.snowflakecomputing.cn'; | ||
} else { | ||
host = account + '.' + region + '.snowflakecomputing.com'; | ||
} | ||
} else { | ||
@@ -531,2 +540,20 @@ host = account + '.snowflakecomputing.com'; | ||
/** | ||
* Returns true if host indicates private link | ||
* | ||
* @returns {boolean} | ||
*/ | ||
exports.isPrivateLink = function (host) { | ||
Errors.checkArgumentExists(this.exists(host), ErrorCodes.ERR_CONN_CREATE_MISSING_HOST); | ||
return host.toLowerCase().includes('privatelink.snowflakecomputing.'); | ||
}; | ||
/** | ||
* Returns true if host indicates private link | ||
* | ||
* @returns {boolean} | ||
*/ | ||
exports.createOcspResponseCacheServerUrl = function (host) { | ||
return `http://ocsp.${host}/ocsp_response_cache.json`; | ||
}; | ||
/** | ||
* Returns if command is a PUT command | ||
@@ -594,3 +621,3 @@ * | ||
exports.isCorrectSubdomain = function (value) { | ||
const subdomainRegex = RegExp(/^\w([\w.-]+\w|)$/i); | ||
const subdomainRegex = RegExp(/^\w+([.-]\w+)*$/i); | ||
return subdomainRegex.test(value); | ||
@@ -659,3 +686,35 @@ }; | ||
exports.buildCredentialCacheKey = function (host, username, credType) { | ||
if (!host || !username || !credType) { | ||
Logger.getInstance().debug('Cannot build the credential cache key because one of host, username, and credType is null'); | ||
return null; | ||
} | ||
return `{${host.toUpperCase()}}:{${username.toUpperCase()}}:{SF_NODE_JS_DRIVER}:{${credType.toUpperCase()}}`; | ||
}; | ||
/** | ||
* | ||
* @param {Object} customCredentialManager | ||
* @returns | ||
*/ | ||
exports.checkValidCustomCredentialManager = function (customCredentialManager) { | ||
if ( typeof customCredentialManager !== 'object') { | ||
return false; | ||
} | ||
const requireMethods = ['write', 'read', 'remove']; | ||
for (const method of requireMethods) { | ||
if (!Object.hasOwnProperty.call(customCredentialManager, method) || typeof customCredentialManager[method] !== 'function') { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
exports.checkParametersDefined = function (...parameters) { | ||
return parameters.every((element) => element !== undefined && element !== null); | ||
}; | ||
/** | ||
* remove http:// or https:// from the input, e.g. used with proxy URL | ||
@@ -669,2 +728,34 @@ * @param input | ||
exports.buildCredentialCacheKey = function (host, username, credType) { | ||
if (!host || !username || !credType) { | ||
Logger.getInstance().debug('Cannot build the credential cache key because one of host, username, and credType is null'); | ||
return null; | ||
} | ||
return `{${host.toUpperCase()}}:{${username.toUpperCase()}}:{SF_NODE_JS_DRIVER}:{${credType.toUpperCase()}}`; | ||
}; | ||
/** | ||
* | ||
* @param {Object} customCredentialManager | ||
* @returns | ||
*/ | ||
exports.checkValidCustomCredentialManager = function (customCredentialManager) { | ||
if ( typeof customCredentialManager !== 'object') { | ||
return false; | ||
} | ||
const requireMethods = ['write', 'read', 'remove']; | ||
for (const method of requireMethods) { | ||
if (!Object.hasOwnProperty.call(customCredentialManager, method) || typeof customCredentialManager[method] !== 'function') { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
exports.checkParametersDefined = function (...parameters) { | ||
return parameters.every((element) => element !== undefined && element !== null); | ||
}; | ||
exports.shouldPerformGCPBucket = function (accessToken) { | ||
@@ -714,1 +805,11 @@ return !!accessToken && process.env.SNOWFLAKE_FORCE_GCP_USE_DOWNSCOPED_CREDENTIAL !== 'true'; | ||
}; | ||
exports.validatePath = function (dir) { | ||
try { | ||
const stat = fs.statSync(dir); | ||
return stat.isDirectory(); | ||
} catch { | ||
Logger.getInstance().error('The path location is invalid. Please check this location is accessible or existing'); | ||
return false; | ||
} | ||
}; |
{ | ||
"name": "snowflake-sdk", | ||
"version": "1.11.0", | ||
"version": "1.12.0", | ||
"description": "Node.js driver for Snowflake", | ||
@@ -8,3 +8,3 @@ "dependencies": { | ||
"@aws-sdk/node-http-handler": "^3.374.0", | ||
"@azure/storage-blob": "^12.11.0", | ||
"@azure/storage-blob": "12.18.x", | ||
"@google-cloud/storage": "^7.7.0", | ||
@@ -34,2 +34,3 @@ "@techteamer/ocsp": "1.0.1", | ||
"simple-lru-cache": "^0.0.2", | ||
"toml": "^3.0.0", | ||
"uuid": "^8.3.2", | ||
@@ -36,0 +37,0 @@ "winston": "^3.1.0" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
603156
73
16958
31
44
+ Addedtoml@^3.0.0
+ Added@aws-sdk/client-s3@3.705.0(transitive)
+ Added@azure/abort-controller@1.1.0(transitive)
+ Added@azure/core-http@3.0.5(transitive)
+ Added@azure/core-tracing@1.0.0-preview.13(transitive)
+ Added@azure/storage-blob@12.18.0(transitive)
+ Added@opentelemetry/api@1.9.0(transitive)
+ Added@types/node-fetch@2.6.12(transitive)
+ Added@types/tunnel@0.0.3(transitive)
+ Addedaxios@1.7.9(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addedprocess@0.11.10(transitive)
+ Addedsax@1.4.1(transitive)
+ Addedtoml@3.0.0(transitive)
+ Addedtunnel@0.0.6(transitive)
+ Addedxml2js@0.5.0(transitive)
+ Addedxmlbuilder@11.0.1(transitive)
- Removed@aws-sdk/client-s3@3.703.0(transitive)
- Removed@azure/core-client@1.9.2(transitive)
- Removed@azure/core-http-compat@2.1.2(transitive)
- Removed@azure/core-rest-pipeline@1.18.1(transitive)
- Removed@azure/core-tracing@1.2.0(transitive)
- Removed@azure/core-xml@1.4.4(transitive)
- Removed@azure/storage-blob@12.26.0(transitive)
- Removedaxios@1.7.8(transitive)
- Removeddebug@4.3.7(transitive)
- Removedhttp-proxy-agent@7.0.2(transitive)
Updated@azure/storage-blob@12.18.x