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-715524: Add SSO token cache #921

Open
wants to merge 98 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
7de3ea6
SNOW-715524: Add SSO token cache
sfc-gh-ext-simba-lf Apr 18, 2024
4457077
Add tests
sfc-gh-ext-simba-lf Apr 18, 2024
15a58be
Remove unused namespace
sfc-gh-ext-simba-lf Apr 18, 2024
632a6b0
Merge branch 'master' into SNOW-715524-SSO-Token-Cache
sfc-gh-ext-simba-lf Apr 18, 2024
7f28fa8
Fix unit test
sfc-gh-ext-simba-lf Apr 18, 2024
a317158
Refactor constructor and tests
sfc-gh-ext-simba-lf Apr 18, 2024
625e04b
Refactor test
sfc-gh-ext-simba-lf Apr 18, 2024
383fe5e
Refactor test and file impl
sfc-gh-ext-simba-lf Apr 19, 2024
8ce9d10
Refactor file name and fix test
sfc-gh-ext-simba-lf Apr 19, 2024
a460e4b
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Apr 19, 2024
e0b65d0
Revert removed lines
sfc-gh-ext-simba-lf Apr 19, 2024
6606823
Refactor code and remove unnecessary check
sfc-gh-ext-simba-lf Apr 19, 2024
4e6869d
Add session test
sfc-gh-ext-simba-lf Apr 19, 2024
3788474
Fix test
sfc-gh-ext-simba-lf Apr 19, 2024
c597c64
Refactor test and rename file
sfc-gh-ext-simba-lf Apr 19, 2024
39b90b2
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Apr 29, 2024
b4ab4ed
Add more logging
sfc-gh-ext-simba-lf May 1, 2024
13ba839
Change log from error to info
sfc-gh-ext-simba-lf May 1, 2024
b334fb3
Add file path to logs
sfc-gh-ext-simba-lf May 2, 2024
623ce6a
Add Meziantou package for credential manager implementation
sfc-gh-ext-simba-lf May 8, 2024
976bac2
Add native implementation of credential cache
sfc-gh-ext-simba-lf May 17, 2024
feab579
Add impl for ReleaseHandle
sfc-gh-ext-simba-lf May 17, 2024
61b2317
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Jun 5, 2024
cb1c84f
Remove MfaToken from TokenType enum
sfc-gh-ext-simba-lf Jun 5, 2024
7f0f801
Add class name to the log
sfc-gh-ext-simba-lf Jun 5, 2024
780d213
Add class name to the log
sfc-gh-ext-simba-lf Jun 5, 2024
575c0a4
Add class name to the log
sfc-gh-ext-simba-lf Jun 5, 2024
aa00982
Add class name to default log message
sfc-gh-ext-simba-lf Jun 5, 2024
ecdcf37
Merge branch 'SNOW-715524-SSO-Token-Cache' of https://github.com/snow…
sfc-gh-ext-simba-lf Jun 5, 2024
6f31fe6
Rename native class and remove impl with external libs
sfc-gh-ext-simba-lf Jun 5, 2024
cdc9f80
Use HomeDirectoryProvider to retrieve the default location
sfc-gh-ext-simba-lf Jun 5, 2024
9622f5c
Check if json file already exists
sfc-gh-ext-simba-lf Jun 5, 2024
a2f9b50
Change parameter from string to enum
sfc-gh-ext-simba-lf Jun 5, 2024
739c40c
Remove encryption for in-memory credential manager
sfc-gh-ext-simba-lf Jun 6, 2024
c502e80
Change modifier for dictionary
sfc-gh-ext-simba-lf Jun 6, 2024
465da80
Change public modifier for credential manager factory
sfc-gh-ext-simba-lf Jun 6, 2024
94bce01
Move credential manager files to core folder
sfc-gh-ext-simba-lf Jun 6, 2024
83119f3
Move interface and implementations to subpackage
sfc-gh-ext-simba-lf Jun 6, 2024
4e57147
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Jun 12, 2024
61855f2
Remove unused packages
sfc-gh-ext-simba-lf Jun 13, 2024
8b38fed
Include ".snowflake" to the default cache directory
sfc-gh-ext-simba-lf Jun 17, 2024
194eafa
Refactor credential manager
sfc-gh-ext-simba-lf Jun 17, 2024
8666194
Refactor credential manager
sfc-gh-ext-simba-lf Jun 17, 2024
403dbd2
Merge branch 'SNOW-715524-SSO-Token-Cache' of https://github.com/snow…
sfc-gh-ext-simba-lf Jun 17, 2024
33ebec5
Refactor external browser authentication
sfc-gh-ext-simba-lf Jun 18, 2024
512de5b
Remove modifying file permission on Windows
sfc-gh-ext-simba-lf Jun 20, 2024
6ef9b35
Modify test to open the second connection before calling close
sfc-gh-ext-simba-lf Jun 25, 2024
d616dcc
Modify session property test
sfc-gh-ext-simba-lf Jun 25, 2024
045fc04
Uncomment line in session property test
sfc-gh-ext-simba-lf Jun 25, 2024
5c9d8d7
Add check for new map parameter value
sfc-gh-ext-simba-lf Jun 25, 2024
adb3218
Replace user and add test explanation
sfc-gh-ext-simba-lf Jun 26, 2024
da0cffb
Remove unused packages
sfc-gh-ext-simba-lf Jun 28, 2024
44c746b
Add mock for browser and tests for external browser authentication
sfc-gh-ext-simba-lf Jul 4, 2024
590e98b
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Jul 4, 2024
2abcad4
Temporarily ignore test while looking for fix
sfc-gh-ext-simba-lf Jul 4, 2024
dd24c76
Temporarily ignore test while looking for fix
sfc-gh-ext-simba-lf Jul 4, 2024
f8b3230
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Jul 4, 2024
7a384e8
Rename internal property based on convention and fix missing comma
sfc-gh-ext-simba-lf Jul 4, 2024
7672901
Fix exception not being thrown while listening for browser response
sfc-gh-ext-simba-lf Jul 6, 2024
2cdbae4
Mark browser tests nonparallelizable
sfc-gh-ext-simba-lf Jul 6, 2024
524b78f
Catch HttpListenerException
sfc-gh-ext-simba-lf Jul 6, 2024
6fdf045
Catch HttpListenerException
sfc-gh-ext-simba-lf Jul 6, 2024
2d2870d
Add error logs
sfc-gh-ext-simba-lf Jul 6, 2024
f8f7336
Add check if event is still waiting for response before getting the c…
sfc-gh-ext-simba-lf Jul 8, 2024
8af5ba8
Revert event check and add log to check port number
sfc-gh-ext-simba-lf Jul 8, 2024
e56b086
Add log for browser port number
sfc-gh-ext-simba-lf Jul 8, 2024
2ba9aaf
Remove printing port number and add check if result is completed
sfc-gh-ext-simba-lf Jul 8, 2024
7e9c098
Remove printing result.IsCompleted line
sfc-gh-ext-simba-lf Jul 8, 2024
e49ed64
Remove catching HttpListenerException
sfc-gh-ext-simba-lf Jul 8, 2024
c434184
Catch HttpListenerException
sfc-gh-ext-simba-lf Jul 8, 2024
67a8fbf
Add check if result is completed and add wait for result
sfc-gh-ext-simba-lf Jul 8, 2024
f8fcf2b
Add detail for listener exception
sfc-gh-ext-simba-lf Jul 8, 2024
a46f1ad
Specify parameter as AsyncCallback
sfc-gh-ext-simba-lf Jul 8, 2024
52bfd3e
Refactor saml request functions
sfc-gh-ext-simba-lf Jul 9, 2024
9edfd79
Increase browser response timeout for test
sfc-gh-ext-simba-lf Jul 9, 2024
f6d8a0f
Remove catching HttpListenerException
sfc-gh-ext-simba-lf Jul 9, 2024
77a69b3
Remove unnecessary NonParallelizable attribute
sfc-gh-ext-simba-lf Jul 9, 2024
9038ba1
Revert remove catching HttpListenerException
sfc-gh-ext-simba-lf Jul 9, 2024
d800e6f
Refactor if check
sfc-gh-ext-simba-lf Jul 9, 2024
58b74d8
Add temp console lines
sfc-gh-ext-simba-lf Jul 9, 2024
8f919e7
Remove console lines
sfc-gh-ext-simba-lf Jul 9, 2024
fbbac23
Stop http listener before throwing browser exceptions
sfc-gh-ext-simba-lf Jul 9, 2024
d53dd8f
Close HttpListener before throwing exception
sfc-gh-ext-simba-lf Jul 9, 2024
98df45e
Close HttpListener before throwing exception
sfc-gh-ext-simba-lf Jul 9, 2024
359cfa3
Modify browser timeout test
sfc-gh-ext-simba-lf Jul 9, 2024
85d04ed
Abort HttpListener before throwing exception
sfc-gh-ext-simba-lf Jul 9, 2024
2d39171
Check if browser timeout already reached before getting context
sfc-gh-ext-simba-lf Jul 9, 2024
f7be152
Use SecureString for tokens
sfc-gh-ext-simba-lf Jul 9, 2024
2f20edd
Add security checks when reading the credential cache json
sfc-gh-ext-simba-lf Jul 10, 2024
ac1d659
Add error logs when reading the json cache file
sfc-gh-ext-simba-lf Jul 10, 2024
8c65f4d
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Jul 17, 2024
330fca9
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Jul 24, 2024
fc928fc
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Aug 6, 2024
d53955a
Merge branch 'master' into SNOW-715524-SSO-Token-Cache
sfc-gh-ext-simba-lf Aug 21, 2024
d6ff05a
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Aug 27, 2024
a1a6a31
Merge branch 'master' into SNOW-715524-SSO-Token-Cache
sfc-gh-ext-simba-lf Aug 30, 2024
74963c7
Merge branch 'master' of https://github.com/snowflakedb/snowflake-con…
sfc-gh-ext-simba-lf Sep 3, 2024
126e337
Merge branch 'master' into SNOW-715524-SSO-Token-Cache
sfc-gh-ext-simba-lf Sep 6, 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
103 changes: 102 additions & 1 deletion Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
*/

Expand All @@ -21,6 +21,8 @@ namespace Snowflake.Data.Tests.IntegrationTests
using Snowflake.Data.Tests.Mock;
using System.Runtime.InteropServices;
using System.Net.Http;
using Snowflake.Data.Core.CredentialManager;
using Snowflake.Data.Core.CredentialManager.Infrastructure;

[TestFixture]
class SFConnectionIT : SFBaseTest
Expand Down Expand Up @@ -1046,6 +1048,71 @@ public void TestSSOConnectionTimeoutAfter10s()
Assert.LessOrEqual(stopwatch.ElapsedMilliseconds, (waitSeconds + 5) * 1000);
}

[Test]
[Ignore("This test requires manual interaction and therefore cannot be run in CI")]
public void TestSSOConnectionWithTokenCaching()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with ignored tests is that they will not be executed so in the future something can break and we will not notice that.
Then if a developer wants to run them manually it takes him a lot of time to create a proper setup for the test.
Could you provide some context (for example in the comments or readme) how to run this test manually for someone who will want to run it for example in one year and would not know any context of this feature?

You provide here a specific user. Also maybe we would need information that we should change it when testing on particular environment. But we should not write about any connection details in this repo since it is publicly visible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, I'll put more details on the comments on how to do the test

As for the user, I'll replace it so the details are not exposed

{
/*
* This test checks that the connector successfully stores an SSO token and uses it for authentication if it exists
* 1. Login normally using external browser with allow_sso_token_caching enabled
* 2. Login again, this time without a browser, as the connector should be using the SSO token retrieved from step 1
*/

using (IDbConnection conn = new SnowflakeDbConnection())
{
// Set the allow_sso_token_caching property to true to enable token caching
// The specified user should be configured for SSO
conn.ConnectionString
= ConnectionStringWithoutAuth
+ $";authenticator=externalbrowser;user={testConfig.user};allow_sso_token_caching=true;";

// Authenticate to retrieve and store the token if doesn't exist or invalid
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);

// Authenticate using the SSO token (the connector will automatically use the token and a browser should not pop-up in this step)
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the connection pool is enabled by default so when you open the connection for the second time you get exactly the same session - so you are not testing getting the token from the credential manager.

You should open the second connection before closing the first one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


conn.Close();
Assert.AreEqual(ConnectionState.Closed, conn.State);
}
}

[Test]
[Ignore("This test requires manual interaction and therefore cannot be run in CI")]
public void TestSSOConnectionWithInvalidCachedToken()
{
/*
* This test checks that the connector will attempt to re-authenticate using external browser if the token retrieved from the cache is invalid
* 1. Create a credential manager and save credentials for the user with a wrong token
* 2. Open a connection which initially should try to use the token and then switch to external browser when the token fails
*/

using (IDbConnection conn = new SnowflakeDbConnection())
{
// Set the allow_sso_token_caching property to true to enable token caching
conn.ConnectionString
= ConnectionStringWithoutAuth
+ $";authenticator=externalbrowser;user={testConfig.user};allow_sso_token_caching=true;";

// Create a credential manager and save a wrong token for the test user
var key = SFCredentialManagerFactory.BuildCredentialKey(testConfig.host, testConfig.user, TokenType.IdToken);
var credentialManager = SFCredentialManagerInMemoryImpl.Instance;
credentialManager.SaveCredentials(key, "wrongToken");

// Use the credential manager with the wrong token
SFCredentialManagerFactory.SetCredentialManager(credentialManager);

// Open a connection which should switch to external browser after trying to connect using the wrong token
conn.Open();
Assert.AreEqual(ConnectionState.Open, conn.State);

// Switch back to the default credential manager
SFCredentialManagerFactory.UseDefaultCredentialManager();
}
}

[Test]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to write a test which would not be ignored? Maybe with something mocked or something that simulates the browser action?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added tests for external browser authentication using a mock browser

[Ignore("This test requires manual interaction and therefore cannot be run in CI")]
public void TestSSOConnectionWithWrongUser()
Expand Down Expand Up @@ -2311,6 +2378,40 @@ public void TestOpenAsyncThrowExceptionWhenOperationIsCancelled()
}
}

[Test]
[Ignore("This test requires manual interaction and therefore cannot be run in CI")]
public void TestSSOConnectionWithTokenCachingAsync()
{
/*
* This test checks that the connector successfully stores an SSO token and uses it for authentication if it exists
* 1. Login normally using external browser with allow_sso_token_caching enabled
* 2. Login again, this time without a browser, as the connector should be using the SSO token retrieved from step 1
*/

using (SnowflakeDbConnection conn = new SnowflakeDbConnection())
{
// Set the allow_sso_token_caching property to true to enable token caching
// The specified user should be configured for SSO
conn.ConnectionString
= ConnectionStringWithoutAuth
+ $";authenticator=externalbrowser;user={testConfig.user};allow_sso_token_caching=true;";

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

// Authenticate using the SSO token (the connector will automatically use the token and a browser should not pop-up in this step)
connectTask = conn.OpenAsync(CancellationToken.None);
connectTask.Wait();
Assert.AreEqual(ConnectionState.Open, conn.State);

connectTask = conn.CloseAsync(CancellationToken.None);
connectTask.Wait();
Assert.AreEqual(ConnectionState.Closed, conn.State);
}
}

[Test]
public void TestCloseSessionWhenGarbageCollectorFinalizesConnection()
{
Expand Down
99 changes: 99 additions & 0 deletions Snowflake.Data.Tests/Mock/MockExternalBrowser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/

using Snowflake.Data.Core;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Snowflake.Data.Tests.Mock
{

class MockExternalBrowserRestRequester : IMockRestRequester
{
public string ProofKey { get; set; }
public string SSOUrl { get; set; }

public T Get<T>(IRestRequest request)
{
throw new System.NotImplementedException();
}

public Task<T> GetAsync<T>(IRestRequest request, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}

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 AuthenticatorRequest)
{
if (string.IsNullOrEmpty(SSOUrl))
{
var body = (AuthenticatorRequest)sfRequest.jsonBody;
var port = body.Data.BrowserModeRedirectPort;
SSOUrl = $"http://localhost:{port}/?token=mockToken";
}

// authenticator
var authnResponse = new AuthenticatorResponse
{
success = true,
data = new AuthenticatorResponseData
{
proofKey = ProofKey,
ssoUrl = SSOUrl,
}
};

return Task.FromResult<T>((T)(object)authnResponse);
}
else
{
// login
var loginResponse = new LoginResponse
{
success = true,
data = new LoginResponseData
{
sessionId = "",
token = "",
masterToken = "",
masterValidityInSeconds = 0,
authResponseSessionInfo = new SessionInfo
{
databaseName = "",
schemaName = "",
roleName = "",
warehouseName = "",
}
}
};

return Task.FromResult<T>((T)(object)loginResponse);
}
}

public HttpResponseMessage Get(IRestRequest request)
{
throw new System.NotImplementedException();
}

public Task<HttpResponseMessage> GetAsync(IRestRequest request, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}

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