Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-715504: MFA token cache support #988

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8f9b360
Squashed commit of the following:
sfc-gh-jmartinezramirez Jul 22, 2024
1b7caa1
SNOW-1490901 Passcode support for mfa authentication
sfc-gh-knozderko Jun 19, 2024
10a901f
Added implementation for MFA token cache base on changes for sso toke…
sfc-gh-jmartinezramirez Jun 26, 2024
31d77f1
Implementing test for new MFA token cache (In progress)
sfc-gh-jmartinezramirez Jun 26, 2024
d3b464a
temp workaround for login request with appid and app version
sfc-gh-jmartinezramirez Jun 26, 2024
4a92c08
Added mechanism to handle connection pooling when using username_pass…
sfc-gh-jmartinezramirez Jul 4, 2024
48b1e63
Added hash encode for credential manager keys using sha256
sfc-gh-jmartinezramirez Jul 4, 2024
3990373
Changed passcode to be an optional argument in ParseConnectionString
sfc-gh-jmartinezramirez Jul 4, 2024
751369a
Changed passcode to be an optional argument in connection and session…
sfc-gh-jmartinezramirez Jul 4, 2024
332f0de
Remove changes related to sso token cache implementation
sfc-gh-jmartinezramirez Jul 4, 2024
b68a3f9
Applying suggestions
sfc-gh-jmartinezramirez Jul 5, 2024
82a8e34
Removed additional sso token cache code related
sfc-gh-jmartinezramirez Jul 5, 2024
e0f87be
Fix testing
sfc-gh-jmartinezramirez Jul 8, 2024
2bdd747
Comment out workaround to test MFA
sfc-gh-jmartinezramirez Jul 8, 2024
ae23407
Additional fixes for test
sfc-gh-jmartinezramirez Jul 9, 2024
155516f
Improve logic to validate if passcode is used with mfaAuthenticator,
sfc-gh-jmartinezramirez Jul 9, 2024
2aed351
Fixed ambiguous constructor issue in mock
sfc-gh-jmartinezramirez Jul 9, 2024
9fc91d6
Fixed mismatch credential exception message
sfc-gh-jmartinezramirez Jul 9, 2024
0d4d055
Change validation process for session pool, if using passcode in conn…
sfc-gh-jmartinezramirez Jul 11, 2024
5852597
Applying PR suggestions
sfc-gh-jmartinezramirez Jul 29, 2024
b8b9790
some refactor
sfc-gh-knozderko Sep 3, 2024
e048d23
Applying PR suggestions
sfc-gh-jmartinezramirez Sep 3, 2024
795f578
Fixed test for mfa
sfc-gh-jmartinezramirez Sep 4, 2024
07eb444
Fix connection pool renamed method.
sfc-gh-jmartinezramirez Oct 4, 2024
d0240f3
Applying PR suggestions
sfc-gh-jmartinezramirez Oct 9, 2024
d87f1cb
Applying new PR suggestions
sfc-gh-jmartinezramirez Oct 17, 2024
d6a884b
Added additional errors that could be thrown when the cached MFA toke…
sfc-gh-jmartinezramirez Oct 19, 2024
2e5b088
Applying PR suggestions
sfc-gh-jmartinezramirez Oct 21, 2024
14d2ea0
Added property to LoginRequestData to specify httpRequest timeout.
sfc-gh-jmartinezramirez Oct 22, 2024
78301da
Apply suggestion to SnowflakeCredentialManagerFactory and other addit…
sfc-gh-jmartinezramirez Oct 24, 2024
3d90b4d
Additional comments
sfc-gh-jmartinezramirez Oct 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 58 additions & 12 deletions Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
* Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
*/

using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Snowflake.Data.Client;
using Snowflake.Data.Core;
using Snowflake.Data.Core.Session;
using Snowflake.Data.Core.Tools;
using Snowflake.Data.Log;
using Snowflake.Data.Tests.Mock;
using Snowflake.Data.Tests.Util;

namespace Snowflake.Data.Tests.IntegrationTests
{
using NUnit.Framework;
using Snowflake.Data.Client;
using System.Data;
using System;
using Snowflake.Data.Core;
using System.Threading.Tasks;
using System.Threading;
using Snowflake.Data.Log;
using System.Diagnostics;
using Snowflake.Data.Tests.Mock;
using System.Runtime.InteropServices;
using System.Net.Http;

[TestFixture]
class SFConnectionIT : SFBaseTest
Expand Down Expand Up @@ -2272,6 +2272,52 @@ public void TestUseMultiplePoolsConnectionPoolByDefault()
Assert.AreEqual(ConnectionPoolType.MultipleConnectionPool, poolVersion);
}

[Test]
// [Ignore("This test requires manual interaction and therefore cannot be run in CI")]
public void TestMFATokenCachingWithPasscodeFromConnectionString()
{
// Use a connection with MFA enabled and set passcode property for mfa authentication. e.g. ConnectionString + ";authenticator=username_password_mfa;passcode=(set proper passcode)"
// ACCOUNT PARAMETER ALLOW_CLIENT_MFA_CACHING should be set to true in the account.
// On Mac/Linux OS default credential manager is in memory so please uncomment following line to use file based credential manager
// SnowflakeCredentialManagerFactory.UseFileCredentialManager();
using (SnowflakeDbConnection conn = new SnowflakeDbConnection())
{
conn.ConnectionString
= ConnectionString
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
+ ";authenticator=username_password_mfa;application=DuoTest;minPoolSize=0;passcode=(set proper passcode)";


// Authenticate to retrieve and store the token if doesn't exist or invalid
Task connectTask = conn.OpenAsync(CancellationToken.None);
connectTask.Wait();
Assert.AreEqual(ConnectionState.Open, conn.State);
}
}

[Test]
[Ignore("Requires manual steps and environment with mfa authentication enrolled")] // to enroll to mfa authentication edit your user profile
public void TestMfaWithPasswordConnectionUsingPasscodeWithSecureString()
{
// Use a connection with MFA enabled and Passcode property on connection instance.
// ACCOUNT PARAMETER ALLOW_CLIENT_MFA_CACHING should be set to true in the account.
// On Mac/Linux OS default credential manager is in memory so please uncomment following line to use file based credential manager
// SnowflakeCredentialManagerFactory.UseFileCredentialManager();
// arrange
using (SnowflakeDbConnection conn = new SnowflakeDbConnection())
{
conn.Passcode = SecureStringHelper.Encode("$(set proper passcode)");
// manual action: stop here in breakpoint to provide proper passcode by: conn.Passcode = SecureStringHelper.Encode("...");
conn.ConnectionString = ConnectionString + "minPoolSize=2;application=DuoTest;";

// act
Task connectTask = conn.OpenAsync(CancellationToken.None);
connectTask.Wait();

// assert
Assert.AreEqual(ConnectionState.Open, conn.State);
}
}

[Test]
[TestCase("connection_timeout=5;")]
[TestCase("")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Snowflake.Data.Core;

namespace Snowflake.Data.Tests.Mock
{
using Microsoft.IdentityModel.Tokens;

class MockLoginMFATokenCacheRestRequester: IMockRestRequester
{
internal Queue<LoginRequest> LoginRequests { get; } = new();

internal Queue<LoginResponseData> LoginResponses { get; } = new();

public T Get<T>(IRestRequest request)
{
return Task.Run(async () => await (GetAsync<T>(request, CancellationToken.None)).ConfigureAwait(false)).Result;
}

public Task<T> GetAsync<T>(IRestRequest request, CancellationToken cancellationToken)
{
return Task.FromResult<T>((T)(object)null);
}

public Task<HttpResponseMessage> GetAsync(IRestRequest request, CancellationToken cancellationToken)
{
return Task.FromResult<HttpResponseMessage>(null);
}

public HttpResponseMessage Get(IRestRequest request)
{
return null;
}

public T Post<T>(IRestRequest postRequest)
{
return Task.Run(async () => await (PostAsync<T>(postRequest, CancellationToken.None)).ConfigureAwait(false)).Result;
}

public Task<T> PostAsync<T>(IRestRequest postRequest, CancellationToken cancellationToken)
{
SFRestRequest sfRequest = (SFRestRequest)postRequest;
if (sfRequest.jsonBody is LoginRequest)
{
LoginRequests.Enqueue((LoginRequest) sfRequest.jsonBody);
var responseData = this.LoginResponses.IsNullOrEmpty() ? new LoginResponseData()
{
token = "session_token",
masterToken = "master_token",
authResponseSessionInfo = new SessionInfo(),
nameValueParameter = new List<NameValueParameter>()
} : this.LoginResponses.Dequeue();
var authnResponse = new LoginResponse
{
data = responseData,
success = true
};

// login request return success
return Task.FromResult<T>((T)(object)authnResponse);
}
else if (sfRequest.jsonBody is CloseResponse)
{
var authnResponse = new CloseResponse()
{
success = true
};

// login request return success
return Task.FromResult<T>((T)(object)authnResponse);
}
throw new NotImplementedException();
}

public void setHttpClient(HttpClient httpClient)
{
// Nothing to do
}

public void Reset()
{
LoginRequests.Clear();
LoginResponses.Clear();
}
}
}
6 changes: 3 additions & 3 deletions Snowflake.Data.Tests/Mock/MockSnowflakeDbConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public override Task OpenAsync(CancellationToken cancellationToken)
cancellationToken);

}

private void SetMockSession()
{
SfSession = new SFSession(ConnectionString, Password, _restRequester);
SfSession = new SFSession(ConnectionString, Password, Passcode, EasyLoggingStarter.Instance, _restRequester);

_connectionTimeout = (int)SfSession.connectionTimeout.TotalSeconds;

Expand All @@ -92,7 +92,7 @@ private void OnSessionEstablished()
{
_connectionState = ConnectionState.Open;
}

protected override bool CanReuseSession(TransactionRollbackStatus transactionRollbackStatus)
{
return false;
Expand Down
Loading
Loading