Skip to content

Commit

Permalink
Applying suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jmartinezramirez committed Jul 5, 2024
1 parent 7142053 commit 6eba99f
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 31 deletions.
4 changes: 2 additions & 2 deletions Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2281,7 +2281,7 @@ public void TestMFATokenCaching()
{
using (SnowflakeDbConnection conn = new SnowflakeDbConnection())
{
//conn.Passcode = SecureStringHelper.Encode("014350");
//conn.Passcode = SecureStringHelper.Encode("123456");
conn.ConnectionString
= ConnectionString
+ ";authenticator=username_password_mfa;minPoolSize=2;application=DuoTest;POOLINGENABLED=false;";
Expand Down Expand Up @@ -2310,7 +2310,7 @@ public void TestMfaWithPasswordConnection()
// arrange
using (SnowflakeDbConnection conn = new SnowflakeDbConnection())
{
conn.Passcode = SecureStringHelper.Encode("323438");
conn.Passcode = SecureStringHelper.Encode("123456");
// manual action: stop here in breakpoint to provide proper passcode by: conn.Passcode = SecureStringHelper.Encode("...");
conn.ConnectionString = ConnectionString + "minPoolSize=2;application=DuoTest;";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,24 @@
* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/

using Snowflake.Data.Client;
using Snowflake.Data.Core.CredentialManager.Infrastructure;
using Snowflake.Data.Log;
using System.Runtime.InteropServices;

namespace Snowflake.Data.Core.CredentialManager
namespace Snowflake.Data.Client
{
internal enum TokenType
{
[StringAttr(value = "ID_TOKEN")]
IdToken,
[StringAttr(value = "MFA_TOKEN")]
MFAToken
}

internal class SFCredentialManagerFactory
using System;
using Snowflake.Data.Core;
using Snowflake.Data.Core.CredentialManager;
using Snowflake.Data.Core.CredentialManager.Infrastructure;
using Snowflake.Data.Log;
using System.Runtime.InteropServices;

public class SFCredentialManagerFactory
{
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<SFCredentialManagerFactory>();

private static ISnowflakeCredentialManager s_customCredentialManager = null;

internal static string BuildCredentialKey(string host, string user, TokenType tokenType)
internal static string BuildCredentialKey(string host, string user, TokenType tokenType, string authenticator = null)
{
return $"{host.ToUpper()}:{user.ToUpper()}:{SFEnvironment.DriverName}:{tokenType.ToString().ToUpper()}";
return $"{host.ToUpper()}:{user.ToUpper()}:{SFEnvironment.DriverName}:{tokenType.ToString().ToUpper()}:{authenticator?.ToUpper() ?? string.Empty}";
}

public static void UseDefaultCredentialManager()
Expand All @@ -40,7 +34,7 @@ public static void SetCredentialManager(ISnowflakeCredentialManager customCreden
s_customCredentialManager = customCredentialManager;
}

internal static ISnowflakeCredentialManager GetCredentialManager()
public static ISnowflakeCredentialManager GetCredentialManager()
{
if (s_customCredentialManager == null)
{
Expand All @@ -49,11 +43,8 @@ internal static ISnowflakeCredentialManager GetCredentialManager()
s_logger.Info($"Using the default credential manager: {defaultCredentialManager.GetType().Name}");
return defaultCredentialManager;
}
else
{
s_logger.Info($"Using a custom credential manager: {s_customCredentialManager.GetType().Name}");
return s_customCredentialManager;
}
s_logger.Info($"Using a custom credential manager: {s_customCredentialManager.GetType().Name}");
return s_customCredentialManager;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

namespace Snowflake.Data.Core.CredentialManager.Infrastructure
{
using System.Security;
using System.Text;

internal class SFCredentialManagerFileImpl : ISnowflakeCredentialManager
{
internal const string CredentialCacheDirectoryEnvironmentName = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR";
Expand Down Expand Up @@ -103,7 +106,8 @@ internal void WriteToJsonFile(string content)

internal KeyToken ReadJsonFile()
{
return JsonConvert.DeserializeObject<KeyToken>(File.ReadAllText(_jsonCacheFilePath));
var contentFile = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? File.ReadAllText(_jsonCacheFilePath) : _unixOperations.ReadAllText(_jsonCacheFilePath);
return JsonConvert.DeserializeObject<KeyToken>(contentFile);
}

public string GetCredentials(string key)
Expand Down
14 changes: 14 additions & 0 deletions Snowflake.Data/Core/CredentialManager/TokenType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/

namespace Snowflake.Data.Core.CredentialManager
{
internal enum TokenType
{
[StringAttr(value = "ID_TOKEN")]
IdToken,
[StringAttr(value = "MFA_TOKEN")]
MFAToken
}
}
5 changes: 4 additions & 1 deletion Snowflake.Data/Core/SFError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ public enum SFError
EXECUTE_COMMAND_ON_CLOSED_CONNECTION,

[SFErrorAttr(errorCode = 270060)]
INCONSISTENT_RESULT_ERROR
INCONSISTENT_RESULT_ERROR,

[SFErrorAttr(errorCode = 390127)]
EXT_AUTHN_INVALID
}

class SFErrorAttr : Attribute
Expand Down
12 changes: 10 additions & 2 deletions Snowflake.Data/Core/Session/SFSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ internal void ProcessLoginResponse(LoginResponse authnResponse)
if (!string.IsNullOrEmpty(authnResponse.data.mfaToken))
{
_mfaToken = SecureStringHelper.Encode(authnResponse.data.mfaToken);
var key = SFCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.MFAToken);
var key = SFCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.MFAToken, properties[SFSessionProperty.AUTHENTICATOR]);
_credManager.SaveCredentials(key, authnResponse.data.mfaToken);
}
logger.Debug($"Session opened: {sessionId}");
Expand All @@ -143,6 +143,14 @@ internal void ProcessLoginResponse(LoginResponse authnResponse)
"");

logger.Error("Authentication failed", e);
if (e.ErrorCode == SFError.EXT_AUTHN_INVALID.GetAttribute<SFErrorAttr>().errorCode)
{
logger.Info("MFA Token has expired or not valid.", e);
_mfaToken = null;
var mfaKey = SFCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.MFAToken, properties[SFSessionProperty.AUTHENTICATOR]);
_credManager.RemoveCredentials(mfaKey);
}

throw e;
}
}
Expand Down Expand Up @@ -211,7 +219,7 @@ internal SFSession(

if (properties.TryGetValue(SFSessionProperty.AUTHENTICATOR, out var _authenticatorType) && _authenticatorType == "username_password_mfa")
{
var mfaKey = SFCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.MFAToken);
var mfaKey = SFCredentialManagerFactory.BuildCredentialKey(properties[SFSessionProperty.HOST], properties[SFSessionProperty.USER], TokenType.MFAToken, _authenticatorType);
_mfaToken = SecureStringHelper.Encode(_credManager.GetCredentials(mfaKey));
}
}
Expand Down
38 changes: 36 additions & 2 deletions Snowflake.Data/Core/Tools/UnixOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
* Copyright (c) 2024 Snowflake Computing Inc. All rights reserved.
*/

using Mono.Unix;
using Mono.Unix.Native;


namespace Snowflake.Data.Core.Tools
{
using System.IO;
using System.Security;
using System.Text;
using Mono.Unix;
using Mono.Unix.Native;

internal class UnixOperations
{
public static readonly UnixOperations Instance = new UnixOperations();
Expand Down Expand Up @@ -38,5 +43,34 @@ public virtual bool CheckFileHasAnyOfPermissions(string path, FileAccessPermissi
var fileInfo = new UnixFileInfo(path);
return (permissions & fileInfo.FileAccessPermissions) != 0;
}


/// <summary>
/// Reads all text from a file at the specified path, ensuring the file is owned by the effective user and group of the current process,
/// and does not have broader permissions than specified.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <param name="forbiddenPermissions">Permissions that are not allowed for the file. Defaults to OtherReadWriteExecute.</param>
/// <returns>The content of the file as a string.</returns>
/// <exception cref="SecurityException">Thrown if the file is not owned by the effective user or group, or if it has forbidden permissions.</exception>

public string ReadAllText(string path, FileAccessPermissions forbiddenPermissions = FileAccessPermissions.OtherReadWriteExecute)
{
var fileInfo = new UnixFileInfo(path: path);

using (var handle = fileInfo.OpenRead())
{
if (handle.OwnerUser.UserId != Syscall.geteuid())
throw new SecurityException("Attempting to read a file not owned by the effective user of the current process");
if (handle.OwnerGroup.GroupId != Syscall.getegid())
throw new SecurityException("Attempting to read a file not owned by the effective group of the current process");
if ((handle.FileAccessPermissions & forbiddenPermissions) != 0)
throw new SecurityException("Attempting to read a file with too broad permissions assigned");
using (var streamReader = new StreamReader(handle, Encoding.Default))
{
return streamReader.ReadToEnd();
}
}
}
}
}

0 comments on commit 6eba99f

Please sign in to comment.