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 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
191bbf0
Squashed commit of the following:
sfc-gh-jmartinezramirez Jul 22, 2024
e01f477
SNOW-1490901 Passcode support for mfa authentication
sfc-gh-knozderko Jun 19, 2024
8e26851
Added implementation for MFA token cache base on changes for sso toke…
sfc-gh-jmartinezramirez Jun 26, 2024
034593b
Implementing test for new MFA token cache (In progress)
sfc-gh-jmartinezramirez Jun 26, 2024
19e6d7c
temp workaround for login request with appid and app version
sfc-gh-jmartinezramirez Jun 26, 2024
cbce1d4
Added mechanism to handle connection pooling when using username_pass…
sfc-gh-jmartinezramirez Jul 4, 2024
ba8dccb
Added hash encode for credential manager keys using sha256
sfc-gh-jmartinezramirez Jul 4, 2024
296b8d9
Changed passcode to be an optional argument in ParseConnectionString
sfc-gh-jmartinezramirez Jul 4, 2024
0665a4f
Changed passcode to be an optional argument in connection and session…
sfc-gh-jmartinezramirez Jul 4, 2024
94573eb
Remove changes related to sso token cache implementation
sfc-gh-jmartinezramirez Jul 4, 2024
2ee3887
Applying suggestions
sfc-gh-jmartinezramirez Jul 5, 2024
852b8c4
Removed additional sso token cache code related
sfc-gh-jmartinezramirez Jul 5, 2024
7480cac
Fix testing
sfc-gh-jmartinezramirez Jul 8, 2024
ec2783f
Comment out workaround to test MFA
sfc-gh-jmartinezramirez Jul 8, 2024
daf5411
Additional fixes for test
sfc-gh-jmartinezramirez Jul 9, 2024
f139cd9
Improve logic to validate if passcode is used with mfaAuthenticator,
sfc-gh-jmartinezramirez Jul 9, 2024
531ad9f
Fixed ambiguous constructor issue in mock
sfc-gh-jmartinezramirez Jul 9, 2024
2abf154
Fixed mismatch credential exception message
sfc-gh-jmartinezramirez Jul 9, 2024
09d4477
Change validation process for session pool, if using passcode in conn…
sfc-gh-jmartinezramirez Jul 11, 2024
d599aad
Applying PR suggestions
sfc-gh-jmartinezramirez Jul 29, 2024
95c7ed0
some refactor
sfc-gh-knozderko Sep 3, 2024
7caf45a
Applying PR suggestions
sfc-gh-jmartinezramirez Sep 3, 2024
d9500bd
Fixed test for mfa
sfc-gh-jmartinezramirez Sep 4, 2024
02bb567
Fix connection pool renamed method.
sfc-gh-jmartinezramirez Oct 4, 2024
dd8d102
Applying PR suggestions
sfc-gh-jmartinezramirez Oct 9, 2024
2ccac09
Applying new PR suggestions
sfc-gh-jmartinezramirez Oct 17, 2024
2925a4a
Added additional errors that could be thrown when the cached MFA toke…
sfc-gh-jmartinezramirez Oct 19, 2024
7a79913
Applying PR suggestions
sfc-gh-jmartinezramirez Oct 21, 2024
a3f0441
Added property to LoginRequestData to specify httpRequest timeout.
sfc-gh-jmartinezramirez Oct 22, 2024
c255f9b
Apply suggestion to SnowflakeCredentialManagerFactory and other addit…
sfc-gh-jmartinezramirez Oct 24, 2024
534f6fb
Additional comments
sfc-gh-jmartinezramirez Oct 25, 2024
cd03870
Using file validator mechanism
sfc-gh-jmartinezramirez Nov 15, 2024
1aae3a1
Removed commented code.
sfc-gh-jmartinezramirez Nov 15, 2024
e542b76
Moved logic to secure credential manager key when created instead of …
sfc-gh-jmartinezramirez Nov 16, 2024
9ae4120
Added lock mechanism when applying credential manager operations.
sfc-gh-jmartinezramirez Nov 20, 2024
c709726
Applying PR suggestions
sfc-gh-jmartinezramirez Nov 27, 2024
efff0c3
Change cache synchronization
sfc-gh-knozderko Dec 3, 2024
f0315b2
Consistent approach to caching with other drivers
sfc-gh-knozderko Dec 6, 2024
5a46335
Change key and file format
sfc-gh-knozderko Dec 6, 2024
5eb2569
Fix file overriding
sfc-gh-knozderko Dec 9, 2024
aba9cf4
remove lock directory if too old
sfc-gh-knozderko Dec 9, 2024
26f52ee
Merge branch 'master' into SNOW-715504-MFA-Token-Cache
sfc-gh-knozderko Dec 9, 2024
bada922
Fix ignored mfa tests
sfc-gh-knozderko Dec 11, 2024
d41d854
Fix test
sfc-gh-knozderko Dec 12, 2024
1865330
trying to fix a test
sfc-gh-knozderko Dec 13, 2024
ef7b2ee
Fix tests
sfc-gh-knozderko Dec 13, 2024
f5e1862
Log exceptions when operating with token cache
sfc-gh-knozderko Dec 13, 2024
47b6333
Support for xdg_cache_home env variable, enforce secure dir permissions
sfc-gh-knozderko Dec 18, 2024
99907c3
Merge branch 'master' into SNOW-715504-MFA-Token-Cache
sfc-gh-knozderko Dec 19, 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
47 changes: 47 additions & 0 deletions Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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;
Expand Down Expand Up @@ -2271,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")] // to enroll to mfa authentication edit your user profile
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 the default credential manager is a file based one. Uncomment the following line to test in memory implementation.
// SnowflakeCredentialManagerFactory.UseInMemoryCredentialManager();
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 the default credential manager is a file based one. Uncomment the following line to test in memory implementation.
// SnowflakeCredentialManagerFactory.UseInMemoryCredentialManager();
// 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
48 changes: 24 additions & 24 deletions Snowflake.Data.Tests/UnitTests/ArrowResultSetTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void BeforeTest()
// by default generate Int32 values from 1 to RowCount
PrepareTestCase(SFDataType.FIXED, 0, Enumerable.Range(1, RowCount).ToArray());
}

[Test]
public void TestResultFormatIsArrow()
{
Expand Down Expand Up @@ -140,7 +140,7 @@ public void TestGetValueReturnsNull()
var arrowResultSet = new ArrowResultSet(responseData, sfStatement, new CancellationToken());

arrowResultSet.Next();

Assert.AreEqual(true, arrowResultSet.IsDBNull(0));
Assert.AreEqual(DBNull.Value, arrowResultSet.GetValue(0));
}
Expand All @@ -152,7 +152,7 @@ public void TestGetDecimal()

TestGetNumber(testValues);
}

[Test]
public void TestGetNumber64()
{
Expand All @@ -165,7 +165,7 @@ public void TestGetNumber64()
public void TestGetNumber32()
{
var testValues = new int[] { 0, 100, -100, Int32.MaxValue, Int32.MinValue };

TestGetNumber(testValues);
}

Expand All @@ -176,7 +176,7 @@ public void TestGetNumber16()

TestGetNumber(testValues);
}

[Test]
public void TestGetNumber8()
{
Expand All @@ -200,7 +200,7 @@ private void TestGetNumber(IEnumerable testValues)
Assert.AreEqual(expectedValue, _arrowResultSet.GetDecimal(ColumnIndex));
Assert.AreEqual(expectedValue, _arrowResultSet.GetDouble(ColumnIndex));
Assert.AreEqual(expectedValue, _arrowResultSet.GetFloat(ColumnIndex));

if (expectedValue >= Int64.MinValue && expectedValue <= Int64.MaxValue)
{
// get integer value
Expand Down Expand Up @@ -230,7 +230,7 @@ public void TestGetBoolean()
var testValues = new bool[] { true, false };

PrepareTestCase(SFDataType.BOOLEAN, 0, testValues);

foreach (var testValue in testValues)
{
_arrowResultSet.Next();
Expand All @@ -245,15 +245,15 @@ public void TestGetReal()
var testValues = new double[] { 0, Double.MinValue, Double.MaxValue };

PrepareTestCase(SFDataType.REAL, 0, testValues);

foreach (var testValue in testValues)
{
_arrowResultSet.Next();
Assert.AreEqual(testValue, _arrowResultSet.GetValue(ColumnIndex));
Assert.AreEqual(testValue, _arrowResultSet.GetDouble(ColumnIndex));
}
}

[Test]
public void TestGetText()
{
Expand All @@ -264,15 +264,15 @@ public void TestGetText()
};

PrepareTestCase(SFDataType.TEXT, 0, testValues);

foreach (var testValue in testValues)
{
_arrowResultSet.Next();
Assert.AreEqual(testValue, _arrowResultSet.GetValue(ColumnIndex));
Assert.AreEqual(testValue, _arrowResultSet.GetString(ColumnIndex));
}
}

[Test]
public void TestGetTextWithOneChar()
{
Expand All @@ -290,14 +290,14 @@ public void TestGetTextWithOneChar()
#endif

PrepareTestCase(SFDataType.TEXT, 0, testValues);

foreach (var testValue in testValues)
{
_arrowResultSet.Next();
Assert.AreEqual(testValue, _arrowResultSet.GetChar(ColumnIndex));
}
}

[Test]
public void TestGetArray()
{
Expand All @@ -308,7 +308,7 @@ public void TestGetArray()
};

PrepareTestCase(SFDataType.ARRAY, 0, testValues);

foreach (var testValue in testValues)
{
_arrowResultSet.Next();
Expand All @@ -320,7 +320,7 @@ public void TestGetArray()
Assert.AreEqual(testValue.Length, str.Length);
}
}

[Test]
public void TestGetBinary()
{
Expand All @@ -342,7 +342,7 @@ public void TestGetBinary()
Assert.AreEqual(testValue[j], buffer[j], "position " + j);
}
}

[Test]
public void TestGetDate()
{
Expand All @@ -354,15 +354,15 @@ public void TestGetDate()
};

PrepareTestCase(SFDataType.DATE, 0, testValues);

foreach (var testValue in testValues)
{
_arrowResultSet.Next();
Assert.AreEqual(testValue, _arrowResultSet.GetValue(ColumnIndex));
Assert.AreEqual(testValue, _arrowResultSet.GetDateTime(ColumnIndex));
}
}

[Test]
public void TestGetTime()
{
Expand All @@ -384,7 +384,7 @@ public void TestGetTime()
Assert.AreEqual(testValue, _arrowResultSet.GetValue(ColumnIndex));
Assert.AreEqual(testValue, _arrowResultSet.GetDateTime(ColumnIndex));
}
}
}
}

[Test]
Expand Down Expand Up @@ -513,10 +513,10 @@ private QueryExecResponseData PrepareResponseData(RecordBatch recordBatch, SFDat
return new QueryExecResponseData
{
rowType = recordBatch.Schema.FieldsList
.Select(col =>
.Select(col =>
new ExecResponseRowType
{
name = col.Name,
name = col.Name,
type = sfType.ToString(),
scale = scale
}).ToList(),
Expand All @@ -531,7 +531,7 @@ private string ConvertToBase64String(RecordBatch recordBatch)
{
if (recordBatch == null)
return "";

using (var stream = new MemoryStream())
{
using (var writer = new ArrowStreamWriter(stream, recordBatch.Schema))
Expand All @@ -542,12 +542,12 @@ private string ConvertToBase64String(RecordBatch recordBatch)
return Convert.ToBase64String(stream.ToArray());
}
}

private SFStatement PrepareStatement()
{
SFSession session = new SFSession("user=user;password=password;account=account;", null);
return new SFStatement(session);
}

}
}
Loading
Loading