From 7de3ea64ff73bdbe14d111cc2e1570cee3ccd422 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 09:54:44 -0700 Subject: [PATCH 01/81] SNOW-715524: Add SSO token cache --- .../ISnowflakeCredentialManager.cs | 25 +++ .../SnowflakeCredentialManagerAdysTechImpl.cs | 48 ++++++ .../SnowflakeCredentialManagerFactory.cs | 47 ++++++ .../SnowflakeCredentialManagerIFileImpl.cs | 132 +++++++++++++++ .../SnowflakeCredentialManagerInMemoryImpl.cs | 94 +++++++++++ .../ExternalBrowserAuthenticator.cs | 152 ++++++++++-------- Snowflake.Data/Core/RestResponse.cs | 3 + Snowflake.Data/Core/SFError.cs | 5 +- Snowflake.Data/Core/Session/SFSession.cs | 31 +++- .../Session/SFSessionHttpClientProperties.cs | 5 +- .../Core/Session/SFSessionParameter.cs | 1 + .../Core/Session/SFSessionProperty.cs | 4 +- Snowflake.Data/Core/Tools/FileOperations.cs | 2 + Snowflake.Data/Core/Tools/UnixOperations.cs | 13 ++ Snowflake.Data/Snowflake.Data.csproj | 1 + 15 files changed, 491 insertions(+), 72 deletions(-) create mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs create mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs create mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs create mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs create mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs new file mode 100644 index 000000000..16cf2a83a --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Core; + +namespace Snowflake.Data.Client +{ + internal enum TokenType + { + [StringAttr(value = "ID_TOKEN")] + IdToken, + [StringAttr(value = "MFATOKEN")] + MfaToken + } + + public interface ISnowflakeCredentialManager + { + string GetCredentials(string key); + + void RemoveCredentials(string key); + + void SaveCredentials(string key, string token); + } +} diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs new file mode 100644 index 000000000..d393a6817 --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using AdysTech.CredentialManager; +using Snowflake.Data.Log; +using System; +using System.Net; + +namespace Snowflake.Data.Client +{ + public class SnowflakeCredentialManagerAdysTechImpl : ISnowflakeCredentialManager + { + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + public string GetCredentials(string key) + { + try + { + var networkCredentials = CredentialManager.GetCredentials(key); + return networkCredentials.Password; + } + catch (NullReferenceException) + { + s_logger.Info("Unable to get credentials for the specified key"); + return ""; + } + } + + public void RemoveCredentials(string key) + { + try + { + CredentialManager.RemoveCredentials(key); + } + catch (CredentialAPIException) + { + s_logger.Info("Unable to remove credentials because the specified key did not exist in the credential manager"); + } + } + + public void SaveCredentials(string key, string token) + { + var networkCredentials = new NetworkCredential(key, token); + CredentialManager.SaveCredentials(key, networkCredentials); + } + } +} diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs new file mode 100644 index 000000000..0d913d61c --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Core; +using Snowflake.Data.Log; +using System.Runtime.InteropServices; + +namespace Snowflake.Data.Client +{ + public class SnowflakeCredentialManagerFactory + { + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + private static ISnowflakeCredentialManager s_customCredentialManager = null; + + internal static string BuildCredentialKey(string host, string user, string tokenType) + { + return $"{host.ToUpper()}:{user.ToUpper()}:{SFEnvironment.DriverName}:{tokenType.ToUpper()}"; + } + + public static void UseDefaultCredentialManager() + { + s_customCredentialManager = null; + } + + public static void SetCredentialManager(ISnowflakeCredentialManager customCredentialManager) + { + s_customCredentialManager = customCredentialManager; + } + + internal static ISnowflakeCredentialManager GetCredentialManager() + { + if (s_customCredentialManager == null) + { + s_logger.Info("Using the default credential manager"); + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) + new SnowflakeCredentialManagerAdysTechImpl() : new SnowflakeCredentialManagerInMemoryImpl(); + } + else + { + s_logger.Info("Using a custom credential manager"); + return s_customCredentialManager; + } + } + } +} diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs new file mode 100644 index 000000000..13891b405 --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Mono.Unix; +using Mono.Unix.Native; +using Newtonsoft.Json; +using Snowflake.Data.Core.Tools; +using Snowflake.Data.Log; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; +using KeyToken = System.Collections.Generic.Dictionary; + +namespace Snowflake.Data.Client +{ + public class SnowflakeCredentialManagerIFileImpl : ISnowflakeCredentialManager + { + private const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; + + private static readonly string CustomCredentialCacheDirectory = Environment.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); + + private static readonly string DefaultCredentialCacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + private static readonly string JsonCacheDirectory = string.IsNullOrEmpty(CustomCredentialCacheDirectory) ? DefaultCredentialCacheDirectory : CustomCredentialCacheDirectory; + + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + private static readonly string s_jsonPath = Path.Combine(JsonCacheDirectory, "temporary_credential.json"); + + private readonly FileOperations _fileOperations; + + private readonly UnixOperations _unixOperations; + + public SnowflakeCredentialManagerIFileImpl() + { + _fileOperations = FileOperations.Instance; + _unixOperations = UnixOperations.Instance; + } + + internal SnowflakeCredentialManagerIFileImpl(FileOperations fileOperations, UnixOperations unixOperations) + { + _fileOperations = fileOperations; + _unixOperations = unixOperations; + } + + internal void WriteToJsonFile(string content) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _fileOperations.Write(s_jsonPath, content); + FileInfo info = new FileInfo(s_jsonPath); + FileSecurity security = info.GetAccessControl(); + FileSystemAccessRule rule = new FileSystemAccessRule( + new SecurityIdentifier(WellKnownSidType.CreatorOwnerSid, null), + FileSystemRights.FullControl, + AccessControlType.Allow); + security.SetAccessRule(rule); + info.SetAccessControl(security); + } + else + { + if (!Directory.Exists(JsonCacheDirectory)) + { + Directory.CreateDirectory(JsonCacheDirectory); + } + var createFileResult = _unixOperations.CreateFileWithPermissions(s_jsonPath, + FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR); + if (createFileResult == -1) + { + var errorMessage = "Failed to create the JSON token cache file"; + s_logger.Error(errorMessage); + throw new Exception(errorMessage); + } + else + { + _fileOperations.Write(s_jsonPath, content); + } + + var jsonPermissions = _unixOperations.GetFilePermissions(s_jsonPath); + if (jsonPermissions != FileAccessPermissions.UserReadWriteExecute) + { + var errorMessage = "Permission for the JSON token cache file should contain only the owner access"; + s_logger.Error(errorMessage); + throw new Exception(errorMessage); + } + } + } + + internal KeyToken ReadJsonFile() + { + return JsonConvert.DeserializeObject(File.ReadAllText(s_jsonPath)); + } + + public string GetCredentials(string key) + { + if (_fileOperations.Exists(s_jsonPath)) + { + var keyTokenPairs = ReadJsonFile(); + + if (keyTokenPairs.TryGetValue(key, out string token)) + { + return token; + } + } + + s_logger.Info("Unable to get credentials for the specified key"); + return ""; + } + + public void RemoveCredentials(string key) + { + if (_fileOperations.Exists(s_jsonPath)) + { + var keyTokenPairs = ReadJsonFile(); + keyTokenPairs.Remove(key); + WriteToJsonFile(JsonConvert.SerializeObject(keyTokenPairs)); + } + } + + public void SaveCredentials(string key, string token) + { + KeyToken keyTokenPairs = _fileOperations.Exists(s_jsonPath) ? ReadJsonFile() : new KeyToken(); + keyTokenPairs[key] = token; + + string jsonString = JsonConvert.SerializeObject(keyTokenPairs); + WriteToJsonFile(jsonString); + } + } +} diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs new file mode 100644 index 000000000..c653978f1 --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Log; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Snowflake.Data.Client +{ + public struct EncryptedToken + { + public byte[] IV; + public byte[] Key; + public byte[] Bytes; + } + + internal class SnowflakeCredentialEncryption + { + internal static EncryptedToken EncryptToken(string token) + { + EncryptedToken encryptedToken; + + using (var aes = Aes.Create()) + { + aes.Mode = CipherMode.CBC; + aes.BlockSize = 128; + aes.GenerateKey(); + encryptedToken.Key = aes.Key; + aes.GenerateIV(); + encryptedToken.IV = aes.IV; + + using (var encryptor = aes.CreateEncryptor()) + { + var tokenBytes = Encoding.UTF8.GetBytes(token); + encryptedToken.Bytes = encryptor.TransformFinalBlock(tokenBytes, 0, tokenBytes.Length); + } + } + + return encryptedToken; + } + + internal static string DecryptToken(EncryptedToken encryptedToken) + { + using (var aes = Aes.Create()) + { + aes.BlockSize = 128; + aes.Mode = CipherMode.CBC; + aes.Key = encryptedToken.Key; + aes.IV = encryptedToken.IV; + + using (var decryptor = aes.CreateDecryptor()) + { + var decryptedBytes = decryptor.TransformFinalBlock(encryptedToken.Bytes, 0, encryptedToken.Bytes.Length); + return Encoding.UTF8.GetString(decryptedBytes); + } + } + } + } + + public class SnowflakeCredentialManagerInMemoryImpl : ISnowflakeCredentialManager + { + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + private static readonly Dictionary s_credentials = new Dictionary(); + + public string GetCredentials(string key) + { + EncryptedToken token; + s_credentials.TryGetValue(key, out token); + + if (token.Bytes == null) + { + s_logger.Info("Unable to get credentials for the specified key"); + return ""; + } + else + { + return SnowflakeCredentialEncryption.DecryptToken(s_credentials[key]); + } + } + + public void RemoveCredentials(string key) + { + s_credentials.Remove(key); + } + + public void SaveCredentials(string key, string token) + { + s_credentials[key] = SnowflakeCredentialEncryption.EncryptToken(token); + } + } +} diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index d6ead6818..909b8afd3 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -49,46 +49,49 @@ async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) { logger.Info("External Browser Authentication"); - int localPort = GetRandomUnusedPort(); - using (var httpListener = GetHttpListener(localPort)) + if (string.IsNullOrEmpty(session._idToken)) { - httpListener.Start(); - - logger.Debug("Get IdpUrl and ProofKey"); - string loginUrl; - if (session._disableConsoleLogin) - { - var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); - var authenticatorRestResponse = - await session.restRequester.PostAsync( - authenticatorRestRequest, - cancellationToken - ).ConfigureAwait(false); - authenticatorRestResponse.FilterFailedResponse(); - - loginUrl = authenticatorRestResponse.data.ssoUrl; - _proofKey = authenticatorRestResponse.data.proofKey; - } - else + int localPort = GetRandomUnusedPort(); + using (var httpListener = GetHttpListener(localPort)) { - _proofKey = GenerateProofKey(); - loginUrl = GetLoginUrl(_proofKey, localPort); - } + httpListener.Start(); - logger.Debug("Open browser"); - StartBrowser(loginUrl); + logger.Debug("Get IdpUrl and ProofKey"); + string loginUrl; + if (session._disableConsoleLogin) + { + var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); + var authenticatorRestResponse = + await session.restRequester.PostAsync( + authenticatorRestRequest, + cancellationToken + ).ConfigureAwait(false); + authenticatorRestResponse.FilterFailedResponse(); + + loginUrl = authenticatorRestResponse.data.ssoUrl; + _proofKey = authenticatorRestResponse.data.proofKey; + } + else + { + _proofKey = GenerateProofKey(); + loginUrl = GetLoginUrl(_proofKey, localPort); + } - logger.Debug("Get the redirect SAML request"); - _successEvent = new ManualResetEvent(false); - httpListener.BeginGetContext(GetContextCallback, httpListener); - var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - if (!_successEvent.WaitOne(timeoutInSec * 1000)) - { - logger.Warn("Browser response timeout"); - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); + logger.Debug("Open browser"); + StartBrowser(loginUrl); + + logger.Debug("Get the redirect SAML request"); + _successEvent = new ManualResetEvent(false); + httpListener.BeginGetContext(GetContextCallback, httpListener); + var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); + if (!_successEvent.WaitOne(timeoutInSec * 1000)) + { + logger.Warn("Browser response timeout"); + throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); + } + + httpListener.Stop(); } - - httpListener.Stop(); } logger.Debug("Send login request"); @@ -100,42 +103,45 @@ void IAuthenticator.Authenticate() { logger.Info("External Browser Authentication"); - int localPort = GetRandomUnusedPort(); - using (var httpListener = GetHttpListener(localPort)) + if (string.IsNullOrEmpty(session._idToken)) { - httpListener.Start(); - - logger.Debug("Get IdpUrl and ProofKey"); - string loginUrl; - if (session._disableConsoleLogin) + int localPort = GetRandomUnusedPort(); + using (var httpListener = GetHttpListener(localPort)) { - var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); - var authenticatorRestResponse = session.restRequester.Post(authenticatorRestRequest); - authenticatorRestResponse.FilterFailedResponse(); + httpListener.Start(); - loginUrl = authenticatorRestResponse.data.ssoUrl; - _proofKey = authenticatorRestResponse.data.proofKey; - } - else - { - _proofKey = GenerateProofKey(); - loginUrl = GetLoginUrl(_proofKey, localPort); - } + logger.Debug("Get IdpUrl and ProofKey"); + string loginUrl; + if (session._disableConsoleLogin) + { + var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); + var authenticatorRestResponse = session.restRequester.Post(authenticatorRestRequest); + authenticatorRestResponse.FilterFailedResponse(); + + loginUrl = authenticatorRestResponse.data.ssoUrl; + _proofKey = authenticatorRestResponse.data.proofKey; + } + else + { + _proofKey = GenerateProofKey(); + loginUrl = GetLoginUrl(_proofKey, localPort); + } - logger.Debug("Open browser"); - StartBrowser(loginUrl); + logger.Debug("Open browser"); + StartBrowser(loginUrl); - logger.Debug("Get the redirect SAML request"); - _successEvent = new ManualResetEvent(false); - httpListener.BeginGetContext(GetContextCallback, httpListener); - var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - if (!_successEvent.WaitOne(timeoutInSec * 1000)) - { - logger.Warn("Browser response timeout"); - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); + logger.Debug("Get the redirect SAML request"); + _successEvent = new ManualResetEvent(false); + httpListener.BeginGetContext(GetContextCallback, httpListener); + var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); + if (!_successEvent.WaitOne(timeoutInSec * 1000)) + { + logger.Warn("Browser response timeout"); + throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); + } + + httpListener.Stop(); } - - httpListener.Stop(); } logger.Debug("Send login request"); @@ -252,6 +258,8 @@ private SFRestRequest BuildAuthenticatorRestRequest(int port) AccountName = session.properties[SFSessionProperty.ACCOUNT], Authenticator = AUTH_NAME, BrowserModeRedirectPort = port.ToString(), + DriverName = SFEnvironment.DriverName, + DriverVersion = SFEnvironment.DriverVersion, }; int connectionTimeoutSec = int.Parse(session.properties[SFSessionProperty.CONNECTION_TIMEOUT]); @@ -262,9 +270,17 @@ private SFRestRequest BuildAuthenticatorRestRequest(int port) /// protected override void SetSpecializedAuthenticatorData(ref LoginRequestData data) { - // Add the token and proof key to the Data - data.Token = _samlResponseToken; - data.ProofKey = _proofKey; + if (string.IsNullOrEmpty(session._idToken)) + { + // Add the token and proof key to the Data + data.Token = _samlResponseToken; + data.ProofKey = _proofKey; + } + else + { + data.Token = session._idToken; + data.Authenticator = TokenType.IdToken.GetAttribute().value; + } } private string GetLoginUrl(string proofKey, int localPort) diff --git a/Snowflake.Data/Core/RestResponse.cs b/Snowflake.Data/Core/RestResponse.cs index 75f1698ea..faab39748 100755 --- a/Snowflake.Data/Core/RestResponse.cs +++ b/Snowflake.Data/Core/RestResponse.cs @@ -91,6 +91,9 @@ internal class LoginResponseData [JsonProperty(PropertyName = "masterValidityInSeconds", NullValueHandling = NullValueHandling.Ignore)] internal int masterValidityInSeconds { get; set; } + + [JsonProperty(PropertyName = "idToken", NullValueHandling = NullValueHandling.Ignore)] + internal string idToken { get; set; } } internal class AuthenticatorResponseData diff --git a/Snowflake.Data/Core/SFError.cs b/Snowflake.Data/Core/SFError.cs index ee59e9241..8698e1a57 100755 --- a/Snowflake.Data/Core/SFError.cs +++ b/Snowflake.Data/Core/SFError.cs @@ -83,7 +83,10 @@ public enum SFError IO_ERROR_ON_GETPUT_COMMAND, [SFErrorAttr(errorCode = 270059)] - EXECUTE_COMMAND_ON_CLOSED_CONNECTION + EXECUTE_COMMAND_ON_CLOSED_CONNECTION, + + [SFErrorAttr(errorCode = 390195)] + ID_TOKEN_INVALID } class SFErrorAttr : Attribute diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 8f56fdda4..c5cfd8eef 100755 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -83,6 +83,12 @@ public class SFSession internal int _maxRetryTimeout; + private readonly ISnowflakeCredentialManager _credManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + internal bool _allowSSOTokenCaching; + + internal string _idToken; + internal void ProcessLoginResponse(LoginResponse authnResponse) { if (authnResponse.success) @@ -99,6 +105,12 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) { logger.Debug("Query context cache disabled."); } + if (_allowSSOTokenCaching && authenticator is ExternalBrowserAuthenticator && !string.IsNullOrEmpty(authnResponse.data.idToken)) + { + _idToken = authnResponse.data.idToken; + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken.ToString()); + _credManager.SaveCredentials(key, _idToken); + } logger.Debug($"Session opened: {sessionId}"); _startTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } @@ -111,7 +123,17 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) ""); logger.Error("Authentication failed", e); - throw e; + + if (e.ErrorCode == SFError.ID_TOKEN_INVALID.GetAttribute().errorCode) + { + logger.Error("SSO Token has expired or not valid. Reauthenticating without SSO token...", e); + _idToken = null; + authenticator.Authenticate(); + } + else + { + throw e; + } } } @@ -170,6 +192,13 @@ internal SFSession( _easyLoggingStarter.Init(easyLoggingConfigFile); _maxRetryCount = extractedProperties.maxHttpRetries; _maxRetryTimeout = extractedProperties.retryTimeout; + _allowSSOTokenCaching = extractedProperties.allowSSOTokenCaching; + + if (_allowSSOTokenCaching) + { + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken.ToString()); + _idToken = _credManager.GetCredentials(key); + } } catch (Exception e) { diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index f129de25a..4a21832d4 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -22,6 +22,7 @@ internal class SFSessionHttpClientProperties internal int maxHttpRetries; internal bool includeRetryReason; internal SFSessionHttpClientProxyProperties proxyProperties; + internal bool allowSSOTokenCaching; internal void CheckPropertiesAreValid() { @@ -92,6 +93,7 @@ internal Dictionary ToParameterMap() var parameterMap = new Dictionary(); parameterMap[SFSessionParameter.CLIENT_VALIDATE_DEFAULT_PARAMETERS] = validateDefaultParameters; parameterMap[SFSessionParameter.CLIENT_SESSION_KEEP_ALIVE] = clientSessionKeepAlive; + parameterMap[SFSessionParameter.CLIENT_STORE_TEMPORARY_CREDENTIAL] = allowSSOTokenCaching; return parameterMap; } @@ -122,7 +124,8 @@ public SFSessionHttpClientProperties ExtractProperties(SFSessionProperties prope retryTimeout = int.Parse(propertiesDictionary[SFSessionProperty.RETRY_TIMEOUT]), maxHttpRetries = int.Parse(propertiesDictionary[SFSessionProperty.MAXHTTPRETRIES]), includeRetryReason = Boolean.Parse(propertiesDictionary[SFSessionProperty.INCLUDERETRYREASON]), - proxyProperties = proxyPropertiesExtractor.ExtractProperties(propertiesDictionary) + proxyProperties = proxyPropertiesExtractor.ExtractProperties(propertiesDictionary), + allowSSOTokenCaching = Boolean.Parse(propertiesDictionary[SFSessionProperty.ALLOW_SSO_TOKEN_CACHING]) }; } } diff --git a/Snowflake.Data/Core/Session/SFSessionParameter.cs b/Snowflake.Data/Core/Session/SFSessionParameter.cs index 97fdcec23..445e4fad5 100755 --- a/Snowflake.Data/Core/Session/SFSessionParameter.cs +++ b/Snowflake.Data/Core/Session/SFSessionParameter.cs @@ -14,5 +14,6 @@ internal enum SFSessionParameter QUERY_CONTEXT_CACHE_SIZE, DATE_OUTPUT_FORMAT, TIME_OUTPUT_FORMAT, + CLIENT_STORE_TEMPORARY_CREDENTIAL, } } diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index 6ed45be81..68829fc2c 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -94,7 +94,9 @@ internal enum SFSessionProperty [SFSessionPropertyAttr(required = false, defaultValue = "true")] DISABLE_CONSOLE_LOGIN, [SFSessionPropertyAttr(required = false, defaultValue = "false")] - ALLOWUNDERSCORESINHOST + ALLOWUNDERSCORESINHOST, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] + ALLOW_SSO_TOKEN_CACHING } class SFSessionPropertyAttr : Attribute diff --git a/Snowflake.Data/Core/Tools/FileOperations.cs b/Snowflake.Data/Core/Tools/FileOperations.cs index 9efe481bd..656c51257 100644 --- a/Snowflake.Data/Core/Tools/FileOperations.cs +++ b/Snowflake.Data/Core/Tools/FileOperations.cs @@ -14,5 +14,7 @@ public virtual bool Exists(string path) { return File.Exists(path); } + + public virtual void Write(string path, string content) => File.WriteAllText(path, content); } } diff --git a/Snowflake.Data/Core/Tools/UnixOperations.cs b/Snowflake.Data/Core/Tools/UnixOperations.cs index cb44099b7..4bcfa9310 100644 --- a/Snowflake.Data/Core/Tools/UnixOperations.cs +++ b/Snowflake.Data/Core/Tools/UnixOperations.cs @@ -4,6 +4,8 @@ using Mono.Unix; using Mono.Unix.Native; +using System; +using System.Runtime.InteropServices; namespace Snowflake.Data.Core.Tools { @@ -11,11 +13,22 @@ internal class UnixOperations { public static readonly UnixOperations Instance = new UnixOperations(); + public virtual int CreateFileWithPermissions(string path, FilePermissions permissions) + { + return Syscall.creat(path, permissions); + } + public virtual int CreateDirectoryWithPermissions(string path, FilePermissions permissions) { return Syscall.mkdir(path, permissions); } + public virtual FileAccessPermissions GetFilePermissions(string path) + { + var fileInfo = new UnixFileInfo(path); + return fileInfo.FileAccessPermissions; + } + public virtual FileAccessPermissions GetDirPermissions(string path) { var dirInfo = new UnixDirectoryInfo(path); diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 0621c5fb0..3c8ac03a6 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -19,6 +19,7 @@ + From 4457077bbde8a9723e91b85fe731810fd8f429f5 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 09:58:30 -0700 Subject: [PATCH 02/81] Add tests --- .../IntegrationTests/SFConnectionIT.cs | 93 +++++++++ .../CredentialManager/SFCredentialManager.cs | 192 ++++++++++++++++++ .../UnitTests/SFSessionPropertyTest.cs | 69 +++++-- 3 files changed, 342 insertions(+), 12 deletions(-) create mode 100644 Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index 8d69fe606..3d89c0dc5 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -1020,6 +1020,66 @@ public void TestSSOConnectionTimeoutAfter10s() Assert.LessOrEqual(stopwatch.ElapsedMilliseconds, (waitSeconds + 5) * 1000); } + [Test] + [Ignore("This test requires manual interaction and therefore cannot be run in CI")] + public void TestSSOConnectionWithTokenCaching() + { + using (IDbConnection conn = new SnowflakeDbConnection()) + { + conn.ConnectionString = String.Format("scheme={0};host={1};port={2};" + + "account={3};user={4};password={5};authenticator={6};allow_sso_token_caching={7}", + testConfig.protocol, + testConfig.host, + testConfig.port, + testConfig.account, + testConfig.user, + "", + "externalbrowser", + true); + + // Authenticate to retrieve and store the token if doesn't exist or invalid + conn.Open(); + Assert.AreEqual(ConnectionState.Open, conn.State); + + conn.Close(); + Assert.AreEqual(ConnectionState.Closed, conn.State); + + // Authenticate using the token + conn.Open(); + Assert.AreEqual(ConnectionState.Open, conn.State); + } + } + + [Test] + [Ignore("This test requires manual interaction and therefore cannot be run in CI")] + public void TestSSOConnectionWithInvalidCachedToken() + { + using (IDbConnection conn = new SnowflakeDbConnection()) + { + conn.ConnectionString = String.Format("scheme={0};host={1};port={2};" + + "account={3};user={4};password={5};authenticator={6};allow_sso_token_caching={7}", + testConfig.protocol, + testConfig.host, + testConfig.port, + testConfig.account, + testConfig.user, + "", + "externalbrowser", + true); + + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken.ToString()); + var credentialManager = new SnowflakeCredentialManagerInMemoryImpl(); + credentialManager.SaveCredentials(key, "wrongToken"); + + SnowflakeCredentialManagerFactory.SetCredentialManager(credentialManager); + + conn.Open(); + Assert.AreEqual(ConnectionState.Open, conn.State); + + SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); + } + } + [Test] [Ignore("This test requires manual interaction and therefore cannot be run in CI")] public void TestSSOConnectionWithWrongUser() @@ -2169,6 +2229,39 @@ public void TestNativeOktaSuccess() Assert.AreEqual(ConnectionState.Open, conn.State); } } + + [Test] + [Ignore("This test requires manual interaction and therefore cannot be run in CI")] + public void TestSSOConnectionWithTokenCachingAsync() + { + using (SnowflakeDbConnection conn = new SnowflakeDbConnection()) + { + conn.ConnectionString = String.Format("scheme={0};host={1};port={2};" + + "account={3};user={4};password={5};authenticator={6};allow_sso_token_caching={7}", + testConfig.protocol, + testConfig.host, + testConfig.port, + testConfig.account, + testConfig.user, + "", + "externalbrowser", + true); + + // Authenticate to retrieve and store the token if doesn't exist or invalid + Task connectTask = conn.OpenAsync(CancellationToken.None); + connectTask.Wait(); + Assert.AreEqual(ConnectionState.Open, conn.State); + + connectTask = conn.CloseAsync(CancellationToken.None); + connectTask.Wait(); + Assert.AreEqual(ConnectionState.Closed, conn.State); + + // Authenticate using the token + connectTask = conn.OpenAsync(CancellationToken.None); + connectTask.Wait(); + Assert.AreEqual(ConnectionState.Open, conn.State); + } + } } } diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs new file mode 100644 index 000000000..035bdd660 --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +namespace Snowflake.Data.Tests.UnitTests +{ + using Mono.Unix; + using Mono.Unix.Native; + using Moq; + using NUnit.Framework; + using Snowflake.Data.Client; + using Snowflake.Data.Core.Tools; + using System; + using System.IO; + using System.Runtime.InteropServices; + + [TestFixture] + class SFCredentialManager + { + ISnowflakeCredentialManager _credentialManager; + + [ThreadStatic] + private static Mock t_fileOperations; + + [ThreadStatic] + private static Mock t_unixOperations; + + private static readonly string s_expectedJsonPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "temporary_credential.json"); + + [SetUp] public void SetUp() + { + t_fileOperations = new Mock(); + t_unixOperations = new Mock(); + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerInMemoryImpl()); + } + + [TearDown] public void TearDown() + { + SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); + } + + [Test] + public void TestUsingDefaultCredentialManager() + { + // arrange + SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); + + // act + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // assert + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.IsInstanceOf(_credentialManager); + } + else + { + Assert.IsInstanceOf(_credentialManager); + } + } + + [Test] + public void TestSettingCustomCredentialManager() + { + // arrange + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl()); + + // act + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // assert + Assert.IsInstanceOf(_credentialManager); + } + + [Test] + public void TestDefaultCredentialManager() + { + // arrange + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); + var expectedToken = "token"; + + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act + var actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + + // act + _credentialManager.SaveCredentials(key, expectedToken); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.AreEqual(expectedToken, actualToken); + + // act + _credentialManager.RemoveCredentials(key); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + } + + [Test] + public void TestJsonCredentialManager() + { + // arrange + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); + var expectedToken = "token"; + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl()); + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act + var actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + + // act + _credentialManager.SaveCredentials(key, expectedToken); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.AreEqual(expectedToken, actualToken); + + // act + _credentialManager.RemoveCredentials(key); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + } + + [Test] + public void TestThatThrowsErrorWhenCacheFileIsNotCreated() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); + var token = "token"; + + t_unixOperations + .Setup(e => e.CreateFileWithPermissions(s_expectedJsonPath, + FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) + .Returns(-1); + + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_unixOperations.Object)); + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act + var thrown = Assert.Throws(() => _credentialManager.SaveCredentials(key, token)); + + // assert + Assert.That(thrown.Message, Does.Contain("Failed to create the JSON token cache file")); + } + + [Test] + public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); + var token = "token"; + + t_unixOperations + .Setup(e => e.CreateFileWithPermissions(s_expectedJsonPath, + FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) + .Returns(0); + t_unixOperations + .Setup(e => e.GetFilePermissions(s_expectedJsonPath)) + .Returns(FileAccessPermissions.AllPermissions); + + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_unixOperations.Object)); + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act + var thrown = Assert.Throws(() => _credentialManager.SaveCredentials(key, token)); + + // assert + Assert.That(thrown.Message, Does.Contain("Permission for the JSON token cache file should contain only the owner access")); + } + } +} diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 4b2e3ec8f..3a499f814 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -138,6 +138,7 @@ public static IEnumerable ConnectionStringTestCases() string defDisableQueryContextCache = "false"; string defDisableConsoleLogin = "true"; string defAllowUnderscoresInHost = "false"; + string defAllowSSOTokenCaching = "false"; var simpleTestCase = new TestCase() { @@ -165,7 +166,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; @@ -194,7 +196,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseWithProxySettings = new TestCase() @@ -225,7 +228,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};useProxy=true;proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -258,7 +262,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -290,7 +295,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseWithIncludeRetryReason = new TestCase() @@ -319,7 +325,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, "false" }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseWithDisableQueryContextCache = new TestCase() @@ -347,7 +354,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, "true" }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLEQUERYCONTEXTCACHE=true" @@ -377,7 +385,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, "false" }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLE_CONSOLE_LOGIN=false" @@ -409,7 +418,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseUnderscoredAccountName = new TestCase() @@ -438,7 +448,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseUnderscoredAccountNameWithEnabledAllowUnderscores = new TestCase() @@ -467,9 +478,42 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, "true" } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, "true" }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; + + var testCaseWithAllowSSOTokenCaching = new TestCase() + { + ExpectedProperties = new SFSessionProperties() + { + { SFSessionProperty.ACCOUNT, defAccount }, + { SFSessionProperty.USER, defUser }, + { SFSessionProperty.HOST, defHost }, + { SFSessionProperty.AUTHENTICATOR, defAuthenticator }, + { SFSessionProperty.SCHEME, defScheme }, + { SFSessionProperty.CONNECTION_TIMEOUT, defConnectionTimeout }, + { SFSessionProperty.PASSWORD, defPassword }, + { SFSessionProperty.PORT, defPort }, + { SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS, "true" }, + { SFSessionProperty.USEPROXY, "false" }, + { SFSessionProperty.INSECUREMODE, "false" }, + { SFSessionProperty.DISABLERETRY, "false" }, + { SFSessionProperty.FORCERETRYON404, "false" }, + { SFSessionProperty.CLIENT_SESSION_KEEP_ALIVE, "false" }, + { SFSessionProperty.FORCEPARSEERROR, "false" }, + { SFSessionProperty.BROWSER_RESPONSE_TIMEOUT, defBrowserResponseTime }, + { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, + { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, + { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, "true" } + }, + ConnectionString = + $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};ALLOW_SSO_TOKEN_CACHING=true" + }; return new TestCase[] { simpleTestCase, @@ -482,7 +526,8 @@ public static IEnumerable ConnectionStringTestCases() testCaseWithDisableConsoleLogin, testCaseComplicatedAccountName, testCaseUnderscoredAccountName, - testCaseUnderscoredAccountNameWithEnabledAllowUnderscores + testCaseUnderscoredAccountNameWithEnabledAllowUnderscores, + testCaseWithAllowSSOTokenCaching }; } From 15a58beaa615000ec1e2003e55675d09396be426 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 09:59:35 -0700 Subject: [PATCH 03/81] Remove unused namespace --- Snowflake.Data/Core/Tools/UnixOperations.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Snowflake.Data/Core/Tools/UnixOperations.cs b/Snowflake.Data/Core/Tools/UnixOperations.cs index 4bcfa9310..c7722ab4b 100644 --- a/Snowflake.Data/Core/Tools/UnixOperations.cs +++ b/Snowflake.Data/Core/Tools/UnixOperations.cs @@ -4,8 +4,6 @@ using Mono.Unix; using Mono.Unix.Native; -using System; -using System.Runtime.InteropServices; namespace Snowflake.Data.Core.Tools { From 7f28fa823f94f6c9958a79f1a8a6612d2999e977 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 10:41:15 -0700 Subject: [PATCH 04/81] Fix unit test --- .../UnitTests/Session/SFHttpClientPropertiesTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs index 617e3d429..84dad9fb9 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs @@ -45,7 +45,7 @@ public void TestConvertToMapOnly2Properties( var parameterMap = properties.ToParameterMap(); // assert - Assert.AreEqual(2, parameterMap.Count); + Assert.AreEqual(3, parameterMap.Count); Assert.AreEqual(validateDefaultParameters, parameterMap[SFSessionParameter.CLIENT_VALIDATE_DEFAULT_PARAMETERS]); Assert.AreEqual(clientSessionKeepAlive, parameterMap[SFSessionParameter.CLIENT_SESSION_KEEP_ALIVE]); } From a3171580933a20c9f8229fa140337a11799c13d2 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 14:09:26 -0700 Subject: [PATCH 05/81] Refactor constructor and tests --- .../CredentialManager/SFCredentialManager.cs | 28 +++++++++++++------ .../SnowflakeCredentialManagerAdysTechImpl.cs | 4 ++- .../SnowflakeCredentialManagerFactory.cs | 4 +-- .../SnowflakeCredentialManagerIFileImpl.cs | 17 ++++++----- .../SnowflakeCredentialManagerInMemoryImpl.cs | 4 ++- Snowflake.Data/Core/Session/SFSession.cs | 6 ++-- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs index 035bdd660..80357ec71 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs @@ -22,16 +22,22 @@ class SFCredentialManager [ThreadStatic] private static Mock t_fileOperations; + [ThreadStatic] + private static Mock t_directoryOperations; + [ThreadStatic] private static Mock t_unixOperations; - private static readonly string s_expectedJsonPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "temporary_credential.json"); + private static readonly string s_expectedJsonDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + private static readonly string s_expectedJsonPath = Path.Combine(s_expectedJsonDir, "temporary_credential.json"); [SetUp] public void SetUp() { t_fileOperations = new Mock(); + t_directoryOperations = new Mock(); t_unixOperations = new Mock(); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerInMemoryImpl()); + SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerInMemoryImpl.Instance); } [TearDown] public void TearDown() @@ -63,7 +69,7 @@ public void TestUsingDefaultCredentialManager() public void TestSettingCustomCredentialManager() { // arrange - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl()); + SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerIFileImpl.Instance); // act _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); @@ -108,7 +114,7 @@ public void TestJsonCredentialManager() // arrange var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); var expectedToken = "token"; - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl()); + SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerIFileImpl.Instance); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // act @@ -144,12 +150,16 @@ public void TestThatThrowsErrorWhenCacheFileIsNotCreated() var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); var token = "token"; + t_directoryOperations + .Setup(d => d.Exists(s_expectedJsonDir)) + .Returns(false); + t_unixOperations - .Setup(e => e.CreateFileWithPermissions(s_expectedJsonPath, + .Setup(u => u.CreateFileWithPermissions(s_expectedJsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(-1); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_unixOperations.Object)); + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // act @@ -172,14 +182,14 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() var token = "token"; t_unixOperations - .Setup(e => e.CreateFileWithPermissions(s_expectedJsonPath, + .Setup(u => u.CreateFileWithPermissions(s_expectedJsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(0); t_unixOperations - .Setup(e => e.GetFilePermissions(s_expectedJsonPath)) + .Setup(u => u.GetFilePermissions(s_expectedJsonPath)) .Returns(FileAccessPermissions.AllPermissions); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_unixOperations.Object)); + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // act diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs index d393a6817..20f925b0e 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ @@ -13,6 +13,8 @@ public class SnowflakeCredentialManagerAdysTechImpl : ISnowflakeCredentialManage { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + public static readonly SnowflakeCredentialManagerAdysTechImpl Instance = new SnowflakeCredentialManagerAdysTechImpl(); + public string GetCredentials(string key) { try diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index 0d913d61c..9f8a41f74 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ @@ -35,7 +35,7 @@ internal static ISnowflakeCredentialManager GetCredentialManager() { s_logger.Info("Using the default credential manager"); return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) - new SnowflakeCredentialManagerAdysTechImpl() : new SnowflakeCredentialManagerInMemoryImpl(); + SnowflakeCredentialManagerAdysTechImpl.Instance : SnowflakeCredentialManagerInMemoryImpl.Instance; } else { diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs index 13891b405..89b4bcade 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ @@ -32,17 +32,16 @@ public class SnowflakeCredentialManagerIFileImpl : ISnowflakeCredentialManager private readonly FileOperations _fileOperations; + private readonly DirectoryOperations _directoryOperations; + private readonly UnixOperations _unixOperations; - public SnowflakeCredentialManagerIFileImpl() - { - _fileOperations = FileOperations.Instance; - _unixOperations = UnixOperations.Instance; - } + public static readonly SnowflakeCredentialManagerIFileImpl Instance = new SnowflakeCredentialManagerIFileImpl(FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance); - internal SnowflakeCredentialManagerIFileImpl(FileOperations fileOperations, UnixOperations unixOperations) + internal SnowflakeCredentialManagerIFileImpl(FileOperations fileOperations, DirectoryOperations directoryOperations, UnixOperations unixOperations) { _fileOperations = fileOperations; + _directoryOperations = directoryOperations; _unixOperations = unixOperations; } @@ -62,9 +61,9 @@ internal void WriteToJsonFile(string content) } else { - if (!Directory.Exists(JsonCacheDirectory)) + if (!_directoryOperations.Exists(JsonCacheDirectory)) { - Directory.CreateDirectory(JsonCacheDirectory); + _directoryOperations.CreateDirectory(JsonCacheDirectory); } var createFileResult = _unixOperations.CreateFileWithPermissions(s_jsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR); diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs index c653978f1..b7b9c2bf4 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ @@ -65,6 +65,8 @@ public class SnowflakeCredentialManagerInMemoryImpl : ISnowflakeCredentialManage private static readonly Dictionary s_credentials = new Dictionary(); + public static readonly SnowflakeCredentialManagerInMemoryImpl Instance = new SnowflakeCredentialManagerInMemoryImpl(); + public string GetCredentials(string key) { EncryptedToken token; diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index c5cfd8eef..1dfbac0ee 100755 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ @@ -100,14 +100,14 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) schema = authnResponse.data.authResponseSessionInfo.schemaName; serverVersion = authnResponse.data.serverVersion; masterValidityInSeconds = authnResponse.data.masterValidityInSeconds; + _idToken = authnResponse.data.idToken; UpdateSessionParameterMap(authnResponse.data.nameValueParameter); if (_disableQueryContextCache) { logger.Debug("Query context cache disabled."); } - if (_allowSSOTokenCaching && authenticator is ExternalBrowserAuthenticator && !string.IsNullOrEmpty(authnResponse.data.idToken)) + if (_allowSSOTokenCaching && authenticator is ExternalBrowserAuthenticator && !string.IsNullOrEmpty(_idToken)) { - _idToken = authnResponse.data.idToken; var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken.ToString()); _credManager.SaveCredentials(key, _idToken); } From 625e04bdb58e151c6fcabccbda28666d01846e9d Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 16:23:26 -0700 Subject: [PATCH 06/81] Refactor test --- .../IntegrationTests/SFConnectionIT.cs | 43 +++------ .../CredentialManager/SFCredentialManager.cs | 95 ++++++++++--------- 2 files changed, 61 insertions(+), 77 deletions(-) diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index 18fd66a4d..d03c7435e 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. */ @@ -1026,16 +1026,9 @@ public void TestSSOConnectionWithTokenCaching() { using (IDbConnection conn = new SnowflakeDbConnection()) { - conn.ConnectionString = String.Format("scheme={0};host={1};port={2};" + - "account={3};user={4};password={5};authenticator={6};allow_sso_token_caching={7}", - testConfig.protocol, - testConfig.host, - testConfig.port, - testConfig.account, - testConfig.user, - "", - "externalbrowser", - true); + conn.ConnectionString + = ConnectionStringWithoutAuth + + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; // Authenticate to retrieve and store the token if doesn't exist or invalid conn.Open(); @@ -1056,19 +1049,12 @@ public void TestSSOConnectionWithInvalidCachedToken() { using (IDbConnection conn = new SnowflakeDbConnection()) { - conn.ConnectionString = String.Format("scheme={0};host={1};port={2};" + - "account={3};user={4};password={5};authenticator={6};allow_sso_token_caching={7}", - testConfig.protocol, - testConfig.host, - testConfig.port, - testConfig.account, - testConfig.user, - "", - "externalbrowser", - true); + conn.ConnectionString + = ConnectionStringWithoutAuth + + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken.ToString()); - var credentialManager = new SnowflakeCredentialManagerInMemoryImpl(); + var credentialManager = SnowflakeCredentialManagerInMemoryImpl.Instance; credentialManager.SaveCredentials(key, "wrongToken"); SnowflakeCredentialManagerFactory.SetCredentialManager(credentialManager); @@ -2290,16 +2276,9 @@ public void TestSSOConnectionWithTokenCachingAsync() { using (SnowflakeDbConnection conn = new SnowflakeDbConnection()) { - conn.ConnectionString = String.Format("scheme={0};host={1};port={2};" + - "account={3};user={4};password={5};authenticator={6};allow_sso_token_caching={7}", - testConfig.protocol, - testConfig.host, - testConfig.port, - testConfig.account, - testConfig.user, - "", - "externalbrowser", - true); + conn.ConnectionString + = ConnectionStringWithoutAuth + + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; // Authenticate to retrieve and store the token if doesn't exist or invalid Task connectTask = conn.OpenAsync(CancellationToken.None); diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs index 80357ec71..31693a28e 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs @@ -45,6 +45,39 @@ [TearDown] public void TearDown() SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); } + private void TestCredentialManagerImplementation() + { + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); + var expectedToken = "token"; + + // act + var actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + + // act + _credentialManager.SaveCredentials(key, expectedToken); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.AreEqual(expectedToken, actualToken); + + // act + _credentialManager.RemoveCredentials(key); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + + // act + _credentialManager.RemoveCredentials(key); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + } + [Test] public void TestUsingDefaultCredentialManager() { @@ -79,63 +112,35 @@ public void TestSettingCustomCredentialManager() } [Test] - public void TestDefaultCredentialManager() + public void TestAdysTechCredentialManager() { - // arrange - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); - var expectedToken = "token"; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on non-Windows"); + } + // arrange + SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerAdysTechImpl.Instance); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + TestCredentialManagerImplementation(); + } - // act - var actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); - - // act - _credentialManager.SaveCredentials(key, expectedToken); - actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.AreEqual(expectedToken, actualToken); - - // act - _credentialManager.RemoveCredentials(key); - actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + [Test] + public void TestInMemoryCredentialManager() + { + // arrange + SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerInMemoryImpl.Instance); + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + TestCredentialManagerImplementation(); } [Test] public void TestJsonCredentialManager() { // arrange - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); - var expectedToken = "token"; SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerIFileImpl.Instance); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); - - // act - var actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); - - // act - _credentialManager.SaveCredentials(key, expectedToken); - actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.AreEqual(expectedToken, actualToken); - - // act - _credentialManager.RemoveCredentials(key); - actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + TestCredentialManagerImplementation(); } [Test] From 383fe5eed92732c8edd2ac5fedfb2135e34ee296 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 20:07:29 -0700 Subject: [PATCH 07/81] Refactor test and file impl --- .../CredentialManager/SFCredentialManager.cs | 34 ++++++++----- .../UnitTests/SFSessionTest.cs | 18 +++++++ .../SnowflakeCredentialManagerIFileImpl.cs | 49 +++++++++++-------- 3 files changed, 67 insertions(+), 34 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs index 31693a28e..af2dd2686 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs @@ -28,6 +28,9 @@ class SFCredentialManager [ThreadStatic] private static Mock t_unixOperations; + [ThreadStatic] + private static Mock t_environmentOperations; + private static readonly string s_expectedJsonDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); private static readonly string s_expectedJsonPath = Path.Combine(s_expectedJsonDir, "temporary_credential.json"); @@ -37,6 +40,7 @@ [SetUp] public void SetUp() t_fileOperations = new Mock(); t_directoryOperations = new Mock(); t_unixOperations = new Mock(); + t_environmentOperations = new Mock(); SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerInMemoryImpl.Instance); } @@ -47,6 +51,7 @@ [TearDown] public void TearDown() private void TestCredentialManagerImplementation() { + // arrange var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); var expectedToken = "token"; @@ -122,6 +127,8 @@ public void TestAdysTechCredentialManager() // arrange SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerAdysTechImpl.Instance); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act & assert TestCredentialManagerImplementation(); } @@ -131,6 +138,8 @@ public void TestInMemoryCredentialManager() // arrange SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerInMemoryImpl.Instance); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act & assert TestCredentialManagerImplementation(); } @@ -140,6 +149,8 @@ public void TestJsonCredentialManager() // arrange SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerIFileImpl.Instance); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act & assert TestCredentialManagerImplementation(); } @@ -152,23 +163,21 @@ public void TestThatThrowsErrorWhenCacheFileIsNotCreated() } // arrange - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); - var token = "token"; - t_directoryOperations .Setup(d => d.Exists(s_expectedJsonDir)) .Returns(false); - t_unixOperations .Setup(u => u.CreateFileWithPermissions(s_expectedJsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(-1); - - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object)); + t_environmentOperations + .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerIFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Returns("testdirectory"); + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // act - var thrown = Assert.Throws(() => _credentialManager.SaveCredentials(key, token)); + var thrown = Assert.Throws(() => _credentialManager.SaveCredentials("key", "token")); // assert Assert.That(thrown.Message, Does.Contain("Failed to create the JSON token cache file")); @@ -183,9 +192,6 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() } // arrange - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); - var token = "token"; - t_unixOperations .Setup(u => u.CreateFileWithPermissions(s_expectedJsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) @@ -193,12 +199,14 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() t_unixOperations .Setup(u => u.GetFilePermissions(s_expectedJsonPath)) .Returns(FileAccessPermissions.AllPermissions); - - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object)); + t_environmentOperations + .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerIFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Returns("testdirectory"); + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // act - var thrown = Assert.Throws(() => _credentialManager.SaveCredentials(key, token)); + var thrown = Assert.Throws(() => _credentialManager.SaveCredentials("key", "token")); // assert Assert.That(thrown.Message, Does.Contain("Permission for the JSON token cache file should contain only the owner access")); diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs index b9530b83b..e96f9d792 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs @@ -9,6 +9,7 @@ namespace Snowflake.Data.Tests.UnitTests { using Snowflake.Data.Core; using NUnit.Framework; + using System; [TestFixture] class SFSessionTest @@ -61,5 +62,22 @@ public void TestThatConfiguresEasyLogging(string configPath) // assert easyLoggingStarter.Verify(starter => starter.Init(configPath)); } + + [Test] + public void TestThatRetriesAuthenticationForInvalidIdToken() + { + // arrange + var connectionString = "account=test;user=test;password=test;allow_sso_token_caching=true"; + var session = new SFSession(connectionString, null); + LoginResponse authnResponse = new LoginResponse + { + code = SFError.ID_TOKEN_INVALID.GetAttribute().errorCode, + message = "", + success = false + }; + + // assert + Assert.Throws(() => session.ProcessLoginResponse(authnResponse)); + } } } diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs index 89b4bcade..521425e36 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs @@ -18,17 +18,13 @@ namespace Snowflake.Data.Client { public class SnowflakeCredentialManagerIFileImpl : ISnowflakeCredentialManager { - private const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; - - private static readonly string CustomCredentialCacheDirectory = Environment.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); - - private static readonly string DefaultCredentialCacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - - private static readonly string JsonCacheDirectory = string.IsNullOrEmpty(CustomCredentialCacheDirectory) ? DefaultCredentialCacheDirectory : CustomCredentialCacheDirectory; + internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private static readonly string s_jsonPath = Path.Combine(JsonCacheDirectory, "temporary_credential.json"); + private readonly string _jsonCacheDirectory; + + private readonly string _jsonCacheFilePath; private readonly FileOperations _fileOperations; @@ -36,21 +32,32 @@ public class SnowflakeCredentialManagerIFileImpl : ISnowflakeCredentialManager private readonly UnixOperations _unixOperations; - public static readonly SnowflakeCredentialManagerIFileImpl Instance = new SnowflakeCredentialManagerIFileImpl(FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance); + private readonly EnvironmentOperations _environmentOperations; - internal SnowflakeCredentialManagerIFileImpl(FileOperations fileOperations, DirectoryOperations directoryOperations, UnixOperations unixOperations) + public static readonly SnowflakeCredentialManagerIFileImpl Instance = new SnowflakeCredentialManagerIFileImpl(FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance, EnvironmentOperations.Instance); + + internal SnowflakeCredentialManagerIFileImpl(FileOperations fileOperations, DirectoryOperations directoryOperations, UnixOperations unixOperations, EnvironmentOperations environmentOperations) { _fileOperations = fileOperations; _directoryOperations = directoryOperations; _unixOperations = unixOperations; + _environmentOperations = environmentOperations; + SetCredentialCachePath(ref _jsonCacheDirectory, ref _jsonCacheFilePath); + } + + private void SetCredentialCachePath(ref string _jsonCacheDirectory, ref string _jsonCacheFilePath) + { + var customDirectory = _environmentOperations.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); + _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? _environmentOperations.GetFolderPath(Environment.SpecialFolder.UserProfile) : customDirectory; + _jsonCacheFilePath = Path.Combine(_jsonCacheDirectory, "temporary_credential.json"); } internal void WriteToJsonFile(string content) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - _fileOperations.Write(s_jsonPath, content); - FileInfo info = new FileInfo(s_jsonPath); + _fileOperations.Write(_jsonCacheFilePath, content); + FileInfo info = new FileInfo(_jsonCacheFilePath); FileSecurity security = info.GetAccessControl(); FileSystemAccessRule rule = new FileSystemAccessRule( new SecurityIdentifier(WellKnownSidType.CreatorOwnerSid, null), @@ -61,11 +68,11 @@ internal void WriteToJsonFile(string content) } else { - if (!_directoryOperations.Exists(JsonCacheDirectory)) + if (!_directoryOperations.Exists(_jsonCacheDirectory)) { - _directoryOperations.CreateDirectory(JsonCacheDirectory); + _directoryOperations.CreateDirectory(_jsonCacheDirectory); } - var createFileResult = _unixOperations.CreateFileWithPermissions(s_jsonPath, + var createFileResult = _unixOperations.CreateFileWithPermissions(_jsonCacheFilePath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR); if (createFileResult == -1) { @@ -75,10 +82,10 @@ internal void WriteToJsonFile(string content) } else { - _fileOperations.Write(s_jsonPath, content); + _fileOperations.Write(_jsonCacheFilePath, content); } - var jsonPermissions = _unixOperations.GetFilePermissions(s_jsonPath); + var jsonPermissions = _unixOperations.GetFilePermissions(_jsonCacheFilePath); if (jsonPermissions != FileAccessPermissions.UserReadWriteExecute) { var errorMessage = "Permission for the JSON token cache file should contain only the owner access"; @@ -90,12 +97,12 @@ internal void WriteToJsonFile(string content) internal KeyToken ReadJsonFile() { - return JsonConvert.DeserializeObject(File.ReadAllText(s_jsonPath)); + return JsonConvert.DeserializeObject(File.ReadAllText(_jsonCacheFilePath)); } public string GetCredentials(string key) { - if (_fileOperations.Exists(s_jsonPath)) + if (_fileOperations.Exists(_jsonCacheFilePath)) { var keyTokenPairs = ReadJsonFile(); @@ -111,7 +118,7 @@ public string GetCredentials(string key) public void RemoveCredentials(string key) { - if (_fileOperations.Exists(s_jsonPath)) + if (_fileOperations.Exists(_jsonCacheFilePath)) { var keyTokenPairs = ReadJsonFile(); keyTokenPairs.Remove(key); @@ -121,7 +128,7 @@ public void RemoveCredentials(string key) public void SaveCredentials(string key, string token) { - KeyToken keyTokenPairs = _fileOperations.Exists(s_jsonPath) ? ReadJsonFile() : new KeyToken(); + KeyToken keyTokenPairs = _fileOperations.Exists(_jsonCacheFilePath) ? ReadJsonFile() : new KeyToken(); keyTokenPairs[key] = token; string jsonString = JsonConvert.SerializeObject(keyTokenPairs); From 8ce9d10f80cc54e10dea2fe04b924ef3d8142d16 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 18 Apr 2024 20:45:05 -0700 Subject: [PATCH 08/81] Refactor file name and fix test --- ...lManager.cs => SFCredentialManagerTest.cs} | 22 +++++++++++-------- .../SnowflakeCredentialManagerIFileImpl.cs | 4 +++- 2 files changed, 16 insertions(+), 10 deletions(-) rename Snowflake.Data.Tests/UnitTests/CredentialManager/{SFCredentialManager.cs => SFCredentialManagerTest.cs} (89%) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs similarity index 89% rename from Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs rename to Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index af2dd2686..85a049e11 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManager.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -15,7 +15,7 @@ namespace Snowflake.Data.Tests.UnitTests using System.Runtime.InteropServices; [TestFixture] - class SFCredentialManager + class SFCredentialManagerTest { ISnowflakeCredentialManager _credentialManager; @@ -31,9 +31,13 @@ class SFCredentialManager [ThreadStatic] private static Mock t_environmentOperations; - private static readonly string s_expectedJsonDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + private static readonly string s_defaultJsonDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - private static readonly string s_expectedJsonPath = Path.Combine(s_expectedJsonDir, "temporary_credential.json"); + private const string CustomJsonDir = "testdirectory"; + + private static readonly string s_defaultJsonPath = Path.Combine(s_defaultJsonDir, SnowflakeCredentialManagerIFileImpl.CredentialCacheFileName); + + private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SnowflakeCredentialManagerIFileImpl.CredentialCacheFileName); [SetUp] public void SetUp() { @@ -164,15 +168,15 @@ public void TestThatThrowsErrorWhenCacheFileIsNotCreated() // arrange t_directoryOperations - .Setup(d => d.Exists(s_expectedJsonDir)) + .Setup(d => d.Exists(s_defaultJsonDir)) .Returns(false); t_unixOperations - .Setup(u => u.CreateFileWithPermissions(s_expectedJsonPath, + .Setup(u => u.CreateFileWithPermissions(s_customJsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(-1); t_environmentOperations .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerIFileImpl.CredentialCacheDirectoryEnvironmentName)) - .Returns("testdirectory"); + .Returns(CustomJsonDir); SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); @@ -193,15 +197,15 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() // arrange t_unixOperations - .Setup(u => u.CreateFileWithPermissions(s_expectedJsonPath, + .Setup(u => u.CreateFileWithPermissions(s_defaultJsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(0); t_unixOperations - .Setup(u => u.GetFilePermissions(s_expectedJsonPath)) + .Setup(u => u.GetFilePermissions(s_defaultJsonPath)) .Returns(FileAccessPermissions.AllPermissions); t_environmentOperations .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerIFileImpl.CredentialCacheDirectoryEnvironmentName)) - .Returns("testdirectory"); + .Returns(CustomJsonDir); SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs index 521425e36..90a4aa6fb 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs @@ -20,6 +20,8 @@ public class SnowflakeCredentialManagerIFileImpl : ISnowflakeCredentialManager { internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; + internal const string CredentialCacheFileName = "temporary_credential.json"; + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); private readonly string _jsonCacheDirectory; @@ -49,7 +51,7 @@ private void SetCredentialCachePath(ref string _jsonCacheDirectory, ref string _ { var customDirectory = _environmentOperations.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? _environmentOperations.GetFolderPath(Environment.SpecialFolder.UserProfile) : customDirectory; - _jsonCacheFilePath = Path.Combine(_jsonCacheDirectory, "temporary_credential.json"); + _jsonCacheFilePath = Path.Combine(_jsonCacheDirectory, CredentialCacheFileName); } internal void WriteToJsonFile(string content) From e0b65d02dc45b6a2aeb2713bd2d713570f8cd1e2 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 19 Apr 2024 10:12:32 -0700 Subject: [PATCH 09/81] Revert removed lines --- .../UnitTests/SFSessionPropertyTest.cs | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 6f0c3646d..91b9d0326 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2019 Snowflake Computing Inc. All rights reserved. */ @@ -138,6 +138,7 @@ public static IEnumerable ConnectionStringTestCases() string defDisableQueryContextCache = "false"; string defDisableConsoleLogin = "true"; string defAllowUnderscoresInHost = "false"; + string defAllowSSOTokenCaching = "false"; var simpleTestCase = new TestCase() { @@ -165,7 +166,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; @@ -194,7 +196,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseWithProxySettings = new TestCase() @@ -225,7 +228,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};useProxy=true;proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -258,7 +262,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -290,7 +295,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseWithIncludeRetryReason = new TestCase() @@ -319,7 +325,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, "false" }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseWithDisableQueryContextCache = new TestCase() @@ -347,7 +354,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, "true" }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLEQUERYCONTEXTCACHE=true" @@ -377,7 +385,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, "false" }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLE_CONSOLE_LOGIN=false" @@ -409,7 +418,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseUnderscoredAccountName = new TestCase() @@ -438,7 +448,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseUnderscoredAccountNameWithEnabledAllowUnderscores = new TestCase() @@ -467,7 +478,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, "true" } + { SFSessionProperty.ALLOWUNDERSCORESINHOST, "true" }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; @@ -499,7 +511,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, { SFSessionProperty.ALLOWUNDERSCORESINHOST, "false" }, - { SFSessionProperty.QUERY_TAG, testQueryTag } + { SFSessionProperty.QUERY_TAG, testQueryTag }, + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } } }; var testCaseWithAllowSSOTokenCaching = new TestCase() From 6606823dc680927d87a0fa6eb7f2b2a3146a9778 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 19 Apr 2024 12:45:26 -0700 Subject: [PATCH 10/81] Refactor code and remove unnecessary check --- Snowflake.Data/Core/Session/SFSession.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index e2312bfa0..a43303c70 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -102,14 +102,14 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) schema = authnResponse.data.authResponseSessionInfo.schemaName; serverVersion = authnResponse.data.serverVersion; masterValidityInSeconds = authnResponse.data.masterValidityInSeconds; - _idToken = authnResponse.data.idToken; UpdateSessionParameterMap(authnResponse.data.nameValueParameter); if (_disableQueryContextCache) { logger.Debug("Query context cache disabled."); } - if (_allowSSOTokenCaching && authenticator is ExternalBrowserAuthenticator && !string.IsNullOrEmpty(_idToken)) + if (_allowSSOTokenCaching && !string.IsNullOrEmpty(_idToken)) { + _idToken = authnResponse.data.idToken; var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken.ToString()); _credManager.SaveCredentials(key, _idToken); } From 4e6869d9f44866d7d37e978be85b1b2ba3d1bb5f Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 19 Apr 2024 12:46:30 -0700 Subject: [PATCH 11/81] Add session test --- .../UnitTests/SFSessionTest.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs index e96f9d792..daf412d46 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs @@ -63,6 +63,30 @@ public void TestThatConfiguresEasyLogging(string configPath) easyLoggingStarter.Verify(starter => starter.Init(configPath)); } + [Test] + public void TestThatIdTokenIsStoredWhenCachingIsEnabled() + { + // arrange + var expectedIdToken = "mockIdToken"; + var connectionString = $"account=account;user=user;password=test;authenticator=externalbrowser;allow_sso_token_caching=true"; + var session = new SFSession(connectionString, null); + LoginResponse authnResponse = new LoginResponse + { + data = new LoginResponseData() + { + idToken = expectedIdToken, + authResponseSessionInfo = new SessionInfo(), + }, + success = true + }; + + // act + session.ProcessLoginResponse(authnResponse); + + // assert + Assert.AreEqual(expectedIdToken, session._idToken); + } + [Test] public void TestThatRetriesAuthenticationForInvalidIdToken() { From 378847492ac2d02d329209c5e6496aa46cfa2069 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 19 Apr 2024 13:53:19 -0700 Subject: [PATCH 12/81] Fix test --- Snowflake.Data/Core/Session/SFSession.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index a43303c70..4b6b27c11 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -107,7 +107,7 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) { logger.Debug("Query context cache disabled."); } - if (_allowSSOTokenCaching && !string.IsNullOrEmpty(_idToken)) + if (_allowSSOTokenCaching && !string.IsNullOrEmpty(authnResponse.data.idToken)) { _idToken = authnResponse.data.idToken; var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken.ToString()); From c597c64337780dce8985ea4c70f3e51808ef3cbc Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 19 Apr 2024 15:45:33 -0700 Subject: [PATCH 13/81] Refactor test and rename file --- .../SFCredentialManagerTest.cs | 200 +++++++++++------- .../UnitTests/SFSessionTest.cs | 4 +- ... => SnowflakeCredentialManagerFileImpl.cs} | 8 +- 3 files changed, 124 insertions(+), 88 deletions(-) rename Snowflake.Data/Client/SnowflakeCredentialManager/{SnowflakeCredentialManagerIFileImpl.cs => SnowflakeCredentialManagerFileImpl.cs} (90%) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 85a049e11..40dbe6ada 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -1,8 +1,8 @@ -/* +/* * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ -namespace Snowflake.Data.Tests.UnitTests +namespace Snowflake.Data.Tests.UnitTests.CredentialManager { using Mono.Unix; using Mono.Unix.Native; @@ -14,6 +14,114 @@ namespace Snowflake.Data.Tests.UnitTests using System.IO; using System.Runtime.InteropServices; + public abstract class SFBaseCredentialManagerTest + { + protected ISnowflakeCredentialManager _credentialManager; + + [Test] + public void TestSavingAndRemovingCredentials() + { + // arrange + var key = "mockKey"; + var expectedToken = "token"; + + // act + _credentialManager.SaveCredentials(key, expectedToken); + var actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.AreEqual(expectedToken, actualToken); + + // act + _credentialManager.RemoveCredentials(key); + actualToken = _credentialManager.GetCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + } + + [Test] + public void TestSavingCredentialsForAnExistingKey() + { + // arrange + var key = "mockKey"; + var firstExpectedToken = "mockToken1"; + var secondExpectedToken = "mockToken2"; + + try + { + // act + _credentialManager.SaveCredentials(key, firstExpectedToken); + + // assert + Assert.AreEqual(firstExpectedToken, _credentialManager.GetCredentials(key)); + + // act + _credentialManager.SaveCredentials(key, secondExpectedToken); + + // assert + Assert.AreEqual(secondExpectedToken, _credentialManager.GetCredentials(key)); + } + catch (Exception ex) + { + // assert + Assert.Fail("Should not throw an exception: " + ex.Message); + } + } + + [Test] + public void TestRemovingCredentialsForKeyThatDoesNotExist() + { + // arrange + var key = "mockKey"; + + try + { + // act + _credentialManager.RemoveCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(_credentialManager.GetCredentials(key))); + } + catch (Exception ex) + { + // assert + Assert.Fail("Should not throw an exception: " + ex.Message); + } + } + } + + [TestFixture] + [Platform("Win")] + public class SFAdysTechCredentialManagerTest : SFBaseCredentialManagerTest + { + [SetUp] + public void SetUp() + { + _credentialManager = SnowflakeCredentialManagerAdysTechImpl.Instance; + } + } + + [TestFixture] + public class SFInMemoryCredentialManagerTest : SFBaseCredentialManagerTest + { + [SetUp] + public void SetUp() + { + _credentialManager = SnowflakeCredentialManagerInMemoryImpl.Instance; + } + } + + [TestFixture] + public class SFFileCredentialManagerTest : SFBaseCredentialManagerTest + { + [SetUp] + public void SetUp() + { + _credentialManager = SnowflakeCredentialManagerFileImpl.Instance; + } + } + [TestFixture] class SFCredentialManagerTest { @@ -35,9 +143,9 @@ class SFCredentialManagerTest private const string CustomJsonDir = "testdirectory"; - private static readonly string s_defaultJsonPath = Path.Combine(s_defaultJsonDir, SnowflakeCredentialManagerIFileImpl.CredentialCacheFileName); + private static readonly string s_defaultJsonPath = Path.Combine(s_defaultJsonDir, SnowflakeCredentialManagerFileImpl.CredentialCacheFileName); - private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SnowflakeCredentialManagerIFileImpl.CredentialCacheFileName); + private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SnowflakeCredentialManagerFileImpl.CredentialCacheFileName); [SetUp] public void SetUp() { @@ -53,40 +161,6 @@ [TearDown] public void TearDown() SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); } - private void TestCredentialManagerImplementation() - { - // arrange - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey("host", "user", "tokentype"); - var expectedToken = "token"; - - // act - var actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); - - // act - _credentialManager.SaveCredentials(key, expectedToken); - actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.AreEqual(expectedToken, actualToken); - - // act - _credentialManager.RemoveCredentials(key); - actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); - - // act - _credentialManager.RemoveCredentials(key); - actualToken = _credentialManager.GetCredentials(key); - - // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); - } - [Test] public void TestUsingDefaultCredentialManager() { @@ -111,51 +185,13 @@ public void TestUsingDefaultCredentialManager() public void TestSettingCustomCredentialManager() { // arrange - SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerIFileImpl.Instance); + SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerFileImpl.Instance); // act _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // assert - Assert.IsInstanceOf(_credentialManager); - } - - [Test] - public void TestAdysTechCredentialManager() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Assert.Ignore("skip test on non-Windows"); - } - - // arrange - SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerAdysTechImpl.Instance); - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); - - // act & assert - TestCredentialManagerImplementation(); - } - - [Test] - public void TestInMemoryCredentialManager() - { - // arrange - SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerInMemoryImpl.Instance); - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); - - // act & assert - TestCredentialManagerImplementation(); - } - - [Test] - public void TestJsonCredentialManager() - { - // arrange - SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerIFileImpl.Instance); - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); - - // act & assert - TestCredentialManagerImplementation(); + Assert.IsInstanceOf(_credentialManager); } [Test] @@ -175,9 +211,9 @@ public void TestThatThrowsErrorWhenCacheFileIsNotCreated() FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(-1); t_environmentOperations - .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerIFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) .Returns(CustomJsonDir); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // act @@ -204,9 +240,9 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() .Setup(u => u.GetFilePermissions(s_defaultJsonPath)) .Returns(FileAccessPermissions.AllPermissions); t_environmentOperations - .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerIFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) .Returns(CustomJsonDir); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerIFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); // act diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs index daf412d46..a30c86389 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. */ @@ -68,7 +68,7 @@ public void TestThatIdTokenIsStoredWhenCachingIsEnabled() { // arrange var expectedIdToken = "mockIdToken"; - var connectionString = $"account=account;user=user;password=test;authenticator=externalbrowser;allow_sso_token_caching=true"; + var connectionString = $"account=account;user=user;password=test;allow_sso_token_caching=true"; var session = new SFSession(connectionString, null); LoginResponse authnResponse = new LoginResponse { diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs similarity index 90% rename from Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs rename to Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs index 90a4aa6fb..1bf141238 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerIFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs @@ -16,13 +16,13 @@ namespace Snowflake.Data.Client { - public class SnowflakeCredentialManagerIFileImpl : ISnowflakeCredentialManager + public class SnowflakeCredentialManagerFileImpl : ISnowflakeCredentialManager { internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; internal const string CredentialCacheFileName = "temporary_credential.json"; - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); private readonly string _jsonCacheDirectory; @@ -36,9 +36,9 @@ public class SnowflakeCredentialManagerIFileImpl : ISnowflakeCredentialManager private readonly EnvironmentOperations _environmentOperations; - public static readonly SnowflakeCredentialManagerIFileImpl Instance = new SnowflakeCredentialManagerIFileImpl(FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance, EnvironmentOperations.Instance); + public static readonly SnowflakeCredentialManagerFileImpl Instance = new SnowflakeCredentialManagerFileImpl(FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance, EnvironmentOperations.Instance); - internal SnowflakeCredentialManagerIFileImpl(FileOperations fileOperations, DirectoryOperations directoryOperations, UnixOperations unixOperations, EnvironmentOperations environmentOperations) + internal SnowflakeCredentialManagerFileImpl(FileOperations fileOperations, DirectoryOperations directoryOperations, UnixOperations unixOperations, EnvironmentOperations environmentOperations) { _fileOperations = fileOperations; _directoryOperations = directoryOperations; From b4ab4ed94d5b4d840a6e4314330f23a41319578c Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 1 May 2024 13:05:52 -0700 Subject: [PATCH 14/81] Add more logging --- .../SnowflakeCredentialManagerAdysTechImpl.cs | 6 ++++-- .../SnowflakeCredentialManagerFactory.cs | 2 ++ .../SnowflakeCredentialManagerFileImpl.cs | 6 ++++++ .../SnowflakeCredentialManagerInMemoryImpl.cs | 3 +++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs index 20f925b0e..395c9e54b 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs @@ -19,12 +19,13 @@ public string GetCredentials(string key) { try { + s_logger.Debug($"Getting the credentials for key: {key}"); var networkCredentials = CredentialManager.GetCredentials(key); return networkCredentials.Password; } catch (NullReferenceException) { - s_logger.Info("Unable to get credentials for the specified key"); + s_logger.Info($"Unable to get credentials for key: {key}"); return ""; } } @@ -37,12 +38,13 @@ public void RemoveCredentials(string key) } catch (CredentialAPIException) { - s_logger.Info("Unable to remove credentials because the specified key did not exist in the credential manager"); + s_logger.Info($"Unable to remove credentials because the specified key did not exist: {key}"); } } public void SaveCredentials(string key, string token) { + s_logger.Debug($"Saving the credentials for key: {key}"); var networkCredentials = new NetworkCredential(key, token); CredentialManager.SaveCredentials(key, networkCredentials); } diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index 9f8a41f74..29be3a122 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -21,11 +21,13 @@ internal static string BuildCredentialKey(string host, string user, string token public static void UseDefaultCredentialManager() { + s_logger.Info("Clearing the custom credential manager"); s_customCredentialManager = null; } public static void SetCredentialManager(ISnowflakeCredentialManager customCredentialManager) { + s_logger.Info("Setting the custom credential manager"); s_customCredentialManager = customCredentialManager; } diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs index 1bf141238..e3778c1bf 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs @@ -49,6 +49,7 @@ internal SnowflakeCredentialManagerFileImpl(FileOperations fileOperations, Direc private void SetCredentialCachePath(ref string _jsonCacheDirectory, ref string _jsonCacheFilePath) { + s_logger.Info("Setting the json credential cache path"); var customDirectory = _environmentOperations.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? _environmentOperations.GetFolderPath(Environment.SpecialFolder.UserProfile) : customDirectory; _jsonCacheFilePath = Path.Combine(_jsonCacheDirectory, CredentialCacheFileName); @@ -56,6 +57,7 @@ private void SetCredentialCachePath(ref string _jsonCacheDirectory, ref string _ internal void WriteToJsonFile(string content) { + s_logger.Debug("Writing credentials to json file"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _fileOperations.Write(_jsonCacheFilePath, content); @@ -74,6 +76,7 @@ internal void WriteToJsonFile(string content) { _directoryOperations.CreateDirectory(_jsonCacheDirectory); } + s_logger.Info("Creating the json file for credential cache"); var createFileResult = _unixOperations.CreateFileWithPermissions(_jsonCacheFilePath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR); if (createFileResult == -1) @@ -104,6 +107,7 @@ internal KeyToken ReadJsonFile() public string GetCredentials(string key) { + s_logger.Debug($"Getting credentials from json file for key: {key}"); if (_fileOperations.Exists(_jsonCacheFilePath)) { var keyTokenPairs = ReadJsonFile(); @@ -120,6 +124,7 @@ public string GetCredentials(string key) public void RemoveCredentials(string key) { + s_logger.Debug($"Removing credentials from json file for key: {key}"); if (_fileOperations.Exists(_jsonCacheFilePath)) { var keyTokenPairs = ReadJsonFile(); @@ -130,6 +135,7 @@ public void RemoveCredentials(string key) public void SaveCredentials(string key, string token) { + s_logger.Debug($"Saving credentials to json file for key: {key}"); KeyToken keyTokenPairs = _fileOperations.Exists(_jsonCacheFilePath) ? ReadJsonFile() : new KeyToken(); keyTokenPairs[key] = token; diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs index b7b9c2bf4..7927d6b63 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs @@ -72,6 +72,7 @@ public string GetCredentials(string key) EncryptedToken token; s_credentials.TryGetValue(key, out token); + s_logger.Debug($"Getting credentials from memory for key: {key}"); if (token.Bytes == null) { s_logger.Info("Unable to get credentials for the specified key"); @@ -85,11 +86,13 @@ public string GetCredentials(string key) public void RemoveCredentials(string key) { + s_logger.Debug($"Removing credentials from memory for key: {key}"); s_credentials.Remove(key); } public void SaveCredentials(string key, string token) { + s_logger.Debug($"Saving credentials into memory for key: {key}"); s_credentials[key] = SnowflakeCredentialEncryption.EncryptToken(token); } } From 13ba839ab14057d7ae5a31db3faf0bb4b4f38f41 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 1 May 2024 13:06:01 -0700 Subject: [PATCH 15/81] Change log from error to info --- Snowflake.Data/Core/Session/SFSession.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 4b6b27c11..25fdb4a16 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -128,7 +128,7 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) if (e.ErrorCode == SFError.ID_TOKEN_INVALID.GetAttribute().errorCode) { - logger.Error("SSO Token has expired or not valid. Reauthenticating without SSO token...", e); + logger.Info("SSO Token has expired or not valid. Reauthenticating without SSO token...", e); _idToken = null; authenticator.Authenticate(); } From b334fb3f069f0689847f3f75e30281592586fbb8 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 2 May 2024 11:45:26 -0700 Subject: [PATCH 16/81] Add file path to logs --- .../SnowflakeCredentialManagerFileImpl.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs index e3778c1bf..b4e780c1b 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs @@ -49,15 +49,15 @@ internal SnowflakeCredentialManagerFileImpl(FileOperations fileOperations, Direc private void SetCredentialCachePath(ref string _jsonCacheDirectory, ref string _jsonCacheFilePath) { - s_logger.Info("Setting the json credential cache path"); var customDirectory = _environmentOperations.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? _environmentOperations.GetFolderPath(Environment.SpecialFolder.UserProfile) : customDirectory; _jsonCacheFilePath = Path.Combine(_jsonCacheDirectory, CredentialCacheFileName); + s_logger.Info($"Setting the json credential cache path to {_jsonCacheFilePath}"); } internal void WriteToJsonFile(string content) { - s_logger.Debug("Writing credentials to json file"); + s_logger.Debug($"Writing credentials to json file in {_jsonCacheFilePath}"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _fileOperations.Write(_jsonCacheFilePath, content); @@ -76,7 +76,7 @@ internal void WriteToJsonFile(string content) { _directoryOperations.CreateDirectory(_jsonCacheDirectory); } - s_logger.Info("Creating the json file for credential cache"); + s_logger.Info($"Creating the json file for credential cache in {_jsonCacheFilePath}"); var createFileResult = _unixOperations.CreateFileWithPermissions(_jsonCacheFilePath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR); if (createFileResult == -1) @@ -107,7 +107,7 @@ internal KeyToken ReadJsonFile() public string GetCredentials(string key) { - s_logger.Debug($"Getting credentials from json file for key: {key}"); + s_logger.Debug($"Getting credentials from json file in {_jsonCacheFilePath} for key: {key}"); if (_fileOperations.Exists(_jsonCacheFilePath)) { var keyTokenPairs = ReadJsonFile(); @@ -124,7 +124,7 @@ public string GetCredentials(string key) public void RemoveCredentials(string key) { - s_logger.Debug($"Removing credentials from json file for key: {key}"); + s_logger.Debug($"Removing credentials from json file in {_jsonCacheFilePath} for key: {key}"); if (_fileOperations.Exists(_jsonCacheFilePath)) { var keyTokenPairs = ReadJsonFile(); @@ -135,7 +135,7 @@ public void RemoveCredentials(string key) public void SaveCredentials(string key, string token) { - s_logger.Debug($"Saving credentials to json file for key: {key}"); + s_logger.Debug($"Saving credentials to json file in {_jsonCacheFilePath} for key: {key}"); KeyToken keyTokenPairs = _fileOperations.Exists(_jsonCacheFilePath) ? ReadJsonFile() : new KeyToken(); keyTokenPairs[key] = token; From 623ce6a1ec81c2fd6e5225a4dd581d58d1bf7fb4 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 8 May 2024 12:12:47 -0700 Subject: [PATCH 17/81] Add Meziantou package for credential manager implementation --- .../SFCredentialManagerTest.cs | 23 +++++++-- ...SnowflakeCredentialManagerMeziantouImpl.cs | 51 +++++++++++++++++++ Snowflake.Data/Snowflake.Data.csproj | 1 + 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 40dbe6ada..914448edc 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -27,17 +27,15 @@ public void TestSavingAndRemovingCredentials() // act _credentialManager.SaveCredentials(key, expectedToken); - var actualToken = _credentialManager.GetCredentials(key); // assert - Assert.AreEqual(expectedToken, actualToken); + Assert.AreEqual(expectedToken, _credentialManager.GetCredentials(key)); // act _credentialManager.RemoveCredentials(key); - actualToken = _credentialManager.GetCredentials(key); // assert - Assert.IsTrue(string.IsNullOrEmpty(actualToken)); + Assert.IsTrue(string.IsNullOrEmpty(_credentialManager.GetCredentials(key))); } [Test] @@ -61,6 +59,12 @@ public void TestSavingCredentialsForAnExistingKey() // assert Assert.AreEqual(secondExpectedToken, _credentialManager.GetCredentials(key)); + + // act + _credentialManager.RemoveCredentials(key); + + // assert + Assert.IsTrue(string.IsNullOrEmpty(_credentialManager.GetCredentials(key))); } catch (Exception ex) { @@ -102,6 +106,17 @@ public void SetUp() } } + [TestFixture] + [Platform("Win")] + public class SFMeziantouCredentialManagerTest : SFBaseCredentialManagerTest + { + [SetUp] + public void SetUp() + { + _credentialManager = SnowflakeCredentialManagerMeziantouImpl.Instance; + } + } + [TestFixture] public class SFInMemoryCredentialManagerTest : SFBaseCredentialManagerTest { diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs new file mode 100644 index 000000000..d7739fd1d --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Meziantou.Framework.Win32; +using Snowflake.Data.Log; +using System; +using System.ComponentModel; + +namespace Snowflake.Data.Client +{ + public class SnowflakeCredentialManagerMeziantouImpl : ISnowflakeCredentialManager + { + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + public static readonly SnowflakeCredentialManagerMeziantouImpl Instance = new SnowflakeCredentialManagerMeziantouImpl(); + + public string GetCredentials(string key) + { + try + { + s_logger.Debug($"Getting the credentials for key: {key}"); + var networkCredentials = CredentialManager.ReadCredential(key); + return networkCredentials.Password; + } + catch (NullReferenceException) + { + s_logger.Info($"Unable to get credentials for key: {key}"); + return ""; + } + } + + public void RemoveCredentials(string key) + { + try + { + CredentialManager.DeleteCredential(key); + } + catch (Win32Exception) + { + s_logger.Info($"Unable to remove credentials because the specified key did not exist: {key}"); + } + } + + public void SaveCredentials(string key, string token) + { + s_logger.Debug($"Saving the credentials for key: {key}"); + CredentialManager.WriteCredential(key, key, token, CredentialPersistence.LocalMachine); + } + } +} diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 3c8ac03a6..5de2524a4 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -25,6 +25,7 @@ + From 976bac26dca4b236a79e8beaf2ffc606800ee157 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 17 May 2024 09:50:34 -0700 Subject: [PATCH 18/81] Add native implementation of credential cache --- .../SFCredentialManagerTest.cs | 11 ++ .../SnowflakeCredentialManagerAdysTechImpl.cs | 1 + ...SnowflakeCredentialManagerMeziantouImpl.cs | 1 + .../SnowflakeCredentialManagerNativeImpl.cs | 116 ++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 914448edc..f2031dcc1 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -95,6 +95,17 @@ public void TestRemovingCredentialsForKeyThatDoesNotExist() } } + [TestFixture] + [Platform("Win")] + public class SFNativeCredentialManagerTest : SFBaseCredentialManagerTest + { + [SetUp] + public void SetUp() + { + _credentialManager = SnowflakeCredentialManagerNativeImpl.Instance; + } + } + [TestFixture] [Platform("Win")] public class SFAdysTechCredentialManagerTest : SFBaseCredentialManagerTest diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs index 395c9e54b..9a134ae4e 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs @@ -34,6 +34,7 @@ public void RemoveCredentials(string key) { try { + s_logger.Debug($"Removing the credentials for key: {key}"); CredentialManager.RemoveCredentials(key); } catch (CredentialAPIException) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs index d7739fd1d..2033b1191 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs @@ -34,6 +34,7 @@ public void RemoveCredentials(string key) { try { + s_logger.Debug($"Removing the credentials for key: {key}"); CredentialManager.DeleteCredential(key); } catch (Win32Exception) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs new file mode 100644 index 000000000..97418ea2e --- /dev/null +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Microsoft.Win32.SafeHandles; +using Snowflake.Data.Log; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Snowflake.Data.Client +{ + public class SnowflakeCredentialManagerNativeImpl : ISnowflakeCredentialManager + { + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + public static readonly SnowflakeCredentialManagerNativeImpl Instance = new SnowflakeCredentialManagerNativeImpl(); + + public string GetCredentials(string key) + { + s_logger.Debug($"Getting the credentials for key: {key}"); + + IntPtr nCredPtr; + if (!CredRead(key, 1 /* Generic */, 0, out nCredPtr)) + { + s_logger.Info($"Unable to get credentials for key: {key}"); + return ""; + } + + var critCred = new CriticalCredentialHandle(nCredPtr); + Credential cred = critCred.GetCredential(); + return cred.CredentialBlob; + } + + public void RemoveCredentials(string key) + { + s_logger.Debug($"Removing the credentials for key: {key}"); + + if (!CredDelete(key, 1 /* Generic */, 0)) + { + s_logger.Info($"Unable to remove credentials because the specified key did not exist: {key}"); + } + } + + public void SaveCredentials(string key, string token) + { + s_logger.Debug($"Saving the credentials for key: {key}"); + + byte[] byteArray = Encoding.Unicode.GetBytes(token); + Credential credential = new Credential(); + credential.AttributeCount = 0; + credential.Attributes = IntPtr.Zero; + credential.Comment = IntPtr.Zero; + credential.TargetAlias = IntPtr.Zero; + credential.Type = 1; // Generic + credential.Persist = 2; // Local Machine + credential.CredentialBlobSize = (uint)(byteArray == null ? 0 : byteArray.Length); + credential.TargetName =key; + credential.CredentialBlob = token; + credential.UserName = Environment.UserName; + + CredWrite(ref credential, 0); + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct Credential + { + public uint Flags; + public uint Type; + [MarshalAs(UnmanagedType.LPWStr)] + public string TargetName; + public IntPtr Comment; + public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; + public uint CredentialBlobSize; + [MarshalAs(UnmanagedType.LPWStr)] + public string CredentialBlob; + public uint Persist; + public uint AttributeCount; + public IntPtr Attributes; + public IntPtr TargetAlias; + [MarshalAs(UnmanagedType.LPWStr)] + public string UserName; + } + + sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid + { + public CriticalCredentialHandle(IntPtr handle) + { + SetHandle(handle); + } + + public Credential GetCredential() + { + var credential = (Credential)Marshal.PtrToStructure(handle, typeof(Credential)); + return credential; + } + + protected override bool ReleaseHandle() + { + throw new NotImplementedException(); + } + } + + [DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern bool CredDelete(string target, uint type, int reservedFlag); + + [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] + static extern bool CredRead(string target, uint type, int reservedFlag, out IntPtr credentialPtr); + + [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] + static extern bool CredWrite([In] ref Credential userCredential, [In] UInt32 flags); + + [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] + static extern bool CredFree([In] IntPtr cred); + } +} From feab579881045f594f8949d591ece4d6eef0685b Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 17 May 2024 10:37:12 -0700 Subject: [PATCH 19/81] Add impl for ReleaseHandle --- .../SnowflakeCredentialManagerNativeImpl.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs index 97418ea2e..93f73bb82 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs @@ -97,7 +97,14 @@ public Credential GetCredential() protected override bool ReleaseHandle() { - throw new NotImplementedException(); + if (IsInvalid) + { + return false; + } + + CredFree(handle); + SetHandleAsInvalid(); + return true; } } From cb1c84f088e3e50d73ada5211cf7683b35834dbf Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 11:32:31 -0700 Subject: [PATCH 20/81] Remove MfaToken from TokenType enum --- .../ISnowflakeCredentialManager.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs index 16cf2a83a..494a6ef17 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ @@ -9,9 +9,7 @@ namespace Snowflake.Data.Client internal enum TokenType { [StringAttr(value = "ID_TOKEN")] - IdToken, - [StringAttr(value = "MFATOKEN")] - MfaToken + IdToken } public interface ISnowflakeCredentialManager From 7f0f801415de2219c18283ba30246952b8f55c78 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 11:47:12 -0700 Subject: [PATCH 21/81] Add class name to the log --- .../SnowflakeCredentialManagerFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index 29be3a122..0172e0073 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -27,7 +27,7 @@ public static void UseDefaultCredentialManager() public static void SetCredentialManager(ISnowflakeCredentialManager customCredentialManager) { - s_logger.Info("Setting the custom credential manager"); + s_logger.Info($"Setting the custom credential manager: {customCredentialManager.GetType().Name}"); s_customCredentialManager = customCredentialManager; } From 780d213d4d8f7068e050f244b5eacd3b83ac1447 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 11:52:47 -0700 Subject: [PATCH 22/81] Add class name to the log --- .../SnowflakeCredentialManagerFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index 0172e0073..42ba8a6f3 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -41,7 +41,7 @@ internal static ISnowflakeCredentialManager GetCredentialManager() } else { - s_logger.Info("Using a custom credential manager"); + s_logger.Info($"Using a custom credential manager: {s_customCredentialManager.GetType().Name}"); return s_customCredentialManager; } } From 575c0a437b2f152b148aa741f0261a5c9d127056 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 11:52:47 -0700 Subject: [PATCH 23/81] Add class name to the log --- .../SnowflakeCredentialManagerFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index 0172e0073..42ba8a6f3 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -41,7 +41,7 @@ internal static ISnowflakeCredentialManager GetCredentialManager() } else { - s_logger.Info("Using a custom credential manager"); + s_logger.Info($"Using a custom credential manager: {s_customCredentialManager.GetType().Name}"); return s_customCredentialManager; } } From aa0098243e0ff4d2bcc180ed684c869b7ab95dc2 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 12:19:41 -0700 Subject: [PATCH 24/81] Add class name to default log message --- .../SnowflakeCredentialManagerFactory.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index 42ba8a6f3..5e6704c35 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -35,9 +35,10 @@ internal static ISnowflakeCredentialManager GetCredentialManager() { if (s_customCredentialManager == null) { - s_logger.Info("Using the default credential manager"); - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) + var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) SnowflakeCredentialManagerAdysTechImpl.Instance : SnowflakeCredentialManagerInMemoryImpl.Instance; + s_logger.Info($"Using the default credential manager: {defaultCredentialManager.GetType().Name}"); + return defaultCredentialManager; } else { From 6f31fe6cc13bd0933772de334ac7c65739de914a Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 12:40:31 -0700 Subject: [PATCH 25/81] Rename native class and remove impl with external libs --- .../SFCredentialManagerTest.cs | 26 +-------- .../SnowflakeCredentialManagerAdysTechImpl.cs | 53 ------------------- .../SnowflakeCredentialManagerFactory.cs | 2 +- ...SnowflakeCredentialManagerMeziantouImpl.cs | 52 ------------------ ...lakeCredentialManagerWindowsNativeImpl.cs} | 6 +-- 5 files changed, 6 insertions(+), 133 deletions(-) delete mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs delete mode 100644 Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs rename Snowflake.Data/Client/SnowflakeCredentialManager/{SnowflakeCredentialManagerNativeImpl.cs => SnowflakeCredentialManagerWindowsNativeImpl.cs} (93%) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index f2031dcc1..9499dd919 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -102,29 +102,7 @@ public class SFNativeCredentialManagerTest : SFBaseCredentialManagerTest [SetUp] public void SetUp() { - _credentialManager = SnowflakeCredentialManagerNativeImpl.Instance; - } - } - - [TestFixture] - [Platform("Win")] - public class SFAdysTechCredentialManagerTest : SFBaseCredentialManagerTest - { - [SetUp] - public void SetUp() - { - _credentialManager = SnowflakeCredentialManagerAdysTechImpl.Instance; - } - } - - [TestFixture] - [Platform("Win")] - public class SFMeziantouCredentialManagerTest : SFBaseCredentialManagerTest - { - [SetUp] - public void SetUp() - { - _credentialManager = SnowflakeCredentialManagerMeziantouImpl.Instance; + _credentialManager = SnowflakeCredentialManagerWindowsNativeImpl.Instance; } } @@ -199,7 +177,7 @@ public void TestUsingDefaultCredentialManager() // assert if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.IsInstanceOf(_credentialManager); + Assert.IsInstanceOf(_credentialManager); } else { diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs deleted file mode 100644 index 9a134ae4e..000000000 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerAdysTechImpl.cs +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. - */ - -using AdysTech.CredentialManager; -using Snowflake.Data.Log; -using System; -using System.Net; - -namespace Snowflake.Data.Client -{ - public class SnowflakeCredentialManagerAdysTechImpl : ISnowflakeCredentialManager - { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - - public static readonly SnowflakeCredentialManagerAdysTechImpl Instance = new SnowflakeCredentialManagerAdysTechImpl(); - - public string GetCredentials(string key) - { - try - { - s_logger.Debug($"Getting the credentials for key: {key}"); - var networkCredentials = CredentialManager.GetCredentials(key); - return networkCredentials.Password; - } - catch (NullReferenceException) - { - s_logger.Info($"Unable to get credentials for key: {key}"); - return ""; - } - } - - public void RemoveCredentials(string key) - { - try - { - s_logger.Debug($"Removing the credentials for key: {key}"); - CredentialManager.RemoveCredentials(key); - } - catch (CredentialAPIException) - { - s_logger.Info($"Unable to remove credentials because the specified key did not exist: {key}"); - } - } - - public void SaveCredentials(string key, string token) - { - s_logger.Debug($"Saving the credentials for key: {key}"); - var networkCredentials = new NetworkCredential(key, token); - CredentialManager.SaveCredentials(key, networkCredentials); - } - } -} diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index 5e6704c35..c29e351aa 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -36,7 +36,7 @@ internal static ISnowflakeCredentialManager GetCredentialManager() if (s_customCredentialManager == null) { var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) - SnowflakeCredentialManagerAdysTechImpl.Instance : SnowflakeCredentialManagerInMemoryImpl.Instance; + SnowflakeCredentialManagerWindowsNativeImpl.Instance : SnowflakeCredentialManagerInMemoryImpl.Instance; s_logger.Info($"Using the default credential manager: {defaultCredentialManager.GetType().Name}"); return defaultCredentialManager; } diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs deleted file mode 100644 index 2033b1191..000000000 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerMeziantouImpl.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. - */ - -using Meziantou.Framework.Win32; -using Snowflake.Data.Log; -using System; -using System.ComponentModel; - -namespace Snowflake.Data.Client -{ - public class SnowflakeCredentialManagerMeziantouImpl : ISnowflakeCredentialManager - { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - - public static readonly SnowflakeCredentialManagerMeziantouImpl Instance = new SnowflakeCredentialManagerMeziantouImpl(); - - public string GetCredentials(string key) - { - try - { - s_logger.Debug($"Getting the credentials for key: {key}"); - var networkCredentials = CredentialManager.ReadCredential(key); - return networkCredentials.Password; - } - catch (NullReferenceException) - { - s_logger.Info($"Unable to get credentials for key: {key}"); - return ""; - } - } - - public void RemoveCredentials(string key) - { - try - { - s_logger.Debug($"Removing the credentials for key: {key}"); - CredentialManager.DeleteCredential(key); - } - catch (Win32Exception) - { - s_logger.Info($"Unable to remove credentials because the specified key did not exist: {key}"); - } - } - - public void SaveCredentials(string key, string token) - { - s_logger.Debug($"Saving the credentials for key: {key}"); - CredentialManager.WriteCredential(key, key, token, CredentialPersistence.LocalMachine); - } - } -} diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerWindowsNativeImpl.cs similarity index 93% rename from Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs rename to Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerWindowsNativeImpl.cs index 93f73bb82..362854aba 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerNativeImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerWindowsNativeImpl.cs @@ -10,11 +10,11 @@ namespace Snowflake.Data.Client { - public class SnowflakeCredentialManagerNativeImpl : ISnowflakeCredentialManager + public class SnowflakeCredentialManagerWindowsNativeImpl : ISnowflakeCredentialManager { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - public static readonly SnowflakeCredentialManagerNativeImpl Instance = new SnowflakeCredentialManagerNativeImpl(); + public static readonly SnowflakeCredentialManagerWindowsNativeImpl Instance = new SnowflakeCredentialManagerWindowsNativeImpl(); public string GetCredentials(string key) { From cdc9f80fa5b3548d7c25ebbf9c906015a9668cd0 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 14:28:49 -0700 Subject: [PATCH 26/81] Use HomeDirectoryProvider to retrieve the default location --- .../SnowflakeCredentialManagerFileImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs index b4e780c1b..b3aa40fb4 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs @@ -50,7 +50,7 @@ internal SnowflakeCredentialManagerFileImpl(FileOperations fileOperations, Direc private void SetCredentialCachePath(ref string _jsonCacheDirectory, ref string _jsonCacheFilePath) { var customDirectory = _environmentOperations.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); - _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? _environmentOperations.GetFolderPath(Environment.SpecialFolder.UserProfile) : customDirectory; + _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? HomeDirectoryProvider.HomeDirectory(_environmentOperations) : customDirectory; _jsonCacheFilePath = Path.Combine(_jsonCacheDirectory, CredentialCacheFileName); s_logger.Info($"Setting the json credential cache path to {_jsonCacheFilePath}"); } From 9622f5c635dc97893622e987ac15b73e868b0b5b Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 16:06:41 -0700 Subject: [PATCH 27/81] Check if json file already exists --- .../SFCredentialManagerTest.cs | 44 ++++++++++++++++--- .../SnowflakeCredentialManagerFileImpl.cs | 4 ++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 9499dd919..7d178d152 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -143,12 +143,8 @@ class SFCredentialManagerTest [ThreadStatic] private static Mock t_environmentOperations; - private static readonly string s_defaultJsonDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - private const string CustomJsonDir = "testdirectory"; - private static readonly string s_defaultJsonPath = Path.Combine(s_defaultJsonDir, SnowflakeCredentialManagerFileImpl.CredentialCacheFileName); - private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SnowflakeCredentialManagerFileImpl.CredentialCacheFileName); [SetUp] public void SetUp() @@ -208,7 +204,7 @@ public void TestThatThrowsErrorWhenCacheFileIsNotCreated() // arrange t_directoryOperations - .Setup(d => d.Exists(s_defaultJsonDir)) + .Setup(d => d.Exists(s_customJsonPath)) .Returns(false); t_unixOperations .Setup(u => u.CreateFileWithPermissions(s_customJsonPath, @@ -237,11 +233,11 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() // arrange t_unixOperations - .Setup(u => u.CreateFileWithPermissions(s_defaultJsonPath, + .Setup(u => u.CreateFileWithPermissions(s_customJsonPath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(0); t_unixOperations - .Setup(u => u.GetFilePermissions(s_defaultJsonPath)) + .Setup(u => u.GetFilePermissions(s_customJsonPath)) .Returns(FileAccessPermissions.AllPermissions); t_environmentOperations .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) @@ -255,5 +251,39 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() // assert Assert.That(thrown.Message, Does.Contain("Permission for the JSON token cache file should contain only the owner access")); } + + [Test] + public void TestThatJsonFileIsCheckedIfAlreadyExists() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + t_unixOperations + .Setup(u => u.CreateFileWithPermissions(s_customJsonPath, + FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) + .Returns(0); + t_unixOperations + .Setup(u => u.GetFilePermissions(s_customJsonPath)) + .Returns(FileAccessPermissions.UserReadWriteExecute); + t_environmentOperations + .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Returns(CustomJsonDir); + t_fileOperations + .SetupSequence(f => f.Exists(s_customJsonPath)) + .Returns(false) + .Returns(true); + + SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + + // act + _credentialManager.SaveCredentials("key", "token"); + + // assert + t_fileOperations.Verify(f => f.Exists(s_customJsonPath), Times.Exactly(2)); + } } } diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs index b3aa40fb4..c6f666e80 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs @@ -77,6 +77,10 @@ internal void WriteToJsonFile(string content) _directoryOperations.CreateDirectory(_jsonCacheDirectory); } s_logger.Info($"Creating the json file for credential cache in {_jsonCacheFilePath}"); + if (_fileOperations.Exists(_jsonCacheFilePath)) + { + s_logger.Info($"The existing json file for credential cache in {_jsonCacheFilePath} will be overwritten"); + } var createFileResult = _unixOperations.CreateFileWithPermissions(_jsonCacheFilePath, FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR); if (createFileResult == -1) From a2f9b5099bb3209199a4ddb8058d76e7ca689aae Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 16:41:18 -0700 Subject: [PATCH 28/81] Change parameter from string to enum --- Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs | 2 +- .../SnowflakeCredentialManagerFactory.cs | 4 ++-- Snowflake.Data/Core/Session/SFSession.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index d3b0cd246..079f5340d 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -1053,7 +1053,7 @@ public void TestSSOConnectionWithInvalidCachedToken() = ConnectionStringWithoutAuth + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken.ToString()); + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken); var credentialManager = SnowflakeCredentialManagerInMemoryImpl.Instance; credentialManager.SaveCredentials(key, "wrongToken"); diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index c29e351aa..f0d5a0812 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -14,9 +14,9 @@ public class SnowflakeCredentialManagerFactory private static ISnowflakeCredentialManager s_customCredentialManager = null; - internal static string BuildCredentialKey(string host, string user, string tokenType) + internal static string BuildCredentialKey(string host, string user, TokenType tokenType) { - return $"{host.ToUpper()}:{user.ToUpper()}:{SFEnvironment.DriverName}:{tokenType.ToUpper()}"; + return $"{host.ToUpper()}:{user.ToUpper()}:{SFEnvironment.DriverName}:{tokenType.ToString().ToUpper()}"; } public static void UseDefaultCredentialManager() diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 25fdb4a16..9233df268 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -110,7 +110,7 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) if (_allowSSOTokenCaching && !string.IsNullOrEmpty(authnResponse.data.idToken)) { _idToken = authnResponse.data.idToken; - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken.ToString()); + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken); _credManager.SaveCredentials(key, _idToken); } logger.Debug($"Session opened: {sessionId}"); @@ -199,7 +199,7 @@ internal SFSession( if (_allowSSOTokenCaching) { - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken.ToString()); + var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken); _idToken = _credManager.GetCredentials(key); } } From 739c40c72006cfeb0e6dd367b9fdb7b37ef7f83e Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 5 Jun 2024 18:01:13 -0700 Subject: [PATCH 29/81] Remove encryption for in-memory credential manager --- .../SnowflakeCredentialManagerInMemoryImpl.cs | 68 ++----------------- 1 file changed, 7 insertions(+), 61 deletions(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs index 7927d6b63..4b778b371 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs @@ -4,83 +4,29 @@ using Snowflake.Data.Log; using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; namespace Snowflake.Data.Client { - public struct EncryptedToken - { - public byte[] IV; - public byte[] Key; - public byte[] Bytes; - } - - internal class SnowflakeCredentialEncryption - { - internal static EncryptedToken EncryptToken(string token) - { - EncryptedToken encryptedToken; - - using (var aes = Aes.Create()) - { - aes.Mode = CipherMode.CBC; - aes.BlockSize = 128; - aes.GenerateKey(); - encryptedToken.Key = aes.Key; - aes.GenerateIV(); - encryptedToken.IV = aes.IV; - - using (var encryptor = aes.CreateEncryptor()) - { - var tokenBytes = Encoding.UTF8.GetBytes(token); - encryptedToken.Bytes = encryptor.TransformFinalBlock(tokenBytes, 0, tokenBytes.Length); - } - } - - return encryptedToken; - } - - internal static string DecryptToken(EncryptedToken encryptedToken) - { - using (var aes = Aes.Create()) - { - aes.BlockSize = 128; - aes.Mode = CipherMode.CBC; - aes.Key = encryptedToken.Key; - aes.IV = encryptedToken.IV; - - using (var decryptor = aes.CreateDecryptor()) - { - var decryptedBytes = decryptor.TransformFinalBlock(encryptedToken.Bytes, 0, encryptedToken.Bytes.Length); - return Encoding.UTF8.GetString(decryptedBytes); - } - } - } - } - public class SnowflakeCredentialManagerInMemoryImpl : ISnowflakeCredentialManager { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private static readonly Dictionary s_credentials = new Dictionary(); + private static readonly Dictionary s_credentials = new Dictionary(); public static readonly SnowflakeCredentialManagerInMemoryImpl Instance = new SnowflakeCredentialManagerInMemoryImpl(); public string GetCredentials(string key) { - EncryptedToken token; - s_credentials.TryGetValue(key, out token); - s_logger.Debug($"Getting credentials from memory for key: {key}"); - if (token.Bytes == null) + string token; + if (s_credentials.TryGetValue(key, out token)) { - s_logger.Info("Unable to get credentials for the specified key"); - return ""; + return token; } else { - return SnowflakeCredentialEncryption.DecryptToken(s_credentials[key]); + s_logger.Info("Unable to get credentials for the specified key"); + return ""; } } @@ -93,7 +39,7 @@ public void RemoveCredentials(string key) public void SaveCredentials(string key, string token) { s_logger.Debug($"Saving credentials into memory for key: {key}"); - s_credentials[key] = SnowflakeCredentialEncryption.EncryptToken(token); + s_credentials[key] = token; } } } From c502e80a1da868c3f12455af293d8170189bd75c Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 6 Jun 2024 11:41:08 -0700 Subject: [PATCH 30/81] Change modifier for dictionary --- .../SnowflakeCredentialManagerInMemoryImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs index 4b778b371..c4f65f547 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs @@ -11,7 +11,7 @@ public class SnowflakeCredentialManagerInMemoryImpl : ISnowflakeCredentialManage { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private static readonly Dictionary s_credentials = new Dictionary(); + private Dictionary s_credentials = new Dictionary(); public static readonly SnowflakeCredentialManagerInMemoryImpl Instance = new SnowflakeCredentialManagerInMemoryImpl(); From 465da803da857e8bef74a68600b52405ed6f59f6 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 6 Jun 2024 13:09:22 -0700 Subject: [PATCH 31/81] Change public modifier for credential manager factory --- .../SnowflakeCredentialManagerFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs index f0d5a0812..27c7b7fae 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs @@ -8,7 +8,7 @@ namespace Snowflake.Data.Client { - public class SnowflakeCredentialManagerFactory + internal class SnowflakeCredentialManagerFactory { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); From 94bce01fc13fac3606d4859b17626e55b9f11466 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 6 Jun 2024 13:29:03 -0700 Subject: [PATCH 32/81] Move credential manager files to core folder --- .../IntegrationTests/SFConnectionIT.cs | 9 +++++---- .../ExternalBrowserAuthenticator.cs | 1 + .../CredentialManager/ISFCredentialManager.cs} | 6 ++---- .../SFCredentialManagerFactory.cs} | 17 ++++++++--------- .../SFCredentialManagerFileImpl.cs} | 10 +++++----- .../SFCredentialManagerInMemoryImpl.cs} | 8 ++++---- .../SFCredentialManagerWindowsNativeImpl.cs} | 12 ++++++------ Snowflake.Data/Core/Session/SFSession.cs | 7 ++++--- 8 files changed, 35 insertions(+), 35 deletions(-) rename Snowflake.Data/{Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs => Core/CredentialManager/ISFCredentialManager.cs} (75%) rename Snowflake.Data/{Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs => Core/CredentialManager/SFCredentialManagerFactory.cs} (69%) rename Snowflake.Data/{Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs => Core/CredentialManager/SFCredentialManagerFileImpl.cs} (90%) rename Snowflake.Data/{Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs => Core/CredentialManager/SFCredentialManagerInMemoryImpl.cs} (79%) rename Snowflake.Data/{Client/SnowflakeCredentialManager/SnowflakeCredentialManagerWindowsNativeImpl.cs => Core/CredentialManager/SFCredentialManagerWindowsNativeImpl.cs} (91%) diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index 079f5340d..31ce6094c 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -20,6 +20,7 @@ namespace Snowflake.Data.Tests.IntegrationTests using Snowflake.Data.Tests.Mock; using System.Runtime.InteropServices; using System.Net.Http; + using Snowflake.Data.Core.CredentialManager; [TestFixture] class SFConnectionIT : SFBaseTest @@ -1053,16 +1054,16 @@ public void TestSSOConnectionWithInvalidCachedToken() = ConnectionStringWithoutAuth + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken); - var credentialManager = SnowflakeCredentialManagerInMemoryImpl.Instance; + var key = SFCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken); + var credentialManager = SFCredentialManagerInMemoryImpl.Instance; credentialManager.SaveCredentials(key, "wrongToken"); - SnowflakeCredentialManagerFactory.SetCredentialManager(credentialManager); + SFCredentialManagerFactory.SetCredentialManager(credentialManager); conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); - SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); + SFCredentialManagerFactory.UseDefaultCredentialManager(); } } diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 909b8afd3..c6b5b8a39 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -13,6 +13,7 @@ using Snowflake.Data.Client; using System.Text.RegularExpressions; using System.Collections.Generic; +using Snowflake.Data.Core.CredentialManager; namespace Snowflake.Data.Core.Authenticator { diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs b/Snowflake.Data/Core/CredentialManager/ISFCredentialManager.cs similarity index 75% rename from Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs rename to Snowflake.Data/Core/CredentialManager/ISFCredentialManager.cs index 494a6ef17..001227fce 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/ISnowflakeCredentialManager.cs +++ b/Snowflake.Data/Core/CredentialManager/ISFCredentialManager.cs @@ -2,9 +2,7 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ -using Snowflake.Data.Core; - -namespace Snowflake.Data.Client +namespace Snowflake.Data.Core.CredentialManager { internal enum TokenType { @@ -12,7 +10,7 @@ internal enum TokenType IdToken } - public interface ISnowflakeCredentialManager + public interface ISFCredentialManager { string GetCredentials(string key); diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs similarity index 69% rename from Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs rename to Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs index 27c7b7fae..deb52ad59 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFactory.cs +++ b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs @@ -2,17 +2,16 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ -using Snowflake.Data.Core; using Snowflake.Data.Log; using System.Runtime.InteropServices; -namespace Snowflake.Data.Client +namespace Snowflake.Data.Core.CredentialManager { - internal class SnowflakeCredentialManagerFactory + internal class SFCredentialManagerFactory { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private static ISnowflakeCredentialManager s_customCredentialManager = null; + private static ISFCredentialManager s_customCredentialManager = null; internal static string BuildCredentialKey(string host, string user, TokenType tokenType) { @@ -25,18 +24,18 @@ public static void UseDefaultCredentialManager() s_customCredentialManager = null; } - public static void SetCredentialManager(ISnowflakeCredentialManager customCredentialManager) + public static void SetCredentialManager(ISFCredentialManager customCredentialManager) { s_logger.Info($"Setting the custom credential manager: {customCredentialManager.GetType().Name}"); s_customCredentialManager = customCredentialManager; } - internal static ISnowflakeCredentialManager GetCredentialManager() + internal static ISFCredentialManager GetCredentialManager() { if (s_customCredentialManager == null) { - var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) - SnowflakeCredentialManagerWindowsNativeImpl.Instance : SnowflakeCredentialManagerInMemoryImpl.Instance; + var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISFCredentialManager) + SFCredentialManagerWindowsNativeImpl.Instance : SFCredentialManagerInMemoryImpl.Instance; s_logger.Info($"Using the default credential manager: {defaultCredentialManager.GetType().Name}"); return defaultCredentialManager; } diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFileImpl.cs similarity index 90% rename from Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs rename to Snowflake.Data/Core/CredentialManager/SFCredentialManagerFileImpl.cs index c6f666e80..30e4d8172 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFileImpl.cs @@ -14,15 +14,15 @@ using System.Security.Principal; using KeyToken = System.Collections.Generic.Dictionary; -namespace Snowflake.Data.Client +namespace Snowflake.Data.Core.CredentialManager { - public class SnowflakeCredentialManagerFileImpl : ISnowflakeCredentialManager + public class SFCredentialManagerFileImpl : ISFCredentialManager { internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; internal const string CredentialCacheFileName = "temporary_credential.json"; - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); private readonly string _jsonCacheDirectory; @@ -36,9 +36,9 @@ public class SnowflakeCredentialManagerFileImpl : ISnowflakeCredentialManager private readonly EnvironmentOperations _environmentOperations; - public static readonly SnowflakeCredentialManagerFileImpl Instance = new SnowflakeCredentialManagerFileImpl(FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance, EnvironmentOperations.Instance); + public static readonly SFCredentialManagerFileImpl Instance = new SFCredentialManagerFileImpl(FileOperations.Instance, DirectoryOperations.Instance, UnixOperations.Instance, EnvironmentOperations.Instance); - internal SnowflakeCredentialManagerFileImpl(FileOperations fileOperations, DirectoryOperations directoryOperations, UnixOperations unixOperations, EnvironmentOperations environmentOperations) + internal SFCredentialManagerFileImpl(FileOperations fileOperations, DirectoryOperations directoryOperations, UnixOperations unixOperations, EnvironmentOperations environmentOperations) { _fileOperations = fileOperations; _directoryOperations = directoryOperations; diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerInMemoryImpl.cs similarity index 79% rename from Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs rename to Snowflake.Data/Core/CredentialManager/SFCredentialManagerInMemoryImpl.cs index c4f65f547..a03459851 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerInMemoryImpl.cs @@ -5,15 +5,15 @@ using Snowflake.Data.Log; using System.Collections.Generic; -namespace Snowflake.Data.Client +namespace Snowflake.Data.Core.CredentialManager { - public class SnowflakeCredentialManagerInMemoryImpl : ISnowflakeCredentialManager + public class SFCredentialManagerInMemoryImpl : ISFCredentialManager { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); private Dictionary s_credentials = new Dictionary(); - public static readonly SnowflakeCredentialManagerInMemoryImpl Instance = new SnowflakeCredentialManagerInMemoryImpl(); + public static readonly SFCredentialManagerInMemoryImpl Instance = new SFCredentialManagerInMemoryImpl(); public string GetCredentials(string key) { diff --git a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerWindowsNativeImpl.cs b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerWindowsNativeImpl.cs similarity index 91% rename from Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerWindowsNativeImpl.cs rename to Snowflake.Data/Core/CredentialManager/SFCredentialManagerWindowsNativeImpl.cs index 362854aba..e7b3217d5 100644 --- a/Snowflake.Data/Client/SnowflakeCredentialManager/SnowflakeCredentialManagerWindowsNativeImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerWindowsNativeImpl.cs @@ -8,13 +8,13 @@ using System.Runtime.InteropServices; using System.Text; -namespace Snowflake.Data.Client +namespace Snowflake.Data.Core.CredentialManager { - public class SnowflakeCredentialManagerWindowsNativeImpl : ISnowflakeCredentialManager + public class SFCredentialManagerWindowsNativeImpl : ISFCredentialManager { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - public static readonly SnowflakeCredentialManagerWindowsNativeImpl Instance = new SnowflakeCredentialManagerWindowsNativeImpl(); + public static readonly SFCredentialManagerWindowsNativeImpl Instance = new SFCredentialManagerWindowsNativeImpl(); public string GetCredentials(string key) { @@ -55,7 +55,7 @@ public void SaveCredentials(string key, string token) credential.Type = 1; // Generic credential.Persist = 2; // Local Machine credential.CredentialBlobSize = (uint)(byteArray == null ? 0 : byteArray.Length); - credential.TargetName =key; + credential.TargetName = key; credential.CredentialBlob = token; credential.UserName = Environment.UserName; @@ -115,7 +115,7 @@ protected override bool ReleaseHandle() static extern bool CredRead(string target, uint type, int reservedFlag, out IntPtr credentialPtr); [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] - static extern bool CredWrite([In] ref Credential userCredential, [In] UInt32 flags); + static extern bool CredWrite([In] ref Credential userCredential, [In] uint flags); [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] static extern bool CredFree([In] IntPtr cred); diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 9233df268..c7bd74eb8 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -16,6 +16,7 @@ using System.Net.Http; using System.Text.RegularExpressions; using Snowflake.Data.Configuration; +using Snowflake.Data.Core.CredentialManager; namespace Snowflake.Data.Core { @@ -85,7 +86,7 @@ public class SFSession internal String _queryTag; - private readonly ISnowflakeCredentialManager _credManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + private readonly ISFCredentialManager _credManager = SFCredentialManagerFactory.GetCredentialManager(); internal bool _allowSSOTokenCaching; @@ -110,7 +111,7 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) if (_allowSSOTokenCaching && !string.IsNullOrEmpty(authnResponse.data.idToken)) { _idToken = authnResponse.data.idToken; - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken); + var key = SFCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken); _credManager.SaveCredentials(key, _idToken); } logger.Debug($"Session opened: {sessionId}"); @@ -199,7 +200,7 @@ internal SFSession( if (_allowSSOTokenCaching) { - var key = SnowflakeCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken); + var key = SFCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.IdToken); _idToken = _credManager.GetCredentials(key); } } From 83119f33b7e680263e2419b42097b4003db6bf31 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 6 Jun 2024 13:37:31 -0700 Subject: [PATCH 33/81] Move interface and implementations to subpackage --- .../IntegrationTests/SFConnectionIT.cs | 1 + .../SFCredentialManagerTest.cs | 51 ++++++++++--------- .../ExternalBrowserAuthenticator.cs | 2 +- .../ISFCredentialManager.cs | 2 +- .../SFCredentialManagerFileImpl.cs | 2 +- .../SFCredentialManagerInMemoryImpl.cs | 2 +- .../SFCredentialManagerWindowsNativeImpl.cs | 2 +- .../SFCredentialManagerFactory.cs | 1 + Snowflake.Data/Core/Session/SFSession.cs | 1 + 9 files changed, 34 insertions(+), 30 deletions(-) rename Snowflake.Data/Core/CredentialManager/{ => Infrastructure}/ISFCredentialManager.cs (85%) rename Snowflake.Data/Core/CredentialManager/{ => Infrastructure}/SFCredentialManagerFileImpl.cs (99%) rename Snowflake.Data/Core/CredentialManager/{ => Infrastructure}/SFCredentialManagerInMemoryImpl.cs (95%) rename Snowflake.Data/Core/CredentialManager/{ => Infrastructure}/SFCredentialManagerWindowsNativeImpl.cs (98%) diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index 31ce6094c..5499094dc 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -21,6 +21,7 @@ namespace Snowflake.Data.Tests.IntegrationTests using System.Runtime.InteropServices; using System.Net.Http; using Snowflake.Data.Core.CredentialManager; + using Snowflake.Data.Core.CredentialManager.Infrastructure; [TestFixture] class SFConnectionIT : SFBaseTest diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 7d178d152..372bd059b 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -8,7 +8,8 @@ namespace Snowflake.Data.Tests.UnitTests.CredentialManager using Mono.Unix.Native; using Moq; using NUnit.Framework; - using Snowflake.Data.Client; + using Snowflake.Data.Core.CredentialManager; + using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Core.Tools; using System; using System.IO; @@ -16,7 +17,7 @@ namespace Snowflake.Data.Tests.UnitTests.CredentialManager public abstract class SFBaseCredentialManagerTest { - protected ISnowflakeCredentialManager _credentialManager; + protected ISFCredentialManager _credentialManager; [Test] public void TestSavingAndRemovingCredentials() @@ -102,7 +103,7 @@ public class SFNativeCredentialManagerTest : SFBaseCredentialManagerTest [SetUp] public void SetUp() { - _credentialManager = SnowflakeCredentialManagerWindowsNativeImpl.Instance; + _credentialManager = SFCredentialManagerWindowsNativeImpl.Instance; } } @@ -112,7 +113,7 @@ public class SFInMemoryCredentialManagerTest : SFBaseCredentialManagerTest [SetUp] public void SetUp() { - _credentialManager = SnowflakeCredentialManagerInMemoryImpl.Instance; + _credentialManager = SFCredentialManagerInMemoryImpl.Instance; } } @@ -122,14 +123,14 @@ public class SFFileCredentialManagerTest : SFBaseCredentialManagerTest [SetUp] public void SetUp() { - _credentialManager = SnowflakeCredentialManagerFileImpl.Instance; + _credentialManager = SFCredentialManagerFileImpl.Instance; } } [TestFixture] class SFCredentialManagerTest { - ISnowflakeCredentialManager _credentialManager; + ISFCredentialManager _credentialManager; [ThreadStatic] private static Mock t_fileOperations; @@ -145,7 +146,7 @@ class SFCredentialManagerTest private const string CustomJsonDir = "testdirectory"; - private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SnowflakeCredentialManagerFileImpl.CredentialCacheFileName); + private static readonly string s_customJsonPath = Path.Combine(CustomJsonDir, SFCredentialManagerFileImpl.CredentialCacheFileName); [SetUp] public void SetUp() { @@ -153,31 +154,31 @@ [SetUp] public void SetUp() t_directoryOperations = new Mock(); t_unixOperations = new Mock(); t_environmentOperations = new Mock(); - SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerInMemoryImpl.Instance); + SFCredentialManagerFactory.SetCredentialManager(SFCredentialManagerInMemoryImpl.Instance); } [TearDown] public void TearDown() { - SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); + SFCredentialManagerFactory.UseDefaultCredentialManager(); } [Test] public void TestUsingDefaultCredentialManager() { // arrange - SnowflakeCredentialManagerFactory.UseDefaultCredentialManager(); + SFCredentialManagerFactory.UseDefaultCredentialManager(); // act - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); // assert if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.IsInstanceOf(_credentialManager); + Assert.IsInstanceOf(_credentialManager); } else { - Assert.IsInstanceOf(_credentialManager); + Assert.IsInstanceOf(_credentialManager); } } @@ -185,13 +186,13 @@ public void TestUsingDefaultCredentialManager() public void TestSettingCustomCredentialManager() { // arrange - SnowflakeCredentialManagerFactory.SetCredentialManager(SnowflakeCredentialManagerFileImpl.Instance); + SFCredentialManagerFactory.SetCredentialManager(SFCredentialManagerFileImpl.Instance); // act - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); // assert - Assert.IsInstanceOf(_credentialManager); + Assert.IsInstanceOf(_credentialManager); } [Test] @@ -211,10 +212,10 @@ public void TestThatThrowsErrorWhenCacheFileIsNotCreated() FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR)) .Returns(-1); t_environmentOperations - .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) .Returns(CustomJsonDir); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + SFCredentialManagerFactory.SetCredentialManager(new SFCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); // act var thrown = Assert.Throws(() => _credentialManager.SaveCredentials("key", "token")); @@ -240,10 +241,10 @@ public void TestThatThrowsErrorWhenCacheFileCanBeAccessedByOthers() .Setup(u => u.GetFilePermissions(s_customJsonPath)) .Returns(FileAccessPermissions.AllPermissions); t_environmentOperations - .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) .Returns(CustomJsonDir); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + SFCredentialManagerFactory.SetCredentialManager(new SFCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); // act var thrown = Assert.Throws(() => _credentialManager.SaveCredentials("key", "token")); @@ -269,15 +270,15 @@ public void TestThatJsonFileIsCheckedIfAlreadyExists() .Setup(u => u.GetFilePermissions(s_customJsonPath)) .Returns(FileAccessPermissions.UserReadWriteExecute); t_environmentOperations - .Setup(e => e.GetEnvironmentVariable(SnowflakeCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) .Returns(CustomJsonDir); t_fileOperations .SetupSequence(f => f.Exists(s_customJsonPath)) .Returns(false) .Returns(true); - SnowflakeCredentialManagerFactory.SetCredentialManager(new SnowflakeCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); - _credentialManager = SnowflakeCredentialManagerFactory.GetCredentialManager(); + SFCredentialManagerFactory.SetCredentialManager(new SFCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); // act _credentialManager.SaveCredentials("key", "token"); diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index c6b5b8a39..4d15f979b 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -13,7 +13,7 @@ using Snowflake.Data.Client; using System.Text.RegularExpressions; using System.Collections.Generic; -using Snowflake.Data.Core.CredentialManager; +using Snowflake.Data.Core.CredentialManager.Infrastructure; namespace Snowflake.Data.Core.Authenticator { diff --git a/Snowflake.Data/Core/CredentialManager/ISFCredentialManager.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs similarity index 85% rename from Snowflake.Data/Core/CredentialManager/ISFCredentialManager.cs rename to Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs index 001227fce..8c67f855b 100644 --- a/Snowflake.Data/Core/CredentialManager/ISFCredentialManager.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs @@ -2,7 +2,7 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ -namespace Snowflake.Data.Core.CredentialManager +namespace Snowflake.Data.Core.CredentialManager.Infrastructure { internal enum TokenType { diff --git a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs similarity index 99% rename from Snowflake.Data/Core/CredentialManager/SFCredentialManagerFileImpl.cs rename to Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index 30e4d8172..9c8eac02f 100644 --- a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -14,7 +14,7 @@ using System.Security.Principal; using KeyToken = System.Collections.Generic.Dictionary; -namespace Snowflake.Data.Core.CredentialManager +namespace Snowflake.Data.Core.CredentialManager.Infrastructure { public class SFCredentialManagerFileImpl : ISFCredentialManager { diff --git a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs similarity index 95% rename from Snowflake.Data/Core/CredentialManager/SFCredentialManagerInMemoryImpl.cs rename to Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs index a03459851..ad57b45ea 100644 --- a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs @@ -5,7 +5,7 @@ using Snowflake.Data.Log; using System.Collections.Generic; -namespace Snowflake.Data.Core.CredentialManager +namespace Snowflake.Data.Core.CredentialManager.Infrastructure { public class SFCredentialManagerInMemoryImpl : ISFCredentialManager { diff --git a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerWindowsNativeImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs similarity index 98% rename from Snowflake.Data/Core/CredentialManager/SFCredentialManagerWindowsNativeImpl.cs rename to Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs index e7b3217d5..870acb510 100644 --- a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerWindowsNativeImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace Snowflake.Data.Core.CredentialManager +namespace Snowflake.Data.Core.CredentialManager.Infrastructure { public class SFCredentialManagerWindowsNativeImpl : ISFCredentialManager { diff --git a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs index deb52ad59..5aebae2e4 100644 --- a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs +++ b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs @@ -2,6 +2,7 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Log; using System.Runtime.InteropServices; diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index c7bd74eb8..55fc4103b 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -17,6 +17,7 @@ using System.Text.RegularExpressions; using Snowflake.Data.Configuration; using Snowflake.Data.Core.CredentialManager; +using Snowflake.Data.Core.CredentialManager.Infrastructure; namespace Snowflake.Data.Core { From 61855f212eeea1a954dd319d06070f496d70d20b Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 12 Jun 2024 17:45:29 -0700 Subject: [PATCH 34/81] Remove unused packages --- Snowflake.Data/Snowflake.Data.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index b5b4096ac..eedf5c2d7 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -1,4 +1,4 @@ - + net8.0;net6.0;net471;net472 net6.0;net8.0 @@ -24,13 +24,11 @@ - - From 8b38fedc1441a85e061580630f1356d2135efb0a Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 17 Jun 2024 11:14:21 -0700 Subject: [PATCH 35/81] Include ".snowflake" to the default cache directory --- .../Infrastructure/SFCredentialManagerFileImpl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index 9c8eac02f..20ca791b0 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -20,6 +20,8 @@ public class SFCredentialManagerFileImpl : ISFCredentialManager { internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; + internal const string CredentialCacheDirName = ".snowflake"; + internal const string CredentialCacheFileName = "temporary_credential.json"; private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); @@ -50,7 +52,11 @@ internal SFCredentialManagerFileImpl(FileOperations fileOperations, DirectoryOpe private void SetCredentialCachePath(ref string _jsonCacheDirectory, ref string _jsonCacheFilePath) { var customDirectory = _environmentOperations.GetEnvironmentVariable(CredentialCacheDirectoryEnvironmentName); - _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? HomeDirectoryProvider.HomeDirectory(_environmentOperations) : customDirectory; + _jsonCacheDirectory = string.IsNullOrEmpty(customDirectory) ? Path.Combine(HomeDirectoryProvider.HomeDirectory(_environmentOperations), CredentialCacheDirName) : customDirectory; + if (!_directoryOperations.Exists(_jsonCacheDirectory)) + { + _directoryOperations.CreateDirectory(_jsonCacheDirectory); + } _jsonCacheFilePath = Path.Combine(_jsonCacheDirectory, CredentialCacheFileName); s_logger.Info($"Setting the json credential cache path to {_jsonCacheFilePath}"); } From 194eafa7c043bc2fac0cade00e4588f93e7187b6 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 17 Jun 2024 14:44:35 -0700 Subject: [PATCH 36/81] Refactor credential manager --- .../CredentialManager/SFCredentialManagerTest.cs | 5 +++-- .../ISnowflakeCredentialManager.cs} | 10 ++-------- .../Infrastructure/SFCredentialManagerFileImpl.cs | 3 ++- .../SFCredentialManagerInMemoryImpl.cs | 3 ++- .../SFCredentialManagerWindowsNativeImpl.cs | 3 ++- .../SFCredentialManagerFactory.cs | 15 +++++++++++---- Snowflake.Data/Core/Session/SFSession.cs | 5 ++--- 7 files changed, 24 insertions(+), 20 deletions(-) rename Snowflake.Data/{Core/CredentialManager/Infrastructure/ISFCredentialManager.cs => Client/ISnowflakeCredentialManager.cs} (53%) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 372bd059b..8dbeec6c0 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -8,6 +8,7 @@ namespace Snowflake.Data.Tests.UnitTests.CredentialManager using Mono.Unix.Native; using Moq; using NUnit.Framework; + using Snowflake.Data.Client; using Snowflake.Data.Core.CredentialManager; using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Core.Tools; @@ -17,7 +18,7 @@ namespace Snowflake.Data.Tests.UnitTests.CredentialManager public abstract class SFBaseCredentialManagerTest { - protected ISFCredentialManager _credentialManager; + protected ISnowflakeCredentialManager _credentialManager; [Test] public void TestSavingAndRemovingCredentials() @@ -130,7 +131,7 @@ public void SetUp() [TestFixture] class SFCredentialManagerTest { - ISFCredentialManager _credentialManager; + ISnowflakeCredentialManager _credentialManager; [ThreadStatic] private static Mock t_fileOperations; diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs b/Snowflake.Data/Client/ISnowflakeCredentialManager.cs similarity index 53% rename from Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs rename to Snowflake.Data/Client/ISnowflakeCredentialManager.cs index 8c67f855b..802d8fe21 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs +++ b/Snowflake.Data/Client/ISnowflakeCredentialManager.cs @@ -2,15 +2,9 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ -namespace Snowflake.Data.Core.CredentialManager.Infrastructure +namespace Snowflake.Data.Client { - internal enum TokenType - { - [StringAttr(value = "ID_TOKEN")] - IdToken - } - - public interface ISFCredentialManager + public interface ISnowflakeCredentialManager { string GetCredentials(string key); diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index 20ca791b0..ef681ee1f 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -5,6 +5,7 @@ using Mono.Unix; using Mono.Unix.Native; using Newtonsoft.Json; +using Snowflake.Data.Client; using Snowflake.Data.Core.Tools; using Snowflake.Data.Log; using System; @@ -16,7 +17,7 @@ namespace Snowflake.Data.Core.CredentialManager.Infrastructure { - public class SFCredentialManagerFileImpl : ISFCredentialManager + internal class SFCredentialManagerFileImpl : ISnowflakeCredentialManager { internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs index ad57b45ea..bcdd15d70 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs @@ -2,12 +2,13 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Client; using Snowflake.Data.Log; using System.Collections.Generic; namespace Snowflake.Data.Core.CredentialManager.Infrastructure { - public class SFCredentialManagerInMemoryImpl : ISFCredentialManager + internal class SFCredentialManagerInMemoryImpl : ISnowflakeCredentialManager { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs index 870acb510..45bef2a38 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs @@ -3,6 +3,7 @@ */ using Microsoft.Win32.SafeHandles; +using Snowflake.Data.Client; using Snowflake.Data.Log; using System; using System.Runtime.InteropServices; @@ -10,7 +11,7 @@ namespace Snowflake.Data.Core.CredentialManager.Infrastructure { - public class SFCredentialManagerWindowsNativeImpl : ISFCredentialManager + internal class SFCredentialManagerWindowsNativeImpl : ISnowflakeCredentialManager { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); diff --git a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs index 5aebae2e4..8e573cde8 100644 --- a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs +++ b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs @@ -2,17 +2,24 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Client; using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Log; using System.Runtime.InteropServices; namespace Snowflake.Data.Core.CredentialManager { + internal enum TokenType + { + [StringAttr(value = "ID_TOKEN")] + IdToken + } + internal class SFCredentialManagerFactory { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private static ISFCredentialManager s_customCredentialManager = null; + private static ISnowflakeCredentialManager s_customCredentialManager = null; internal static string BuildCredentialKey(string host, string user, TokenType tokenType) { @@ -25,17 +32,17 @@ public static void UseDefaultCredentialManager() s_customCredentialManager = null; } - public static void SetCredentialManager(ISFCredentialManager customCredentialManager) + public static void SetCredentialManager(ISnowflakeCredentialManager customCredentialManager) { s_logger.Info($"Setting the custom credential manager: {customCredentialManager.GetType().Name}"); s_customCredentialManager = customCredentialManager; } - internal static ISFCredentialManager GetCredentialManager() + internal static ISnowflakeCredentialManager GetCredentialManager() { if (s_customCredentialManager == null) { - var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISFCredentialManager) + var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) SFCredentialManagerWindowsNativeImpl.Instance : SFCredentialManagerInMemoryImpl.Instance; s_logger.Info($"Using the default credential manager: {defaultCredentialManager.GetType().Name}"); return defaultCredentialManager; diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 1e04c5ad2..8d995dac2 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. */ @@ -15,7 +15,6 @@ using System.Net.Http; using System.Text.RegularExpressions; using Snowflake.Data.Core.CredentialManager; -using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Core.Session; using Snowflake.Data.Core.Tools; @@ -98,7 +97,7 @@ public void SetPooling(bool isEnabled) internal String _queryTag; - private readonly ISFCredentialManager _credManager = SFCredentialManagerFactory.GetCredentialManager(); + private readonly ISnowflakeCredentialManager _credManager = SFCredentialManagerFactory.GetCredentialManager(); internal bool _allowSSOTokenCaching; From 8666194413eb25b8b51da545f9a3ec5c1764039f Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 17 Jun 2024 14:44:35 -0700 Subject: [PATCH 37/81] Refactor credential manager --- .../CredentialManager/SFCredentialManagerTest.cs | 5 +++-- .../ISnowflakeCredentialManager.cs} | 10 ++-------- .../Authenticator/ExternalBrowserAuthenticator.cs | 2 +- .../Infrastructure/SFCredentialManagerFileImpl.cs | 3 ++- .../SFCredentialManagerInMemoryImpl.cs | 3 ++- .../SFCredentialManagerWindowsNativeImpl.cs | 3 ++- .../SFCredentialManagerFactory.cs | 15 +++++++++++---- Snowflake.Data/Core/Session/SFSession.cs | 5 ++--- 8 files changed, 25 insertions(+), 21 deletions(-) rename Snowflake.Data/{Core/CredentialManager/Infrastructure/ISFCredentialManager.cs => Client/ISnowflakeCredentialManager.cs} (53%) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 372bd059b..8dbeec6c0 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -8,6 +8,7 @@ namespace Snowflake.Data.Tests.UnitTests.CredentialManager using Mono.Unix.Native; using Moq; using NUnit.Framework; + using Snowflake.Data.Client; using Snowflake.Data.Core.CredentialManager; using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Core.Tools; @@ -17,7 +18,7 @@ namespace Snowflake.Data.Tests.UnitTests.CredentialManager public abstract class SFBaseCredentialManagerTest { - protected ISFCredentialManager _credentialManager; + protected ISnowflakeCredentialManager _credentialManager; [Test] public void TestSavingAndRemovingCredentials() @@ -130,7 +131,7 @@ public void SetUp() [TestFixture] class SFCredentialManagerTest { - ISFCredentialManager _credentialManager; + ISnowflakeCredentialManager _credentialManager; [ThreadStatic] private static Mock t_fileOperations; diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs b/Snowflake.Data/Client/ISnowflakeCredentialManager.cs similarity index 53% rename from Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs rename to Snowflake.Data/Client/ISnowflakeCredentialManager.cs index 8c67f855b..802d8fe21 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/ISFCredentialManager.cs +++ b/Snowflake.Data/Client/ISnowflakeCredentialManager.cs @@ -2,15 +2,9 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ -namespace Snowflake.Data.Core.CredentialManager.Infrastructure +namespace Snowflake.Data.Client { - internal enum TokenType - { - [StringAttr(value = "ID_TOKEN")] - IdToken - } - - public interface ISFCredentialManager + public interface ISnowflakeCredentialManager { string GetCredentials(string key); diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 550c92749..3b9a89dbc 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -13,7 +13,7 @@ using Snowflake.Data.Client; using System.Text.RegularExpressions; using System.Collections.Generic; -using Snowflake.Data.Core.CredentialManager.Infrastructure; +using Snowflake.Data.Core.CredentialManager; namespace Snowflake.Data.Core.Authenticator { diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index 20ca791b0..ef681ee1f 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -5,6 +5,7 @@ using Mono.Unix; using Mono.Unix.Native; using Newtonsoft.Json; +using Snowflake.Data.Client; using Snowflake.Data.Core.Tools; using Snowflake.Data.Log; using System; @@ -16,7 +17,7 @@ namespace Snowflake.Data.Core.CredentialManager.Infrastructure { - public class SFCredentialManagerFileImpl : ISFCredentialManager + internal class SFCredentialManagerFileImpl : ISnowflakeCredentialManager { internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR"; diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs index ad57b45ea..bcdd15d70 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs @@ -2,12 +2,13 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Client; using Snowflake.Data.Log; using System.Collections.Generic; namespace Snowflake.Data.Core.CredentialManager.Infrastructure { - public class SFCredentialManagerInMemoryImpl : ISFCredentialManager + internal class SFCredentialManagerInMemoryImpl : ISnowflakeCredentialManager { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs index 870acb510..45bef2a38 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerWindowsNativeImpl.cs @@ -3,6 +3,7 @@ */ using Microsoft.Win32.SafeHandles; +using Snowflake.Data.Client; using Snowflake.Data.Log; using System; using System.Runtime.InteropServices; @@ -10,7 +11,7 @@ namespace Snowflake.Data.Core.CredentialManager.Infrastructure { - public class SFCredentialManagerWindowsNativeImpl : ISFCredentialManager + internal class SFCredentialManagerWindowsNativeImpl : ISnowflakeCredentialManager { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); diff --git a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs index 5aebae2e4..8e573cde8 100644 --- a/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs +++ b/Snowflake.Data/Core/CredentialManager/SFCredentialManagerFactory.cs @@ -2,17 +2,24 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Client; using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Log; using System.Runtime.InteropServices; namespace Snowflake.Data.Core.CredentialManager { + internal enum TokenType + { + [StringAttr(value = "ID_TOKEN")] + IdToken + } + internal class SFCredentialManagerFactory { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private static ISFCredentialManager s_customCredentialManager = null; + private static ISnowflakeCredentialManager s_customCredentialManager = null; internal static string BuildCredentialKey(string host, string user, TokenType tokenType) { @@ -25,17 +32,17 @@ public static void UseDefaultCredentialManager() s_customCredentialManager = null; } - public static void SetCredentialManager(ISFCredentialManager customCredentialManager) + public static void SetCredentialManager(ISnowflakeCredentialManager customCredentialManager) { s_logger.Info($"Setting the custom credential manager: {customCredentialManager.GetType().Name}"); s_customCredentialManager = customCredentialManager; } - internal static ISFCredentialManager GetCredentialManager() + internal static ISnowflakeCredentialManager GetCredentialManager() { if (s_customCredentialManager == null) { - var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISFCredentialManager) + var defaultCredentialManager = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (ISnowflakeCredentialManager) SFCredentialManagerWindowsNativeImpl.Instance : SFCredentialManagerInMemoryImpl.Instance; s_logger.Info($"Using the default credential manager: {defaultCredentialManager.GetType().Name}"); return defaultCredentialManager; diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 1e04c5ad2..8d995dac2 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. */ @@ -15,7 +15,6 @@ using System.Net.Http; using System.Text.RegularExpressions; using Snowflake.Data.Core.CredentialManager; -using Snowflake.Data.Core.CredentialManager.Infrastructure; using Snowflake.Data.Core.Session; using Snowflake.Data.Core.Tools; @@ -98,7 +97,7 @@ public void SetPooling(bool isEnabled) internal String _queryTag; - private readonly ISFCredentialManager _credManager = SFCredentialManagerFactory.GetCredentialManager(); + private readonly ISnowflakeCredentialManager _credManager = SFCredentialManagerFactory.GetCredentialManager(); internal bool _allowSSOTokenCaching; From 33ebec5beb72f4eb398b3574fb1411f1c22b625b Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 17 Jun 2024 17:52:56 -0700 Subject: [PATCH 38/81] Refactor external browser authentication --- .../ExternalBrowserAuthenticator.cs | 114 +++++++++--------- 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 3b9a89dbc..44aa0e671 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -56,41 +56,12 @@ async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) using (var httpListener = GetHttpListener(localPort)) { httpListener.Start(); - logger.Debug("Get IdpUrl and ProofKey"); - string loginUrl; - if (session._disableConsoleLogin) - { - var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); - var authenticatorRestResponse = - await session.restRequester.PostAsync( - authenticatorRestRequest, - cancellationToken - ).ConfigureAwait(false); - authenticatorRestResponse.FilterFailedResponse(); - - loginUrl = authenticatorRestResponse.data.ssoUrl; - _proofKey = authenticatorRestResponse.data.proofKey; - } - else - { - _proofKey = GenerateProofKey(); - loginUrl = GetLoginUrl(_proofKey, localPort); - } - + var loginUrl = await GetIdpUrlAndProofKeyAsync(localPort, cancellationToken); logger.Debug("Open browser"); StartBrowser(loginUrl); - logger.Debug("Get the redirect SAML request"); - _successEvent = new ManualResetEvent(false); - httpListener.BeginGetContext(GetContextCallback, httpListener); - var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - if (!_successEvent.WaitOne(timeoutInSec * 1000)) - { - logger.Warn("Browser response timeout"); - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); - } - + GetRedirectSamlRequest(httpListener); httpListener.Stop(); } } @@ -110,37 +81,12 @@ void IAuthenticator.Authenticate() using (var httpListener = GetHttpListener(localPort)) { httpListener.Start(); - logger.Debug("Get IdpUrl and ProofKey"); - string loginUrl; - if (session._disableConsoleLogin) - { - var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); - var authenticatorRestResponse = session.restRequester.Post(authenticatorRestRequest); - authenticatorRestResponse.FilterFailedResponse(); - - loginUrl = authenticatorRestResponse.data.ssoUrl; - _proofKey = authenticatorRestResponse.data.proofKey; - } - else - { - _proofKey = GenerateProofKey(); - loginUrl = GetLoginUrl(_proofKey, localPort); - } - + var loginUrl = GetIdpUrlAndProofKey(localPort); logger.Debug("Open browser"); StartBrowser(loginUrl); - logger.Debug("Get the redirect SAML request"); - _successEvent = new ManualResetEvent(false); - httpListener.BeginGetContext(GetContextCallback, httpListener); - var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - if (!_successEvent.WaitOne(timeoutInSec * 1000)) - { - logger.Warn("Browser response timeout"); - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); - } - + GetRedirectSamlRequest(httpListener); httpListener.Stop(); } } @@ -149,6 +95,58 @@ void IAuthenticator.Authenticate() base.Login(); } + private string GetIdpUrlAndProofKey(int localPort) + { + if (session._disableConsoleLogin) + { + var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); + var authenticatorRestResponse = session.restRequester.Post(authenticatorRestRequest); + authenticatorRestResponse.FilterFailedResponse(); + + _proofKey = authenticatorRestResponse.data.proofKey; + return authenticatorRestResponse.data.ssoUrl; + } + else + { + _proofKey = GenerateProofKey(); + return GetLoginUrl(_proofKey, localPort); + } + } + + private async Task GetIdpUrlAndProofKeyAsync(int localPort, CancellationToken cancellationToken) + { + if (session._disableConsoleLogin) + { + var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); + var authenticatorRestResponse = + await session.restRequester.PostAsync( + authenticatorRestRequest, + cancellationToken + ).ConfigureAwait(false); + authenticatorRestResponse.FilterFailedResponse(); + + _proofKey = authenticatorRestResponse.data.proofKey; + return authenticatorRestResponse.data.ssoUrl; + } + else + { + _proofKey = GenerateProofKey(); + return GetLoginUrl(_proofKey, localPort); + } + } + + private void GetRedirectSamlRequest(HttpListener httpListener) + { + _successEvent = new ManualResetEvent(false); + httpListener.BeginGetContext(GetContextCallback, httpListener); + var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); + if (!_successEvent.WaitOne(timeoutInSec * 1000)) + { + logger.Warn("Browser response timeout"); + throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); + } + } + private void GetContextCallback(IAsyncResult result) { HttpListener httpListener = (HttpListener) result.AsyncState; From 512de5b442f847c739c87e96b5e7f3af0ad06bae Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 20 Jun 2024 11:35:49 -0700 Subject: [PATCH 39/81] Remove modifying file permission on Windows --- .../Infrastructure/SFCredentialManagerFileImpl.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index ef681ee1f..1e8e0067b 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -68,14 +68,6 @@ internal void WriteToJsonFile(string content) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _fileOperations.Write(_jsonCacheFilePath, content); - FileInfo info = new FileInfo(_jsonCacheFilePath); - FileSecurity security = info.GetAccessControl(); - FileSystemAccessRule rule = new FileSystemAccessRule( - new SecurityIdentifier(WellKnownSidType.CreatorOwnerSid, null), - FileSystemRights.FullControl, - AccessControlType.Allow); - security.SetAccessRule(rule); - info.SetAccessControl(security); } else { From 6ef9b35378a86a9c7e2e531a06b329243022fd51 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 25 Jun 2024 11:37:16 -0700 Subject: [PATCH 40/81] Modify test to open the second connection before calling close --- .../IntegrationTests/SFConnectionIT.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index e0b75d26e..0743fcc1b 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -1062,12 +1062,12 @@ public void TestSSOConnectionWithTokenCaching() conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); - conn.Close(); - Assert.AreEqual(ConnectionState.Closed, conn.State); - // Authenticate using the token conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); + + conn.Close(); + Assert.AreEqual(ConnectionState.Closed, conn.State); } } @@ -2335,14 +2335,14 @@ public void TestSSOConnectionWithTokenCachingAsync() connectTask.Wait(); Assert.AreEqual(ConnectionState.Open, conn.State); - connectTask = conn.CloseAsync(CancellationToken.None); - connectTask.Wait(); - Assert.AreEqual(ConnectionState.Closed, conn.State); - // Authenticate using the token connectTask = conn.OpenAsync(CancellationToken.None); connectTask.Wait(); Assert.AreEqual(ConnectionState.Open, conn.State); + + connectTask = conn.CloseAsync(CancellationToken.None); + connectTask.Wait(); + Assert.AreEqual(ConnectionState.Closed, conn.State); } } } From d616dcccdd17b45530634c98fb9b2599d8aa22ca Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 25 Jun 2024 12:26:32 -0700 Subject: [PATCH 41/81] Modify session property test --- .../UnitTests/SFSessionPropertyTest.cs | 79 +++++++------------ 1 file changed, 28 insertions(+), 51 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index c788c8aa1..7b7f2c4f4 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -139,6 +139,21 @@ public void TestValidateSupportEscapedQuotesInsideValuesForObjectProperties(stri Assert.AreEqual(expectedValue, properties[sessionProperty]); } + [Test] + [TestCase("true")] + [TestCase("false")] + public void TestValidateAllowSSOTokenCachingProperty(string expectedAllowSsoTokenCaching) + { + // arrange + var connectionString = $"ACCOUNT=account;USER=test;PASSWORD=test;ALLOW_SSO_TOKEN_CACHING={expectedAllowSsoTokenCaching}"; + + // act + var properties = SFSessionProperties.ParseConnectionString(connectionString, null); + + // assert + Assert.AreEqual(expectedAllowSsoTokenCaching, properties[SFSessionProperty.ALLOW_SSO_TOKEN_CACHING]); + } + public static IEnumerable ConnectionStringTestCases() { string defAccount = "testaccount"; @@ -161,7 +176,6 @@ public static IEnumerable ConnectionStringTestCases() string defDisableQueryContextCache = "false"; string defDisableConsoleLogin = "true"; string defAllowUnderscoresInHost = "false"; - string defAllowSSOTokenCaching = "false"; var simpleTestCase = new TestCase() { @@ -196,7 +210,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + //{ SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; @@ -232,7 +246,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; var testCaseWithProxySettings = new TestCase() @@ -270,7 +284,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};useProxy=true;proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -310,7 +324,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -349,7 +363,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; var testCaseWithIncludeRetryReason = new TestCase() @@ -385,7 +399,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; var testCaseWithDisableQueryContextCache = new TestCase() @@ -420,7 +434,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLEQUERYCONTEXTCACHE=true" @@ -457,7 +471,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLE_CONSOLE_LOGIN=false" @@ -496,7 +510,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; var testCaseUnderscoredAccountName = new TestCase() @@ -532,7 +546,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; var testCaseUnderscoredAccountNameWithEnabledAllowUnderscores = new TestCase() @@ -568,7 +582,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; @@ -607,43 +621,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, defAllowSSOTokenCaching } - } - }; - var testCaseWithAllowSSOTokenCaching = new TestCase() - { - ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};ALLOW_SSO_TOKEN_CACHING=true", - ExpectedProperties = new SFSessionProperties() - { - { SFSessionProperty.ACCOUNT, defAccount }, - { SFSessionProperty.USER, defUser }, - { SFSessionProperty.HOST, defHost }, - { SFSessionProperty.AUTHENTICATOR, defAuthenticator }, - { SFSessionProperty.SCHEME, defScheme }, - { SFSessionProperty.CONNECTION_TIMEOUT, defConnectionTimeout }, - { SFSessionProperty.PASSWORD, defPassword }, - { SFSessionProperty.PORT, defPort }, - { SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS, "true" }, - { SFSessionProperty.USEPROXY, "false" }, - { SFSessionProperty.INSECUREMODE, "false" }, - { SFSessionProperty.DISABLERETRY, "false" }, - { SFSessionProperty.FORCERETRYON404, "false" }, - { SFSessionProperty.CLIENT_SESSION_KEEP_ALIVE, "false" }, - { SFSessionProperty.FORCEPARSEERROR, "false" }, - { SFSessionProperty.BROWSER_RESPONSE_TIMEOUT, defBrowserResponseTime }, - { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, - { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, - { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, - { SFSessionProperty.DISABLE_CONSOLE_LOGIN, defDisableConsoleLogin }, - { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost }, - { SFSessionProperty.MAXPOOLSIZE, DefaultValue(SFSessionProperty.MAXPOOLSIZE) }, - { SFSessionProperty.MINPOOLSIZE, DefaultValue(SFSessionProperty.MINPOOLSIZE) }, - { SFSessionProperty.CHANGEDSESSION, DefaultValue(SFSessionProperty.CHANGEDSESSION) }, - { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, - { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, - { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, "true" } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; @@ -660,8 +638,7 @@ public static IEnumerable ConnectionStringTestCases() testCaseComplicatedAccountName, testCaseUnderscoredAccountName, testCaseUnderscoredAccountNameWithEnabledAllowUnderscores, - testCaseQueryTag, - testCaseWithAllowSSOTokenCaching + testCaseQueryTag }; } From 045fc04d2b3d7e23738d944daa1446ccc14c51fb Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 25 Jun 2024 12:44:11 -0700 Subject: [PATCH 42/81] Uncomment line in session property test --- Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 7b7f2c4f4..9b46904a1 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -210,7 +210,7 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT, DefaultValue(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT) }, { SFSessionProperty.EXPIRATIONTIMEOUT, DefaultValue(SFSessionProperty.EXPIRATIONTIMEOUT) }, { SFSessionProperty.POOLINGENABLED, DefaultValue(SFSessionProperty.POOLINGENABLED) }, - //{ SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } + { SFSessionProperty.ALLOW_SSO_TOKEN_CACHING, DefaultValue(SFSessionProperty.ALLOW_SSO_TOKEN_CACHING) } } }; From 5c9d8d7fc1780fcc4b5a7f63718d0cf6469cd49f Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 25 Jun 2024 14:02:05 -0700 Subject: [PATCH 43/81] Add check for new map parameter value --- .../UnitTests/Session/SFHttpClientPropertiesTest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs index 5b90e3054..f56a8bc07 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs @@ -17,7 +17,8 @@ public class SFHttpClientPropertiesTest [Test] public void TestConvertToMapOnly2Properties( [Values(true, false)] bool validateDefaultParameters, - [Values(true, false)] bool clientSessionKeepAlive) + [Values(true, false)] bool clientSessionKeepAlive, + [Values(true, false)] bool clientStoreTemporaryCredential) { // arrange var proxyProperties = new SFSessionHttpClientProxyProperties() @@ -32,6 +33,7 @@ public void TestConvertToMapOnly2Properties( { validateDefaultParameters = validateDefaultParameters, clientSessionKeepAlive = clientSessionKeepAlive, + allowSSOTokenCaching = clientStoreTemporaryCredential, connectionTimeout = SFSessionHttpClientProperties.DefaultRetryTimeout, insecureMode = false, disableRetry = false, @@ -48,6 +50,7 @@ public void TestConvertToMapOnly2Properties( Assert.AreEqual(3, parameterMap.Count); Assert.AreEqual(validateDefaultParameters, parameterMap[SFSessionParameter.CLIENT_VALIDATE_DEFAULT_PARAMETERS]); Assert.AreEqual(clientSessionKeepAlive, parameterMap[SFSessionParameter.CLIENT_SESSION_KEEP_ALIVE]); + Assert.AreEqual(clientStoreTemporaryCredential, parameterMap[SFSessionParameter.CLIENT_STORE_TEMPORARY_CREDENTIAL]); } [Test] From adb3218850b67982eb0a119d8c67d656b743715b Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 26 Jun 2024 14:34:13 -0700 Subject: [PATCH 44/81] Replace user and add test explanation --- .../IntegrationTests/SFConnectionIT.cs | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index 0743fcc1b..417125da0 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -1052,17 +1052,25 @@ public void TestSSOConnectionTimeoutAfter10s() [Ignore("This test requires manual interaction and therefore cannot be run in CI")] public void TestSSOConnectionWithTokenCaching() { + /* + * This test checks that the connector successfully stores an SSO token and uses it for authentication if it exists + * 1. Login normally using external browser with allow_sso_token_caching enabled + * 2. Login again, this time without a browser, as the connector should be using the SSO token retrieved from step 1 + */ + using (IDbConnection conn = new SnowflakeDbConnection()) { + // Set the allow_sso_token_caching property to true to enable token caching + // The specified user should be configured for SSO conn.ConnectionString = ConnectionStringWithoutAuth - + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; + + $";authenticator=externalbrowser;user={testConfig.user};allow_sso_token_caching=true;"; // Authenticate to retrieve and store the token if doesn't exist or invalid conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); - // Authenticate using the token + // Authenticate using the SSO token (the connector will automatically use the token and a browser should not pop-up in this step) conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); @@ -1075,21 +1083,32 @@ public void TestSSOConnectionWithTokenCaching() [Ignore("This test requires manual interaction and therefore cannot be run in CI")] public void TestSSOConnectionWithInvalidCachedToken() { + /* + * This test checks that the connector will attempt to re-authenticate using external browser if the token retrieved from the cache is invalid + * 1. Create a credential manager and save credentials for the user with a wrong token + * 2. Open a connection which initially should try to use the token and then switch to external browser when the token fails + */ + using (IDbConnection conn = new SnowflakeDbConnection()) { + // Set the allow_sso_token_caching property to true to enable token caching conn.ConnectionString = ConnectionStringWithoutAuth - + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; + + $";authenticator=externalbrowser;user={testConfig.user};allow_sso_token_caching=true;"; + // Create a credential manager and save a wrong token for the test user var key = SFCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken); var credentialManager = SFCredentialManagerInMemoryImpl.Instance; credentialManager.SaveCredentials(key, "wrongToken"); + // Use the credential manager with the wrong token SFCredentialManagerFactory.SetCredentialManager(credentialManager); + // Open a connection which should switch to external browser after trying to connect using the wrong token conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); + // Switch back to the default credential manager SFCredentialManagerFactory.UseDefaultCredentialManager(); } } @@ -2324,18 +2343,26 @@ public void TestUseMultiplePoolsConnectionPoolByDefault() [Ignore("This test requires manual interaction and therefore cannot be run in CI")] public void TestSSOConnectionWithTokenCachingAsync() { + /* + * This test checks that the connector successfully stores an SSO token and uses it for authentication if it exists + * 1. Login normally using external browser with allow_sso_token_caching enabled + * 2. Login again, this time without a browser, as the connector should be using the SSO token retrieved from step 1 + */ + using (SnowflakeDbConnection conn = new SnowflakeDbConnection()) { + // Set the allow_sso_token_caching property to true to enable token caching + // The specified user should be configured for SSO conn.ConnectionString = ConnectionStringWithoutAuth - + ";authenticator=externalbrowser;user=qa@snowflakecomputing.com;allow_sso_token_caching=true;"; + + $";authenticator=externalbrowser;user={testConfig.user};allow_sso_token_caching=true;"; // Authenticate to retrieve and store the token if doesn't exist or invalid Task connectTask = conn.OpenAsync(CancellationToken.None); connectTask.Wait(); Assert.AreEqual(ConnectionState.Open, conn.State); - // Authenticate using the token + // Authenticate using the SSO token (the connector will automatically use the token and a browser should not pop-up in this step) connectTask = conn.OpenAsync(CancellationToken.None); connectTask.Wait(); Assert.AreEqual(ConnectionState.Open, conn.State); From da0cffb1cfa5a28ef2b664b29173a0f7e515a7c3 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 28 Jun 2024 16:17:58 -0700 Subject: [PATCH 45/81] Remove unused packages --- .../Infrastructure/SFCredentialManagerFileImpl.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index 1e8e0067b..a03e82fb6 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -11,8 +11,6 @@ using System; using System.IO; using System.Runtime.InteropServices; -using System.Security.AccessControl; -using System.Security.Principal; using KeyToken = System.Collections.Generic.Dictionary; namespace Snowflake.Data.Core.CredentialManager.Infrastructure From 44c746b76d99bae6f2a7617dd74e41fc4a8af4ef Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 3 Jul 2024 17:30:25 -0700 Subject: [PATCH 46/81] Add mock for browser and tests for external browser authentication --- .../Mock/MockExternalBrowser.cs | 99 ++++++ .../UnitTests/SFExternalBrowserTest.cs | 308 ++++++++++++++++++ .../ExternalBrowserAuthenticator.cs | 40 +-- Snowflake.Data/Core/ErrorMessages.resx | 3 + Snowflake.Data/Core/Session/SFSession.cs | 7 + .../Core/Tools/BrowserOperations.cs | 43 +++ 6 files changed, 465 insertions(+), 35 deletions(-) create mode 100644 Snowflake.Data.Tests/Mock/MockExternalBrowser.cs create mode 100644 Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs create mode 100644 Snowflake.Data/Core/Tools/BrowserOperations.cs diff --git a/Snowflake.Data.Tests/Mock/MockExternalBrowser.cs b/Snowflake.Data.Tests/Mock/MockExternalBrowser.cs new file mode 100644 index 000000000..147a2d1b1 --- /dev/null +++ b/Snowflake.Data.Tests/Mock/MockExternalBrowser.cs @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Core; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Snowflake.Data.Tests.Mock +{ + + class MockExternalBrowserRestRequester : IMockRestRequester + { + public string ProofKey { get; set; } + public string SSOUrl { get; set; } + + public T Get(IRestRequest request) + { + throw new System.NotImplementedException(); + } + + public Task GetAsync(IRestRequest request, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + public T Post(IRestRequest postRequest) + { + return Task.Run(async () => await (PostAsync(postRequest, CancellationToken.None)).ConfigureAwait(false)).Result; + } + + public Task PostAsync(IRestRequest postRequest, CancellationToken cancellationToken) + { + SFRestRequest sfRequest = (SFRestRequest)postRequest; + if (sfRequest.jsonBody is AuthenticatorRequest) + { + if (string.IsNullOrEmpty(SSOUrl)) + { + var body = (AuthenticatorRequest)sfRequest.jsonBody; + var port = body.Data.BrowserModeRedirectPort; + SSOUrl = $"http://localhost:{port}/?token=mockToken"; + } + + // authenticator + var authnResponse = new AuthenticatorResponse + { + success = true, + data = new AuthenticatorResponseData + { + proofKey = ProofKey, + ssoUrl = SSOUrl, + } + }; + + return Task.FromResult((T)(object)authnResponse); + } + else + { + // login + var loginResponse = new LoginResponse + { + success = true, + data = new LoginResponseData + { + sessionId = "", + token = "", + masterToken = "", + masterValidityInSeconds = 0, + authResponseSessionInfo = new SessionInfo + { + databaseName = "", + schemaName = "", + roleName = "", + warehouseName = "", + } + } + }; + + return Task.FromResult((T)(object)loginResponse); + } + } + + public HttpResponseMessage Get(IRestRequest request) + { + throw new System.NotImplementedException(); + } + + public Task GetAsync(IRestRequest request, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + public void setHttpClient(HttpClient httpClient) + { + // Nothing to do + } + } +} diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs new file mode 100644 index 000000000..51e7981b0 --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -0,0 +1,308 @@ +using Moq; +using NUnit.Framework; +using Snowflake.Data.Client; +using Snowflake.Data.Core; +using Snowflake.Data.Core.CredentialManager; +using Snowflake.Data.Core.CredentialManager.Infrastructure; +using Snowflake.Data.Core.Tools; +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web; + +namespace Snowflake.Data.Tests.UnitTests +{ + [TestFixture] + class SFExternalBrowserTest + { + [ThreadStatic] + private static Mock t_browserOperations; + + private static HttpClient s_httpClient = new HttpClient(); + + [SetUp] + public void BeforeEach() + { + t_browserOperations = new Mock(); + } + + [Test] + public void TestDefaultAuthentication() + { + try + { + t_browserOperations + .Setup(b => b.OpenUrl(It.IsAny())) + .Callback((string url) => { + s_httpClient.GetAsync(url); + }); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + }; + var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + + t_browserOperations.Verify(b => b.OpenUrl(It.IsAny()), Times.Once()); + } catch (SnowflakeDbException e) + { + Assert.Fail("Should pass without exception", e); + } + } + + [Test] + public void TestConsoleLogin() + { + try + { + t_browserOperations + .Setup(b => b.OpenUrl(It.IsAny())) + .Callback((string url) => { + Uri uri = new Uri(url); + var port = HttpUtility.ParseQueryString(uri.Query).Get("browser_mode_redirect_port"); + var browserUrl = $"http://localhost:{port}/?token=mockToken"; + s_httpClient.GetAsync(browserUrl); + }); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + }; + var sfSession = new SFSession("disable_console_login=false;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + + t_browserOperations.Verify(b => b.OpenUrl(It.IsAny()), Times.Once()); + } + catch (SnowflakeDbException e) + { + Assert.Fail("Should pass without exception", e); + } + } + + [Test] + public void TestSSOToken() + { + try + { + var user = "test"; + var host = $"{user}.okta.com"; + var key = SFCredentialManagerFactory.BuildCredentialKey(host, user, TokenType.IdToken); + var credentialManager = SFCredentialManagerInMemoryImpl.Instance; + credentialManager.SaveCredentials(key, "mockIdToken"); + SFCredentialManagerFactory.SetCredentialManager(credentialManager); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + SSOUrl = "https://www.mockSSOUrl.com", + }; + var sfSession = new SFSession($"allow_sso_token_caching=true;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + + t_browserOperations.Verify(b => b.OpenUrl(It.IsAny()), Times.Never()); + } + catch (SnowflakeDbException e) + { + Assert.Fail("Should pass without exception", e); + } + } + + [Test] + public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() + { + try + { + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + }; + var sfSession = new SFSession("browser_response_timeout=0;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + Assert.Fail("Should fail"); + } + catch (SnowflakeDbException e) + { + Assert.AreEqual(SFError.BROWSER_RESPONSE_TIMEOUT.GetAttribute().errorCode, e.ErrorCode); + } + } + + [Test] + public void TestThatThrowsErrorWhenUrlDoesNotMatchRegex() + { + try + { + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + SSOUrl = "non-matching-regex.com" + }; + var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + Assert.Fail("Should fail"); + } + catch (SnowflakeDbException e) + { + Assert.AreEqual(SFError.INVALID_BROWSER_URL.GetAttribute().errorCode, e.ErrorCode); + } + } + + [Test] + public void TestThatThrowsErrorWhenUrlIsNotWellFormedUriString() + { + try + { + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + SSOUrl = "http://localhost:123/?token=mockToken\\\\" + }; + var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + Assert.Fail("Should fail"); + } + catch (SnowflakeDbException e) + { + Assert.AreEqual(SFError.INVALID_BROWSER_URL.GetAttribute().errorCode, e.ErrorCode); + } + } + + [Test] + public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() + { + try + { + t_browserOperations + .Setup(b => b.OpenUrl(It.IsAny())) + .Callback((string url) => { + s_httpClient.PostAsync(url, new StringContent("")); + }); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + }; + var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + Assert.Fail("Should fail"); + } + catch (SnowflakeDbException e) + { + Assert.AreEqual(SFError.BROWSER_RESPONSE_WRONG_METHOD.GetAttribute().errorCode, e.ErrorCode); + } + } + + [Test] + public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery() + { + try + { + t_browserOperations + .Setup(b => b.OpenUrl(It.IsAny())) + .Callback((string url) => { + var urlWithoutQuery = url.Substring(0, url.IndexOf("?token=")); + s_httpClient.GetAsync(urlWithoutQuery); + }); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + }; + var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + sfSession.Open(); + Assert.Fail("Should fail"); + } + catch (SnowflakeDbException e) + { + Assert.AreEqual(SFError.BROWSER_RESPONSE_INVALID_PREFIX.GetAttribute().errorCode, e.ErrorCode); + } + } + + [Test] + public void TestDefaultAuthenticationAsync() + { + try + { + t_browserOperations + .Setup(b => b.OpenUrl(It.IsAny())) + .Callback((string url) => { + s_httpClient.GetAsync(url); + }); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + }; + var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + Task connectTask = sfSession.OpenAsync(CancellationToken.None); + connectTask.Wait(); + + t_browserOperations.Verify(b => b.OpenUrl(It.IsAny()), Times.Once()); + } + catch (SnowflakeDbException e) + { + Assert.Fail("Should pass without exception", e); + } + } + + [Test] + public void TestConsoleLoginAsync() + { + try + { + t_browserOperations + .Setup(b => b.OpenUrl(It.IsAny())) + .Callback((string url) => { + Uri uri = new Uri(url); + var port = HttpUtility.ParseQueryString(uri.Query).Get("browser_mode_redirect_port"); + var browserUrl = $"http://localhost:{port}/?token=mockToken"; + s_httpClient.GetAsync(browserUrl); + }); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + }; + var sfSession = new SFSession("disable_console_login=false;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + Task connectTask = sfSession.OpenAsync(CancellationToken.None); + connectTask.Wait(); + + t_browserOperations.Verify(b => b.OpenUrl(It.IsAny()), Times.Once()); + } + catch (SnowflakeDbException e) + { + Assert.Fail("Should pass without exception", e); + } + } + + [Test] + public void TestSSOTokenAsync() + { + try + { + var user = "test"; + var host = $"{user}.okta.com"; + var key = SFCredentialManagerFactory.BuildCredentialKey(host, user, TokenType.IdToken); + var credentialManager = SFCredentialManagerInMemoryImpl.Instance; + credentialManager.SaveCredentials(key, "mockIdToken"); + SFCredentialManagerFactory.SetCredentialManager(credentialManager); + + var restRequester = new Mock.MockExternalBrowserRestRequester() + { + ProofKey = "mockProofKey", + SSOUrl = "https://www.mockSSOUrl.com", + }; + var sfSession = new SFSession($"allow_sso_token_caching=true;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", null, restRequester, t_browserOperations.Object); + Task connectTask = sfSession.OpenAsync(CancellationToken.None); + connectTask.Wait(); + + t_browserOperations.Verify(b => b.OpenUrl(It.IsAny()), Times.Never()); + } + catch (SnowflakeDbException e) + { + Assert.Fail("Should pass without exception", e); + } + } + } +} diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 44aa0e671..09c183e3e 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -3,10 +3,8 @@ */ using System; -using System.Diagnostics; using System.Net; using System.Net.Sockets; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Snowflake.Data.Log; @@ -45,6 +43,7 @@ class ExternalBrowserAuthenticator : BaseAuthenticator, IAuthenticator internal ExternalBrowserAuthenticator(SFSession session) : base(session, AUTH_NAME) { } + /// async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) { @@ -192,46 +191,17 @@ private static HttpListener GetHttpListener(int port) return listener; } - private static void StartBrowser(string url) + private void StartBrowser(string url) { string regexStr = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$"; Match m = Regex.Match(url, regexStr, RegexOptions.IgnoreCase); - if (!m.Success) + if (!m.Success || !Uri.IsWellFormedUriString(url, UriKind.Absolute)) { logger.Error("Failed to start browser. Invalid url."); - throw new SnowflakeDbException(SFError.INVALID_BROWSER_URL); + throw new SnowflakeDbException(SFError.INVALID_BROWSER_URL, url); } - if (!Uri.IsWellFormedUriString(url, UriKind.Absolute)) - { - logger.Error("Failed to start browser. Invalid url."); - throw new SnowflakeDbException(SFError.INVALID_BROWSER_URL); - } - - // The following code is learnt from https://brockallen.com/2016/09/24/process-start-for-urls-on-net-core/ -#if NETFRAMEWORK - // .net standard would pass here - Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); -#else - // hack because of this: https://github.com/dotnet/corefx/issues/10361 - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - url = url.Replace("&", "^&"); - Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { UseShellExecute = true }); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - Process.Start("xdg-open", url); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - Process.Start("open", url); - } - else - { - throw new SnowflakeDbException(SFError.UNSUPPORTED_PLATFORM); - } -#endif + session._browserOperations.OpenUrl(url); } private static string ValidateAndExtractToken(HttpListenerRequest request) diff --git a/Snowflake.Data/Core/ErrorMessages.resx b/Snowflake.Data/Core/ErrorMessages.resx index c8e65e465..ff036ef27 100755 --- a/Snowflake.Data/Core/ErrorMessages.resx +++ b/Snowflake.Data/Core/ErrorMessages.resx @@ -180,6 +180,9 @@ Snowflake type {0} is not supported for parameters. + + Invalid browser url "{0}" cannot be used for authentication. + Browser response timed out after {0} seconds. diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 8d995dac2..d652d1b3c 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -70,6 +70,8 @@ public class SFSession private readonly EasyLoggingStarter _easyLoggingStarter = EasyLoggingStarter.Instance; + internal readonly BrowserOperations _browserOperations = BrowserOperations.Instance; + private long _startTime = 0; internal string ConnectionString { get; } internal SecureString Password { get; } @@ -256,6 +258,11 @@ internal SFSession(String connectionString, SecureString password, IMockRestRequ this.restRequester = restRequester; } + internal SFSession(String connectionString, SecureString password, IMockRestRequester restRequester, BrowserOperations browserOperations) : this(connectionString, password, restRequester) + { + _browserOperations = browserOperations; + } + internal Uri BuildUri(string path, Dictionary queryParams = null) { UriBuilder uriBuilder = new UriBuilder(); diff --git a/Snowflake.Data/Core/Tools/BrowserOperations.cs b/Snowflake.Data/Core/Tools/BrowserOperations.cs new file mode 100644 index 000000000..48ca1baff --- /dev/null +++ b/Snowflake.Data/Core/Tools/BrowserOperations.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ + +using Snowflake.Data.Client; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Snowflake.Data.Core.Tools +{ + internal class BrowserOperations + { + public static readonly BrowserOperations Instance = new BrowserOperations(); + + public virtual void OpenUrl(string url) + { + // The following code is learnt from https://brockallen.com/2016/09/24/process-start-for-urls-on-net-core/ +#if NETFRAMEWORK + // .net standard would pass here + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); +#else + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + url = url.Replace("&", "^&"); + Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { UseShellExecute = true }); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", url); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", url); + } + else + { + throw new SnowflakeDbException(SFError.UNSUPPORTED_PLATFORM); + } +#endif + } + } +} From 2abcad41dc42ce0f83ce02d08ed4704e8a1e683e Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 3 Jul 2024 19:49:17 -0700 Subject: [PATCH 47/81] Temporarily ignore test while looking for fix --- Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index 51e7981b0..dfd752ee7 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -169,6 +169,7 @@ public void TestThatThrowsErrorWhenUrlIsNotWellFormedUriString() } [Test] + [Ignore("Temporary only. Looking for fix when tests are ran parallel")] public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() { try @@ -194,6 +195,7 @@ public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() } [Test] + [Ignore("Temporary only. Looking for fix when tests are ran parallel")] public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery() { try From dd24c76b83034a4dc4bdb4aaecb8f9e266fb678b Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Wed, 3 Jul 2024 20:53:12 -0700 Subject: [PATCH 48/81] Temporarily ignore test while looking for fix --- Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index dfd752ee7..0e18ad34c 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -110,6 +110,7 @@ public void TestSSOToken() } [Test] + [Ignore("Temporary only. Looking for fix when tests are ran parallel")] public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() { try From 7a384e8bb6c88ed2d9fe03f27720abb4318f3f38 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Thu, 4 Jul 2024 12:39:31 -0700 Subject: [PATCH 49/81] Rename internal property based on convention and fix missing comma --- .../UnitTests/Session/SFHttpClientPropertiesTest.cs | 2 +- Snowflake.Data/Core/Session/SFSession.cs | 2 +- .../Core/Session/SFSessionHttpClientProperties.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs index f56a8bc07..0c76fff29 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs @@ -33,7 +33,7 @@ public void TestConvertToMapOnly2Properties( { validateDefaultParameters = validateDefaultParameters, clientSessionKeepAlive = clientSessionKeepAlive, - allowSSOTokenCaching = clientStoreTemporaryCredential, + _allowSSOTokenCaching = clientStoreTemporaryCredential, connectionTimeout = SFSessionHttpClientProperties.DefaultRetryTimeout, insecureMode = false, disableRetry = false, diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 4dcde589f..ae6ad8ad7 100644 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -215,7 +215,7 @@ internal SFSession( _maxRetryCount = extractedProperties.maxHttpRetries; _maxRetryTimeout = extractedProperties.retryTimeout; _disableSamlUrlCheck = extractedProperties._disableSamlUrlCheck; - _allowSSOTokenCaching = extractedProperties.allowSSOTokenCaching; + _allowSSOTokenCaching = extractedProperties._allowSSOTokenCaching; if (_allowSSOTokenCaching) { diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index d0f4d9644..1cd2b2c98 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -40,7 +40,7 @@ internal class SFSessionHttpClientProperties private TimeSpan _waitingForSessionIdleTimeout; private TimeSpan _expirationTimeout; private bool _poolingEnabled; - internal bool allowSSOTokenCaching; + internal bool _allowSSOTokenCaching; public static SFSessionHttpClientProperties ExtractAndValidate(SFSessionProperties properties) { @@ -208,7 +208,7 @@ internal Dictionary ToParameterMap() var parameterMap = new Dictionary(); parameterMap[SFSessionParameter.CLIENT_VALIDATE_DEFAULT_PARAMETERS] = validateDefaultParameters; parameterMap[SFSessionParameter.CLIENT_SESSION_KEEP_ALIVE] = clientSessionKeepAlive; - parameterMap[SFSessionParameter.CLIENT_STORE_TEMPORARY_CREDENTIAL] = allowSSOTokenCaching; + parameterMap[SFSessionParameter.CLIENT_STORE_TEMPORARY_CREDENTIAL] = _allowSSOTokenCaching; return parameterMap; } @@ -247,8 +247,8 @@ public SFSessionHttpClientProperties ExtractProperties(SFSessionProperties prope _waitingForSessionIdleTimeout = extractor.ExtractTimeout(SFSessionProperty.WAITINGFORIDLESESSIONTIMEOUT), _expirationTimeout = extractor.ExtractTimeout(SFSessionProperty.EXPIRATIONTIMEOUT), _poolingEnabled = extractor.ExtractBooleanWithDefaultValue(SFSessionProperty.POOLINGENABLED), - _disableSamlUrlCheck = extractor.ExtractBooleanWithDefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK) - allowSSOTokenCaching = Boolean.Parse(propertiesDictionary[SFSessionProperty.ALLOW_SSO_TOKEN_CACHING]) + _disableSamlUrlCheck = extractor.ExtractBooleanWithDefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK), + _allowSSOTokenCaching = Boolean.Parse(propertiesDictionary[SFSessionProperty.ALLOW_SSO_TOKEN_CACHING]), }; } From 76729010c394d7abe54b864098404ebe9d1877c8 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 5 Jul 2024 17:28:24 -0700 Subject: [PATCH 50/81] Fix exception not being thrown while listening for browser response --- .../UnitTests/SFExternalBrowserTest.cs | 3 -- .../ExternalBrowserAuthenticator.cs | 39 ++++++++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index 0e18ad34c..51e7981b0 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -110,7 +110,6 @@ public void TestSSOToken() } [Test] - [Ignore("Temporary only. Looking for fix when tests are ran parallel")] public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() { try @@ -170,7 +169,6 @@ public void TestThatThrowsErrorWhenUrlIsNotWellFormedUriString() } [Test] - [Ignore("Temporary only. Looking for fix when tests are ran parallel")] public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() { try @@ -196,7 +194,6 @@ public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() } [Test] - [Ignore("Temporary only. Looking for fix when tests are ran parallel")] public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery() { try diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 09c183e3e..ae037c630 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -35,6 +35,8 @@ class ExternalBrowserAuthenticator : BaseAuthenticator, IAuthenticator private string _proofKey; // Event for successful authentication. private ManualResetEvent _successEvent; + // Placeholder in case an exception occurs while listening for a browser response. + private Exception _eventException; /// /// Constructor of the External authenticator @@ -137,6 +139,7 @@ await session.restRequester.PostAsync( private void GetRedirectSamlRequest(HttpListener httpListener) { _successEvent = new ManualResetEvent(false); + _eventException = null; httpListener.BeginGetContext(GetContextCallback, httpListener); var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) @@ -144,11 +147,16 @@ private void GetRedirectSamlRequest(HttpListener httpListener) logger.Warn("Browser response timeout"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } + + if (_eventException != null) + { + throw _eventException; + } } private void GetContextCallback(IAsyncResult result) { - HttpListener httpListener = (HttpListener) result.AsyncState; + HttpListener httpListener = (HttpListener)result.AsyncState; if (httpListener.IsListening) { @@ -156,18 +164,21 @@ private void GetContextCallback(IAsyncResult result) HttpListenerRequest request = context.Request; _samlResponseToken = ValidateAndExtractToken(request); - HttpListenerResponse response = context.Response; - try + if (!string.IsNullOrEmpty(_samlResponseToken)) { - using (var output = response.OutputStream) + HttpListenerResponse response = context.Response; + try { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + using (var output = response.OutputStream) + { + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + } + } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); } - } - catch - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); } } @@ -204,16 +215,18 @@ private void StartBrowser(string url) session._browserOperations.OpenUrl(url); } - private static string ValidateAndExtractToken(HttpListenerRequest request) + private string ValidateAndExtractToken(HttpListenerRequest request) { if (request.HttpMethod != "GET") { - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.HttpMethod); + _eventException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.Url.Query); + return null; } if (request.Url.Query == null || !request.Url.Query.StartsWith(TOKEN_REQUEST_PREFIX)) { - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_INVALID_PREFIX, request.Url.Query); + _eventException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_INVALID_PREFIX, request.Url.Query); + return null; } return Uri.UnescapeDataString(request.Url.Query.Substring(TOKEN_REQUEST_PREFIX.Length)); From 2cdbae44ec820fadd41498500fa9e2938a6c18be Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 5 Jul 2024 17:48:59 -0700 Subject: [PATCH 51/81] Mark browser tests nonparallelizable --- Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index 51e7981b0..c8398d601 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -109,7 +109,7 @@ public void TestSSOToken() } } - [Test] + [Test, NonParallelizable] public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() { try @@ -168,7 +168,7 @@ public void TestThatThrowsErrorWhenUrlIsNotWellFormedUriString() } } - [Test] + [Test, NonParallelizable] public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() { try @@ -193,7 +193,7 @@ public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() } } - [Test] + [Test, NonParallelizable] public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery() { try From 524b78f9be9e0473b31b102f244268b3b6ef6147 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 5 Jul 2024 18:33:07 -0700 Subject: [PATCH 52/81] Catch HttpListenerException --- .../ExternalBrowserAuthenticator.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index ae037c630..5dfcb640f 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -160,25 +160,32 @@ private void GetContextCallback(IAsyncResult result) if (httpListener.IsListening) { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; - - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + try { - HttpListenerResponse response = context.Response; - try + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; + + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) { - using (var output = response.OutputStream) + HttpListenerResponse response = context.Response; + try { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + using (var output = response.OutputStream) + { + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + } + } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); } } - catch - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); - } + } + catch (HttpListenerException) + { + // Don't do anything. } } From 6fdf04590d9e670583735a7de8ecf2a725a1fd42 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 5 Jul 2024 19:18:15 -0700 Subject: [PATCH 53/81] Catch HttpListenerException --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 5dfcb640f..175e885d3 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -185,7 +185,8 @@ private void GetContextCallback(IAsyncResult result) } catch (HttpListenerException) { - // Don't do anything. + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("HttpListenerException thrown while trying to get context"); } } From 2d2870d3e3f9b17e20c4c7fd63b3e830eba92279 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Fri, 5 Jul 2024 20:39:04 -0700 Subject: [PATCH 54/81] Add error logs --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 175e885d3..96f20eb29 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -144,7 +144,7 @@ private void GetRedirectSamlRequest(HttpListener httpListener) var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { - logger.Warn("Browser response timeout"); + logger.Error("Browser response timeout has been reached"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } @@ -186,7 +186,7 @@ private void GetContextCallback(IAsyncResult result) catch (HttpListenerException) { // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("HttpListenerException thrown while trying to get context"); + logger.Error("HttpListenerException thrown while trying to get context"); } } @@ -227,12 +227,14 @@ private string ValidateAndExtractToken(HttpListenerRequest request) { if (request.HttpMethod != "GET") { + logger.Error("Failed to extract token due to invalid HTTP method."); _eventException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.Url.Query); return null; } if (request.Url.Query == null || !request.Url.Query.StartsWith(TOKEN_REQUEST_PREFIX)) { + logger.Error("Failed to extract token due to invalid query."); _eventException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_INVALID_PREFIX, request.Url.Query); return null; } From f8f7336b0466cf4ab146d6a959e935ba42593255 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 10:39:06 -0700 Subject: [PATCH 55/81] Add check if event is still waiting for response before getting the context --- .../ExternalBrowserAuthenticator.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 96f20eb29..1c2ba51de 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -162,24 +162,27 @@ private void GetContextCallback(IAsyncResult result) { try { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; - - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + if (!_successEvent.WaitOne(0)) { - HttpListenerResponse response = context.Response; - try + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; + + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) { - using (var output = response.OutputStream) + HttpListenerResponse response = context.Response; + try { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + using (var output = response.OutputStream) + { + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + } + } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); } - } - catch - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); } } } From 8af5ba8535594bce447dedf1189dec6a02441c2c Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 14:51:16 -0700 Subject: [PATCH 56/81] Revert event check and add log to check port number --- .../ExternalBrowserAuthenticator.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 1c2ba51de..4b0691990 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -54,6 +54,7 @@ async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) if (string.IsNullOrEmpty(session._idToken)) { int localPort = GetRandomUnusedPort(); + Console.WriteLine("External browser port number: " + localPort); using (var httpListener = GetHttpListener(localPort)) { httpListener.Start(); @@ -162,28 +163,25 @@ private void GetContextCallback(IAsyncResult result) { try { - if (!_successEvent.WaitOne(0)) - { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) + { + HttpListenerResponse response = context.Response; + try { - HttpListenerResponse response = context.Response; - try - { - using (var output = response.OutputStream) - { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); - } - } - catch + using (var output = response.OutputStream) { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); } } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); + } } } catch (HttpListenerException) From e56b086809f58b51d2e20c9bb04e918bf092d03f Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 14:53:01 -0700 Subject: [PATCH 57/81] Add log for browser port number --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 4b0691990..425b2cd97 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -54,7 +54,6 @@ async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) if (string.IsNullOrEmpty(session._idToken)) { int localPort = GetRandomUnusedPort(); - Console.WriteLine("External browser port number: " + localPort); using (var httpListener = GetHttpListener(localPort)) { httpListener.Start(); @@ -80,6 +79,7 @@ void IAuthenticator.Authenticate() if (string.IsNullOrEmpty(session._idToken)) { int localPort = GetRandomUnusedPort(); + Console.WriteLine("External browser port number: " + localPort); using (var httpListener = GetHttpListener(localPort)) { httpListener.Start(); From 2ba9aaf028f8527041a40888b81e212c692ec7f8 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 15:18:23 -0700 Subject: [PATCH 58/81] Remove printing port number and add check if result is completed --- .../ExternalBrowserAuthenticator.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 425b2cd97..cf5e3ab7c 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -79,7 +79,6 @@ void IAuthenticator.Authenticate() if (string.IsNullOrEmpty(session._idToken)) { int localPort = GetRandomUnusedPort(); - Console.WriteLine("External browser port number: " + localPort); using (var httpListener = GetHttpListener(localPort)) { httpListener.Start(); @@ -163,24 +162,28 @@ private void GetContextCallback(IAsyncResult result) { try { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; - - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + Console.WriteLine("External browser result completed: " + result.IsCompleted); + if (result.IsCompleted) { - HttpListenerResponse response = context.Response; - try + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; + + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) { - using (var output = response.OutputStream) + HttpListenerResponse response = context.Response; + try { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + using (var output = response.OutputStream) + { + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + } + } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); } - } - catch - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); } } } From 7e9c09885aebebfe2e2fadc8d82cdcf128cf883f Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 15:36:48 -0700 Subject: [PATCH 59/81] Remove printing result.IsCompleted line --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index cf5e3ab7c..1740bec62 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -162,7 +162,6 @@ private void GetContextCallback(IAsyncResult result) { try { - Console.WriteLine("External browser result completed: " + result.IsCompleted); if (result.IsCompleted) { HttpListenerContext context = httpListener.EndGetContext(result); From e49ed64e726e6ee51dfaac8faaef3d6596b3ad17 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 15:44:10 -0700 Subject: [PATCH 60/81] Remove catching HttpListenerException --- .../ExternalBrowserAuthenticator.cs | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 1740bec62..38c9d78ee 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -160,37 +160,29 @@ private void GetContextCallback(IAsyncResult result) if (httpListener.IsListening) { - try + if (result.IsCompleted) { - if (result.IsCompleted) - { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) + { + HttpListenerResponse response = context.Response; + try { - HttpListenerResponse response = context.Response; - try - { - using (var output = response.OutputStream) - { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); - } - } - catch + using (var output = response.OutputStream) { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); } } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); + } } } - catch (HttpListenerException) - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Error("HttpListenerException thrown while trying to get context"); - } } _successEvent.Set(); From c4341843cd4d6f4d06e7dab5b12c7ebbeb1b4a37 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 16:07:11 -0700 Subject: [PATCH 61/81] Catch HttpListenerException --- .../ExternalBrowserAuthenticator.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 38c9d78ee..1740bec62 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -160,29 +160,37 @@ private void GetContextCallback(IAsyncResult result) if (httpListener.IsListening) { - if (result.IsCompleted) + try { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; - - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + if (result.IsCompleted) { - HttpListenerResponse response = context.Response; - try + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; + + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) { - using (var output = response.OutputStream) + HttpListenerResponse response = context.Response; + try { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + using (var output = response.OutputStream) + { + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + } + } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); } - } - catch - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); } } } + catch (HttpListenerException) + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Error("HttpListenerException thrown while trying to get context"); + } } _successEvent.Set(); From 67a8fbfebbbb5627d11496a9812baf8f45f0d35c Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 16:47:44 -0700 Subject: [PATCH 62/81] Add check if result is completed and add wait for result --- .../ExternalBrowserAuthenticator.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 1740bec62..1d19daa78 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -162,28 +162,29 @@ private void GetContextCallback(IAsyncResult result) { try { - if (result.IsCompleted) + if (!result.IsCompleted) { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; + result.AsyncWaitHandle.WaitOne(); + } + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) + { + HttpListenerResponse response = context.Response; + try { - HttpListenerResponse response = context.Response; - try - { - using (var output = response.OutputStream) - { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); - } - } - catch + using (var output = response.OutputStream) { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); } } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); + } } } catch (HttpListenerException) From f8fcf2b978e710b70cecbc886260e24d27d36294 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 16:48:14 -0700 Subject: [PATCH 63/81] Add detail for listener exception --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 1d19daa78..8923d8ee6 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -187,10 +187,10 @@ private void GetContextCallback(IAsyncResult result) } } } - catch (HttpListenerException) + catch (HttpListenerException ex) { // Ignore the exception as it does not affect the overall authentication flow - logger.Error("HttpListenerException thrown while trying to get context"); + logger.Error("HttpListenerException thrown while trying to get context: " + ex.Message); } } From a46f1ad62510865d251f9bc0ba4bf429d478cd34 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 16:51:58 -0700 Subject: [PATCH 64/81] Specify parameter as AsyncCallback --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 8923d8ee6..9f188400d 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -140,7 +140,7 @@ private void GetRedirectSamlRequest(HttpListener httpListener) { _successEvent = new ManualResetEvent(false); _eventException = null; - httpListener.BeginGetContext(GetContextCallback, httpListener); + httpListener.BeginGetContext(new AsyncCallback(GetContextCallback), httpListener); var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { From 52bfd3eab4193507183a895f83fcd97a30c738c3 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 17:31:36 -0700 Subject: [PATCH 65/81] Refactor saml request functions --- .../ExternalBrowserAuthenticator.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 9f188400d..3b0cffe63 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -35,8 +35,8 @@ class ExternalBrowserAuthenticator : BaseAuthenticator, IAuthenticator private string _proofKey; // Event for successful authentication. private ManualResetEvent _successEvent; - // Placeholder in case an exception occurs while listening for a browser response. - private Exception _eventException; + // Placeholder in case an exception occurs while extracting the token from the browser response. + private Exception _tokenExtractionException; /// /// Constructor of the External authenticator @@ -139,18 +139,18 @@ await session.restRequester.PostAsync( private void GetRedirectSamlRequest(HttpListener httpListener) { _successEvent = new ManualResetEvent(false); - _eventException = null; + _tokenExtractionException = null; httpListener.BeginGetContext(new AsyncCallback(GetContextCallback), httpListener); var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - if (!_successEvent.WaitOne(timeoutInSec * 1000)) + var signalReceived = _successEvent.WaitOne(timeoutInSec * 1000); + if (_tokenExtractionException != null) { - logger.Error("Browser response timeout has been reached"); - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); + throw _tokenExtractionException; } - - if (_eventException != null) + if (!signalReceived) { - throw _eventException; + logger.Error("Browser response timeout has been reached"); + throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } } @@ -160,13 +160,18 @@ private void GetContextCallback(IAsyncResult result) if (httpListener.IsListening) { + HttpListenerContext context = null; try { - if (!result.IsCompleted) - { - result.AsyncWaitHandle.WaitOne(); - } - HttpListenerContext context = httpListener.EndGetContext(result); + context = httpListener.EndGetContext(result); + } + catch (HttpListenerException ex) + { + // Log the error that happens when getting the context from the browser resopnse + logger.Error("HttpListenerException thrown while trying to get context: " + ex.Message); + } + if (context != null) + { HttpListenerRequest request = context.Request; _samlResponseToken = ValidateAndExtractToken(request); @@ -187,11 +192,6 @@ private void GetContextCallback(IAsyncResult result) } } } - catch (HttpListenerException ex) - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Error("HttpListenerException thrown while trying to get context: " + ex.Message); - } } _successEvent.Set(); @@ -232,14 +232,14 @@ private string ValidateAndExtractToken(HttpListenerRequest request) if (request.HttpMethod != "GET") { logger.Error("Failed to extract token due to invalid HTTP method."); - _eventException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.Url.Query); + _tokenExtractionException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.Url.Query); return null; } if (request.Url.Query == null || !request.Url.Query.StartsWith(TOKEN_REQUEST_PREFIX)) { logger.Error("Failed to extract token due to invalid query."); - _eventException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_INVALID_PREFIX, request.Url.Query); + _tokenExtractionException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_INVALID_PREFIX, request.Url.Query); return null; } From 9edfd799841b693b73b464df44e689ca70cfa327 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 17:50:43 -0700 Subject: [PATCH 66/81] Increase browser response timeout for test --- Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index c8398d601..6afa66b1b 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -118,7 +118,7 @@ public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() { ProofKey = "mockProofKey", }; - var sfSession = new SFSession("browser_response_timeout=0;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + var sfSession = new SFSession("browser_response_timeout=10;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); sfSession.Open(); Assert.Fail("Should fail"); } From f6d8a0f09aefdc324e12543391af9277833afee6 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 18:56:08 -0700 Subject: [PATCH 67/81] Remove catching HttpListenerException --- .../ExternalBrowserAuthenticator.cs | 51 +++++++------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 3b0cffe63..36d35fe5b 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -142,16 +142,15 @@ private void GetRedirectSamlRequest(HttpListener httpListener) _tokenExtractionException = null; httpListener.BeginGetContext(new AsyncCallback(GetContextCallback), httpListener); var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - var signalReceived = _successEvent.WaitOne(timeoutInSec * 1000); - if (_tokenExtractionException != null) - { - throw _tokenExtractionException; - } - if (!signalReceived) + if (!_successEvent.WaitOne(timeoutInSec * 1000)) { logger.Error("Browser response timeout has been reached"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } + if (_tokenExtractionException != null) + { + throw _tokenExtractionException; + } } private void GetContextCallback(IAsyncResult result) @@ -160,37 +159,25 @@ private void GetContextCallback(IAsyncResult result) if (httpListener.IsListening) { - HttpListenerContext context = null; - try - { - context = httpListener.EndGetContext(result); - } - catch (HttpListenerException ex) - { - // Log the error that happens when getting the context from the browser resopnse - logger.Error("HttpListenerException thrown while trying to get context: " + ex.Message); - } - if (context != null) - { - HttpListenerRequest request = context.Request; + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) + { + HttpListenerResponse response = context.Response; + try { - HttpListenerResponse response = context.Response; - try + using (var output = response.OutputStream) { - using (var output = response.OutputStream) - { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); - } - } - catch - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); } } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); + } } } From 77a69b349728c69686461759eecdec43c670fc36 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 19:12:39 -0700 Subject: [PATCH 68/81] Remove unnecessary NonParallelizable attribute --- Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index 6afa66b1b..b9d877d57 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -109,7 +109,7 @@ public void TestSSOToken() } } - [Test, NonParallelizable] + [Test] public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() { try @@ -168,7 +168,7 @@ public void TestThatThrowsErrorWhenUrlIsNotWellFormedUriString() } } - [Test, NonParallelizable] + [Test] public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() { try @@ -193,7 +193,7 @@ public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet() } } - [Test, NonParallelizable] + [Test] public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery() { try From 9038ba11964472dc03ea0c7c2ef24265b4ae42e7 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 19:13:16 -0700 Subject: [PATCH 69/81] Revert remove catching HttpListenerException --- .../ExternalBrowserAuthenticator.cs | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 36d35fe5b..3b0cffe63 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -142,15 +142,16 @@ private void GetRedirectSamlRequest(HttpListener httpListener) _tokenExtractionException = null; httpListener.BeginGetContext(new AsyncCallback(GetContextCallback), httpListener); var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - if (!_successEvent.WaitOne(timeoutInSec * 1000)) - { - logger.Error("Browser response timeout has been reached"); - throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); - } + var signalReceived = _successEvent.WaitOne(timeoutInSec * 1000); if (_tokenExtractionException != null) { throw _tokenExtractionException; } + if (!signalReceived) + { + logger.Error("Browser response timeout has been reached"); + throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); + } } private void GetContextCallback(IAsyncResult result) @@ -159,24 +160,36 @@ private void GetContextCallback(IAsyncResult result) if (httpListener.IsListening) { - HttpListenerContext context = httpListener.EndGetContext(result); - HttpListenerRequest request = context.Request; - - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + HttpListenerContext context = null; + try { - HttpListenerResponse response = context.Response; - try + context = httpListener.EndGetContext(result); + } + catch (HttpListenerException ex) + { + // Log the error that happens when getting the context from the browser resopnse + logger.Error("HttpListenerException thrown while trying to get context: " + ex.Message); + } + if (context != null) + { + HttpListenerRequest request = context.Request; + + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) { - using (var output = response.OutputStream) + HttpListenerResponse response = context.Response; + try { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + using (var output = response.OutputStream) + { + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); + } + } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); } - } - catch - { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); } } } From d800e6fee055c8b931d0ed330c2cf564858afcc2 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 20:07:40 -0700 Subject: [PATCH 70/81] Refactor if check --- .../Authenticator/ExternalBrowserAuthenticator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 3b0cffe63..26ad3cc1d 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -142,16 +142,15 @@ private void GetRedirectSamlRequest(HttpListener httpListener) _tokenExtractionException = null; httpListener.BeginGetContext(new AsyncCallback(GetContextCallback), httpListener); var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); - var signalReceived = _successEvent.WaitOne(timeoutInSec * 1000); - if (_tokenExtractionException != null) - { - throw _tokenExtractionException; - } - if (!signalReceived) + if (!_successEvent.WaitOne(timeoutInSec * 1000)) { logger.Error("Browser response timeout has been reached"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } + if (_tokenExtractionException != null) + { + throw _tokenExtractionException; + } } private void GetContextCallback(IAsyncResult result) From 58b74d84af5a4e82c569d5c7b5d9e7f6a2656a85 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 20:33:16 -0700 Subject: [PATCH 71/81] Add temp console lines --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 26ad3cc1d..e33fd077b 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -144,6 +144,7 @@ private void GetRedirectSamlRequest(HttpListener httpListener) var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { + Console.WriteLine("Throw browser response timeout error"); logger.Error("Browser response timeout has been reached"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } @@ -162,12 +163,14 @@ private void GetContextCallback(IAsyncResult result) HttpListenerContext context = null; try { + Console.WriteLine("Has signal already been received before get context: " + _successEvent.WaitOne(0)); context = httpListener.EndGetContext(result); } catch (HttpListenerException ex) { - // Log the error that happens when getting the context from the browser resopnse - logger.Error("HttpListenerException thrown while trying to get context: " + ex.Message); + // Log the exception that happens when getting the context from the browser resopnse + logger.Error("HttpListenerException caught while trying to get context: " + ex.Message); + Console.WriteLine("Has signal already been received after get context: " + _successEvent.WaitOne(0)); } if (context != null) { From 8f919e72f625b40f203a71801344ba3929770ef6 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Mon, 8 Jul 2024 22:12:37 -0700 Subject: [PATCH 72/81] Remove console lines --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index e33fd077b..a7b5c8977 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -144,7 +144,6 @@ private void GetRedirectSamlRequest(HttpListener httpListener) var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { - Console.WriteLine("Throw browser response timeout error"); logger.Error("Browser response timeout has been reached"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } @@ -163,14 +162,12 @@ private void GetContextCallback(IAsyncResult result) HttpListenerContext context = null; try { - Console.WriteLine("Has signal already been received before get context: " + _successEvent.WaitOne(0)); context = httpListener.EndGetContext(result); } catch (HttpListenerException ex) { // Log the exception that happens when getting the context from the browser resopnse logger.Error("HttpListenerException caught while trying to get context: " + ex.Message); - Console.WriteLine("Has signal already been received after get context: " + _successEvent.WaitOne(0)); } if (context != null) { From fbbac236db161cb8b296fb62808d5fa1998900a0 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 08:33:26 -0700 Subject: [PATCH 73/81] Stop http listener before throwing browser exceptions --- .../UnitTests/SFExternalBrowserTest.cs | 2 +- .../ExternalBrowserAuthenticator.cs | 42 +++++++------------ 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index b9d877d57..1e932b7c0 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -118,7 +118,7 @@ public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() { ProofKey = "mockProofKey", }; - var sfSession = new SFSession("browser_response_timeout=10;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + var sfSession = new SFSession("browser_response_timeout=1;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); sfSession.Open(); Assert.Fail("Should fail"); } diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index a7b5c8977..2bf508323 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -145,10 +145,12 @@ private void GetRedirectSamlRequest(HttpListener httpListener) if (!_successEvent.WaitOne(timeoutInSec * 1000)) { logger.Error("Browser response timeout has been reached"); + httpListener.Stop(); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } if (_tokenExtractionException != null) { + httpListener.Stop(); throw _tokenExtractionException; } } @@ -159,37 +161,25 @@ private void GetContextCallback(IAsyncResult result) if (httpListener.IsListening) { - HttpListenerContext context = null; - try - { - context = httpListener.EndGetContext(result); - } - catch (HttpListenerException ex) - { - // Log the exception that happens when getting the context from the browser resopnse - logger.Error("HttpListenerException caught while trying to get context: " + ex.Message); - } - if (context != null) - { - HttpListenerRequest request = context.Request; + HttpListenerContext context = httpListener.EndGetContext(result); + HttpListenerRequest request = context.Request; - _samlResponseToken = ValidateAndExtractToken(request); - if (!string.IsNullOrEmpty(_samlResponseToken)) + _samlResponseToken = ValidateAndExtractToken(request); + if (!string.IsNullOrEmpty(_samlResponseToken)) + { + HttpListenerResponse response = context.Response; + try { - HttpListenerResponse response = context.Response; - try - { - using (var output = response.OutputStream) - { - output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); - } - } - catch + using (var output = response.OutputStream) { - // Ignore the exception as it does not affect the overall authentication flow - logger.Warn("External browser response not sent out"); + output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length); } } + catch + { + // Ignore the exception as it does not affect the overall authentication flow + logger.Warn("External browser response not sent out"); + } } } From d53dd8f1ca0edc8223bd1ece582e6e0f3133ee10 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 09:35:12 -0700 Subject: [PATCH 74/81] Close HttpListener before throwing exception --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 2bf508323..6ee17319e 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -144,13 +144,14 @@ private void GetRedirectSamlRequest(HttpListener httpListener) var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { + Console.WriteLine("HttpListener timeout reached: " + httpListener.IsListening); logger.Error("Browser response timeout has been reached"); - httpListener.Stop(); + httpListener.Close(); + Console.WriteLine("HttpListener before throwing exception: " + httpListener.IsListening); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } if (_tokenExtractionException != null) { - httpListener.Stop(); throw _tokenExtractionException; } } @@ -158,9 +159,10 @@ private void GetRedirectSamlRequest(HttpListener httpListener) private void GetContextCallback(IAsyncResult result) { HttpListener httpListener = (HttpListener)result.AsyncState; - + Console.WriteLine("HttpListener in callback: " + httpListener.IsListening); if (httpListener.IsListening) { + Console.WriteLine("HttpListener before getting context: " + httpListener.IsListening); HttpListenerContext context = httpListener.EndGetContext(result); HttpListenerRequest request = context.Request; From 98df45e52cb509fd98544a2a38e2659a9b16f907 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 10:19:50 -0700 Subject: [PATCH 75/81] Close HttpListener before throwing exception --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 6ee17319e..7cd1847d0 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -144,9 +144,8 @@ private void GetRedirectSamlRequest(HttpListener httpListener) var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { - Console.WriteLine("HttpListener timeout reached: " + httpListener.IsListening); - logger.Error("Browser response timeout has been reached"); httpListener.Close(); + logger.Error("Browser response timeout has been reached"); Console.WriteLine("HttpListener before throwing exception: " + httpListener.IsListening); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } From 359cfa3613f6607186ecf7596e05abb5e46c51e3 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 11:08:44 -0700 Subject: [PATCH 76/81] Modify browser timeout test --- .../UnitTests/SFExternalBrowserTest.cs | 11 ++++++++++- .../Authenticator/ExternalBrowserAuthenticator.cs | 3 --- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs index 1e932b7c0..7816e335f 100644 --- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs @@ -114,11 +114,20 @@ public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse() { try { + t_browserOperations + .Setup(b => b.OpenUrl(It.IsAny())) + .Callback(async (string url) => { + await Task.Delay(1000).ContinueWith(_ => + { + s_httpClient.GetAsync(url); + }); + }); + var restRequester = new Mock.MockExternalBrowserRestRequester() { ProofKey = "mockProofKey", }; - var sfSession = new SFSession("browser_response_timeout=1;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); + var sfSession = new SFSession($"browser_response_timeout=0;account=test;user=test;password=test;authenticator=externalbrowser;host=test.okta.com", null, restRequester, t_browserOperations.Object); sfSession.Open(); Assert.Fail("Should fail"); } diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 7cd1847d0..fa00b5003 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -146,7 +146,6 @@ private void GetRedirectSamlRequest(HttpListener httpListener) { httpListener.Close(); logger.Error("Browser response timeout has been reached"); - Console.WriteLine("HttpListener before throwing exception: " + httpListener.IsListening); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } if (_tokenExtractionException != null) @@ -158,10 +157,8 @@ private void GetRedirectSamlRequest(HttpListener httpListener) private void GetContextCallback(IAsyncResult result) { HttpListener httpListener = (HttpListener)result.AsyncState; - Console.WriteLine("HttpListener in callback: " + httpListener.IsListening); if (httpListener.IsListening) { - Console.WriteLine("HttpListener before getting context: " + httpListener.IsListening); HttpListenerContext context = httpListener.EndGetContext(result); HttpListenerRequest request = context.Request; From 85d04ed9763953f55a4f582f35be49088b84a47e Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 11:47:10 -0700 Subject: [PATCH 77/81] Abort HttpListener before throwing exception --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index fa00b5003..cbf21fbef 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -144,7 +144,7 @@ private void GetRedirectSamlRequest(HttpListener httpListener) var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { - httpListener.Close(); + httpListener.Abort(); logger.Error("Browser response timeout has been reached"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } From 2d39171aa1e191ba378b4383528e40c745d6d680 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 12:14:05 -0700 Subject: [PATCH 78/81] Check if browser timeout already reached before getting context --- .../Core/Authenticator/ExternalBrowserAuthenticator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index cbf21fbef..2cbd89e9d 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -144,7 +144,7 @@ private void GetRedirectSamlRequest(HttpListener httpListener) var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { - httpListener.Abort(); + _successEvent.Set(); logger.Error("Browser response timeout has been reached"); throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec); } @@ -157,7 +157,7 @@ private void GetRedirectSamlRequest(HttpListener httpListener) private void GetContextCallback(IAsyncResult result) { HttpListener httpListener = (HttpListener)result.AsyncState; - if (httpListener.IsListening) + if (httpListener.IsListening && !_successEvent.WaitOne(0)) { HttpListenerContext context = httpListener.EndGetContext(result); HttpListenerRequest request = context.Request; From f7be15279e2ba7b2548112c5417d94d0a2ff8fc6 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 13:50:12 -0700 Subject: [PATCH 79/81] Use SecureString for tokens --- .../Infrastructure/SFCredentialManagerInMemoryImpl.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs index bcdd15d70..5b7fac8b3 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerInMemoryImpl.cs @@ -3,8 +3,10 @@ */ using Snowflake.Data.Client; +using Snowflake.Data.Core.Tools; using Snowflake.Data.Log; using System.Collections.Generic; +using System.Security; namespace Snowflake.Data.Core.CredentialManager.Infrastructure { @@ -12,17 +14,17 @@ internal class SFCredentialManagerInMemoryImpl : ISnowflakeCredentialManager { private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - private Dictionary s_credentials = new Dictionary(); + private Dictionary s_credentials = new Dictionary(); public static readonly SFCredentialManagerInMemoryImpl Instance = new SFCredentialManagerInMemoryImpl(); public string GetCredentials(string key) { s_logger.Debug($"Getting credentials from memory for key: {key}"); - string token; + SecureString token; if (s_credentials.TryGetValue(key, out token)) { - return token; + return SecureStringHelper.Decode(token); } else { @@ -40,7 +42,7 @@ public void RemoveCredentials(string key) public void SaveCredentials(string key, string token) { s_logger.Debug($"Saving credentials into memory for key: {key}"); - s_credentials[key] = token; + s_credentials[key] = SecureStringHelper.Encode(token); } } } From 2f20edd9a393da56702d83af6bd4fc02607df6ad Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 20:07:52 -0700 Subject: [PATCH 80/81] Add security checks when reading the credential cache json --- .../SFCredentialManagerTest.cs | 88 +++++++++++++++++++ .../SFCredentialManagerFileImpl.cs | 24 ++++- Snowflake.Data/Core/Tools/UnixOperations.cs | 32 +++++++ 3 files changed, 143 insertions(+), 1 deletion(-) diff --git a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs index 8dbeec6c0..83d4d84fe 100644 --- a/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/CredentialManager/SFCredentialManagerTest.cs @@ -15,6 +15,7 @@ namespace Snowflake.Data.Tests.UnitTests.CredentialManager using System; using System.IO; using System.Runtime.InteropServices; + using System.Security; public abstract class SFBaseCredentialManagerTest { @@ -287,5 +288,92 @@ public void TestThatJsonFileIsCheckedIfAlreadyExists() // assert t_fileOperations.Verify(f => f.Exists(s_customJsonPath), Times.Exactly(2)); } + + [Test] + public void TestThatJsonFileIsCheckedIfOwnedByCurrentUser() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + t_unixOperations + .Setup(u => u.CheckFileIsNotOwnedByCurrentUser(s_customJsonPath)) + .Returns(true); + t_environmentOperations + .Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Returns(CustomJsonDir); + t_fileOperations + .SetupSequence(f => f.Exists(s_customJsonPath)) + .Returns(true); + + SFCredentialManagerFactory.SetCredentialManager(new SFCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); + + // act + var thrown = Assert.Throws(() => _credentialManager.GetCredentials("key")); + + // assert + Assert.That(thrown.Message, Does.Contain("Attempting to read a file not owned by the effective user of the current process")); + } + + [Test] + public void TestThatJsonFileIsCheckedIfOwnedByCurrentGroup() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + t_unixOperations + .Setup(u => u.CheckFileIsNotOwnedByCurrentGroup(s_customJsonPath)) + .Returns(true); + t_environmentOperations + .Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Returns(CustomJsonDir); + t_fileOperations + .SetupSequence(f => f.Exists(s_customJsonPath)) + .Returns(true); + + SFCredentialManagerFactory.SetCredentialManager(new SFCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); + + // act + var thrown = Assert.Throws(() => _credentialManager.GetCredentials("key")); + + // assert + Assert.That(thrown.Message, Does.Contain("Attempting to read a file not owned by the effective group of the current process")); + } + + [Test] + public void TestThatJsonFileIsCheckedIfItHasTooBroadPermissions() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("skip test on Windows"); + } + + // arrange + t_unixOperations + .Setup(u => u.CheckFileHasAnyOfPermissions(s_customJsonPath, FileAccessPermissions.GroupReadWriteExecute | FileAccessPermissions.OtherReadWriteExecute)) + .Returns(true); + t_environmentOperations + .Setup(e => e.GetEnvironmentVariable(SFCredentialManagerFileImpl.CredentialCacheDirectoryEnvironmentName)) + .Returns(CustomJsonDir); + t_fileOperations + .SetupSequence(f => f.Exists(s_customJsonPath)) + .Returns(true); + + SFCredentialManagerFactory.SetCredentialManager(new SFCredentialManagerFileImpl(t_fileOperations.Object, t_directoryOperations.Object, t_unixOperations.Object, t_environmentOperations.Object)); + _credentialManager = SFCredentialManagerFactory.GetCredentialManager(); + + // act + var thrown = Assert.Throws(() => _credentialManager.GetCredentials("key")); + + // assert + Assert.That(thrown.Message, Does.Contain("Attempting to read a file with too broad permissions assigned")); + } } } diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index a03e82fb6..ba3e6ae06 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -11,6 +11,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Security; using KeyToken = System.Collections.Generic.Dictionary; namespace Snowflake.Data.Core.CredentialManager.Infrastructure @@ -103,7 +104,28 @@ internal void WriteToJsonFile(string content) internal KeyToken ReadJsonFile() { - return JsonConvert.DeserializeObject(File.ReadAllText(_jsonCacheFilePath)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return JsonConvert.DeserializeObject(File.ReadAllText(_jsonCacheFilePath)); + } + else + { + if (_unixOperations.CheckFileIsNotOwnedByCurrentUser(_jsonCacheFilePath)) + { + throw new SecurityException("Attempting to read a file not owned by the effective user of the current process"); + } + if (_unixOperations.CheckFileIsNotOwnedByCurrentGroup(_jsonCacheFilePath)) + { + throw new SecurityException("Attempting to read a file not owned by the effective group of the current process"); + } + if (_unixOperations.CheckFileHasAnyOfPermissions(_jsonCacheFilePath, + FileAccessPermissions.GroupReadWriteExecute | FileAccessPermissions.OtherReadWriteExecute)) + { + throw new SecurityException("Attempting to read a file with too broad permissions assigned"); + } + + return JsonConvert.DeserializeObject(_unixOperations.ReadFile(_jsonCacheFilePath)); + } } public string GetCredentials(string key) diff --git a/Snowflake.Data/Core/Tools/UnixOperations.cs b/Snowflake.Data/Core/Tools/UnixOperations.cs index c7722ab4b..4d3d82f59 100644 --- a/Snowflake.Data/Core/Tools/UnixOperations.cs +++ b/Snowflake.Data/Core/Tools/UnixOperations.cs @@ -4,6 +4,8 @@ using Mono.Unix; using Mono.Unix.Native; +using System.IO; +using System.Text; namespace Snowflake.Data.Core.Tools { @@ -16,6 +18,18 @@ public virtual int CreateFileWithPermissions(string path, FilePermissions permis return Syscall.creat(path, permissions); } + public virtual string ReadFile(string path) + { + var fileInfo = new UnixFileInfo(path); + using (var handle = fileInfo.OpenRead()) + { + using (var streamReader = new StreamReader(handle, Encoding.Default)) + { + return streamReader.ReadToEnd(); + } + } + } + public virtual int CreateDirectoryWithPermissions(string path, FilePermissions permissions) { return Syscall.mkdir(path, permissions); @@ -38,5 +52,23 @@ public virtual bool CheckFileHasAnyOfPermissions(string path, FileAccessPermissi var fileInfo = new UnixFileInfo(path); return (permissions & fileInfo.FileAccessPermissions) != 0; } + + public virtual bool CheckFileIsNotOwnedByCurrentUser(string path) + { + var fileInfo = new UnixFileInfo(path); + using (var handle = fileInfo.OpenRead()) + { + return handle.OwnerUser.UserId != Syscall.geteuid(); + } + } + + public virtual bool CheckFileIsNotOwnedByCurrentGroup(string path) + { + var fileInfo = new UnixFileInfo(path); + using (var handle = fileInfo.OpenRead()) + { + return handle.OwnerGroup.GroupId != Syscall.getegid(); + } + } } } From ac1d659a9c27e1e873cc981dfe44e7aabf4fd151 Mon Sep 17 00:00:00 2001 From: sfc-gh-ext-simba-lf Date: Tue, 9 Jul 2024 20:53:04 -0700 Subject: [PATCH 81/81] Add error logs when reading the json cache file --- .../Infrastructure/SFCredentialManagerFileImpl.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs index ba3e6ae06..bd9dbf386 100644 --- a/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs +++ b/Snowflake.Data/Core/CredentialManager/Infrastructure/SFCredentialManagerFileImpl.cs @@ -112,16 +112,22 @@ internal KeyToken ReadJsonFile() { if (_unixOperations.CheckFileIsNotOwnedByCurrentUser(_jsonCacheFilePath)) { - throw new SecurityException("Attempting to read a file not owned by the effective user of the current process"); + var errorMessage = "Attempting to read a file not owned by the effective user of the current process"; + s_logger.Error(errorMessage); + throw new SecurityException(errorMessage); } if (_unixOperations.CheckFileIsNotOwnedByCurrentGroup(_jsonCacheFilePath)) { - throw new SecurityException("Attempting to read a file not owned by the effective group of the current process"); + var errorMessage = "Attempting to read a file not owned by the effective group of the current process"; + s_logger.Error(errorMessage); + throw new SecurityException(errorMessage); } if (_unixOperations.CheckFileHasAnyOfPermissions(_jsonCacheFilePath, FileAccessPermissions.GroupReadWriteExecute | FileAccessPermissions.OtherReadWriteExecute)) { - throw new SecurityException("Attempting to read a file with too broad permissions assigned"); + var errorMessage = "Attempting to read a file with too broad permissions assigned"; + s_logger.Error(errorMessage); + throw new SecurityException(errorMessage); } return JsonConvert.DeserializeObject(_unixOperations.ReadFile(_jsonCacheFilePath));