From 6c93c9c7c957dce4336e1b6ca4cee62b9fa431cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Smyka=C5=82a?= Date: Tue, 30 Jan 2024 18:47:49 +0100 Subject: [PATCH 1/7] cleaned up --- Snowflake.Data.Tests/SFBaseTest.cs | 2 +- .../Core/Authenticator/BasicAuthenticator.cs | 2 +- .../ExternalBrowserAuthenticator.cs | 24 +-- .../Core/Authenticator/IAuthenticator.cs | 83 ++++---- .../Authenticator/KeyPairAuthenticator.cs | 12 +- .../Core/Authenticator/OAuthAuthenticator.cs | 4 +- .../Core/Authenticator/OktaAuthenticator.cs | 184 ++++++++---------- 7 files changed, 149 insertions(+), 162 deletions(-) diff --git a/Snowflake.Data.Tests/SFBaseTest.cs b/Snowflake.Data.Tests/SFBaseTest.cs index 01ae94501..1a8455c66 100755 --- a/Snowflake.Data.Tests/SFBaseTest.cs +++ b/Snowflake.Data.Tests/SFBaseTest.cs @@ -148,7 +148,7 @@ public SFBaseTestAsync() protected TestConfig testConfig { get; } } - [SetUpFixture] + // [SetUpFixture] public class TestEnvironment { private const string ConnectionStringFmt = "scheme={0};host={1};port={2};" + diff --git a/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs b/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs index d7a7a29d1..5625316a7 100644 --- a/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs @@ -33,7 +33,7 @@ void IAuthenticator.Authenticate() protected override void SetSpecializedAuthenticatorData(ref LoginRequestData data) { // Only need to add the password to Data for basic authentication - data.password = session.properties[SFSessionProperty.PASSWORD]; + data.password = Session.properties[SFSessionProperty.PASSWORD]; } } diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index d6ead6818..619f73b57 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -56,11 +56,11 @@ async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) logger.Debug("Get IdpUrl and ProofKey"); string loginUrl; - if (session._disableConsoleLogin) + if (Session._disableConsoleLogin) { var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); var authenticatorRestResponse = - await session.restRequester.PostAsync( + await Session.restRequester.PostAsync( authenticatorRestRequest, cancellationToken ).ConfigureAwait(false); @@ -81,7 +81,7 @@ await session.restRequester.PostAsync( 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]); + var timeoutInSec = int.Parse(Session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { logger.Warn("Browser response timeout"); @@ -107,10 +107,10 @@ void IAuthenticator.Authenticate() logger.Debug("Get IdpUrl and ProofKey"); string loginUrl; - if (session._disableConsoleLogin) + if (Session._disableConsoleLogin) { var authenticatorRestRequest = BuildAuthenticatorRestRequest(localPort); - var authenticatorRestResponse = session.restRequester.Post(authenticatorRestRequest); + var authenticatorRestResponse = Session.restRequester.Post(authenticatorRestRequest); authenticatorRestResponse.FilterFailedResponse(); loginUrl = authenticatorRestResponse.data.ssoUrl; @@ -128,7 +128,7 @@ void IAuthenticator.Authenticate() 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]); + var timeoutInSec = int.Parse(Session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]); if (!_successEvent.WaitOne(timeoutInSec * 1000)) { logger.Warn("Browser response timeout"); @@ -246,17 +246,17 @@ private static string ValidateAndExtractToken(HttpListenerRequest request) private SFRestRequest BuildAuthenticatorRestRequest(int port) { - var fedUrl = session.BuildUri(RestPath.SF_AUTHENTICATOR_REQUEST_PATH); + var fedUrl = Session.BuildUri(RestPath.SF_AUTHENTICATOR_REQUEST_PATH); var data = new AuthenticatorRequestData() { - AccountName = session.properties[SFSessionProperty.ACCOUNT], + AccountName = Session.properties[SFSessionProperty.ACCOUNT], Authenticator = AUTH_NAME, BrowserModeRedirectPort = port.ToString(), }; - int connectionTimeoutSec = int.Parse(session.properties[SFSessionProperty.CONNECTION_TIMEOUT]); + int connectionTimeoutSec = int.Parse(Session.properties[SFSessionProperty.CONNECTION_TIMEOUT]); - return session.BuildTimeoutRestRequest(fedUrl, new AuthenticatorRequest() { Data = data }); + return Session.BuildTimeoutRestRequest(fedUrl, new AuthenticatorRequest() { Data = data }); } /// @@ -271,11 +271,11 @@ private string GetLoginUrl(string proofKey, int localPort) { Dictionary parameters = new Dictionary() { - { "login_name", session.properties[SFSessionProperty.USER]}, + { "login_name", Session.properties[SFSessionProperty.USER]}, { "proof_key", proofKey }, { "browser_mode_redirect_port", localPort.ToString() } }; - Uri loginUrl = session.BuildUri(RestPath.SF_CONSOLE_LOGIN, parameters); + Uri loginUrl = Session.BuildUri(RestPath.SF_CONSOLE_LOGIN, parameters); return loginUrl.ToString(); } diff --git a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs index 150551f91..7080ddccb 100644 --- a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs @@ -44,34 +44,30 @@ internal enum SFAuthenticatorType /// internal abstract class BaseAuthenticator { - // The logger. - private static readonly SFLogger logger = - SFLoggerFactory.GetLogger(); - // The name of the authenticator. - protected string authName; + private readonly string _authName; // The session which created this authenticator. - protected SFSession session; + protected SFSession Session; // The client environment properties - protected LoginRequestClientEnv ClientEnv = SFEnvironment.ClientEnv; + private readonly LoginRequestClientEnv _clientEnv = SFEnvironment.ClientEnv; /// /// The abstract base for all authenticators. /// /// The session which created the authenticator. - public BaseAuthenticator(SFSession session, string authName) + protected BaseAuthenticator(SFSession session, string authName) { - this.session = session; - this.authName = authName; + this.Session = session; + this._authName = authName; // Update the value for insecureMode because it can be different for each session - ClientEnv.insecureMode = session.properties[SFSessionProperty.INSECUREMODE]; + _clientEnv.insecureMode = session.properties[SFSessionProperty.INSECUREMODE]; if (session.properties.TryGetValue(SFSessionProperty.APPLICATION, out var applicationName)) { // If an application name has been specified in the connection setting, use it // Otherwise, it will default to the running process name - ClientEnv.application = applicationName; + _clientEnv.application = applicationName; } } @@ -80,9 +76,9 @@ protected async Task LoginAsync(CancellationToken cancellationToken) { var loginRequest = BuildLoginRequest(); - var response = await session.restRequester.PostAsync(loginRequest, cancellationToken).ConfigureAwait(false); + var response = await Session.restRequester.PostAsync(loginRequest, cancellationToken).ConfigureAwait(false); - session.ProcessLoginResponse(response); + Session.ProcessLoginResponse(response); } /// @@ -90,9 +86,9 @@ protected void Login() { var loginRequest = BuildLoginRequest(); - var response = session.restRequester.Post(loginRequest); + var response = Session.restRequester.Post(loginRequest); - session.ProcessLoginResponse(response); + Session.ProcessLoginResponse(response); } /// @@ -110,22 +106,22 @@ protected void Login() private SFRestRequest BuildLoginRequest() { // build uri - var loginUrl = session.BuildLoginUrl(); + var loginUrl = Session.BuildLoginUrl(); - LoginRequestData data = new LoginRequestData() + var data = new LoginRequestData() { - loginName = session.properties[SFSessionProperty.USER], - accountName = session.properties[SFSessionProperty.ACCOUNT], + loginName = Session.properties[SFSessionProperty.USER], + accountName = Session.properties[SFSessionProperty.ACCOUNT], clientAppId = SFEnvironment.DriverName, clientAppVersion = SFEnvironment.DriverVersion, - clientEnv = ClientEnv, - SessionParameters = session.ParameterMap, - Authenticator = authName, + clientEnv = _clientEnv, + SessionParameters = Session.ParameterMap, + Authenticator = _authName, }; SetSpecializedAuthenticatorData(ref data); - return session.BuildTimeoutRestRequest(loginUrl, new LoginRequest() { data = data }); + return Session.BuildTimeoutRestRequest(loginUrl, new LoginRequest() { data = data }); } } @@ -170,27 +166,30 @@ internal static IAuthenticator GetAuthenticator(SFSession session) return new KeyPairAuthenticator(session); } - else if (type.Equals(OAuthAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + else { - // Get private key path or private key from connection settings - if (!session.properties.TryGetValue(SFSessionProperty.TOKEN, out var pkPath)) + if (type.Equals(OAuthAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) { - // There is no TOKEN defined, can't authenticate with oauth - string invalidStringDetail = - "Missing required TOKEN for Oauth authentication"; - var error = new SnowflakeDbException( - SFError.INVALID_CONNECTION_STRING, - new object[] { invalidStringDetail }); - logger.Error(error.Message, error); - throw error; + // Get private key path or private key from connection settings + if (!session.properties.TryGetValue(SFSessionProperty.TOKEN, out var pkPath)) + { + // There is no TOKEN defined, can't authenticate with oauth + string invalidStringDetail = + "Missing required TOKEN for Oauth authentication"; + var error = new SnowflakeDbException( + SFError.INVALID_CONNECTION_STRING, + new object[] { invalidStringDetail }); + logger.Error(error.Message, error); + throw error; + } + + return new OAuthAuthenticator(session); + } + // Okta would provide a url of form: https://xxxxxx.okta.com or https://xxxxxx.oktapreview.com or https://vanity.url/snowflake/okta + if (type.Contains("okta") && type.StartsWith("https://")) + { + return new OktaAuthenticator(session, type); } - - return new OAuthAuthenticator(session); - } - // Okta would provide a url of form: https://xxxxxx.okta.com or https://xxxxxx.oktapreview.com or https://vanity.url/snowflake/okta - else if (type.Contains("okta") && type.StartsWith("https://")) - { - return new OktaAuthenticator(session, type); } var e = new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type); diff --git a/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs b/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs index fcfb70695..c30ded3ec 100644 --- a/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs @@ -46,7 +46,7 @@ class KeyPairAuthenticator : BaseAuthenticator, IAuthenticator /// Session which created this authenticator internal KeyPairAuthenticator(SFSession session) : base(session, AUTH_NAME) { - this.session = session; + this.Session = session; this.rsaProvider = new RSACryptoServiceProvider(); } @@ -86,10 +86,10 @@ private string GenerateJwtToken() logger.Info("Key-pair Authentication"); bool hasPkPath = - session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_FILE, out var pkPath); + Session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_FILE, out var pkPath); bool hasPkContent = - session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY, out var pkContent); - session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_PWD, out var pkPwd); + Session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY, out var pkContent); + Session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_PWD, out var pkPwd); // Extract the public key from the private key to generate the fingerprints RSAParameters rsaParams; @@ -169,9 +169,9 @@ private string GenerateJwtToken() * Note : Lifetime = 120sec for Python impl, 60sec for Jdbc and Odbc */ String accountUser = - session.properties[SFSessionProperty.ACCOUNT].ToUpper() + + Session.properties[SFSessionProperty.ACCOUNT].ToUpper() + "." + - session.properties[SFSessionProperty.USER].ToUpper(); + Session.properties[SFSessionProperty.USER].ToUpper(); String issuer = accountUser + "." + publicKeyFingerPrint; var claims = new[] { new Claim( diff --git a/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs b/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs index 5e8f4a310..ed56f91b7 100644 --- a/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs @@ -26,7 +26,7 @@ class OAuthAuthenticator : BaseAuthenticator, IAuthenticator /// Session which created this authenticator internal OAuthAuthenticator(SFSession session) : base(session, AUTH_NAME) { - this.session = session; + this.Session = session; } /// @@ -45,7 +45,7 @@ async public Task AuthenticateAsync(CancellationToken cancellationToken) protected override void SetSpecializedAuthenticatorData(ref LoginRequestData data) { // Add the token to the Data attribute - data.Token = session.properties[SFSessionProperty.TOKEN]; + data.Token = Session.properties[SFSessionProperty.TOKEN]; // Remove the login name for an OAuth session data.loginName = ""; } diff --git a/Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs b/Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs index 296bf518b..ac88386c5 100644 --- a/Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs @@ -16,19 +16,19 @@ namespace Snowflake.Data.Core.Authenticator { /// - /// OktaAuthenticator would perform serveral steps of authentication with Snowflake and Okta idp + /// OktaAuthenticator would perform several steps of authentication with Snowflake and Okta idp /// - class OktaAuthenticator : BaseAuthenticator, IAuthenticator + internal class OktaAuthenticator : BaseAuthenticator, IAuthenticator { - private static readonly SFLogger logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); /// /// url of the okta idp /// - private Uri oktaUrl; + private readonly Uri _oktaUrl; // The raw Saml token. - private string samlRawHtmlString; + private string _samlRawHtmlString; /// /// Constructor of the Okta authenticator @@ -38,96 +38,94 @@ class OktaAuthenticator : BaseAuthenticator, IAuthenticator internal OktaAuthenticator(SFSession session, string oktaUriString) : base(session, oktaUriString) { - oktaUrl = new Uri(oktaUriString); + _oktaUrl = new Uri(oktaUriString); } /// async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) { - logger.Info("Okta Authentication"); + s_logger.Info("Okta Authentication"); - logger.Debug("step 1: get sso and token url"); + s_logger.Debug("step 1: get sso and token url"); var authenticatorRestRequest = BuildAuthenticatorRestRequest(); - var authenticatorResponse = await session.restRequester.PostAsync(authenticatorRestRequest, cancellationToken).ConfigureAwait(false); + var authenticatorResponse = await Session.restRequester.PostAsync(authenticatorRestRequest, cancellationToken); authenticatorResponse.FilterFailedResponse(); - Uri ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); - Uri tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); - - logger.Debug("step 2: verify urls fetched from step 1"); - logger.Debug("Checking sso url"); - VerifyUrls(ssoUrl, oktaUrl); - logger.Debug("Checking token url"); - VerifyUrls(tokenUrl, oktaUrl); - - logger.Debug("step 3: get idp onetime token"); - IdpTokenRestRequest idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); - var idpResponse = await session.restRequester.PostAsync(idpTokenRestRequest, cancellationToken).ConfigureAwait(false); - string onetimeToken = idpResponse.SessionToken != null ? idpResponse.SessionToken : idpResponse.CookieToken; - - logger.Debug("step 4: get SAML reponse from sso"); - var samlRestRequest = BuildSAMLRestRequest(ssoUrl, onetimeToken); - using (var samlRawResponse = await session.restRequester.GetAsync(samlRestRequest, cancellationToken).ConfigureAwait(false)) + var ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); + var tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); + + s_logger.Debug("step 2: verify urls fetched from step 1"); + s_logger.Debug("Checking sso url"); + VerifyUrls(ssoUrl, _oktaUrl); + s_logger.Debug("Checking token url"); + VerifyUrls(tokenUrl, _oktaUrl); + + s_logger.Debug("step 3: get idp onetime token"); + var idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); + var idpResponse = await Session.restRequester.PostAsync(idpTokenRestRequest, cancellationToken); + var onetimeToken = idpResponse.SessionToken ?? idpResponse.CookieToken; + + s_logger.Debug("step 4: get SAML response from sso"); + var samlRestRequest = BuildSamlRestRequest(ssoUrl, onetimeToken); + using (var samlRawResponse = await Session.restRequester.GetAsync(samlRestRequest, cancellationToken)) { - samlRawHtmlString = await samlRawResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _samlRawHtmlString = await samlRawResponse.Content.ReadAsStringAsync(cancellationToken); } - logger.Debug("step 5: verify postback url in SAML reponse"); + s_logger.Debug("step 5: verify postback url in SAML response"); VerifyPostbackUrl(); - logger.Debug("step 6: send SAML reponse to snowflake to login"); - await base.LoginAsync(cancellationToken).ConfigureAwait(false); + s_logger.Debug("step 6: send SAML response to snowflake to login"); + await LoginAsync(cancellationToken); } void IAuthenticator.Authenticate() { - logger.Info("Okta Authentication"); + s_logger.Info("Okta Authentication"); - logger.Debug("step 1: get sso and token url"); + s_logger.Debug("step 1: get sso and token url"); var authenticatorRestRequest = BuildAuthenticatorRestRequest(); - var authenticatorResponse = session.restRequester.Post(authenticatorRestRequest); + var authenticatorResponse = Session.restRequester.Post(authenticatorRestRequest); authenticatorResponse.FilterFailedResponse(); - Uri ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); - Uri tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); - - logger.Debug("step 2: verify urls fetched from step 1"); - logger.Debug("Checking sso url"); - VerifyUrls(ssoUrl, oktaUrl); - logger.Debug("Checking token url"); - VerifyUrls(tokenUrl, oktaUrl); - - logger.Debug("step 3: get idp onetime token"); - IdpTokenRestRequest idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); - var idpResponse = session.restRequester.Post(idpTokenRestRequest); - string onetimeToken = idpResponse.SessionToken != null ? idpResponse.SessionToken : idpResponse.CookieToken; - - logger.Debug("step 4: get SAML reponse from sso"); - var samlRestRequest = BuildSAMLRestRequest(ssoUrl, onetimeToken); - using (var samlRawResponse = session.restRequester.Get(samlRestRequest)) + var ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); + var tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); + + s_logger.Debug("step 2: verify urls fetched from step 1"); + s_logger.Debug("Checking sso url"); + VerifyUrls(ssoUrl, _oktaUrl); + s_logger.Debug("Checking token url"); + VerifyUrls(tokenUrl, _oktaUrl); + + s_logger.Debug("step 3: get idp onetime token"); + var idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); + var idpResponse = Session.restRequester.Post(idpTokenRestRequest); + var onetimeToken = idpResponse.SessionToken != null ? idpResponse.SessionToken : idpResponse.CookieToken; + + s_logger.Debug("step 4: get SAML response from sso"); + var samlRestRequest = BuildSamlRestRequest(ssoUrl, onetimeToken); + using (var samlRawResponse = Session.restRequester.Get(samlRestRequest)) { - samlRawHtmlString = Task.Run(async () => await samlRawResponse.Content.ReadAsStringAsync().ConfigureAwait(false)).Result; + _samlRawHtmlString = Task.Run(async () => await samlRawResponse.Content.ReadAsStringAsync().ConfigureAwait(false)).Result; } - logger.Debug("step 5: verify postback url in SAML reponse"); + s_logger.Debug("step 5: verify postback url in SAML response"); VerifyPostbackUrl(); - logger.Debug("step 6: send SAML reponse to snowflake to login"); + s_logger.Debug("step 6: send SAML response to snowflake to login"); base.Login(); } private SFRestRequest BuildAuthenticatorRestRequest() { - var fedUrl = session.BuildUri(RestPath.SF_AUTHENTICATOR_REQUEST_PATH); + var fedUrl = Session.BuildUri(RestPath.SF_AUTHENTICATOR_REQUEST_PATH); var data = new AuthenticatorRequestData() { - AccountName = session.properties[SFSessionProperty.ACCOUNT], - Authenticator = oktaUrl.ToString(), + AccountName = Session.properties[SFSessionProperty.ACCOUNT], + Authenticator = _oktaUrl.ToString(), DriverVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), DriverName = ".NET" }; - int connectionTimeoutSec = int.Parse(session.properties[SFSessionProperty.CONNECTION_TIMEOUT]); - - return session.BuildTimeoutRestRequest(fedUrl, new AuthenticatorRequest() { Data = data }); + return Session.BuildTimeoutRestRequest(fedUrl, new AuthenticatorRequest() { Data = data }); } private IdpTokenRestRequest BuildIdpTokenRestRequest(Uri tokenUrl) @@ -135,22 +133,22 @@ private IdpTokenRestRequest BuildIdpTokenRestRequest(Uri tokenUrl) return new IdpTokenRestRequest() { Url = tokenUrl, - RestTimeout = session.connectionTimeout, + RestTimeout = Session.connectionTimeout, HttpTimeout = TimeSpan.FromSeconds(16), JsonBody = new IdpTokenRequest() { - Username = session.properties[SFSessionProperty.USER], - Password = session.properties[SFSessionProperty.PASSWORD], + Username = Session.properties[SFSessionProperty.USER], + Password = Session.properties[SFSessionProperty.PASSWORD], }, }; } - private SAMLRestRequest BuildSAMLRestRequest(Uri ssoUrl, string onetimeToken) + private SamlRestRequest BuildSamlRestRequest(Uri ssoUrl, string onetimeToken) { - return new SAMLRestRequest() + return new SamlRestRequest() { Url = ssoUrl, - RestTimeout = session.connectionTimeout, + RestTimeout = Session.connectionTimeout, HttpTimeout = Timeout.InfiniteTimeSpan, OnetimeToken = onetimeToken, }; @@ -159,7 +157,7 @@ private SAMLRestRequest BuildSAMLRestRequest(Uri ssoUrl, string onetimeToken) /// protected override void SetSpecializedAuthenticatorData(ref LoginRequestData data) { - data.RawSamlResponse = samlRawHtmlString; + data.RawSamlResponse = _samlRawHtmlString; } private void VerifyUrls(Uri tokenOrSsoUrl, Uri sessionUrl) @@ -167,33 +165,31 @@ private void VerifyUrls(Uri tokenOrSsoUrl, Uri sessionUrl) if (tokenOrSsoUrl.Scheme != sessionUrl.Scheme || tokenOrSsoUrl.Host != sessionUrl.Host) { var e = new SnowflakeDbException( - SFError.IDP_SSO_TOKEN_URL_MISMATCH, tokenOrSsoUrl.ToString(), oktaUrl.ToString()); - logger.Error("Different urls", e); + SFError.IDP_SSO_TOKEN_URL_MISMATCH, tokenOrSsoUrl.ToString(), _oktaUrl.ToString()); + s_logger.Error("Different urls", e); throw e; } } private void VerifyPostbackUrl() { - int formIndex = samlRawHtmlString.IndexOf(" Date: Tue, 30 Jan 2024 19:29:10 +0100 Subject: [PATCH 2/7] prepared for refactor --- .../IntegrationTests/SFConnectionIT.cs | 1 + Snowflake.Data.Tests/Mock/MockOkta.cs | 1 + .../Mock/MockSnowflakeDbConnection.cs | 1 + .../UnitTests/ArrowResultSetTest.cs | 1 + .../UnitTests/ChunkDownloaderFactoryTest.cs | 2 + .../UnitTests/ChunkStreamingParserTest.cs | 33 +++--- .../Okta/SamlRestRequestFactoryTests.cs | 28 +++++ .../UnitTests/FastMemoryStreamTest.cs | 2 +- .../UnitTests/FileBackedOutputStreamTest.cs | 2 +- .../UnitTests/Logger/SFLoggerTest.cs | 7 +- .../UnitTests/SFAuthenticatorFactoryTest.cs | 3 + .../UnitTests/SFDbParameterCollectionTest.cs | 15 +-- .../UnitTests/SFDbParameterTest.cs | 13 ++- .../UnitTests/SFFileTransferAgentTests.cs | 3 + Snowflake.Data.Tests/UnitTests/SFOktaTest.cs | 1 + .../UnitTests/SFReusableChunkTest.cs | 1 + .../UnitTests/SFSessionPropertyTest.cs | 1 + .../UnitTests/SFSessionTest.cs | 1 + .../UnitTests/SFStatementTest.cs | 2 + .../Session/EasyLoggingStarterTest.cs | 1 + .../Session/SFHttpClientPropertiesTest.cs | 1 + .../SFHttpClientProxyPropertiesTest.cs | 1 + .../Client/SnowflakeDbConnection.cs | 1 + Snowflake.Data/Client/SnowflakeDbException.cs | 29 ++--- Snowflake.Data/Core/ArrowResultSet.cs | 1 + .../Core/Authenticator/BasicAuthenticator.cs | 1 + .../ExternalBrowserAuthenticator.cs | 1 + .../Core/Authenticator/IAuthenticator.cs | 2 + .../Authenticator/KeyPairAuthenticator.cs | 2 + .../Core/Authenticator/OAuthAuthenticator.cs | 1 + .../Okta/ISamlRestRequestFactory.cs | 9 ++ .../Okta/Models/IdpTokenRequest.cs | 13 +++ .../Okta/Models/IdpTokenResponse.cs | 12 ++ .../Okta/Models/IdpTokenRestRequest.cs | 25 ++++ .../Okta/Models/SamlRestRequest.cs | 24 ++++ .../{ => Okta}/OktaAuthenticator.cs | 109 +++++------------- .../Okta/SamlRestRequestFactory.cs | 12 ++ .../Core/FileTransfer/SFFileMetadata.cs | 1 + .../Core/FileTransfer/SFFileTransferAgent.cs | 10 +- .../StorageClient/ISFRemoteStorageClient.cs | 3 +- .../StorageClient/SFLocalStorageUtil.cs | 2 +- .../StorageClient/SFRemoteStorageUtil.cs | 3 +- .../FileTransfer/StorageClient/SFS3Client.cs | 13 --- Snowflake.Data/Core/HeartBeatBackground.cs | 1 + Snowflake.Data/Core/RestRequest.cs | 5 +- Snowflake.Data/Core/RestRequester.cs | 2 +- Snowflake.Data/Core/SFBindUploader.cs | 1 + .../Core/SFBlockingChunkDownloader.cs | 1 + .../Core/SFBlockingChunkDownloaderV3.cs | 1 + .../Core/SFMultiStatementsResultSet.cs | 1 + Snowflake.Data/Core/SFResultSet.cs | 1 + Snowflake.Data/Core/SFResultSetMetaData.cs | 1 + Snowflake.Data/Core/SFStatement.cs | 3 +- .../Core/Session/EasyLoggingStarter.cs | 2 +- Snowflake.Data/Core/Session/SFSession.cs | 14 +-- .../Session/SFSessionHttpClientProperties.cs | 2 +- .../SFSessionHttpClientProxyProperties.cs | 2 +- .../Core/Session/SFSessionParameter.cs | 2 +- .../Core/Session/SFSessionProperty.cs | 10 +- 59 files changed, 268 insertions(+), 176 deletions(-) create mode 100644 Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs create mode 100644 Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs create mode 100644 Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRequest.cs create mode 100644 Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenResponse.cs create mode 100644 Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRestRequest.cs create mode 100644 Snowflake.Data/Core/Authenticator/Okta/Models/SamlRestRequest.cs rename Snowflake.Data/Core/Authenticator/{ => Okta}/OktaAuthenticator.cs (67%) create mode 100644 Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index 5a4976162..91539670b 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -3,6 +3,7 @@ */ using System.Data.Common; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Tests.IntegrationTests { diff --git a/Snowflake.Data.Tests/Mock/MockOkta.cs b/Snowflake.Data.Tests/Mock/MockOkta.cs index 8c39bf74e..f53f78287 100644 --- a/Snowflake.Data.Tests/Mock/MockOkta.cs +++ b/Snowflake.Data.Tests/Mock/MockOkta.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Snowflake.Data.Core.Authenticator.Okta.Models; namespace Snowflake.Data.Tests.Mock { diff --git a/Snowflake.Data.Tests/Mock/MockSnowflakeDbConnection.cs b/Snowflake.Data.Tests/Mock/MockSnowflakeDbConnection.cs index c6d8f0698..ec540602f 100644 --- a/Snowflake.Data.Tests/Mock/MockSnowflakeDbConnection.cs +++ b/Snowflake.Data.Tests/Mock/MockSnowflakeDbConnection.cs @@ -9,6 +9,7 @@ using System.Data; using System.Threading; using System.Threading.Tasks; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Tests.Mock { diff --git a/Snowflake.Data.Tests/UnitTests/ArrowResultSetTest.cs b/Snowflake.Data.Tests/UnitTests/ArrowResultSetTest.cs index cf8746b75..22b219d29 100755 --- a/Snowflake.Data.Tests/UnitTests/ArrowResultSetTest.cs +++ b/Snowflake.Data.Tests/UnitTests/ArrowResultSetTest.cs @@ -13,6 +13,7 @@ using Apache.Arrow.Ipc; using NUnit.Framework; using Snowflake.Data.Core; +using Snowflake.Data.Core.Session; using Snowflake.Data.Tests.Util; namespace Snowflake.Data.Tests.UnitTests diff --git a/Snowflake.Data.Tests/UnitTests/ChunkDownloaderFactoryTest.cs b/Snowflake.Data.Tests/UnitTests/ChunkDownloaderFactoryTest.cs index cf8b952a0..33a2fe25e 100644 --- a/Snowflake.Data.Tests/UnitTests/ChunkDownloaderFactoryTest.cs +++ b/Snowflake.Data.Tests/UnitTests/ChunkDownloaderFactoryTest.cs @@ -2,6 +2,8 @@ * Copyright (c) 2023 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Core.Session; + namespace Snowflake.Data.Tests.UnitTests { using NUnit.Framework; diff --git a/Snowflake.Data.Tests/UnitTests/ChunkStreamingParserTest.cs b/Snowflake.Data.Tests/UnitTests/ChunkStreamingParserTest.cs index e32541440..e19aef4e6 100644 --- a/Snowflake.Data.Tests/UnitTests/ChunkStreamingParserTest.cs +++ b/Snowflake.Data.Tests/UnitTests/ChunkStreamingParserTest.cs @@ -2,12 +2,13 @@ * Copyright (c) 2023 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Core; + namespace Snowflake.Data.Tests.UnitTests { using NUnit.Framework; - using Snowflake.Data.Client; + using Client; using Snowflake.Data.Configuration; - using Snowflake.Data.Core; using System; using System.IO; using System.Text; @@ -31,19 +32,12 @@ public void AfterTest() SFConfiguration.Instance().ChunkParserVersion = _chunkParserVersionDefault; // Return to default version } - public IChunkParser getParser(string data) - { - byte[] bytes = Encoding.UTF8.GetBytes(data); - Stream stream = new MemoryStream(bytes); - return ChunkParserFactory.Instance.GetParser(ResultFormat.JSON, stream); - } - [Test] public async Task TestParsingEmptyChunk() { // Create sample data for parser string data = "[ ]"; - IChunkParser parser = getParser(data); + IChunkParser parser = GetParser(data); SFResultChunk chunk = new SFResultChunk(new string[0, 0]); @@ -60,7 +54,7 @@ public async Task TestParsingEmptyArraysInChunk() { // Create sample data for parser string data = "[ [], [] ]"; - IChunkParser parser = getParser(data); + IChunkParser parser = GetParser(data); SFResultChunk chunk = new SFResultChunk(new string[2, 0]); @@ -77,7 +71,7 @@ public async Task TestParsingNonJsonArrayChunk() { // Create a sample data using data not contained in an array string data = "[ \"1\", \"1.234\", \"abcde\" ]"; - IChunkParser parser = getParser(data); + IChunkParser parser = GetParser(data); SFResultChunk chunk = new SFResultChunk(new string[1, 3]); @@ -95,7 +89,7 @@ public void TestThrowingExceptionOnUsupportedNestedJsonArrays() { // Create a sample data using non-array data and an array string data = "[ \"1\", \"1.234\", \"abcde\", [\"2\", \"5.678\", \"fghi\"] ]"; - IChunkParser parser = getParser(data); + IChunkParser parser = GetParser(data); SFResultChunk chunk = new SFResultChunk(new string[1, 3]); @@ -108,7 +102,7 @@ public void TestThrowingExceptionForJsonObjectElementsInAnArray() { // Create a sample data using JSON objects instead string data = "[ {\"1\", \"1.234\", \"abcde\"}, {\"2\", \"5.678\", \"fghi\"} ]"; - IChunkParser parser = getParser(data); + IChunkParser parser = GetParser(data); SFResultChunk chunk = new SFResultChunk(new string[2, 3]); @@ -122,7 +116,7 @@ public async Task TestParsingSimpleChunk() { // Create sample data for parser string data = "[ [\"1\", \"1.234\", \"abcde\"], [\"2\", \"5.678\", \"fghi\"] ]"; - IChunkParser parser = getParser(data); + IChunkParser parser = GetParser(data); SFResultChunk chunk = new SFResultChunk(new string[2, 3]); @@ -144,7 +138,7 @@ public async Task TestParsingChunkWithNullValue() { // Create sample data that contain null values string data = "[ [null, \"1.234\", null], [\"2\", null, \"fghi\"] ]"; - IChunkParser parser = getParser(data); + IChunkParser parser = GetParser(data); SFResultChunk chunk = new SFResultChunk(new string[2, 3]); @@ -160,5 +154,12 @@ public async Task TestParsingChunkWithNullValue() Assert.AreEqual(null, chunk.ExtractCell(1).SafeToString()); Assert.AreEqual("fghi", chunk.ExtractCell(2).SafeToString()); } + + private static IChunkParser GetParser(string data) + { + byte[] bytes = Encoding.UTF8.GetBytes(data); + Stream stream = new MemoryStream(bytes); + return ChunkParserFactory.Instance.GetParser(ResultFormat.JSON, stream); + } } } diff --git a/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs b/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs new file mode 100644 index 000000000..e0c5400f1 --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using Snowflake.Data.Core.Authenticator.Okta; + +namespace Snowflake.Data.Tests.UnitTests.Core.Authenticator.Okta +{ + public class SamlRestRequestFactoryTests + { + private ISamlRestRequestFactory _samlRestRequestFactory; + + [SetUp] + public void SetUp() + { + _samlRestRequestFactory = new SamlRestRequestFactory(); + } + + [Test] + public void TestIfCorrectSamlRestRequestIsCreated() + { + // arrange + + // act + var actual = _samlRestRequestFactory.Create(); + + // assert + + } + } +} \ No newline at end of file diff --git a/Snowflake.Data.Tests/UnitTests/FastMemoryStreamTest.cs b/Snowflake.Data.Tests/UnitTests/FastMemoryStreamTest.cs index e755a4604..4a95cb210 100644 --- a/Snowflake.Data.Tests/UnitTests/FastMemoryStreamTest.cs +++ b/Snowflake.Data.Tests/UnitTests/FastMemoryStreamTest.cs @@ -11,7 +11,7 @@ namespace Snowflake.Data.Tests.UnitTests [TestFixture] class FastMemoryStreamTest { - FastMemoryStream _fastMemoryStream; + private FastMemoryStream _fastMemoryStream; [SetUp] public void BeforeTest() diff --git a/Snowflake.Data.Tests/UnitTests/FileBackedOutputStreamTest.cs b/Snowflake.Data.Tests/UnitTests/FileBackedOutputStreamTest.cs index 8e5a2da27..fc88574e3 100644 --- a/Snowflake.Data.Tests/UnitTests/FileBackedOutputStreamTest.cs +++ b/Snowflake.Data.Tests/UnitTests/FileBackedOutputStreamTest.cs @@ -7,7 +7,7 @@ using NUnit.Framework; using Snowflake.Data.Core.FileTransfer; -namespace Snowflake.Data.Tests +namespace Snowflake.Data.Tests.UnitTests { public class FileBackedOutputStreamTest { diff --git a/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs b/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs index 710a8d645..c7ad66bd2 100644 --- a/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Logger/SFLoggerTest.cs @@ -2,13 +2,12 @@ * Copyright (c) 2023 Snowflake Computing Inc. All rights reserved. */ +using NUnit.Framework; using Snowflake.Data.Configuration; +using Snowflake.Data.Log; -namespace Snowflake.Data.Tests.UnitTests +namespace Snowflake.Data.Tests.UnitTests.Logger { - using NUnit.Framework; - using Snowflake.Data.Log; - [TestFixture, NonParallelizable] class SFLoggerTest { diff --git a/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs b/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs index 3157619ae..1b213f7d4 100644 --- a/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs @@ -2,6 +2,9 @@ * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Core.Authenticator.Okta; +using Snowflake.Data.Core.Session; + namespace Snowflake.Data.Tests.UnitTests { using NUnit.Framework; diff --git a/Snowflake.Data.Tests/UnitTests/SFDbParameterCollectionTest.cs b/Snowflake.Data.Tests/UnitTests/SFDbParameterCollectionTest.cs index c3f798f97..8da3da81d 100644 --- a/Snowflake.Data.Tests/UnitTests/SFDbParameterCollectionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFDbParameterCollectionTest.cs @@ -2,14 +2,15 @@ * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. */ -namespace Snowflake.Data.Tests +using System; +using System.Collections; +using NUnit.Framework; +using Snowflake.Data.Client; +using Snowflake.Data.Core; +using Snowflake.Data.Core.Session; + +namespace Snowflake.Data.Tests.UnitTests { - using NUnit.Framework; - using Snowflake.Data.Client; - using Snowflake.Data.Core; - using System; - using System.Collections; - [TestFixture] class SFDbParameterCollectionTest { diff --git a/Snowflake.Data.Tests/UnitTests/SFDbParameterTest.cs b/Snowflake.Data.Tests/UnitTests/SFDbParameterTest.cs index 7674d096a..b87be3918 100644 --- a/Snowflake.Data.Tests/UnitTests/SFDbParameterTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFDbParameterTest.cs @@ -2,13 +2,14 @@ * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. */ -namespace Snowflake.Data.Tests -{ - using NUnit.Framework; - using Snowflake.Data.Client; - using Snowflake.Data.Core; - using System.Data; +using System.Data; +using NUnit.Framework; +using Snowflake.Data.Client; +using Snowflake.Data.Core; +using Snowflake.Data.Core.Session; +namespace Snowflake.Data.Tests.UnitTests +{ [TestFixture] class SFDbParameterTest { diff --git a/Snowflake.Data.Tests/UnitTests/SFFileTransferAgentTests.cs b/Snowflake.Data.Tests/UnitTests/SFFileTransferAgentTests.cs index 5d81a610e..2be86ff94 100644 --- a/Snowflake.Data.Tests/UnitTests/SFFileTransferAgentTests.cs +++ b/Snowflake.Data.Tests/UnitTests/SFFileTransferAgentTests.cs @@ -2,6 +2,9 @@ * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Core.FileTransfer.StorageClient; +using Snowflake.Data.Core.Session; + namespace Snowflake.Data.Tests.UnitTests { using NUnit.Framework; diff --git a/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs b/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs index 5de0c6c06..86a7e6016 100644 --- a/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFOktaTest.cs @@ -2,6 +2,7 @@ using Snowflake.Data.Client; using Snowflake.Data.Core; using System.Net.Http; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Tests.UnitTests { diff --git a/Snowflake.Data.Tests/UnitTests/SFReusableChunkTest.cs b/Snowflake.Data.Tests/UnitTests/SFReusableChunkTest.cs index 4ca4eec79..ee570a4a4 100755 --- a/Snowflake.Data.Tests/UnitTests/SFReusableChunkTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFReusableChunkTest.cs @@ -3,6 +3,7 @@ */ using System; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Tests.UnitTests { diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 37fa3d815..8ebd3c0b4 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Snowflake.Data.Client; using Snowflake.Data.Core.Authenticator; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Tests.UnitTests { diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs index b9530b83b..e140246af 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs @@ -3,6 +3,7 @@ */ using Snowflake.Data.Configuration; +using Snowflake.Data.Core.Session; using Snowflake.Data.Log; namespace Snowflake.Data.Tests.UnitTests diff --git a/Snowflake.Data.Tests/UnitTests/SFStatementTest.cs b/Snowflake.Data.Tests/UnitTests/SFStatementTest.cs index 330b19f96..aa1ed8281 100755 --- a/Snowflake.Data.Tests/UnitTests/SFStatementTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFStatementTest.cs @@ -2,6 +2,8 @@ * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. */ +using Snowflake.Data.Core.Session; + namespace Snowflake.Data.Tests.UnitTests { using Snowflake.Data.Core; diff --git a/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs b/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs index 00a2ed9b7..1d364f350 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/EasyLoggingStarterTest.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Snowflake.Data.Configuration; using Snowflake.Data.Core; +using Snowflake.Data.Core.Session; using Snowflake.Data.Core.Tools; using Snowflake.Data.Log; diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs index 72421f588..4f5073850 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs @@ -6,6 +6,7 @@ using Moq; using NUnit.Framework; using Snowflake.Data.Core; +using Snowflake.Data.Core.Session; using Snowflake.Data.Tests.Util; namespace Snowflake.Data.Tests.UnitTests.Session diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs index a39d9bede..f97ec2edd 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using NUnit.Framework; using Snowflake.Data.Core; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Tests.UnitTests.Session { diff --git a/Snowflake.Data/Client/SnowflakeDbConnection.cs b/Snowflake.Data/Client/SnowflakeDbConnection.cs index b773a0150..6b13fbb84 100755 --- a/Snowflake.Data/Client/SnowflakeDbConnection.cs +++ b/Snowflake.Data/Client/SnowflakeDbConnection.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Data; using System.Threading; +using Snowflake.Data.Core.Session; using Snowflake.Data.Log; namespace Snowflake.Data.Client diff --git a/Snowflake.Data/Client/SnowflakeDbException.cs b/Snowflake.Data/Client/SnowflakeDbException.cs index 014ad62e1..c16a4d225 100755 --- a/Snowflake.Data/Client/SnowflakeDbException.cs +++ b/Snowflake.Data/Client/SnowflakeDbException.cs @@ -6,6 +6,7 @@ using System.Data.Common; using System.Resources; using Snowflake.Data.Core; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Client { @@ -74,21 +75,21 @@ public SnowflakeDbException(Exception innerException, string sqlState, SFError e static string FormatExceptionMessage(SFError error, object[] args, string sqlState, - string queryId) - { - return FormatExceptionMessage(string.Format(rm.GetString(error.ToString()), args) - , error.GetAttribute().errorCode - , sqlState - , queryId); - } - - static string FormatExceptionMessage(string errorMessage, - int vendorCode, - string sqlState, string queryId) - { - return string.Format("Error: {0} SqlState: {1}, VendorCode: {2}, QueryId: {3}", - errorMessage, sqlState, vendorCode, queryId); + { + return FormatExceptionMessage(string.Format(rm.GetString(error.ToString()), args) + , error.GetAttribute().errorCode + , sqlState + , queryId); + } + + static string FormatExceptionMessage(string errorMessage, + int vendorCode, + string sqlState, + string queryId) + { + return string.Format("Error: {0} SqlState: {1}, VendorCode: {2}, QueryId: {3}", + errorMessage, sqlState, vendorCode, queryId); } } } diff --git a/Snowflake.Data/Core/ArrowResultSet.cs b/Snowflake.Data/Core/ArrowResultSet.cs index 31e0eccca..3d5a180f8 100755 --- a/Snowflake.Data/Core/ArrowResultSet.cs +++ b/Snowflake.Data/Core/ArrowResultSet.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Apache.Arrow.Ipc; using Snowflake.Data.Client; +using Snowflake.Data.Core.Session; using Snowflake.Data.Log; namespace Snowflake.Data.Core diff --git a/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs b/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs index 5625316a7..4e7d7d8ea 100644 --- a/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs @@ -5,6 +5,7 @@ using Snowflake.Data.Log; using System.Threading; using System.Threading.Tasks; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core.Authenticator { diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 619f73b57..48cde7aab 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.Session; namespace Snowflake.Data.Core.Authenticator { diff --git a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs index 7080ddccb..9a8e2cdb1 100644 --- a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs @@ -6,6 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Snowflake.Data.Client; +using Snowflake.Data.Core.Authenticator.Okta; +using Snowflake.Data.Core.Session; using Snowflake.Data.Log; namespace Snowflake.Data.Core.Authenticator diff --git a/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs b/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs index c30ded3ec..717a5d44d 100644 --- a/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs @@ -19,6 +19,8 @@ using Org.BouncyCastle.X509; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; +using Snowflake.Data.Core.Session; + namespace Snowflake.Data.Core.Authenticator { /// diff --git a/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs b/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs index ed56f91b7..aa32b6775 100644 --- a/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/OAuthAuthenticator.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core.Authenticator { diff --git a/Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs b/Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs new file mode 100644 index 000000000..e7fa1e9f7 --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs @@ -0,0 +1,9 @@ +using Snowflake.Data.Core.Authenticator.Okta.Models; + +namespace Snowflake.Data.Core.Authenticator.Okta +{ + internal interface ISamlRestRequestFactory + { + SamlRestRequest Create(); + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRequest.cs b/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRequest.cs new file mode 100644 index 000000000..4f1c1f7dc --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRequest.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Snowflake.Data.Core.Authenticator.Okta.Models +{ + internal class IdpTokenRequest + { + [JsonProperty(PropertyName = "username")] + internal string Username { get; set; } + + [JsonProperty(PropertyName = "password")] + internal string Password { get; set; } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenResponse.cs b/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenResponse.cs new file mode 100644 index 000000000..4c28bcbb1 --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenResponse.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Snowflake.Data.Core.Authenticator.Okta.Models +{ + internal class IdpTokenResponse + { + [JsonProperty(PropertyName = "cookieToken")] + internal string CookieToken { get; set; } + [JsonProperty(PropertyName = "sessionToken")] + internal string SessionToken { get; set; } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRestRequest.cs b/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRestRequest.cs new file mode 100644 index 000000000..88ebecac6 --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/Models/IdpTokenRestRequest.cs @@ -0,0 +1,25 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using Newtonsoft.Json; + +namespace Snowflake.Data.Core.Authenticator.Okta.Models +{ + internal class IdpTokenRestRequest : BaseRestRequest, IRestRequest + { + private static readonly MediaTypeWithQualityHeaderValue JsonHeader = new MediaTypeWithQualityHeaderValue("application/json"); + + internal IdpTokenRequest JsonBody { get; set; } + + HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method) + { + var message = newMessage(method, Url); + message.Headers.Accept.Add(JsonHeader); + + var json = JsonConvert.SerializeObject(JsonBody, JsonUtils.JsonSettings); + message.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + return message; + } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/Okta/Models/SamlRestRequest.cs b/Snowflake.Data/Core/Authenticator/Okta/Models/SamlRestRequest.cs new file mode 100644 index 000000000..fa8880449 --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/Models/SamlRestRequest.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.Http; +using System.Net.Http.Headers; + +namespace Snowflake.Data.Core.Authenticator.Okta.Models +{ + internal class SamlRestRequest : BaseRestRequest, IRestRequest + { + internal string OnetimeToken { set; get; } + + HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method) + { + var builder = new UriBuilder(Url) + { + Query = "RelayState=%2Fsome%2Fdeep%2Flink&onetimetoken=" + OnetimeToken + }; + var message = newMessage(method, builder.Uri); + + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); + + return message; + } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs similarity index 67% rename from Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs rename to Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs index ac88386c5..c58497e2a 100644 --- a/Snowflake.Data/Core/Authenticator/OktaAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs @@ -3,24 +3,22 @@ */ using System; -using Newtonsoft.Json; -using System.Net.Http; -using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; -using Snowflake.Data.Log; -using Snowflake.Data.Client; -using System.Text; using System.Web; +using Snowflake.Data.Client; +using Snowflake.Data.Core.Authenticator.Okta.Models; +using Snowflake.Data.Core.Session; +using Snowflake.Data.Log; -namespace Snowflake.Data.Core.Authenticator +namespace Snowflake.Data.Core.Authenticator.Okta { /// /// OktaAuthenticator would perform several steps of authentication with Snowflake and Okta idp /// internal class OktaAuthenticator : BaseAuthenticator, IAuthenticator { - private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger _logger = SFLoggerFactory.GetLogger(); /// /// url of the okta idp @@ -44,73 +42,73 @@ internal OktaAuthenticator(SFSession session, string oktaUriString) : /// async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) { - s_logger.Info("Okta Authentication"); + _logger.Info("Okta Authentication"); - s_logger.Debug("step 1: get sso and token url"); + _logger.Debug("step 1: get sso and token url"); var authenticatorRestRequest = BuildAuthenticatorRestRequest(); var authenticatorResponse = await Session.restRequester.PostAsync(authenticatorRestRequest, cancellationToken); authenticatorResponse.FilterFailedResponse(); var ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); var tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); - s_logger.Debug("step 2: verify urls fetched from step 1"); - s_logger.Debug("Checking sso url"); + _logger.Debug("step 2: verify urls fetched from step 1"); + _logger.Debug("Checking sso url"); VerifyUrls(ssoUrl, _oktaUrl); - s_logger.Debug("Checking token url"); + _logger.Debug("Checking token url"); VerifyUrls(tokenUrl, _oktaUrl); - s_logger.Debug("step 3: get idp onetime token"); + _logger.Debug("step 3: get idp onetime token"); var idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); var idpResponse = await Session.restRequester.PostAsync(idpTokenRestRequest, cancellationToken); var onetimeToken = idpResponse.SessionToken ?? idpResponse.CookieToken; - s_logger.Debug("step 4: get SAML response from sso"); + _logger.Debug("step 4: get SAML response from sso"); var samlRestRequest = BuildSamlRestRequest(ssoUrl, onetimeToken); using (var samlRawResponse = await Session.restRequester.GetAsync(samlRestRequest, cancellationToken)) { _samlRawHtmlString = await samlRawResponse.Content.ReadAsStringAsync(cancellationToken); } - s_logger.Debug("step 5: verify postback url in SAML response"); + _logger.Debug("step 5: verify postback url in SAML response"); VerifyPostbackUrl(); - s_logger.Debug("step 6: send SAML response to snowflake to login"); + _logger.Debug("step 6: send SAML response to snowflake to login"); await LoginAsync(cancellationToken); } void IAuthenticator.Authenticate() { - s_logger.Info("Okta Authentication"); + _logger.Info("Okta Authentication"); - s_logger.Debug("step 1: get sso and token url"); + _logger.Debug("step 1: get sso and token url"); var authenticatorRestRequest = BuildAuthenticatorRestRequest(); var authenticatorResponse = Session.restRequester.Post(authenticatorRestRequest); authenticatorResponse.FilterFailedResponse(); var ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); var tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); - s_logger.Debug("step 2: verify urls fetched from step 1"); - s_logger.Debug("Checking sso url"); + _logger.Debug("step 2: verify urls fetched from step 1"); + _logger.Debug("Checking sso url"); VerifyUrls(ssoUrl, _oktaUrl); - s_logger.Debug("Checking token url"); + _logger.Debug("Checking token url"); VerifyUrls(tokenUrl, _oktaUrl); - s_logger.Debug("step 3: get idp onetime token"); + _logger.Debug("step 3: get idp onetime token"); var idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); var idpResponse = Session.restRequester.Post(idpTokenRestRequest); var onetimeToken = idpResponse.SessionToken != null ? idpResponse.SessionToken : idpResponse.CookieToken; - s_logger.Debug("step 4: get SAML response from sso"); + _logger.Debug("step 4: get SAML response from sso"); var samlRestRequest = BuildSamlRestRequest(ssoUrl, onetimeToken); using (var samlRawResponse = Session.restRequester.Get(samlRestRequest)) { _samlRawHtmlString = Task.Run(async () => await samlRawResponse.Content.ReadAsStringAsync().ConfigureAwait(false)).Result; } - s_logger.Debug("step 5: verify postback url in SAML response"); + _logger.Debug("step 5: verify postback url in SAML response"); VerifyPostbackUrl(); - s_logger.Debug("step 6: send SAML response to snowflake to login"); + _logger.Debug("step 6: send SAML response to snowflake to login"); base.Login(); } @@ -166,7 +164,7 @@ private void VerifyUrls(Uri tokenOrSsoUrl, Uri sessionUrl) { var e = new SnowflakeDbException( SFError.IDP_SSO_TOKEN_URL_MISMATCH, tokenOrSsoUrl.ToString(), _oktaUrl.ToString()); - s_logger.Error("Different urls", e); + _logger.Error("Different urls", e); throw e; } } @@ -184,7 +182,7 @@ private void VerifyPostbackUrl() postBackUrl = new Uri(HttpUtility.HtmlDecode(_samlRawHtmlString.Substring(startIndex, length))); } catch (Exception e) { - s_logger.Error("Fail to extract SAML from html", e); + _logger.Error("Fail to extract SAML from html", e); throw new SnowflakeDbException(e, SFError.IDP_SAML_POSTBACK_NOTFOUND); } @@ -197,62 +195,9 @@ private void VerifyPostbackUrl() SFError.IDP_SAML_POSTBACK_INVALID, postBackUrl.ToString(), sessionScheme + ":\\\\" + sessionHost); - s_logger.Error("Different urls", e); + _logger.Error("Different urls", e); throw e; } } } - - internal class IdpTokenRestRequest : BaseRestRequest, IRestRequest - { - private static MediaTypeWithQualityHeaderValue jsonHeader = new MediaTypeWithQualityHeaderValue("application/json"); - - internal IdpTokenRequest JsonBody { get; set; } - - HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method) - { - HttpRequestMessage message = newMessage(method, Url); - message.Headers.Accept.Add(jsonHeader); - - var json = JsonConvert.SerializeObject(JsonBody, JsonUtils.JsonSettings); - message.Content = new StringContent(json, Encoding.UTF8, "application/json"); - - return message; - } - } - - internal class IdpTokenRequest - { - [JsonProperty(PropertyName = "username")] - internal string Username { get; set; } - - [JsonProperty(PropertyName = "password")] - internal string Password { get; set; } - } - - internal class IdpTokenResponse - { - [JsonProperty(PropertyName = "cookieToken")] - internal string CookieToken { get; set; } - [JsonProperty(PropertyName = "sessionToken")] - internal string SessionToken { get; set; } - } - - internal class SamlRestRequest : BaseRestRequest, IRestRequest - { - internal string OnetimeToken { set; get; } - - HttpRequestMessage IRestRequest.ToRequestMessage(HttpMethod method) - { - var builder = new UriBuilder(Url) - { - Query = "RelayState=%2Fsome%2Fdeep%2Flink&onetimetoken=" + OnetimeToken - }; - var message = newMessage(method, builder.Uri); - - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); - - return message; - } - } } diff --git a/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs b/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs new file mode 100644 index 000000000..63836ffbf --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs @@ -0,0 +1,12 @@ +using Snowflake.Data.Core.Authenticator.Okta.Models; + +namespace Snowflake.Data.Core.Authenticator.Okta +{ + internal class SamlRestRequestFactory : ISamlRestRequestFactory + { + public SamlRestRequest Create() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/FileTransfer/SFFileMetadata.cs b/Snowflake.Data/Core/FileTransfer/SFFileMetadata.cs index 605de0be1..26c197203 100644 --- a/Snowflake.Data/Core/FileTransfer/SFFileMetadata.cs +++ b/Snowflake.Data/Core/FileTransfer/SFFileMetadata.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Text; +using Snowflake.Data.Core.FileTransfer.StorageClient; using static Snowflake.Data.Core.FileTransfer.SFFileCompressionTypes; namespace Snowflake.Data.Core.FileTransfer diff --git a/Snowflake.Data/Core/FileTransfer/SFFileTransferAgent.cs b/Snowflake.Data/Core/FileTransfer/SFFileTransferAgent.cs index e5badcf19..aebfb6d5e 100644 --- a/Snowflake.Data/Core/FileTransfer/SFFileTransferAgent.cs +++ b/Snowflake.Data/Core/FileTransfer/SFFileTransferAgent.cs @@ -2,20 +2,20 @@ * Copyright (c) 2021 Snowflake Computing Inc. All rights reserved. */ -using Snowflake.Data.Client; -using Snowflake.Data.Core.FileTransfer; -using Snowflake.Data.Log; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Snowflake.Data.Client; +using Snowflake.Data.Core.FileTransfer.StorageClient; +using Snowflake.Data.Core.Session; +using Snowflake.Data.Log; -namespace Snowflake.Data.Core +namespace Snowflake.Data.Core.FileTransfer { /// /// The status of the file to be uploaded/downloaded. diff --git a/Snowflake.Data/Core/FileTransfer/StorageClient/ISFRemoteStorageClient.cs b/Snowflake.Data/Core/FileTransfer/StorageClient/ISFRemoteStorageClient.cs index b20907d9a..cf2b9c82a 100644 --- a/Snowflake.Data/Core/FileTransfer/StorageClient/ISFRemoteStorageClient.cs +++ b/Snowflake.Data/Core/FileTransfer/StorageClient/ISFRemoteStorageClient.cs @@ -2,12 +2,11 @@ * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. */ -using System; using System.IO; using System.Threading; using System.Threading.Tasks; -namespace Snowflake.Data.Core.FileTransfer +namespace Snowflake.Data.Core.FileTransfer.StorageClient { internal class WrappedContentInfo { diff --git a/Snowflake.Data/Core/FileTransfer/StorageClient/SFLocalStorageUtil.cs b/Snowflake.Data/Core/FileTransfer/StorageClient/SFLocalStorageUtil.cs index 6b98f9fb1..a622797b3 100644 --- a/Snowflake.Data/Core/FileTransfer/StorageClient/SFLocalStorageUtil.cs +++ b/Snowflake.Data/Core/FileTransfer/StorageClient/SFLocalStorageUtil.cs @@ -4,7 +4,7 @@ using System.IO; -namespace Snowflake.Data.Core.FileTransfer +namespace Snowflake.Data.Core.FileTransfer.StorageClient { /// /// The storage client for local upload/download. diff --git a/Snowflake.Data/Core/FileTransfer/StorageClient/SFRemoteStorageUtil.cs b/Snowflake.Data/Core/FileTransfer/StorageClient/SFRemoteStorageUtil.cs index 92e8dd467..b9677a589 100644 --- a/Snowflake.Data/Core/FileTransfer/StorageClient/SFRemoteStorageUtil.cs +++ b/Snowflake.Data/Core/FileTransfer/StorageClient/SFRemoteStorageUtil.cs @@ -2,13 +2,12 @@ * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. */ -using Snowflake.Data.Core.FileTransfer.StorageClient; using System; using System.IO; using System.Threading; using System.Threading.Tasks; -namespace Snowflake.Data.Core.FileTransfer +namespace Snowflake.Data.Core.FileTransfer.StorageClient { /// /// The class containing file header information. diff --git a/Snowflake.Data/Core/FileTransfer/StorageClient/SFS3Client.cs b/Snowflake.Data/Core/FileTransfer/StorageClient/SFS3Client.cs index e68fbbd3e..244881622 100644 --- a/Snowflake.Data/Core/FileTransfer/StorageClient/SFS3Client.cs +++ b/Snowflake.Data/Core/FileTransfer/StorageClient/SFS3Client.cs @@ -20,19 +20,6 @@ namespace Snowflake.Data.Core.FileTransfer.StorageClient /// class SFS3Client : ISFRemoteStorageClient { - /// - /// The metadata of the S3 file. - /// - internal class S3Metadata - { - public string HTTP_HEADER_CONTENT_TYPE { get; set; } - public string SFC_DIGEST { get; set; } - public string AMZ_IV { get; set; } - public string AMZ_KEY { get; set; } - public string AMZ_MATDESC { get; set; } - - } - /// /// The metadata header keys. /// diff --git a/Snowflake.Data/Core/HeartBeatBackground.cs b/Snowflake.Data/Core/HeartBeatBackground.cs index f8a042ac3..b45663189 100644 --- a/Snowflake.Data/Core/HeartBeatBackground.cs +++ b/Snowflake.Data/Core/HeartBeatBackground.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Snowflake.Data.Client; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core { diff --git a/Snowflake.Data/Core/RestRequest.cs b/Snowflake.Data/Core/RestRequest.cs index bc4ec8d21..54feee81b 100644 --- a/Snowflake.Data/Core/RestRequest.cs +++ b/Snowflake.Data/Core/RestRequest.cs @@ -8,6 +8,7 @@ using System.Text; using System.Collections.Generic; using Newtonsoft.Json; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core { @@ -15,7 +16,7 @@ internal interface IRestRequest { HttpRequestMessage ToRequestMessage(HttpMethod method); TimeSpan GetRestTimeout(); - string getSid(); + string GetSid(); } /// @@ -62,7 +63,7 @@ TimeSpan IRestRequest.GetRestTimeout() return RestTimeout; } - string IRestRequest.getSid() + string IRestRequest.GetSid() { return sid; } diff --git a/Snowflake.Data/Core/RestRequester.cs b/Snowflake.Data/Core/RestRequester.cs index e9064495d..0ddeca0d5 100644 --- a/Snowflake.Data/Core/RestRequester.cs +++ b/Snowflake.Data/Core/RestRequester.cs @@ -94,7 +94,7 @@ private async Task SendAsync(HttpMethod method, CancellationToken externalCancellationToken) { HttpRequestMessage message = request.ToRequestMessage(method); - return await SendAsync(message, request.GetRestTimeout(), externalCancellationToken, request.getSid()).ConfigureAwait(false); + return await SendAsync(message, request.GetRestTimeout(), externalCancellationToken, request.GetSid()).ConfigureAwait(false); } protected virtual async Task SendAsync(HttpRequestMessage message, diff --git a/Snowflake.Data/Core/SFBindUploader.cs b/Snowflake.Data/Core/SFBindUploader.cs index 56cd376bd..8cf6a253c 100644 --- a/Snowflake.Data/Core/SFBindUploader.cs +++ b/Snowflake.Data/Core/SFBindUploader.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Snowflake.Data.Core.Session; using Snowflake.Data.Log; namespace Snowflake.Data.Core diff --git a/Snowflake.Data/Core/SFBlockingChunkDownloader.cs b/Snowflake.Data/Core/SFBlockingChunkDownloader.cs index b3dae7928..b79af6860 100755 --- a/Snowflake.Data/Core/SFBlockingChunkDownloader.cs +++ b/Snowflake.Data/Core/SFBlockingChunkDownloader.cs @@ -11,6 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Snowflake.Data.Core.Session; using Snowflake.Data.Log; namespace Snowflake.Data.Core diff --git a/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs b/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs index 33451a0da..ce6789cd0 100755 --- a/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs +++ b/Snowflake.Data/Core/SFBlockingChunkDownloaderV3.cs @@ -16,6 +16,7 @@ using Newtonsoft.Json; using System.Diagnostics; using Newtonsoft.Json.Serialization; +using Snowflake.Data.Core.Session; using Snowflake.Data.Log; namespace Snowflake.Data.Core diff --git a/Snowflake.Data/Core/SFMultiStatementsResultSet.cs b/Snowflake.Data/Core/SFMultiStatementsResultSet.cs index c811deb8b..c2ad642af 100644 --- a/Snowflake.Data/Core/SFMultiStatementsResultSet.cs +++ b/Snowflake.Data/Core/SFMultiStatementsResultSet.cs @@ -8,6 +8,7 @@ using Snowflake.Data.Log; using Snowflake.Data.Client; using System.Collections.Generic; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core { diff --git a/Snowflake.Data/Core/SFResultSet.cs b/Snowflake.Data/Core/SFResultSet.cs index 55b069806..e7ea0ff44 100755 --- a/Snowflake.Data/Core/SFResultSet.cs +++ b/Snowflake.Data/Core/SFResultSet.cs @@ -9,6 +9,7 @@ using Snowflake.Data.Client; using System.Collections.Generic; using System.Diagnostics; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core { diff --git a/Snowflake.Data/Core/SFResultSetMetaData.cs b/Snowflake.Data/Core/SFResultSetMetaData.cs index 4a8c5651c..b24480f5a 100755 --- a/Snowflake.Data/Core/SFResultSetMetaData.cs +++ b/Snowflake.Data/Core/SFResultSetMetaData.cs @@ -7,6 +7,7 @@ using System.Data; using Snowflake.Data.Log; using Snowflake.Data.Client; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core { diff --git a/Snowflake.Data/Core/SFStatement.cs b/Snowflake.Data/Core/SFStatement.cs index 3c48688ee..9372ec3ee 100644 --- a/Snowflake.Data/Core/SFStatement.cs +++ b/Snowflake.Data/Core/SFStatement.cs @@ -3,8 +3,6 @@ */ using System; -using System.Web; -using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using System.Text; +using Snowflake.Data.Core.Session; namespace Snowflake.Data.Core { diff --git a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs index 9800e5186..04066f416 100644 --- a/Snowflake.Data/Core/Session/EasyLoggingStarter.cs +++ b/Snowflake.Data/Core/Session/EasyLoggingStarter.cs @@ -7,7 +7,7 @@ using Snowflake.Data.Core.Tools; using Snowflake.Data.Log; -namespace Snowflake.Data.Core +namespace Snowflake.Data.Core.Session { internal class EasyLoggingStarter { diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 2ad440407..76625a15b 100755 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -4,20 +4,18 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; +using System.Net.Http; using System.Security; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; using System.Web; -using Snowflake.Data.Log; using Snowflake.Data.Client; using Snowflake.Data.Core.Authenticator; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Http; -using System.Text.RegularExpressions; -using Snowflake.Data.Configuration; +using Snowflake.Data.Log; -namespace Snowflake.Data.Core +namespace Snowflake.Data.Core.Session { public class SFSession { diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index f129de25a..7025d8e29 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -3,7 +3,7 @@ using System.Threading; using Snowflake.Data.Log; -namespace Snowflake.Data.Core +namespace Snowflake.Data.Core.Session { internal class SFSessionHttpClientProperties diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs index 4266c585d..b7c927cde 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs @@ -1,7 +1,7 @@ using System; using System.Web; -namespace Snowflake.Data.Core +namespace Snowflake.Data.Core.Session { internal class SFSessionHttpClientProxyProperties diff --git a/Snowflake.Data/Core/Session/SFSessionParameter.cs b/Snowflake.Data/Core/Session/SFSessionParameter.cs index 97fdcec23..2bbfee1b5 100755 --- a/Snowflake.Data/Core/Session/SFSessionParameter.cs +++ b/Snowflake.Data/Core/Session/SFSessionParameter.cs @@ -2,7 +2,7 @@ * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. */ -namespace Snowflake.Data.Core +namespace Snowflake.Data.Core.Session { internal enum SFSessionParameter { diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index 5953a7bbc..080309589 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -4,16 +4,16 @@ using System; using System.Collections.Generic; +using System.Data.Common; +using System.Linq; using System.Net; using System.Security; -using Snowflake.Data.Log; +using System.Text.RegularExpressions; using Snowflake.Data.Client; using Snowflake.Data.Core.Authenticator; -using System.Data.Common; -using System.Linq; -using System.Text.RegularExpressions; +using Snowflake.Data.Log; -namespace Snowflake.Data.Core +namespace Snowflake.Data.Core.Session { internal enum SFSessionProperty { From 8219ee5b154a9da2f483f14dbd07fb6ea5fea50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Smyka=C5=82a?= Date: Wed, 31 Jan 2024 10:15:10 +0100 Subject: [PATCH 3/7] added SamlRestRequestFactory --- .../Okta/SamlRestRequestFactoryTests.cs | 12 ++++- .../Core/Authenticator/IAuthenticator.cs | 53 ++++++++----------- .../Okta/ISamlRestRequestFactory.cs | 3 +- .../Okta/SamlRestRequestFactory.cs | 12 ++++- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs b/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs index e0c5400f1..d8613a937 100644 --- a/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs +++ b/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/SamlRestRequestFactoryTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Threading; using NUnit.Framework; using Snowflake.Data.Core.Authenticator.Okta; @@ -17,12 +19,18 @@ public void SetUp() public void TestIfCorrectSamlRestRequestIsCreated() { // arrange + var uri = new Uri("https://test.com"); + var onetimeToken = Guid.NewGuid().ToString(); + var timeout = TimeSpan.Parse("00:10:00"); // act - var actual = _samlRestRequestFactory.Create(); + var actual = _samlRestRequestFactory.Create(uri, onetimeToken, timeout); // assert - + Assert.AreEqual(uri, actual.Url); + Assert.AreEqual(timeout, actual.RestTimeout); + Assert.AreEqual(Timeout.InfiniteTimeSpan, actual.HttpTimeout); + Assert.AreEqual(onetimeToken, actual.OnetimeToken); } } } \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs index 9a8e2cdb1..7f5229406 100644 --- a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs @@ -33,14 +33,6 @@ internal interface IAuthenticator void Authenticate(); } - /// - /// Types of authenticators - /// - internal enum SFAuthenticatorType - { - SNOWFLAKE, - OKTA, - } /// /// A base implementation for all authenticators to create and send a login request. /// @@ -146,11 +138,13 @@ internal static IAuthenticator GetAuthenticator(SFSession session) { return new BasicAuthenticator(session); } - else if (type.Equals(ExternalBrowserAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + + if (type.Equals(ExternalBrowserAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) { return new ExternalBrowserAuthenticator(session); } - else if (type.Equals(KeyPairAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + + if (type.Equals(KeyPairAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) { // Get private key path or private key from connection settings if (!session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_FILE, out var pkPath) && @@ -168,30 +162,27 @@ internal static IAuthenticator GetAuthenticator(SFSession session) return new KeyPairAuthenticator(session); } - else + if (type.Equals(OAuthAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) { - if (type.Equals(OAuthAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) - { - // Get private key path or private key from connection settings - if (!session.properties.TryGetValue(SFSessionProperty.TOKEN, out var pkPath)) - { - // There is no TOKEN defined, can't authenticate with oauth - string invalidStringDetail = - "Missing required TOKEN for Oauth authentication"; - var error = new SnowflakeDbException( - SFError.INVALID_CONNECTION_STRING, - new object[] { invalidStringDetail }); - logger.Error(error.Message, error); - throw error; - } - - return new OAuthAuthenticator(session); - } - // Okta would provide a url of form: https://xxxxxx.okta.com or https://xxxxxx.oktapreview.com or https://vanity.url/snowflake/okta - if (type.Contains("okta") && type.StartsWith("https://")) + // Get private key path or private key from connection settings + if (!session.properties.TryGetValue(SFSessionProperty.TOKEN, out var pkPath)) { - return new OktaAuthenticator(session, type); + // There is no TOKEN defined, can't authenticate with oauth + string invalidStringDetail = + "Missing required TOKEN for Oauth authentication"; + var error = new SnowflakeDbException( + SFError.INVALID_CONNECTION_STRING, + new object[] { invalidStringDetail }); + logger.Error(error.Message, error); + throw error; } + + return new OAuthAuthenticator(session); + } + // Okta would provide a url of form: https://xxxxxx.okta.com or https://xxxxxx.oktapreview.com or https://vanity.url/snowflake/okta + if (type.Contains("okta") && type.StartsWith("https://")) + { + return new OktaAuthenticator(session, type); } var e = new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type); diff --git a/Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs b/Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs index e7fa1e9f7..7713550bd 100644 --- a/Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs +++ b/Snowflake.Data/Core/Authenticator/Okta/ISamlRestRequestFactory.cs @@ -1,9 +1,10 @@ +using System; using Snowflake.Data.Core.Authenticator.Okta.Models; namespace Snowflake.Data.Core.Authenticator.Okta { internal interface ISamlRestRequestFactory { - SamlRestRequest Create(); + SamlRestRequest Create(Uri ssoUrl, string onetimeToken, TimeSpan timeout); } } \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs b/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs index 63836ffbf..48aa5416f 100644 --- a/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs +++ b/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs @@ -1,12 +1,20 @@ +using System; +using System.Threading; using Snowflake.Data.Core.Authenticator.Okta.Models; namespace Snowflake.Data.Core.Authenticator.Okta { internal class SamlRestRequestFactory : ISamlRestRequestFactory { - public SamlRestRequest Create() + public SamlRestRequest Create(Uri ssoUrl, string onetimeToken, TimeSpan timeout) { - throw new System.NotImplementedException(); + return new SamlRestRequest() + { + Url = ssoUrl, + RestTimeout = timeout, + HttpTimeout = Timeout.InfiniteTimeSpan, + OnetimeToken = onetimeToken, + }; } } } \ No newline at end of file From be0419df9375572d8a024b2c9898b0097735ccd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Smyka=C5=82a?= Date: Wed, 31 Jan 2024 21:19:10 +0100 Subject: [PATCH 4/7] implemented IdpTokenRestRequestFactory --- .../Okta/IdpTokenRestRequestFactoryTests.cs | 37 ++++++++++++ .../UnitTests/SFSessionPropertyTest.cs | 6 +- .../UnitTests/SFSessionTest.cs | 2 +- .../Session/SFHttpClientPropertiesTest.cs | 2 +- .../SFHttpClientProxyPropertiesTest.cs | 2 +- .../Client/SnowflakeDbConnection.cs | 4 +- .../Core/Authenticator/IAuthenticator.cs | 2 +- .../Okta/IIdpTokenRestRequestFactory.cs | 11 ++++ .../Okta/IdpTokenRestRequestFactory.cs | 26 +++++++++ .../Authenticator/Okta/OktaAuthenticator.cs | 5 +- .../Okta/SamlRestRequestFactory.cs | 2 +- Snowflake.Data/Core/Session/SFSession.cs | 56 +++++++++---------- .../Core/Session/SFSessionProperty.cs | 12 ++-- Snowflake.Data/Core/Session/SessionPool.cs | 8 +-- 14 files changed, 124 insertions(+), 51 deletions(-) create mode 100644 Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/IdpTokenRestRequestFactoryTests.cs create mode 100644 Snowflake.Data/Core/Authenticator/Okta/IIdpTokenRestRequestFactory.cs create mode 100644 Snowflake.Data/Core/Authenticator/Okta/IdpTokenRestRequestFactory.cs diff --git a/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/IdpTokenRestRequestFactoryTests.cs b/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/IdpTokenRestRequestFactoryTests.cs new file mode 100644 index 000000000..74dc17983 --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/Core/Authenticator/Okta/IdpTokenRestRequestFactoryTests.cs @@ -0,0 +1,37 @@ +using System; +using System.Security; +using NUnit.Framework; +using Snowflake.Data.Core.Authenticator.Okta; +using Snowflake.Data.Core.Session; + +namespace Snowflake.Data.Tests.UnitTests.Core.Authenticator.Okta +{ + public class IdpTokenRestRequestFactoryTests + { + private IdpTokenRestRequestFactory _idpTokenRestRequestFactory; + + [SetUp] + public void SetUp() + { + _idpTokenRestRequestFactory = new IdpTokenRestRequestFactory(); + } + + [Test] + public void TestIfCreateCorrectRequest() + { + // arrange + var tokenUrl = new Uri("https://test.com/"); + var session = new SFSession("ACCOUNT=account;USER=username1;", new SecureString()); + + // act + var actual = _idpTokenRestRequestFactory.Create(tokenUrl, session); + + // assert + Assert.AreEqual(tokenUrl, actual.Url); + Assert.AreEqual(session.connectionTimeout, actual.RestTimeout); + Assert.AreEqual(TimeSpan.FromSeconds(16), actual.HttpTimeout); + Assert.AreEqual("username1", actual.JsonBody.Username); + Assert.AreEqual("", actual.JsonBody.Password); + } + } +} \ No newline at end of file diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index 8ebd3c0b4..4b76b5e59 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -19,7 +19,7 @@ class SFSessionPropertyTest public void TestThatPropertiesAreParsed(TestCase testcase) { // act - var properties = SFSessionProperties.parseConnectionString( + var properties = SFSessionProperties.ParseConnectionString( testcase.ConnectionString, testcase.SecurePassword); @@ -37,7 +37,7 @@ public void TestThatItFailsForWrongConnectionParameter(string connectionString, { // act var exception = Assert.Throws( - () => SFSessionProperties.parseConnectionString(connectionString, null) + () => SFSessionProperties.ParseConnectionString(connectionString, null) ); // assert @@ -52,7 +52,7 @@ public void TestThatItFailsIfNoAccountSpecified(string connectionString) { // act var exception = Assert.Throws( - () => SFSessionProperties.parseConnectionString(connectionString, null) + () => SFSessionProperties.ParseConnectionString(connectionString, null) ); // assert diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs index e140246af..20efa1bee 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionTest.cs @@ -21,7 +21,7 @@ public void TestSessionGoneWhenClose() Mock.MockCloseSessionGone restRequester = new Mock.MockCloseSessionGone(); SFSession sfSession = new SFSession("account=test;user=test;password=test", null, restRequester); sfSession.Open(); - sfSession.close(); // no exception is raised. + sfSession.Close(); // no exception is raised. } [Test] diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs index 4f5073850..724ab4680 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs @@ -116,7 +116,7 @@ public void TestExtractProperties(PropertiesTestCase testCase) // arrange var proxyExtractorMock = new Moq.Mock(); var extractor = new SFSessionHttpClientProperties.Extractor(proxyExtractorMock.Object); - var properties = SFSessionProperties.parseConnectionString(testCase.conectionString, null); + var properties = SFSessionProperties.ParseConnectionString(testCase.conectionString, null); var proxyProperties = new SFSessionHttpClientProxyProperties(); proxyExtractorMock .Setup(e => e.ExtractProperties(properties)) diff --git a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs index f97ec2edd..aae6c3418 100644 --- a/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Session/SFHttpClientProxyPropertiesTest.cs @@ -18,7 +18,7 @@ public void ShouldExtractProxyProperties(ProxyPropertiesTestCase testCase) { // given var extractor = new SFSessionHttpClientProxyProperties.Extractor(); - var properties = SFSessionProperties.parseConnectionString(testCase.conectionString, null); + var properties = SFSessionProperties.ParseConnectionString(testCase.conectionString, null); // when var proxyProperties = extractor.ExtractProperties(properties); diff --git a/Snowflake.Data/Client/SnowflakeDbConnection.cs b/Snowflake.Data/Client/SnowflakeDbConnection.cs index 6b13fbb84..d04e31de8 100755 --- a/Snowflake.Data/Client/SnowflakeDbConnection.cs +++ b/Snowflake.Data/Client/SnowflakeDbConnection.cs @@ -160,7 +160,7 @@ public override void Close() } else { - SfSession.close(); + SfSession.Close(); } SfSession = null; } @@ -378,7 +378,7 @@ protected override void Dispose(bool disposing) } else { - SfSession?.close(); + SfSession?.Close(); SfSession = null; _connectionState = ConnectionState.Closed; } diff --git a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs index 7f5229406..18d9e7306 100644 --- a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs @@ -182,7 +182,7 @@ internal static IAuthenticator GetAuthenticator(SFSession session) // Okta would provide a url of form: https://xxxxxx.okta.com or https://xxxxxx.oktapreview.com or https://vanity.url/snowflake/okta if (type.Contains("okta") && type.StartsWith("https://")) { - return new OktaAuthenticator(session, type); + return new OktaAuthenticator(session, type, new SamlRestRequestFactory()); } var e = new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type); diff --git a/Snowflake.Data/Core/Authenticator/Okta/IIdpTokenRestRequestFactory.cs b/Snowflake.Data/Core/Authenticator/Okta/IIdpTokenRestRequestFactory.cs new file mode 100644 index 000000000..f15005319 --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/IIdpTokenRestRequestFactory.cs @@ -0,0 +1,11 @@ +using System; +using Snowflake.Data.Core.Authenticator.Okta.Models; +using Snowflake.Data.Core.Session; + +namespace Snowflake.Data.Core.Authenticator.Okta +{ + internal interface IIdpTokenRestRequestFactory + { + IdpTokenRestRequest Create(Uri tokenUrl, SFSession session); + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/Okta/IdpTokenRestRequestFactory.cs b/Snowflake.Data/Core/Authenticator/Okta/IdpTokenRestRequestFactory.cs new file mode 100644 index 000000000..3426a35bc --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/Okta/IdpTokenRestRequestFactory.cs @@ -0,0 +1,26 @@ +using System; +using Snowflake.Data.Core.Authenticator.Okta.Models; +using Snowflake.Data.Core.Session; + +namespace Snowflake.Data.Core.Authenticator.Okta +{ + internal class IdpTokenRestRequestFactory : IIdpTokenRestRequestFactory + { + private readonly TimeSpan _httpTimeout = TimeSpan.FromSeconds(16); + + public IdpTokenRestRequest Create(Uri tokenUrl, SFSession session) + { + return new IdpTokenRestRequest() + { + Url = tokenUrl, + RestTimeout = session.connectionTimeout, + HttpTimeout = _httpTimeout, + JsonBody = new IdpTokenRequest() + { + Username = session.properties[SFSessionProperty.USER], + Password = session.properties[SFSessionProperty.PASSWORD], + }, + }; + } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs index c58497e2a..43e7ed9a3 100644 --- a/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs @@ -18,6 +18,8 @@ namespace Snowflake.Data.Core.Authenticator.Okta /// internal class OktaAuthenticator : BaseAuthenticator, IAuthenticator { + + private ISamlRestRequestFactory _samlRestRequestFactory; private static readonly SFLogger _logger = SFLoggerFactory.GetLogger(); /// @@ -33,9 +35,10 @@ internal class OktaAuthenticator : BaseAuthenticator, IAuthenticator /// /// /// - internal OktaAuthenticator(SFSession session, string oktaUriString) : + internal OktaAuthenticator(SFSession session, string oktaUriString, ISamlRestRequestFactory samlRestRequestFactory) : base(session, oktaUriString) { + _samlRestRequestFactory = samlRestRequestFactory; _oktaUrl = new Uri(oktaUriString); } diff --git a/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs b/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs index 48aa5416f..5ea87386a 100644 --- a/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs +++ b/Snowflake.Data/Core/Authenticator/Okta/SamlRestRequestFactory.cs @@ -4,7 +4,7 @@ namespace Snowflake.Data.Core.Authenticator.Okta { - internal class SamlRestRequestFactory : ISamlRestRequestFactory + internal sealed class SamlRestRequestFactory : ISamlRestRequestFactory { public SamlRestRequest Create(Uri ssoUrl, string onetimeToken, TimeSpan timeout) { diff --git a/Snowflake.Data/Core/Session/SFSession.cs b/Snowflake.Data/Core/Session/SFSession.cs index 76625a15b..d6115bbdb 100755 --- a/Snowflake.Data/Core/Session/SFSession.cs +++ b/Snowflake.Data/Core/Session/SFSession.cs @@ -53,29 +53,29 @@ public class SFSession internal bool InsecureMode; - internal bool isHeartBeatEnabled; + private bool _isHeartBeatEnabled; - private HttpClient _HttpClient; + private readonly HttpClient _httpClient; private string arrayBindStage = null; private int arrayBindStageThreshold = 0; - internal int masterValidityInSeconds = 0; - - internal static readonly SFSessionHttpClientProperties.Extractor propertiesExtractor = new SFSessionHttpClientProperties.Extractor( + private int masterValidityInSeconds = 0; + + private static readonly SFSessionHttpClientProperties.Extractor propertiesExtractor = new SFSessionHttpClientProperties.Extractor( new SFSessionHttpClientProxyProperties.Extractor()); private readonly EasyLoggingStarter _easyLoggingStarter = EasyLoggingStarter.Instance; private long _startTime = 0; - internal string connStr = null; + internal readonly string ConnStr = null; - private QueryContextCache _queryContextCache = new QueryContextCache(_defaultQueryContextCacheSize); + private readonly QueryContextCache _queryContextCache = new QueryContextCache(_defaultQueryContextCacheSize); private int _queryContextCacheSize = _defaultQueryContextCacheSize; - private bool _disableQueryContextCache = false; + private readonly bool _disableQueryContextCache = false; - internal bool _disableConsoleLogin; + internal readonly bool _disableConsoleLogin; internal void ProcessLoginResponse(LoginResponse authnResponse) { @@ -98,7 +98,7 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) } else { - SnowflakeDbException e = new SnowflakeDbException + var e = new SnowflakeDbException (SnowflakeDbException.CONNECTION_FAILURE_SSTATE, authnResponse.code, authnResponse.message, @@ -114,14 +114,10 @@ internal void ProcessLoginResponse(LoginResponse authnResponse) internal Uri BuildLoginUrl() { var queryParams = new Dictionary(); - string warehouseValue; - string dbValue; - string schemaValue; - string roleName; - queryParams[RestParams.SF_QUERY_WAREHOUSE] = properties.TryGetValue(SFSessionProperty.WAREHOUSE, out warehouseValue) ? warehouseValue : ""; - queryParams[RestParams.SF_QUERY_DB] = properties.TryGetValue(SFSessionProperty.DB, out dbValue) ? dbValue : ""; - queryParams[RestParams.SF_QUERY_SCHEMA] = properties.TryGetValue(SFSessionProperty.SCHEMA, out schemaValue) ? schemaValue : ""; - queryParams[RestParams.SF_QUERY_ROLE] = properties.TryGetValue(SFSessionProperty.ROLE, out roleName) ? roleName : ""; + queryParams[RestParams.SF_QUERY_WAREHOUSE] = properties.TryGetValue(SFSessionProperty.WAREHOUSE, out var warehouseValue) ? warehouseValue : ""; + queryParams[RestParams.SF_QUERY_DB] = properties.TryGetValue(SFSessionProperty.DB, out var dbValue) ? dbValue : ""; + queryParams[RestParams.SF_QUERY_SCHEMA] = properties.TryGetValue(SFSessionProperty.SCHEMA, out var schemaValue) ? schemaValue : ""; + queryParams[RestParams.SF_QUERY_ROLE] = properties.TryGetValue(SFSessionProperty.ROLE, out var roleName) ? roleName : ""; queryParams[RestParams.SF_QUERY_REQUEST_ID] = Guid.NewGuid().ToString(); queryParams[RestParams.SF_QUERY_REQUEST_GUID] = Guid.NewGuid().ToString(); @@ -134,19 +130,19 @@ internal Uri BuildLoginUrl() /// /// A string in the form of "key1=value1;key2=value2" internal SFSession( - String connectionString, + string connectionString, SecureString password) : this(connectionString, password, EasyLoggingStarter.Instance) { } internal SFSession( - String connectionString, + string connectionString, SecureString password, EasyLoggingStarter easyLoggingStarter) { _easyLoggingStarter = easyLoggingStarter; - connStr = connectionString; - properties = SFSessionProperties.parseConnectionString(connectionString, password); + ConnStr = connectionString; + properties = SFSessionProperties.ParseConnectionString(connectionString, password); _disableQueryContextCache = bool.Parse(properties[SFSessionProperty.DISABLEQUERYCONTEXTCACHE]); _disableConsoleLogin = bool.Parse(properties[SFSessionProperty.DISABLE_CONSOLE_LOGIN]); ValidateApplicationName(properties); @@ -156,8 +152,8 @@ internal SFSession( var httpClientConfig = extractedProperties.BuildHttpClientConfig(); ParameterMap = extractedProperties.ToParameterMap(); InsecureMode = extractedProperties.insecureMode; - _HttpClient = HttpUtil.Instance.GetHttpClient(httpClientConfig); - restRequester = new RestRequester(_HttpClient); + _httpClient = HttpUtil.Instance.GetHttpClient(httpClientConfig); + restRequester = new RestRequester(_httpClient); extractedProperties.CheckPropertiesAreValid(); connectionTimeout = extractedProperties.TimeoutDuration(); properties.TryGetValue(SFSessionProperty.CLIENT_CONFIG_FILE, out var easyLoggingConfigFile); @@ -191,7 +187,7 @@ private void ValidateApplicationName(SFSessionProperties properties) internal SFSession(String connectionString, SecureString password, IMockRestRequester restRequester) : this(connectionString, password) { // Inject the HttpClient to use with the Mock requester - restRequester.setHttpClient(_HttpClient); + restRequester.setHttpClient(_httpClient); // Override the Rest requester with the mock for testing this.restRequester = restRequester; } @@ -240,7 +236,7 @@ internal async Task OpenAsync(CancellationToken cancellationToken) await authenticator.AuthenticateAsync(cancellationToken).ConfigureAwait(false); } - internal void close() + internal void Close() { // Nothing to do if the session is not open if (!IsEstablished()) return; @@ -458,7 +454,7 @@ internal void UpdateDatabaseAndSchema(string databaseName, string schemaName) internal void startHeartBeatForThisSession() { - if (!this.isHeartBeatEnabled) + if (!this._isHeartBeatEnabled) { HeartBeatBackground heartBeatBg = HeartBeatBackground.Instance; if (this.masterValidityInSeconds == 0) @@ -468,16 +464,16 @@ internal void startHeartBeatForThisSession() this.masterValidityInSeconds = DEFAULT_TIMEOUT_IN_SECOND; } heartBeatBg.addConnection(this, this.masterValidityInSeconds); - this.isHeartBeatEnabled = true; + this._isHeartBeatEnabled = true; } } internal void stopHeartBeatForThisSession() { - if (this.isHeartBeatEnabled) + if (this._isHeartBeatEnabled) { HeartBeatBackground heartBeatBg = HeartBeatBackground.Instance; heartBeatBg.removeConnection(this); - this.isHeartBeatEnabled = false; + this._isHeartBeatEnabled = false; } } diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index 080309589..4beff0a36 100644 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -128,11 +128,11 @@ public override bool Equals(object obj) SFSessionProperties prop = (SFSessionProperties)obj; foreach (SFSessionProperty sessionProperty in Enum.GetValues(typeof(SFSessionProperty))) { - if (this.ContainsKey(sessionProperty) ^ prop.ContainsKey(sessionProperty)) + if (ContainsKey(sessionProperty) ^ prop.ContainsKey(sessionProperty)) { return false; } - if (!this.ContainsKey(sessionProperty)) + if (!ContainsKey(sessionProperty)) { continue; } @@ -155,7 +155,7 @@ public override int GetHashCode() return base.GetHashCode(); } - internal static SFSessionProperties parseConnectionString(String connectionString, SecureString password) + internal static SFSessionProperties ParseConnectionString(string connectionString, SecureString password) { logger.Info("Start parsing connection string."); DbConnectionStringBuilder builder = new DbConnectionStringBuilder(); @@ -170,10 +170,10 @@ internal static SFSessionProperties parseConnectionString(String connectionStrin SFError.INVALID_CONNECTION_STRING, e.Message); } - SFSessionProperties properties = new SFSessionProperties(); + var properties = new SFSessionProperties(); - string[] keys = new string[builder.Keys.Count]; - string[] values = new string[builder.Values.Count]; + var keys = new string[builder.Keys.Count]; + var values = new string[builder.Values.Count]; builder.Keys.CopyTo(keys, 0); builder.Values.CopyTo(values,0); diff --git a/Snowflake.Data/Core/Session/SessionPool.cs b/Snowflake.Data/Core/Session/SessionPool.cs index 14ad70848..643e01c93 100644 --- a/Snowflake.Data/Core/Session/SessionPool.cs +++ b/Snowflake.Data/Core/Session/SessionPool.cs @@ -57,7 +57,7 @@ private void CleanExpiredSessions() { if (item.IsExpired(_timeout, timeNow)) { - Task.Run(() => item.close()); + Task.Run(() => item.Close()); _sessions.Remove(item); } } @@ -89,14 +89,14 @@ private SFSession GetIdleSession(string connStr) { for (int i = 0; i < _sessions.Count; i++) { - if (_sessions[i].connStr.Equals(connStr)) + if (_sessions[i].ConnStr.Equals(connStr)) { SFSession session = _sessions[i]; _sessions.RemoveAt(i); long timeNow = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (session.IsExpired(_timeout, timeNow)) { - Task.Run(() => session.close()); + Task.Run(() => session.Close()); i--; } else @@ -187,7 +187,7 @@ internal void ClearAllPools() { foreach (SFSession session in _sessions) { - session.close(); // it is left synchronously here because too much async tasks slows down testing + session.Close(); // it is left synchronously here because too much async tasks slows down testing } _sessions.Clear(); } From 8a1727ac7b7b088134f0c0ece7a68f6b2412db32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Smyka=C5=82a?= Date: Wed, 31 Jan 2024 21:28:39 +0100 Subject: [PATCH 5/7] cleaned up --- .../Authenticator/AuthenticatorFactory.cs | 82 +++++++++ .../Core/Authenticator/BaseAuthenticator.cs | 92 ++++++++++ .../Core/Authenticator/IAuthenticator.cs | 166 +----------------- .../Authenticator/Okta/OktaAuthenticator.cs | 73 ++------ 4 files changed, 185 insertions(+), 228 deletions(-) create mode 100644 Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs create mode 100644 Snowflake.Data/Core/Authenticator/BaseAuthenticator.cs diff --git a/Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs b/Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs new file mode 100644 index 000000000..8144f5670 --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs @@ -0,0 +1,82 @@ +using System; +using Snowflake.Data.Client; +using Snowflake.Data.Core.Authenticator.Okta; +using Snowflake.Data.Core.Session; +using Snowflake.Data.Log; + +namespace Snowflake.Data.Core.Authenticator +{ + /// + /// Authenticator Factory to build authenticators + /// + internal class AuthenticatorFactory + { + private static readonly SFLogger logger = SFLoggerFactory.GetLogger(); + /// + /// Generate the authenticator given the session + /// + /// session that requires the authentication + /// authenticator + /// when authenticator is unknown + internal static IAuthenticator GetAuthenticator(SFSession session) + { + string type = session.properties[SFSessionProperty.AUTHENTICATOR]; + if (type.Equals(BasicAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + { + return new BasicAuthenticator(session); + } + + if (type.Equals(ExternalBrowserAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + { + return new ExternalBrowserAuthenticator(session); + } + + if (type.Equals(KeyPairAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + { + // Get private key path or private key from connection settings + if (!session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_FILE, out var pkPath) && + !session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY, out var pkContent)) + { + // There is no PRIVATE_KEY_FILE defined, can't authenticate with key-pair + string invalidStringDetail = + "Missing required PRIVATE_KEY_FILE or PRIVATE_KEY for key pair authentication"; + var error = new SnowflakeDbException( + SFError.INVALID_CONNECTION_STRING, + new object[] { invalidStringDetail }); + logger.Error(error.Message, error); + throw error; + } + + return new KeyPairAuthenticator(session); + } + if (type.Equals(OAuthAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + { + // Get private key path or private key from connection settings + if (!session.properties.TryGetValue(SFSessionProperty.TOKEN, out var pkPath)) + { + // There is no TOKEN defined, can't authenticate with oauth + string invalidStringDetail = + "Missing required TOKEN for Oauth authentication"; + var error = new SnowflakeDbException( + SFError.INVALID_CONNECTION_STRING, + new object[] { invalidStringDetail }); + logger.Error(error.Message, error); + throw error; + } + + return new OAuthAuthenticator(session); + } + // Okta would provide a url of form: https://xxxxxx.okta.com or https://xxxxxx.oktapreview.com or https://vanity.url/snowflake/okta + if (type.Contains("okta") && type.StartsWith("https://")) + { + return new OktaAuthenticator(session, type, new SamlRestRequestFactory(), new IdpTokenRestRequestFactory()); + } + + var e = new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type); + + logger.Error("Unknown authenticator", e); + + throw e; + } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/BaseAuthenticator.cs b/Snowflake.Data/Core/Authenticator/BaseAuthenticator.cs new file mode 100644 index 000000000..4ce006462 --- /dev/null +++ b/Snowflake.Data/Core/Authenticator/BaseAuthenticator.cs @@ -0,0 +1,92 @@ +using System.Threading; +using System.Threading.Tasks; +using Snowflake.Data.Core.Session; + +namespace Snowflake.Data.Core.Authenticator +{ + /// + /// A base implementation for all authenticators to create and send a login request. + /// + internal abstract class BaseAuthenticator + { + // The name of the authenticator. + private readonly string _authName; + + // The session which created this authenticator. + protected SFSession Session; + + // The client environment properties + private readonly LoginRequestClientEnv _clientEnv = SFEnvironment.ClientEnv; + + /// + /// The abstract base for all authenticators. + /// + /// The session which created the authenticator. + protected BaseAuthenticator(SFSession session, string authName) + { + this.Session = session; + this._authName = authName; + // Update the value for insecureMode because it can be different for each session + _clientEnv.insecureMode = session.properties[SFSessionProperty.INSECUREMODE]; + if (session.properties.TryGetValue(SFSessionProperty.APPLICATION, out var applicationName)) + { + // If an application name has been specified in the connection setting, use it + // Otherwise, it will default to the running process name + _clientEnv.application = applicationName; + } + } + + //// + protected async Task LoginAsync(CancellationToken cancellationToken) + { + var loginRequest = BuildLoginRequest(); + + var response = await Session.restRequester.PostAsync(loginRequest, cancellationToken).ConfigureAwait(false); + + Session.ProcessLoginResponse(response); + } + + /// + protected void Login() + { + var loginRequest = BuildLoginRequest(); + + var response = Session.restRequester.Post(loginRequest); + + Session.ProcessLoginResponse(response); + } + + /// + /// Specialized authenticator data to add to the login request. + /// + /// The login request data to update. + protected abstract void SetSpecializedAuthenticatorData(ref LoginRequestData data); + + /// + /// Builds a simple login request. Each authenticator will fill the Data part with their + /// specialized information. The common Data attributes are already filled (clientAppId, + /// ClienAppVersion...). + /// + /// A login request to send to the server. + private SFRestRequest BuildLoginRequest() + { + // build uri + var loginUrl = Session.BuildLoginUrl(); + + var data = new LoginRequestData() + { + loginName = Session.properties[SFSessionProperty.USER], + accountName = Session.properties[SFSessionProperty.ACCOUNT], + clientAppId = SFEnvironment.DriverName, + clientAppVersion = SFEnvironment.DriverVersion, + clientEnv = _clientEnv, + SessionParameters = Session.ParameterMap, + Authenticator = _authName, + }; + + SetSpecializedAuthenticatorData(ref data); + + return Session.BuildTimeoutRestRequest(loginUrl, new LoginRequest() { data = data }); + } + } +} \ No newline at end of file diff --git a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs index 18d9e7306..74f4fae2e 100644 --- a/Snowflake.Data/Core/Authenticator/IAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/IAuthenticator.cs @@ -2,13 +2,9 @@ * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ -using System; using System.Threading; using System.Threading.Tasks; using Snowflake.Data.Client; -using Snowflake.Data.Core.Authenticator.Okta; -using Snowflake.Data.Core.Session; -using Snowflake.Data.Log; namespace Snowflake.Data.Core.Authenticator { @@ -19,7 +15,7 @@ namespace Snowflake.Data.Core.Authenticator internal interface IAuthenticator { /// - /// Process the authentication asynchronouly + /// Process the authentication asynchronously /// /// /// @@ -32,164 +28,4 @@ internal interface IAuthenticator /// void Authenticate(); } - - /// - /// A base implementation for all authenticators to create and send a login request. - /// - internal abstract class BaseAuthenticator - { - // The name of the authenticator. - private readonly string _authName; - - // The session which created this authenticator. - protected SFSession Session; - - // The client environment properties - private readonly LoginRequestClientEnv _clientEnv = SFEnvironment.ClientEnv; - - /// - /// The abstract base for all authenticators. - /// - /// The session which created the authenticator. - protected BaseAuthenticator(SFSession session, string authName) - { - this.Session = session; - this._authName = authName; - // Update the value for insecureMode because it can be different for each session - _clientEnv.insecureMode = session.properties[SFSessionProperty.INSECUREMODE]; - if (session.properties.TryGetValue(SFSessionProperty.APPLICATION, out var applicationName)) - { - // If an application name has been specified in the connection setting, use it - // Otherwise, it will default to the running process name - _clientEnv.application = applicationName; - } - } - - //// - protected async Task LoginAsync(CancellationToken cancellationToken) - { - var loginRequest = BuildLoginRequest(); - - var response = await Session.restRequester.PostAsync(loginRequest, cancellationToken).ConfigureAwait(false); - - Session.ProcessLoginResponse(response); - } - - /// - protected void Login() - { - var loginRequest = BuildLoginRequest(); - - var response = Session.restRequester.Post(loginRequest); - - Session.ProcessLoginResponse(response); - } - - /// - /// Specialized authenticator data to add to the login request. - /// - /// The login request data to update. - protected abstract void SetSpecializedAuthenticatorData(ref LoginRequestData data); - - /// - /// Builds a simple login request. Each authenticator will fill the Data part with their - /// specialized information. The common Data attributes are already filled (clientAppId, - /// ClienAppVersion...). - /// - /// A login request to send to the server. - private SFRestRequest BuildLoginRequest() - { - // build uri - var loginUrl = Session.BuildLoginUrl(); - - var data = new LoginRequestData() - { - loginName = Session.properties[SFSessionProperty.USER], - accountName = Session.properties[SFSessionProperty.ACCOUNT], - clientAppId = SFEnvironment.DriverName, - clientAppVersion = SFEnvironment.DriverVersion, - clientEnv = _clientEnv, - SessionParameters = Session.ParameterMap, - Authenticator = _authName, - }; - - SetSpecializedAuthenticatorData(ref data); - - return Session.BuildTimeoutRestRequest(loginUrl, new LoginRequest() { data = data }); - } - } - - /// - /// Authenticator Factory to build authenticators - /// - internal class AuthenticatorFactory - { - private static readonly SFLogger logger = SFLoggerFactory.GetLogger(); - /// - /// Generate the authenticator given the session - /// - /// session that requires the authentication - /// authenticator - /// when authenticator is unknown - internal static IAuthenticator GetAuthenticator(SFSession session) - { - string type = session.properties[SFSessionProperty.AUTHENTICATOR]; - if (type.Equals(BasicAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) - { - return new BasicAuthenticator(session); - } - - if (type.Equals(ExternalBrowserAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) - { - return new ExternalBrowserAuthenticator(session); - } - - if (type.Equals(KeyPairAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) - { - // Get private key path or private key from connection settings - if (!session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY_FILE, out var pkPath) && - !session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY, out var pkContent)) - { - // There is no PRIVATE_KEY_FILE defined, can't authenticate with key-pair - string invalidStringDetail = - "Missing required PRIVATE_KEY_FILE or PRIVATE_KEY for key pair authentication"; - var error = new SnowflakeDbException( - SFError.INVALID_CONNECTION_STRING, - new object[] { invalidStringDetail }); - logger.Error(error.Message, error); - throw error; - } - - return new KeyPairAuthenticator(session); - } - if (type.Equals(OAuthAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) - { - // Get private key path or private key from connection settings - if (!session.properties.TryGetValue(SFSessionProperty.TOKEN, out var pkPath)) - { - // There is no TOKEN defined, can't authenticate with oauth - string invalidStringDetail = - "Missing required TOKEN for Oauth authentication"; - var error = new SnowflakeDbException( - SFError.INVALID_CONNECTION_STRING, - new object[] { invalidStringDetail }); - logger.Error(error.Message, error); - throw error; - } - - return new OAuthAuthenticator(session); - } - // Okta would provide a url of form: https://xxxxxx.okta.com or https://xxxxxx.oktapreview.com or https://vanity.url/snowflake/okta - if (type.Contains("okta") && type.StartsWith("https://")) - { - return new OktaAuthenticator(session, type, new SamlRestRequestFactory()); - } - - var e = new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type); - - logger.Error("Unknown authenticator", e); - - throw e; - } - } } diff --git a/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs index 43e7ed9a3..9185f1806 100644 --- a/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs @@ -19,7 +19,8 @@ namespace Snowflake.Data.Core.Authenticator.Okta internal class OktaAuthenticator : BaseAuthenticator, IAuthenticator { - private ISamlRestRequestFactory _samlRestRequestFactory; + private readonly ISamlRestRequestFactory _samlRestRequestFactory; + private readonly IIdpTokenRestRequestFactory _idpTokenRestRequestFactory; private static readonly SFLogger _logger = SFLoggerFactory.GetLogger(); /// @@ -35,15 +36,18 @@ internal class OktaAuthenticator : BaseAuthenticator, IAuthenticator /// /// /// - internal OktaAuthenticator(SFSession session, string oktaUriString, ISamlRestRequestFactory samlRestRequestFactory) : + /// + /// + internal OktaAuthenticator(SFSession session, string oktaUriString, ISamlRestRequestFactory samlRestRequestFactory, IIdpTokenRestRequestFactory idpTokenRestRequestFactory) : base(session, oktaUriString) { _samlRestRequestFactory = samlRestRequestFactory; + _idpTokenRestRequestFactory = idpTokenRestRequestFactory; _oktaUrl = new Uri(oktaUriString); } /// - async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) + public async Task AuthenticateAsync(CancellationToken cancellationToken) { _logger.Info("Okta Authentication"); @@ -61,12 +65,12 @@ async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) VerifyUrls(tokenUrl, _oktaUrl); _logger.Debug("step 3: get idp onetime token"); - var idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); + var idpTokenRestRequest = _idpTokenRestRequestFactory.Create(tokenUrl, Session); var idpResponse = await Session.restRequester.PostAsync(idpTokenRestRequest, cancellationToken); var onetimeToken = idpResponse.SessionToken ?? idpResponse.CookieToken; _logger.Debug("step 4: get SAML response from sso"); - var samlRestRequest = BuildSamlRestRequest(ssoUrl, onetimeToken); + var samlRestRequest = _samlRestRequestFactory.Create(ssoUrl, onetimeToken, Session.connectionTimeout); using (var samlRawResponse = await Session.restRequester.GetAsync(samlRestRequest, cancellationToken)) { _samlRawHtmlString = await samlRawResponse.Content.ReadAsStringAsync(cancellationToken); @@ -81,38 +85,7 @@ async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) void IAuthenticator.Authenticate() { - _logger.Info("Okta Authentication"); - - _logger.Debug("step 1: get sso and token url"); - var authenticatorRestRequest = BuildAuthenticatorRestRequest(); - var authenticatorResponse = Session.restRequester.Post(authenticatorRestRequest); - authenticatorResponse.FilterFailedResponse(); - var ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); - var tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); - - _logger.Debug("step 2: verify urls fetched from step 1"); - _logger.Debug("Checking sso url"); - VerifyUrls(ssoUrl, _oktaUrl); - _logger.Debug("Checking token url"); - VerifyUrls(tokenUrl, _oktaUrl); - - _logger.Debug("step 3: get idp onetime token"); - var idpTokenRestRequest = BuildIdpTokenRestRequest(tokenUrl); - var idpResponse = Session.restRequester.Post(idpTokenRestRequest); - var onetimeToken = idpResponse.SessionToken != null ? idpResponse.SessionToken : idpResponse.CookieToken; - - _logger.Debug("step 4: get SAML response from sso"); - var samlRestRequest = BuildSamlRestRequest(ssoUrl, onetimeToken); - using (var samlRawResponse = Session.restRequester.Get(samlRestRequest)) - { - _samlRawHtmlString = Task.Run(async () => await samlRawResponse.Content.ReadAsStringAsync().ConfigureAwait(false)).Result; - } - - _logger.Debug("step 5: verify postback url in SAML response"); - VerifyPostbackUrl(); - - _logger.Debug("step 6: send SAML response to snowflake to login"); - base.Login(); + AuthenticateAsync(CancellationToken.None).Wait(); } private SFRestRequest BuildAuthenticatorRestRequest() @@ -129,32 +102,6 @@ private SFRestRequest BuildAuthenticatorRestRequest() return Session.BuildTimeoutRestRequest(fedUrl, new AuthenticatorRequest() { Data = data }); } - private IdpTokenRestRequest BuildIdpTokenRestRequest(Uri tokenUrl) - { - return new IdpTokenRestRequest() - { - Url = tokenUrl, - RestTimeout = Session.connectionTimeout, - HttpTimeout = TimeSpan.FromSeconds(16), - JsonBody = new IdpTokenRequest() - { - Username = Session.properties[SFSessionProperty.USER], - Password = Session.properties[SFSessionProperty.PASSWORD], - }, - }; - } - - private SamlRestRequest BuildSamlRestRequest(Uri ssoUrl, string onetimeToken) - { - return new SamlRestRequest() - { - Url = ssoUrl, - RestTimeout = Session.connectionTimeout, - HttpTimeout = Timeout.InfiniteTimeSpan, - OnetimeToken = onetimeToken, - }; - } - /// protected override void SetSpecializedAuthenticatorData(ref LoginRequestData data) { From 9c5c03bacdd5725f786150ebb83a10f2c41eff29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Smyka=C5=82a?= Date: Wed, 31 Jan 2024 21:45:19 +0100 Subject: [PATCH 6/7] reverted SetUpFixture --- Snowflake.Data.Tests/SFBaseTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Snowflake.Data.Tests/SFBaseTest.cs b/Snowflake.Data.Tests/SFBaseTest.cs index 1a8455c66..01ae94501 100755 --- a/Snowflake.Data.Tests/SFBaseTest.cs +++ b/Snowflake.Data.Tests/SFBaseTest.cs @@ -148,7 +148,7 @@ public SFBaseTestAsync() protected TestConfig testConfig { get; } } - // [SetUpFixture] + [SetUpFixture] public class TestEnvironment { private const string ConnectionStringFmt = "scheme={0};host={1};port={2};" + From 4a838d86e317bdde5c121a561f49ab2d415858d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Smyka=C5=82a?= Date: Wed, 31 Jan 2024 21:53:15 +0100 Subject: [PATCH 7/7] applied coding conventions --- .../UnitTests/SFAuthenticatorFactoryTest.cs | 2 +- .../Authenticator/AuthenticatorFactory.cs | 14 +++++----- .../Core/Authenticator/BasicAuthenticator.cs | 10 +++---- .../Authenticator/Okta/OktaAuthenticator.cs | 27 +++++++++---------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs b/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs index 1b213f7d4..9e990b236 100644 --- a/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFAuthenticatorFactoryTest.cs @@ -28,7 +28,7 @@ private IAuthenticator GetAuthenticator(string authenticatorName, string extraPa [Test] public void TestGetAuthenticatorBasic() { - _authenticator = GetAuthenticator(BasicAuthenticator.AUTH_NAME); + _authenticator = GetAuthenticator(BasicAuthenticator.AuthName); Assert.IsInstanceOf(_authenticator); } diff --git a/Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs b/Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs index 8144f5670..779b7ea96 100644 --- a/Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs +++ b/Snowflake.Data/Core/Authenticator/AuthenticatorFactory.cs @@ -9,9 +9,9 @@ namespace Snowflake.Data.Core.Authenticator /// /// Authenticator Factory to build authenticators /// - internal class AuthenticatorFactory + internal sealed class AuthenticatorFactory { - private static readonly SFLogger logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); /// /// Generate the authenticator given the session /// @@ -21,7 +21,7 @@ internal class AuthenticatorFactory internal static IAuthenticator GetAuthenticator(SFSession session) { string type = session.properties[SFSessionProperty.AUTHENTICATOR]; - if (type.Equals(BasicAuthenticator.AUTH_NAME, StringComparison.InvariantCultureIgnoreCase)) + if (type.Equals(BasicAuthenticator.AuthName, StringComparison.InvariantCultureIgnoreCase)) { return new BasicAuthenticator(session); } @@ -38,12 +38,12 @@ internal static IAuthenticator GetAuthenticator(SFSession session) !session.properties.TryGetValue(SFSessionProperty.PRIVATE_KEY, out var pkContent)) { // There is no PRIVATE_KEY_FILE defined, can't authenticate with key-pair - string invalidStringDetail = + var invalidStringDetail = "Missing required PRIVATE_KEY_FILE or PRIVATE_KEY for key pair authentication"; var error = new SnowflakeDbException( SFError.INVALID_CONNECTION_STRING, new object[] { invalidStringDetail }); - logger.Error(error.Message, error); + s_logger.Error(error.Message, error); throw error; } @@ -60,7 +60,7 @@ internal static IAuthenticator GetAuthenticator(SFSession session) var error = new SnowflakeDbException( SFError.INVALID_CONNECTION_STRING, new object[] { invalidStringDetail }); - logger.Error(error.Message, error); + s_logger.Error(error.Message, error); throw error; } @@ -74,7 +74,7 @@ internal static IAuthenticator GetAuthenticator(SFSession session) var e = new SnowflakeDbException(SFError.UNKNOWN_AUTHENTICATOR, type); - logger.Error("Unknown authenticator", e); + s_logger.Error("Unknown authenticator", e); throw e; } diff --git a/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs b/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs index 4e7d7d8ea..edd639e00 100644 --- a/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/BasicAuthenticator.cs @@ -11,23 +11,23 @@ namespace Snowflake.Data.Core.Authenticator { class BasicAuthenticator : BaseAuthenticator, IAuthenticator { - public static readonly string AUTH_NAME = "snowflake"; - private static readonly SFLogger logger = SFLoggerFactory.GetLogger(); + public const string AuthName = "snowflake"; + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); - internal BasicAuthenticator(SFSession session) : base(session, AUTH_NAME) + internal BasicAuthenticator(SFSession session) : base(session, AuthName) { } /// async Task IAuthenticator.AuthenticateAsync(CancellationToken cancellationToken) { - await base.LoginAsync(cancellationToken); + await LoginAsync(cancellationToken); } /// void IAuthenticator.Authenticate() { - base.Login(); + Login(); } /// diff --git a/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs index 9185f1806..5c85376a9 100644 --- a/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/Okta/OktaAuthenticator.cs @@ -18,10 +18,9 @@ namespace Snowflake.Data.Core.Authenticator.Okta /// internal class OktaAuthenticator : BaseAuthenticator, IAuthenticator { - private readonly ISamlRestRequestFactory _samlRestRequestFactory; private readonly IIdpTokenRestRequestFactory _idpTokenRestRequestFactory; - private static readonly SFLogger _logger = SFLoggerFactory.GetLogger(); + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); /// /// url of the okta idp @@ -49,37 +48,37 @@ internal OktaAuthenticator(SFSession session, string oktaUriString, ISamlRestReq /// public async Task AuthenticateAsync(CancellationToken cancellationToken) { - _logger.Info("Okta Authentication"); + s_logger.Info("Okta Authentication"); - _logger.Debug("step 1: get sso and token url"); + s_logger.Debug("step 1: get sso and token url"); var authenticatorRestRequest = BuildAuthenticatorRestRequest(); var authenticatorResponse = await Session.restRequester.PostAsync(authenticatorRestRequest, cancellationToken); authenticatorResponse.FilterFailedResponse(); var ssoUrl = new Uri(authenticatorResponse.data.ssoUrl); var tokenUrl = new Uri(authenticatorResponse.data.tokenUrl); - _logger.Debug("step 2: verify urls fetched from step 1"); - _logger.Debug("Checking sso url"); + s_logger.Debug("step 2: verify urls fetched from step 1"); + s_logger.Debug("Checking sso url"); VerifyUrls(ssoUrl, _oktaUrl); - _logger.Debug("Checking token url"); + s_logger.Debug("Checking token url"); VerifyUrls(tokenUrl, _oktaUrl); - _logger.Debug("step 3: get idp onetime token"); + s_logger.Debug("step 3: get idp onetime token"); var idpTokenRestRequest = _idpTokenRestRequestFactory.Create(tokenUrl, Session); var idpResponse = await Session.restRequester.PostAsync(idpTokenRestRequest, cancellationToken); var onetimeToken = idpResponse.SessionToken ?? idpResponse.CookieToken; - _logger.Debug("step 4: get SAML response from sso"); + s_logger.Debug("step 4: get SAML response from sso"); var samlRestRequest = _samlRestRequestFactory.Create(ssoUrl, onetimeToken, Session.connectionTimeout); using (var samlRawResponse = await Session.restRequester.GetAsync(samlRestRequest, cancellationToken)) { _samlRawHtmlString = await samlRawResponse.Content.ReadAsStringAsync(cancellationToken); } - _logger.Debug("step 5: verify postback url in SAML response"); + s_logger.Debug("step 5: verify postback url in SAML response"); VerifyPostbackUrl(); - _logger.Debug("step 6: send SAML response to snowflake to login"); + s_logger.Debug("step 6: send SAML response to snowflake to login"); await LoginAsync(cancellationToken); } @@ -114,7 +113,7 @@ private void VerifyUrls(Uri tokenOrSsoUrl, Uri sessionUrl) { var e = new SnowflakeDbException( SFError.IDP_SSO_TOKEN_URL_MISMATCH, tokenOrSsoUrl.ToString(), _oktaUrl.ToString()); - _logger.Error("Different urls", e); + s_logger.Error("Different urls", e); throw e; } } @@ -132,7 +131,7 @@ private void VerifyPostbackUrl() postBackUrl = new Uri(HttpUtility.HtmlDecode(_samlRawHtmlString.Substring(startIndex, length))); } catch (Exception e) { - _logger.Error("Fail to extract SAML from html", e); + s_logger.Error("Fail to extract SAML from html", e); throw new SnowflakeDbException(e, SFError.IDP_SAML_POSTBACK_NOTFOUND); } @@ -145,7 +144,7 @@ private void VerifyPostbackUrl() SFError.IDP_SAML_POSTBACK_INVALID, postBackUrl.ToString(), sessionScheme + ":\\\\" + sessionHost); - _logger.Error("Different urls", e); + s_logger.Error("Different urls", e); throw e; } }