Skip to content

Commit e283dd3

Browse files
SNOW-2039993 Add APPLICATION_PATH to CLIENT_ENVIRONMENT (#1261)
1 parent 31f8031 commit e283dd3

File tree

7 files changed

+224
-101
lines changed

7 files changed

+224
-101
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#### For the official .NET Release Notes please refer to https://docs.snowflake.com/en/release-notes/clients-drivers/dotnet
22

33
# Changelog
4+
- v5.1.0
5+
- Added `APPLICATION_PATH` to `CLIENT_ENVIRONMENT` sent during authentication to identify the application connecting to Snowflake.
6+
- Renew idle sessions in the pool if keep alive is enabled.
47
- v5.0.0
58
- Disabled CRL checks by default.
69
- Added support for alternative, memory efficient and thread safe CRL (Certificate Revocation List) checks.
@@ -10,5 +13,4 @@
1013
- Added the `changelog.yml` GitHub workflow to ensure changelog is updated on release PRs.
1114
- Removed internal classes from public API.
1215
- Added support for explicitly setting Azure managed identity client ID via `MANAGED_IDENTITY_CLIENT_ID` environmen
13-
- v5.0.1
14-
- Renew idle sessions in the pool if keep alive is enabled.
16+

Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,34 @@ public void TestApplicationName()
9999
}
100100
}
101101

102+
[Test]
103+
[RunOnlyOnCI]
104+
public void TestApplicationPathIsSentDuringAuthentication()
105+
{
106+
using (var conn = new SnowflakeDbConnection())
107+
{
108+
conn.ConnectionString = ConnectionString;
109+
conn.Open();
110+
111+
var authenticator = (BaseAuthenticator)conn.SfSession.authenticator;
112+
var clientEnv = authenticator.BuildLoginRequestData().clientEnv;
113+
var lowerPath = clientEnv.applicationPath.ToLower();
114+
#if NETFRAMEWORK
115+
Assert.IsTrue(
116+
lowerPath.Contains("testhost") &&
117+
(lowerPath.EndsWith(".dll") || lowerPath.EndsWith(".exe")),
118+
$"APPLICATION_PATH should contain 'testhost' and end with .dll or .exe. Got: {clientEnv.applicationPath}");
119+
#else
120+
Assert.IsTrue(
121+
lowerPath.Contains("snowflake.data.tests") &&
122+
lowerPath.Contains("bin") &&
123+
lowerPath.Contains("testhost") &&
124+
(lowerPath.EndsWith(".dll") || lowerPath.EndsWith(".exe")),
125+
$"APPLICATION_PATH should contain 'snowflake.data.tests', 'bin', 'testhost' and end with .dll or .exe. Got: {clientEnv.applicationPath}");
126+
#endif
127+
}
128+
}
129+
102130
[Test]
103131
public void TestIncorrectUserOrPasswordBasicConnection()
104132
{

Snowflake.Data.Tests/SFBaseTest.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,37 @@ public void AfterTest(ITest test)
636636
public ActionTargets Targets => ActionTargets.Test | ActionTargets.Suite;
637637
}
638638

639+
public class IgnoreOnEnvNotSetAttribute : Attribute, ITestAction
640+
{
641+
private readonly string _key;
642+
643+
public IgnoreOnEnvNotSetAttribute(string key)
644+
{
645+
_key = key;
646+
}
647+
648+
public void BeforeTest(ITest test)
649+
{
650+
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(_key)))
651+
{
652+
Assert.Ignore("Test is ignored when environment variable {0} is not set", _key);
653+
}
654+
}
655+
656+
public void AfterTest(ITest test)
657+
{
658+
}
659+
660+
public ActionTargets Targets => ActionTargets.Test | ActionTargets.Suite;
661+
}
662+
663+
public class RunOnlyOnCI : IgnoreOnEnvNotSetAttribute
664+
{
665+
public RunOnlyOnCI() : base("CI")
666+
{
667+
}
668+
}
669+
639670
public class IgnoreOnCI : IgnoreOnEnvIsAttribute
640671
{
641672
public IgnoreOnCI(string reason = null) : base("CI", new[] { "true" }, reason)

Snowflake.Data.Tests/UnitTests/SFEnvironmentTest.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,33 @@ public void TestRuntimeExtraction()
4040
Assert.AreEqual(expectedVersion, actualVersion);
4141
}
4242

43+
[Test]
44+
[RunOnlyOnCI]
45+
public void TestApplicationPathExtraction()
46+
{
47+
var applicationPath = SFEnvironment.ExtractApplicationPath();
48+
49+
Assert.IsNotNull(applicationPath);
50+
Assert.IsNotEmpty(applicationPath);
51+
Assert.IsTrue(System.IO.Path.IsPathRooted(applicationPath),
52+
$"Application path should be absolute. Got: {applicationPath}");
53+
54+
var lowerPath = applicationPath.ToLower();
55+
#if NETFRAMEWORK
56+
Assert.IsTrue(
57+
lowerPath.Contains("testhost") &&
58+
(lowerPath.EndsWith(".dll") || lowerPath.EndsWith(".exe")),
59+
$"Application path should contain 'testhost' and end with .dll or .exe. Got: {applicationPath}");
60+
#else
61+
Assert.IsTrue(
62+
lowerPath.Contains("snowflake.data.tests") &&
63+
lowerPath.Contains("bin") &&
64+
lowerPath.Contains("testhost") &&
65+
(lowerPath.EndsWith(".dll") || lowerPath.EndsWith(".exe")),
66+
$"Application path should contain 'snowflake.data.tests', 'bin', 'testhost' and end with .dll or .exe. Got: {applicationPath}");
67+
#endif
68+
}
69+
4370
[Test]
4471
public void TestClientEnvironmentDoesNotInterfereForDifferentAuthenticators()
4572
{
@@ -48,6 +75,7 @@ public void TestClientEnvironmentDoesNotInterfereForDifferentAuthenticators()
4875
var osVersion = Environment.OSVersion.VersionString;
4976
var netRuntime = SFEnvironment.ExtractRuntime();
5077
var netVersion = SFEnvironment.ExtractVersion();
78+
var applicationPath = SFEnvironment.ExtractApplicationPath();
5179
var clientCredentialAuthenticator = CreateAuthenticator("authenticator=oauth_client_credentials;account=test;db=testDb;role=testRole;oauthClientId=abc;oauthClientSecret=def;user=testUser;oauthTokenRequestUrl=https://test.snowflake.com;");
5280
((OAuthClientCredentialsAuthenticator)clientCredentialAuthenticator).AccessToken = SecureStringHelper.Encode("qwe");
5381
var clientCredentialLoginClientEnv = clientCredentialAuthenticator.BuildLoginRequestData().clientEnv;
@@ -62,6 +90,7 @@ public void TestClientEnvironmentDoesNotInterfereForDifferentAuthenticators()
6290
Assert.AreEqual(netVersion, clientCredentialLoginClientEnv.netVersion);
6391
Assert.AreEqual(processName, clientCredentialLoginClientEnv.application);
6492
Assert.AreEqual(processName, clientCredentialLoginClientEnv.processName);
93+
Assert.AreEqual(applicationPath, clientCredentialLoginClientEnv.applicationPath);
6594
Assert.AreEqual("disabled", clientCredentialLoginClientEnv.certRevocationCheckMode);
6695
Assert.AreEqual("oauth_client_credentials", clientCredentialLoginClientEnv.oauthType);
6796
// asserts for client credential second login
@@ -70,6 +99,7 @@ public void TestClientEnvironmentDoesNotInterfereForDifferentAuthenticators()
7099
Assert.AreEqual(netVersion, clientCredentialLoginClientEnv2.netVersion);
71100
Assert.AreEqual(processName, clientCredentialLoginClientEnv2.application);
72101
Assert.AreEqual(processName, clientCredentialLoginClientEnv2.processName);
102+
Assert.AreEqual(applicationPath, clientCredentialLoginClientEnv2.applicationPath);
73103
Assert.AreEqual("disabled", clientCredentialLoginClientEnv2.certRevocationCheckMode);
74104
Assert.AreEqual("oauth_client_credentials", clientCredentialLoginClientEnv2.oauthType);
75105
// asserts for PAT login
@@ -78,6 +108,7 @@ public void TestClientEnvironmentDoesNotInterfereForDifferentAuthenticators()
78108
Assert.AreEqual(netVersion, insecurePatLoginClientEnv.netVersion);
79109
Assert.AreEqual("MyApp", insecurePatLoginClientEnv.application);
80110
Assert.AreEqual(processName, insecurePatLoginClientEnv.processName);
111+
Assert.AreEqual(applicationPath, insecurePatLoginClientEnv.applicationPath);
81112
Assert.AreEqual("enabled", insecurePatLoginClientEnv.certRevocationCheckMode);
82113
Assert.IsNull(insecurePatLoginClientEnv.oauthType);
83114
// asserts that first and second client credential login produced the same json

Snowflake.Data.Tests/UnitTests/SFOktaTest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ public void TestLoginRequestToString()
207207
$"OS_VERSION: , " +
208208
$"NET_RUNTIME: , " +
209209
$"NET_VERSION: , " +
210-
$"CERT_REVOCATION_CHECK_MODE: }},\n " +
210+
$"CERT_REVOCATION_CHECK_MODE: , " +
211+
$"APPLICATION_PATH: }},\n " +
211212
$"authenticator: {expectedOktaUrl} }} }}",
212213
loginRequest.ToString());
213214
}

Snowflake.Data/Core/RestParams.cs

Lines changed: 121 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,121 @@
1-
using System;
2-
using System.Reflection;
3-
using System.Runtime.InteropServices;
4-
5-
namespace Snowflake.Data.Core
6-
{
7-
internal static class RestParams
8-
{
9-
internal const string SF_QUERY_WAREHOUSE = "warehouse";
10-
11-
internal const string SF_QUERY_DB = "databaseName";
12-
13-
internal const string SF_QUERY_SCHEMA = "schemaName";
14-
15-
internal const string SF_QUERY_ROLE = "roleName";
16-
17-
internal const string SF_QUERY_REQUEST_ID = "requestId";
18-
19-
internal const string SF_QUERY_REQUEST_GUID = "request_guid";
20-
21-
internal const string SF_QUERY_START_TIME = "clientStartTime";
22-
23-
internal const string SF_QUERY_RETRY_COUNT = "retryCount";
24-
25-
internal const string SF_QUERY_RETRY_REASON = "retryReason";
26-
27-
internal const string SF_QUERY_SESSION_DELETE = "delete";
28-
}
29-
30-
internal static class RestPath
31-
{
32-
internal const string SF_SESSION_PATH = "/session";
33-
34-
internal const string SF_LOGIN_PATH = SF_SESSION_PATH + "/v1/login-request";
35-
36-
internal const string SF_TOKEN_REQUEST_PATH = SF_SESSION_PATH + "/token-request";
37-
38-
internal const string SF_AUTHENTICATOR_REQUEST_PATH = SF_SESSION_PATH + "/authenticator-request";
39-
40-
internal const string SF_QUERY_PATH = "/queries/v1/query-request";
41-
42-
internal const string SF_MONITOR_QUERY_PATH = "/monitoring/queries/";
43-
44-
internal const string SF_SESSION_HEARTBEAT_PATH = SF_SESSION_PATH + "/heartbeat";
45-
46-
internal const string SF_CONSOLE_LOGIN = "/console/login";
47-
}
48-
49-
internal static class OAuthFlowConfig
50-
{
51-
internal const string SnowflakeAuthorizeUrl = "/oauth/authorize";
52-
internal const string SnowflakeTokenUrl = "/oauth/token-request";
53-
internal const string DefaultScopePrefixBeforeRole = "session:role:";
54-
}
55-
56-
internal static class OktaUrl
57-
{
58-
internal const string DOMAIN = "okta.com";
59-
internal const string SSO_SAML_PATH = "/sso/saml";
60-
}
61-
62-
internal class SFEnvironment
63-
{
64-
static SFEnvironment()
65-
{
66-
ClientEnv = new LoginRequestClientEnv()
67-
{
68-
processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName,
69-
osVersion = Environment.OSVersion.VersionString,
70-
netRuntime = ExtractRuntime(),
71-
netVersion = ExtractVersion(),
72-
};
73-
74-
DriverVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
75-
DriverName = ".NET";
76-
}
77-
78-
//temporary change for pretend as ODBC
79-
internal static string DriverName { get; set; }
80-
internal static string DriverVersion { get; set; }
81-
internal static LoginRequestClientEnv ClientEnv { get; private set; }
82-
83-
internal static string ExtractRuntime()
84-
{
85-
return RuntimeInformation.FrameworkDescription.Substring(0, RuntimeInformation.FrameworkDescription.LastIndexOf(' ')).Replace(" ", "");
86-
}
87-
88-
internal static string ExtractVersion()
89-
{
90-
var version = RuntimeInformation.FrameworkDescription.Substring(RuntimeInformation.FrameworkDescription.LastIndexOf(' ')).Replace(" ", "");
91-
int secondPeriodIndex = version.IndexOf('.', version.IndexOf('.') + 1);
92-
return version.Substring(0, secondPeriodIndex);
93-
}
94-
}
95-
}
1+
using System;
2+
using System.Reflection;
3+
using System.Runtime.InteropServices;
4+
5+
namespace Snowflake.Data.Core
6+
{
7+
internal static class RestParams
8+
{
9+
internal const string SF_QUERY_WAREHOUSE = "warehouse";
10+
11+
internal const string SF_QUERY_DB = "databaseName";
12+
13+
internal const string SF_QUERY_SCHEMA = "schemaName";
14+
15+
internal const string SF_QUERY_ROLE = "roleName";
16+
17+
internal const string SF_QUERY_REQUEST_ID = "requestId";
18+
19+
internal const string SF_QUERY_REQUEST_GUID = "request_guid";
20+
21+
internal const string SF_QUERY_START_TIME = "clientStartTime";
22+
23+
internal const string SF_QUERY_RETRY_COUNT = "retryCount";
24+
25+
internal const string SF_QUERY_RETRY_REASON = "retryReason";
26+
27+
internal const string SF_QUERY_SESSION_DELETE = "delete";
28+
}
29+
30+
internal static class RestPath
31+
{
32+
internal const string SF_SESSION_PATH = "/session";
33+
34+
internal const string SF_LOGIN_PATH = SF_SESSION_PATH + "/v1/login-request";
35+
36+
internal const string SF_TOKEN_REQUEST_PATH = SF_SESSION_PATH + "/token-request";
37+
38+
internal const string SF_AUTHENTICATOR_REQUEST_PATH = SF_SESSION_PATH + "/authenticator-request";
39+
40+
internal const string SF_QUERY_PATH = "/queries/v1/query-request";
41+
42+
internal const string SF_MONITOR_QUERY_PATH = "/monitoring/queries/";
43+
44+
internal const string SF_SESSION_HEARTBEAT_PATH = SF_SESSION_PATH + "/heartbeat";
45+
46+
internal const string SF_CONSOLE_LOGIN = "/console/login";
47+
}
48+
49+
internal static class OAuthFlowConfig
50+
{
51+
internal const string SnowflakeAuthorizeUrl = "/oauth/authorize";
52+
internal const string SnowflakeTokenUrl = "/oauth/token-request";
53+
internal const string DefaultScopePrefixBeforeRole = "session:role:";
54+
}
55+
56+
internal static class OktaUrl
57+
{
58+
internal const string DOMAIN = "okta.com";
59+
internal const string SSO_SAML_PATH = "/sso/saml";
60+
}
61+
62+
internal class SFEnvironment
63+
{
64+
static SFEnvironment()
65+
{
66+
ClientEnv = new LoginRequestClientEnv()
67+
{
68+
processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName,
69+
osVersion = Environment.OSVersion.VersionString,
70+
netRuntime = ExtractRuntime(),
71+
netVersion = ExtractVersion(),
72+
applicationPath = ExtractApplicationPath(),
73+
};
74+
75+
DriverVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
76+
DriverName = ".NET";
77+
}
78+
79+
//temporary change for pretend as ODBC
80+
internal static string DriverName { get; set; }
81+
internal static string DriverVersion { get; set; }
82+
internal static LoginRequestClientEnv ClientEnv { get; private set; }
83+
84+
internal static string ExtractRuntime()
85+
{
86+
return RuntimeInformation.FrameworkDescription.Substring(0, RuntimeInformation.FrameworkDescription.LastIndexOf(' ')).Replace(" ", "");
87+
}
88+
89+
internal static string ExtractVersion()
90+
{
91+
var version = RuntimeInformation.FrameworkDescription.Substring(RuntimeInformation.FrameworkDescription.LastIndexOf(' ')).Replace(" ", "");
92+
int secondPeriodIndex = version.IndexOf('.', version.IndexOf('.') + 1);
93+
return version.Substring(0, secondPeriodIndex);
94+
}
95+
96+
internal static string ExtractApplicationPath()
97+
{
98+
try
99+
{
100+
var assembly = Assembly.GetEntryAssembly();
101+
if (assembly != null && !string.IsNullOrEmpty(assembly.Location))
102+
{
103+
return assembly.Location;
104+
}
105+
106+
var process = System.Diagnostics.Process.GetCurrentProcess();
107+
var mainModule = process.MainModule;
108+
if (mainModule != null && !string.IsNullOrEmpty(mainModule.FileName))
109+
{
110+
return mainModule.FileName;
111+
}
112+
113+
return "UNKNOWN";
114+
}
115+
catch (Exception)
116+
{
117+
return "UNKNOWN";
118+
}
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)