mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Credentials store API (#38)
* CredentialService initial impl with Win32 support - Basic CredentialService APIs for Save, Read, Delete - E2E unit tests for Credential Service - Win32 implementation with unit tests * Save Password support on Mac v1 - Basic keychain support on Mac using Interop with the KeyChain APIs - All but 1 unit test passing. This will pass once API is changed, but checking this in with the existing API so that if we decide to alter behavior, we have a reference point. * Remove Username from Credentials API - Removed Username option from Credentials as this caused conflicting behavior on Mac vs Windows * Cleanup Using Statements and add Copyright * Linux CredentialStore Prototype * Linux credential store support - Full support for Linux credential store with tests * Plumbed CredentialService into Program init * Addressing Pull Request comments
This commit is contained in:
@@ -9,7 +9,6 @@ using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// A Credential containing information needed to log into a resource. This is primarily
|
||||
/// defined as a unique <see cref="CredentialId"/> with an associated <see cref="Password"/>
|
||||
/// that's linked to it.
|
||||
/// </summary>
|
||||
public class Credential
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique ID to identify the credential being saved.
|
||||
/// </summary>
|
||||
public string CredentialId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Password stored for this credential.
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default Constructor
|
||||
/// </summary>
|
||||
public Credential()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used when only <paramref name="credentialId"/> is known
|
||||
/// </summary>
|
||||
/// <param name="credentialId"><see cref="CredentialId"/></param>
|
||||
public Credential(string credentialId)
|
||||
: this(credentialId, null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="credentialId"><see cref="CredentialId"/></param>
|
||||
/// <param name="password"><see cref="Password"/></param>
|
||||
public Credential(string credentialId, string password)
|
||||
{
|
||||
CredentialId = credentialId;
|
||||
Password = password;
|
||||
}
|
||||
|
||||
internal static Credential Copy(Credential credential)
|
||||
{
|
||||
return new Credential
|
||||
{
|
||||
CredentialId = credential.CredentialId,
|
||||
Password = credential.Password
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the credential has all the properties needed to look up the password
|
||||
/// </summary>
|
||||
public static void ValidateForLookup(Credential credential)
|
||||
{
|
||||
Validate.IsNotNull("credential", credential);
|
||||
Validate.IsNotNullOrEmptyString("credential.CredentialId", credential.CredentialId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Validates the credential has all the properties needed to save a password
|
||||
/// </summary>
|
||||
public static void ValidateForSave(Credential credential)
|
||||
{
|
||||
ValidateForLookup(credential);
|
||||
Validate.IsNotNullOrEmptyString("credential.Password", credential.Password);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read Credential request mapping entry. Expects a Credential with CredentialId,
|
||||
/// and responds with the <see cref="Credential.Password"/> filled in if found
|
||||
/// </summary>
|
||||
public class ReadCredentialRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Request definition
|
||||
/// </summary>
|
||||
public static readonly
|
||||
RequestType<Credential, Credential> Type =
|
||||
RequestType<Credential, Credential>.Create("credential/read");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save Credential request mapping entry
|
||||
/// </summary>
|
||||
public class SaveCredentialRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Request definition
|
||||
/// </summary>
|
||||
public static readonly
|
||||
RequestType<Credential, bool> Type =
|
||||
RequestType<Credential, bool>.Create("credential/save");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Credential request mapping entry
|
||||
/// </summary>
|
||||
public class DeleteCredentialRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Request definition
|
||||
/// </summary>
|
||||
public static readonly
|
||||
RequestType<Credential, bool> Type =
|
||||
RequestType<Credential, bool>.Create("credential/delete");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.OSX;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
/// <summary>
|
||||
/// Service responsible for securing credentials in a platform-neutral manner. This provides
|
||||
/// a generic API for read, save and delete credentials
|
||||
/// </summary>
|
||||
public class CredentialService
|
||||
{
|
||||
internal static string DefaultSecretsFolder = ".sqlsecrets";
|
||||
internal const string DefaultSecretsFile = "sqlsecrets.json";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Singleton service instance
|
||||
/// </summary>
|
||||
private static Lazy<CredentialService> instance
|
||||
= new Lazy<CredentialService>(() => new CredentialService());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton service instance
|
||||
/// </summary>
|
||||
public static CredentialService Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private ICredentialStore credStore;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor is private since it's a singleton class
|
||||
/// </summary>
|
||||
private CredentialService()
|
||||
: this(null, new LinuxCredentialStore.StoreConfig()
|
||||
{ CredentialFolder = DefaultSecretsFolder, CredentialFile = DefaultSecretsFile, IsRelativeToUserHomeDir = true})
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal CredentialService(ICredentialStore store, LinuxCredentialStore.StoreConfig config)
|
||||
{
|
||||
this.credStore = store != null ? store : GetStoreForOS(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal static ICredentialStore GetStoreForOS(LinuxCredentialStore.StoreConfig config)
|
||||
{
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return new Win32CredentialStore();
|
||||
}
|
||||
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return new OSXCredentialStore();
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return new LinuxCredentialStore(config);
|
||||
}
|
||||
throw new InvalidOperationException("Platform not currently supported");
|
||||
}
|
||||
|
||||
public void InitializeService(IProtocolEndpoint serviceHost)
|
||||
{
|
||||
// Register request and event handlers with the Service Host
|
||||
serviceHost.SetRequestHandler(ReadCredentialRequest.Type, HandleReadCredentialRequest);
|
||||
serviceHost.SetRequestHandler(SaveCredentialRequest.Type, HandleSaveCredentialRequest);
|
||||
serviceHost.SetRequestHandler(DeleteCredentialRequest.Type, HandleDeleteCredentialRequest);
|
||||
}
|
||||
|
||||
public async Task HandleReadCredentialRequest(Credential credential, RequestContext<Credential> requestContext)
|
||||
{
|
||||
Func<Credential> doRead = () =>
|
||||
{
|
||||
return ReadCredential(credential);
|
||||
};
|
||||
await HandleRequest(doRead, requestContext, "HandleReadCredentialRequest");
|
||||
}
|
||||
|
||||
|
||||
private Credential ReadCredential(Credential credential)
|
||||
{
|
||||
Credential.ValidateForLookup(credential);
|
||||
|
||||
Credential result = Credential.Copy(credential);
|
||||
string password;
|
||||
if (credStore.TryGetPassword(credential.CredentialId, out password))
|
||||
{
|
||||
result.Password = password;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task HandleSaveCredentialRequest(Credential credential, RequestContext<bool> requestContext)
|
||||
{
|
||||
Func<bool> doSave = () =>
|
||||
{
|
||||
Credential.ValidateForSave(credential);
|
||||
return credStore.Save(credential);
|
||||
};
|
||||
await HandleRequest(doSave, requestContext, "HandleSaveCredentialRequest");
|
||||
}
|
||||
|
||||
public async Task HandleDeleteCredentialRequest(Credential credential, RequestContext<bool> requestContext)
|
||||
{
|
||||
Func<bool> doDelete = () =>
|
||||
{
|
||||
Credential.ValidateForLookup(credential);
|
||||
return credStore.DeletePassword(credential.CredentialId);
|
||||
};
|
||||
await HandleRequest(doDelete, requestContext, "HandleDeleteCredentialRequest");
|
||||
}
|
||||
|
||||
private async Task HandleRequest<T>(Func<T> handler, RequestContext<T> requestContext, string requestType)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, requestType);
|
||||
|
||||
try
|
||||
{
|
||||
T result = handler();
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await requestContext.SendError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="ICredentialStore"/> support securely saving and retrieving passwords
|
||||
/// </summary>
|
||||
public interface ICredentialStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves a Password linked to a given Credential
|
||||
/// </summary>
|
||||
/// <param name="credential">
|
||||
/// A <see cref="Credential"/> to be saved.
|
||||
/// <see cref="Credential.CredentialId"/> and <see cref="Credential.Password"/> are required
|
||||
/// </param>
|
||||
/// <returns>True if successful, false otherwise</returns>
|
||||
bool Save(Credential credential);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Password and sets it into a <see cref="Credential"/> object
|
||||
/// </summary>
|
||||
/// <param name="credentialId">The name of the credential to find the password for. This is required</param>
|
||||
/// <param name="password">Out value</param>
|
||||
/// <returns>true if password was found, false otherwise</returns>
|
||||
bool TryGetPassword(string credentialId, out string password);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a password linked to a given credential
|
||||
/// </summary>
|
||||
/// <param name="credentialId">The name of the credential to find the password for. This is required</param>
|
||||
/// <returns>True if password existed and was deleted, false otherwise</returns>
|
||||
bool DeletePassword(string credentialId);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
internal static class InteropUtils
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length in bytes for a Unicode string, for use in interop where length must be defined
|
||||
/// </summary>
|
||||
public static UInt32 GetLengthInBytes(string value)
|
||||
{
|
||||
|
||||
return Convert.ToUInt32( (value != null ? Encoding.Unicode.GetByteCount(value) : 0) );
|
||||
}
|
||||
|
||||
public static string CopyToString(IntPtr ptr, int length)
|
||||
{
|
||||
if (ptr == IntPtr.Zero || length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] pwdBytes = new byte[length];
|
||||
Marshal.Copy(ptr, pwdBytes, 0, (int)length);
|
||||
return Encoding.Unicode.GetString(pwdBytes, 0, (int)length);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
/// <summary>
|
||||
/// Simplified class to enable writing a set of credentials to/from disk
|
||||
/// </summary>
|
||||
public class CredentialsWrapper
|
||||
{
|
||||
public List<Credential> Credentials { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
public class FileTokenStorage
|
||||
{
|
||||
private const int OwnerAccessMode = 384; // Permission 0600 - owner read/write, nobody else has access
|
||||
|
||||
private object lockObject = new object();
|
||||
|
||||
private string fileName;
|
||||
|
||||
public FileTokenStorage(string fileName)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("fileName", fileName);
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public void AddEntries(IEnumerable<Credential> newEntries, IEnumerable<Credential> existingEntries)
|
||||
{
|
||||
var allEntries = existingEntries.Concat(newEntries);
|
||||
this.SaveEntries(allEntries);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.SaveEntries(new List<Credential>());
|
||||
}
|
||||
|
||||
public IEnumerable<Credential> LoadEntries()
|
||||
{
|
||||
if(!File.Exists(this.fileName))
|
||||
{
|
||||
return Enumerable.Empty<Credential>();
|
||||
}
|
||||
|
||||
string serializedCreds;
|
||||
lock (lockObject)
|
||||
{
|
||||
serializedCreds = File.ReadAllText(this.fileName);
|
||||
}
|
||||
|
||||
CredentialsWrapper creds = JsonConvert.DeserializeObject<CredentialsWrapper>(serializedCreds, Constants.JsonSerializerSettings);
|
||||
if(creds != null)
|
||||
{
|
||||
return creds.Credentials;
|
||||
}
|
||||
return Enumerable.Empty<Credential>();
|
||||
}
|
||||
|
||||
public void SaveEntries(IEnumerable<Credential> entries)
|
||||
{
|
||||
CredentialsWrapper credentials = new CredentialsWrapper() { Credentials = entries.ToList() };
|
||||
string serializedCreds = JsonConvert.SerializeObject(credentials, Constants.JsonSerializerSettings);
|
||||
|
||||
lock(lockObject)
|
||||
{
|
||||
WriteToFile(this.fileName, serializedCreds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteToFile(string filePath, string fileContents)
|
||||
{
|
||||
string dir = Path.GetDirectoryName(filePath);
|
||||
if(!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
// Overwrite file, then use ChMod to ensure we have
|
||||
File.WriteAllText(filePath, fileContents);
|
||||
// set appropriate permissions so only current user can read/write
|
||||
Interop.Sys.ChMod(filePath, OwnerAccessMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
internal static partial class Interop
|
||||
{
|
||||
/// <summary>Common Unix errno error codes.</summary>
|
||||
internal enum Error
|
||||
{
|
||||
// These values were defined in src/Native/System.Native/fxerrno.h
|
||||
//
|
||||
// They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error()
|
||||
// which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to
|
||||
// prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to
|
||||
// correlate these to the underlying platform values or obtain the corresponding error message.
|
||||
//
|
||||
|
||||
SUCCESS = 0,
|
||||
|
||||
E2BIG = 0x10001, // Argument list too long.
|
||||
EACCES = 0x10002, // Permission denied.
|
||||
EADDRINUSE = 0x10003, // Address in use.
|
||||
EADDRNOTAVAIL = 0x10004, // Address not available.
|
||||
EAFNOSUPPORT = 0x10005, // Address family not supported.
|
||||
EAGAIN = 0x10006, // Resource unavailable, try again (same value as EWOULDBLOCK),
|
||||
EALREADY = 0x10007, // Connection already in progress.
|
||||
EBADF = 0x10008, // Bad file descriptor.
|
||||
EBADMSG = 0x10009, // Bad message.
|
||||
EBUSY = 0x1000A, // Device or resource busy.
|
||||
ECANCELED = 0x1000B, // Operation canceled.
|
||||
ECHILD = 0x1000C, // No child processes.
|
||||
ECONNABORTED = 0x1000D, // Connection aborted.
|
||||
ECONNREFUSED = 0x1000E, // Connection refused.
|
||||
ECONNRESET = 0x1000F, // Connection reset.
|
||||
EDEADLK = 0x10010, // Resource deadlock would occur.
|
||||
EDESTADDRREQ = 0x10011, // Destination address required.
|
||||
EDOM = 0x10012, // Mathematics argument out of domain of function.
|
||||
EDQUOT = 0x10013, // Reserved.
|
||||
EEXIST = 0x10014, // File exists.
|
||||
EFAULT = 0x10015, // Bad address.
|
||||
EFBIG = 0x10016, // File too large.
|
||||
EHOSTUNREACH = 0x10017, // Host is unreachable.
|
||||
EIDRM = 0x10018, // Identifier removed.
|
||||
EILSEQ = 0x10019, // Illegal byte sequence.
|
||||
EINPROGRESS = 0x1001A, // Operation in progress.
|
||||
EINTR = 0x1001B, // Interrupted function.
|
||||
EINVAL = 0x1001C, // Invalid argument.
|
||||
EIO = 0x1001D, // I/O error.
|
||||
EISCONN = 0x1001E, // Socket is connected.
|
||||
EISDIR = 0x1001F, // Is a directory.
|
||||
ELOOP = 0x10020, // Too many levels of symbolic links.
|
||||
EMFILE = 0x10021, // File descriptor value too large.
|
||||
EMLINK = 0x10022, // Too many links.
|
||||
EMSGSIZE = 0x10023, // Message too large.
|
||||
EMULTIHOP = 0x10024, // Reserved.
|
||||
ENAMETOOLONG = 0x10025, // Filename too long.
|
||||
ENETDOWN = 0x10026, // Network is down.
|
||||
ENETRESET = 0x10027, // Connection aborted by network.
|
||||
ENETUNREACH = 0x10028, // Network unreachable.
|
||||
ENFILE = 0x10029, // Too many files open in system.
|
||||
ENOBUFS = 0x1002A, // No buffer space available.
|
||||
ENODEV = 0x1002C, // No such device.
|
||||
ENOENT = 0x1002D, // No such file or directory.
|
||||
ENOEXEC = 0x1002E, // Executable file format error.
|
||||
ENOLCK = 0x1002F, // No locks available.
|
||||
ENOLINK = 0x10030, // Reserved.
|
||||
ENOMEM = 0x10031, // Not enough space.
|
||||
ENOMSG = 0x10032, // No message of the desired type.
|
||||
ENOPROTOOPT = 0x10033, // Protocol not available.
|
||||
ENOSPC = 0x10034, // No space left on device.
|
||||
ENOSYS = 0x10037, // Function not supported.
|
||||
ENOTCONN = 0x10038, // The socket is not connected.
|
||||
ENOTDIR = 0x10039, // Not a directory or a symbolic link to a directory.
|
||||
ENOTEMPTY = 0x1003A, // Directory not empty.
|
||||
ENOTSOCK = 0x1003C, // Not a socket.
|
||||
ENOTSUP = 0x1003D, // Not supported (same value as EOPNOTSUP).
|
||||
ENOTTY = 0x1003E, // Inappropriate I/O control operation.
|
||||
ENXIO = 0x1003F, // No such device or address.
|
||||
EOVERFLOW = 0x10040, // Value too large to be stored in data type.
|
||||
EPERM = 0x10042, // Operation not permitted.
|
||||
EPIPE = 0x10043, // Broken pipe.
|
||||
EPROTO = 0x10044, // Protocol error.
|
||||
EPROTONOSUPPORT = 0x10045, // Protocol not supported.
|
||||
EPROTOTYPE = 0x10046, // Protocol wrong type for socket.
|
||||
ERANGE = 0x10047, // Result too large.
|
||||
EROFS = 0x10048, // Read-only file system.
|
||||
ESPIPE = 0x10049, // Invalid seek.
|
||||
ESRCH = 0x1004A, // No such process.
|
||||
ESTALE = 0x1004B, // Reserved.
|
||||
ETIMEDOUT = 0x1004D, // Connection timed out.
|
||||
ETXTBSY = 0x1004E, // Text file busy.
|
||||
EXDEV = 0x1004F, // Cross-device link.
|
||||
ESOCKTNOSUPPORT = 0x1005E, // Socket type not supported.
|
||||
EPFNOSUPPORT = 0x10060, // Protocol family not supported.
|
||||
ESHUTDOWN = 0x1006C, // Socket shutdown.
|
||||
EHOSTDOWN = 0x10070, // Host is down.
|
||||
ENODATA = 0x10071, // No data available.
|
||||
|
||||
// POSIX permits these to have the same value and we make them always equal so
|
||||
// that CoreFX cannot introduce a dependency on distinguishing between them that
|
||||
// would not work on all platforms.
|
||||
EOPNOTSUPP = ENOTSUP, // Operation not supported on socket.
|
||||
EWOULDBLOCK = EAGAIN, // Operation would block.
|
||||
}
|
||||
|
||||
|
||||
// Represents a platform-agnostic Error and underlying platform-specific errno
|
||||
internal struct ErrorInfo
|
||||
{
|
||||
private Error _error;
|
||||
private int _rawErrno;
|
||||
|
||||
internal ErrorInfo(int errno)
|
||||
{
|
||||
_error = Interop.Sys.ConvertErrorPlatformToPal(errno);
|
||||
_rawErrno = errno;
|
||||
}
|
||||
|
||||
internal ErrorInfo(Error error)
|
||||
{
|
||||
_error = error;
|
||||
_rawErrno = -1;
|
||||
}
|
||||
|
||||
internal Error Error
|
||||
{
|
||||
get { return _error; }
|
||||
}
|
||||
|
||||
internal int RawErrno
|
||||
{
|
||||
get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform(_error)) : _rawErrno; }
|
||||
}
|
||||
|
||||
internal string GetErrorMessage()
|
||||
{
|
||||
return Interop.Sys.StrError(RawErrno);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
"RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes
|
||||
RawErrno, Error, GetErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class Sys
|
||||
{
|
||||
internal static Error GetLastError()
|
||||
{
|
||||
return ConvertErrorPlatformToPal(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
internal static ErrorInfo GetLastErrorInfo()
|
||||
{
|
||||
return new ErrorInfo(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
internal static string StrError(int platformErrno)
|
||||
{
|
||||
int maxBufferLength = 1024; // should be long enough for most any UNIX error
|
||||
IntPtr buffer = Marshal.AllocHGlobal(maxBufferLength);
|
||||
try
|
||||
{
|
||||
IntPtr message = StrErrorR(platformErrno, buffer, maxBufferLength);
|
||||
|
||||
if (message == IntPtr.Zero)
|
||||
{
|
||||
// This means the buffer was not large enough, but still contains
|
||||
// as much of the error message as possible and is guaranteed to
|
||||
// be null-terminated. We're not currently resizing/retrying because
|
||||
// maxBufferLength is large enough in practice, but we could do
|
||||
// so here in the future if necessary.
|
||||
message = buffer;
|
||||
}
|
||||
|
||||
string returnMsg = Marshal.PtrToStringAnsi(message);
|
||||
return returnMsg;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Deallocate the buffer we created
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")]
|
||||
internal static extern Error ConvertErrorPlatformToPal(int platformErrno);
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")]
|
||||
internal static extern int ConvertErrorPalToPlatform(Error error);
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")]
|
||||
private static extern IntPtr StrErrorR(int platformErrno, IntPtr buffer, int bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: extension method can't be nested inside Interop class.
|
||||
internal static class InteropErrorExtensions
|
||||
{
|
||||
// Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity
|
||||
// vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing
|
||||
// errors. Errors originated from the system should be obtained
|
||||
// via GetLastErrorInfo(), not GetLastError().Info() as that will
|
||||
// convert twice, which is not only inefficient but also lossy if
|
||||
// we ever encounter a raw errno that no equivalent in the Error
|
||||
// enum.
|
||||
public static Interop.ErrorInfo Info(this Interop.Error error)
|
||||
{
|
||||
return new Interop.ErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class Sys
|
||||
{
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ChMod", SetLastError = true)]
|
||||
internal static extern int ChMod(string path, int mode);
|
||||
|
||||
internal struct Passwd
|
||||
{
|
||||
internal IntPtr Name; // char*
|
||||
internal IntPtr Password; // char*
|
||||
internal uint UserId;
|
||||
internal uint GroupId;
|
||||
internal IntPtr UserInfo; // char*
|
||||
internal IntPtr HomeDirectory; // char*
|
||||
internal IntPtr Shell; // char*
|
||||
};
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetPwUidR", SetLastError = false)]
|
||||
internal static extern int GetPwUidR(uint uid, out Passwd pwd, IntPtr buf, int bufLen);
|
||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetEUid")]
|
||||
internal static extern uint GetEUid();
|
||||
|
||||
private static partial class Libraries
|
||||
{
|
||||
internal const string SystemNative = "System.Native";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
/// <summary>
|
||||
/// Linux implementation of the credential store.
|
||||
///
|
||||
/// <remarks>
|
||||
/// This entire implementation may need to be revised to support encryption of
|
||||
/// passwords and protection of them when loaded into memory.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
internal class LinuxCredentialStore : ICredentialStore
|
||||
{
|
||||
internal struct StoreConfig
|
||||
{
|
||||
public string CredentialFolder { get; set; }
|
||||
public string CredentialFile { get; set; }
|
||||
public bool IsRelativeToUserHomeDir { get; set; }
|
||||
}
|
||||
|
||||
private string credentialFolderPath;
|
||||
private string credentialFileName;
|
||||
private FileTokenStorage storage;
|
||||
|
||||
public LinuxCredentialStore(StoreConfig config)
|
||||
{
|
||||
Validate.IsNotNull("config", config);
|
||||
Validate.IsNotNullOrEmptyString("credentialFolder", config.CredentialFolder);
|
||||
Validate.IsNotNullOrEmptyString("credentialFileName", config.CredentialFile);
|
||||
|
||||
this.credentialFolderPath = config.IsRelativeToUserHomeDir ? GetUserScopedDirectory(config.CredentialFolder) : config.CredentialFolder;
|
||||
this.credentialFileName = config.CredentialFile;
|
||||
|
||||
|
||||
string combinedPath = Path.Combine(this.credentialFolderPath, this.credentialFileName);
|
||||
storage = new FileTokenStorage(combinedPath);
|
||||
}
|
||||
|
||||
public bool DeletePassword(string credentialId)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
IEnumerable<Credential> creds;
|
||||
if (LoadCredentialsAndFilterById(credentialId, out creds))
|
||||
{
|
||||
storage.SaveEntries(creds);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets filtered credentials with a specific ID filtered out
|
||||
/// </summary>
|
||||
/// <returns>True if the credential to filter was removed, false if it was not found</returns>
|
||||
private bool LoadCredentialsAndFilterById(string idToFilter, out IEnumerable<Credential> creds)
|
||||
{
|
||||
bool didRemove = false;
|
||||
creds = storage.LoadEntries().Where(cred =>
|
||||
{
|
||||
if (IsCredentialMatch(idToFilter, cred))
|
||||
{
|
||||
didRemove = true;
|
||||
return false; // filter this out
|
||||
}
|
||||
return true;
|
||||
}).ToList(); // Call ToList ensures Where clause is executed so didRemove can be evaluated
|
||||
|
||||
return didRemove;
|
||||
}
|
||||
|
||||
private static bool IsCredentialMatch(string credentialId, Credential cred)
|
||||
{
|
||||
return string.Equals(credentialId, cred.CredentialId, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public bool TryGetPassword(string credentialId, out string password)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
Credential cred = storage.LoadEntries().FirstOrDefault(c => IsCredentialMatch(credentialId, c));
|
||||
if (cred != null)
|
||||
{
|
||||
password = cred.Password;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Else this was not found in the list
|
||||
password = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Save(Credential credential)
|
||||
{
|
||||
Credential.ValidateForSave(credential);
|
||||
|
||||
// Load the credentials, removing the existing Cred for this
|
||||
IEnumerable<Credential> creds;
|
||||
LoadCredentialsAndFilterById(credential.CredentialId, out creds);
|
||||
storage.SaveEntries(creds.Append(credential));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal string CredentialFolderPath
|
||||
{
|
||||
get { return this.credentialFolderPath; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates a directory to the user home directory's path
|
||||
/// </summary>
|
||||
internal static string GetUserScopedDirectory(string userPath)
|
||||
{
|
||||
string homeDir = GetHomeDirectory() ?? string.Empty;
|
||||
return Path.Combine(homeDir, userPath);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Gets the current user's home directory.</summary>
|
||||
/// <returns>The path to the home directory, or null if it could not be determined.</returns>
|
||||
internal static string GetHomeDirectory()
|
||||
{
|
||||
// First try to get the user's home directory from the HOME environment variable.
|
||||
// This should work in most cases.
|
||||
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
|
||||
if (!string.IsNullOrEmpty(userHomeDirectory))
|
||||
{
|
||||
return userHomeDirectory;
|
||||
}
|
||||
|
||||
// In initialization conditions, however, the "HOME" environment variable may
|
||||
// not yet be set. For such cases, consult with the password entry.
|
||||
|
||||
// First try with a buffer that should suffice for 99% of cases.
|
||||
// Note that, theoretically, userHomeDirectory may be null in the success case
|
||||
// if we simply couldn't find a home directory for the current user.
|
||||
// In that case, we pass back the null value and let the caller decide
|
||||
// what to do.
|
||||
return GetHomeDirectoryFromPw();
|
||||
}
|
||||
|
||||
internal static string GetHomeDirectoryFromPw()
|
||||
{
|
||||
string userHomeDirectory = null;
|
||||
const int BufLen = 1024;
|
||||
if (TryGetHomeDirectoryFromPasswd(BufLen, out userHomeDirectory))
|
||||
{
|
||||
return userHomeDirectory;
|
||||
}
|
||||
// Fallback to heap allocations if necessary, growing the buffer until
|
||||
// we succeed. TryGetHomeDirectory will throw if there's an unexpected error.
|
||||
int lastBufLen = BufLen;
|
||||
while (true)
|
||||
{
|
||||
lastBufLen *= 2;
|
||||
if (TryGetHomeDirectoryFromPasswd(lastBufLen, out userHomeDirectory))
|
||||
{
|
||||
return userHomeDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Wrapper for getpwuid_r.</summary>
|
||||
/// <param name="bufLen">The length of the buffer to use when storing the password result.</param>
|
||||
/// <param name="path">The resulting path; null if the user didn't have an entry.</param>
|
||||
/// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns>
|
||||
private static bool TryGetHomeDirectoryFromPasswd(int bufLen, out string path)
|
||||
{
|
||||
// Call getpwuid_r to get the passwd struct
|
||||
Interop.Sys.Passwd passwd;
|
||||
IntPtr buffer = Marshal.AllocHGlobal(bufLen);
|
||||
try
|
||||
{
|
||||
int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buffer, bufLen);
|
||||
|
||||
// If the call succeeds, give back the home directory path retrieved
|
||||
if (error == 0)
|
||||
{
|
||||
Debug.Assert(passwd.HomeDirectory != IntPtr.Zero);
|
||||
path = Marshal.PtrToStringAnsi(passwd.HomeDirectory);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the current user's entry could not be found, give back null
|
||||
// path, but still return true as false indicates the buffer was
|
||||
// too small.
|
||||
if (error == -1)
|
||||
{
|
||||
path = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
var errorInfo = new Interop.ErrorInfo(error);
|
||||
|
||||
// If the call failed because the buffer was too small, return false to
|
||||
// indicate the caller should try again with a larger buffer.
|
||||
if (errorInfo.Error == Interop.Error.ERANGE)
|
||||
{
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, fail.
|
||||
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Deallocate the buffer we created
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class CoreFoundation
|
||||
{
|
||||
/// <summary>
|
||||
/// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework.
|
||||
/// </summary>
|
||||
private enum CFStringBuiltInEncodings : uint
|
||||
{
|
||||
kCFStringEncodingMacRoman = 0,
|
||||
kCFStringEncodingWindowsLatin1 = 0x0500,
|
||||
kCFStringEncodingISOLatin1 = 0x0201,
|
||||
kCFStringEncodingNextStepLatin = 0x0B01,
|
||||
kCFStringEncodingASCII = 0x0600,
|
||||
kCFStringEncodingUnicode = 0x0100,
|
||||
kCFStringEncodingUTF8 = 0x08000100,
|
||||
kCFStringEncodingNonLossyASCII = 0x0BFF,
|
||||
|
||||
kCFStringEncodingUTF16 = 0x0100,
|
||||
kCFStringEncodingUTF16BE = 0x10000100,
|
||||
kCFStringEncodingUTF16LE = 0x14000100,
|
||||
kCFStringEncodingUTF32 = 0x0c000100,
|
||||
kCFStringEncodingUTF32BE = 0x18000100,
|
||||
kCFStringEncodingUTF32LE = 0x1c000100
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
|
||||
/// </summary>
|
||||
/// <param name="allocator">Should be IntPtr.Zero</param>
|
||||
/// <param name="str">The string to get a CFStringRef for</param>
|
||||
/// <param name="encoding">The encoding of the str variable. This should be UTF 8 for OS X</param>
|
||||
/// <returns>Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero</returns>
|
||||
/// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks>
|
||||
[DllImport(Interop.Libraries.CoreFoundationLibrary, CharSet = CharSet.Ansi)]
|
||||
private static extern SafeCreateHandle CFStringCreateWithCString(
|
||||
IntPtr allocator,
|
||||
string str,
|
||||
CFStringBuiltInEncodings encoding);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to get a CFStringRef for</param>
|
||||
/// <returns>Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle</returns>
|
||||
internal static SafeCreateHandle CFStringCreateWithCString(string str)
|
||||
{
|
||||
return CFStringCreateWithCString(IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
|
||||
/// </summary>
|
||||
/// <param name="allocator">Should be IntPtr.Zero</param>
|
||||
/// <param name="values">The values to put in the array</param>
|
||||
/// <param name="numValues">The number of values in the array</param>
|
||||
/// <param name="callbacks">Should be IntPtr.Zero</param>
|
||||
/// <returns>Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero</returns>
|
||||
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
|
||||
private static extern SafeCreateHandle CFArrayCreate(
|
||||
IntPtr allocator,
|
||||
[MarshalAs(UnmanagedType.LPArray)]
|
||||
IntPtr[] values,
|
||||
ulong numValues,
|
||||
IntPtr callbacks);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
|
||||
/// </summary>
|
||||
/// <param name="values">The values to put in the array</param>
|
||||
/// <param name="numValues">The number of values in the array</param>
|
||||
/// <returns>Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle</returns>
|
||||
internal static SafeCreateHandle CFArrayCreate(IntPtr[] values, ulong numValues)
|
||||
{
|
||||
return CFArrayCreate(IntPtr.Zero, values, numValues, IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// You should retain a Core Foundation object when you receive it from elsewhere
|
||||
/// (that is, you did not create or copy it) and you want it to persist. If you
|
||||
/// retain a Core Foundation object you are responsible for releasing it
|
||||
/// </summary>
|
||||
/// <param name="ptr">The CFType object to retain. This value must not be NULL</param>
|
||||
/// <returns>The input value</param>
|
||||
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
|
||||
internal extern static IntPtr CFRetain(IntPtr ptr);
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer on which to decrement the reference count.</param>
|
||||
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
|
||||
internal extern static void CFRelease(IntPtr ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
internal static partial class Interop
|
||||
{
|
||||
private static partial class Libraries
|
||||
{
|
||||
internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
|
||||
internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices";
|
||||
internal const string SecurityLibrary = "/System/Library/Frameworks/Security.framework/Security";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
internal partial class Interop
|
||||
{
|
||||
internal partial class Security
|
||||
{
|
||||
|
||||
[DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern OSStatus SecKeychainAddGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
|
||||
UInt32 accountNameLength, string accountName, UInt32 passwordLength, IntPtr password, [Out] IntPtr itemRef);
|
||||
|
||||
/// <summary>
|
||||
/// Find a generic password based on the attributes passed
|
||||
/// </summary>
|
||||
/// <param name="keyChainRef">
|
||||
/// A reference to an array of keychains to search, a single keychain, or NULL to search the user's default keychain search list.
|
||||
/// </param>
|
||||
/// <param name="serviceNameLength">The length of the buffer pointed to by serviceName.</param>
|
||||
/// <param name="serviceName">A pointer to a string containing the service name.</param>
|
||||
/// <param name="accountNameLength">The length of the buffer pointed to by accountName.</param>
|
||||
/// <param name="accountName">A pointer to a string containing the account name.</param>
|
||||
/// <param name="passwordLength">On return, the length of the buffer pointed to by passwordData.</param>
|
||||
/// <param name="password">
|
||||
/// On return, a pointer to a data buffer containing the password.
|
||||
/// Your application must call SecKeychainItemFreeContent(NULL, passwordData)
|
||||
/// to release this data buffer when it is no longer needed.Pass NULL if you are not interested in retrieving the password data at
|
||||
/// this time, but simply want to find the item reference.
|
||||
/// </param>
|
||||
/// <param name="itemRef">On return, a reference to the keychain item which was found.</param>
|
||||
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
|
||||
/// <remarks>
|
||||
/// The SecKeychainFindGenericPassword function finds the first generic password item which matches the attributes you provide.
|
||||
/// Most attributes are optional; you should pass only as many as you need to narrow the search sufficiently for your application's intended use.
|
||||
/// SecKeychainFindGenericPassword optionally returns a reference to the found item.
|
||||
/// </remarks>
|
||||
[DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern OSStatus SecKeychainFindGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
|
||||
UInt32 accountNameLength, string accountName, out UInt32 passwordLength, out IntPtr password, out IntPtr itemRef);
|
||||
|
||||
/// <summary>
|
||||
/// Releases the memory used by the keychain attribute list and the keychain data retrieved in a previous call to SecKeychainItemCopyContent.
|
||||
/// </summary>
|
||||
/// <param name="attrList">A pointer to the attribute list to release. Pass NULL to ignore this parameter.</param>
|
||||
/// <param name="data">A pointer to the data buffer to release. Pass NULL to ignore this parameter.</param>
|
||||
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
|
||||
[DllImport(Libraries.SecurityLibrary, SetLastError = true)]
|
||||
internal static extern OSStatus SecKeychainItemFreeContent([In] IntPtr attrList, [In] IntPtr data);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a keychain item from the default keychain's permanent data store.
|
||||
/// </summary>
|
||||
/// <param name="itemRef">A keychain item reference of the item to delete.</param>
|
||||
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
|
||||
/// <remarks>
|
||||
/// If itemRef has not previously been added to the keychain, SecKeychainItemDelete does nothing and returns ErrSecSuccess.
|
||||
/// IMPORTANT: SecKeychainItemDelete does not dispose the memory occupied by the item reference itself;
|
||||
/// use the CFRelease function when you are completely * * finished with an item.
|
||||
/// </remarks>
|
||||
[DllImport(Libraries.SecurityLibrary, SetLastError = true)]
|
||||
internal static extern OSStatus SecKeychainItemDelete(SafeHandle itemRef);
|
||||
|
||||
#region OSStatus Codes
|
||||
/// <summary>Common Unix errno error codes.</summary>
|
||||
internal enum OSStatus
|
||||
{
|
||||
ErrSecSuccess = 0, /* No error. */
|
||||
ErrSecUnimplemented = -4, /* Function or operation not implemented. */
|
||||
ErrSecDskFull = -34,
|
||||
ErrSecIO = -36, /*I/O error (bummers)*/
|
||||
|
||||
ErrSecParam = -50, /* One or more parameters passed to a function were not valid. */
|
||||
ErrSecWrPerm = -61, /* write permissions error*/
|
||||
ErrSecAllocate = -108, /* Failed to allocate memory. */
|
||||
ErrSecUserCanceled = -128, /* User canceled the operation. */
|
||||
ErrSecBadReq = -909, /* Bad parameter or invalid state for operation. */
|
||||
|
||||
ErrSecInternalComponent = -2070,
|
||||
ErrSecCoreFoundationUnknown = -4960,
|
||||
|
||||
ErrSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
|
||||
ErrSecReadOnly = -25292, /* This keychain cannot be modified. */
|
||||
ErrSecAuthFailed = -25293, /* The user name or passphrase you entered is not correct. */
|
||||
ErrSecNoSuchKeychain = -25294, /* The specified keychain could not be found. */
|
||||
ErrSecInvalidKeychain = -25295, /* The specified keychain is not a valid keychain file. */
|
||||
ErrSecDuplicateKeychain = -25296, /* A keychain with the same name already exists. */
|
||||
ErrSecDuplicateCallback = -25297, /* The specified callback function is already installed. */
|
||||
ErrSecInvalidCallback = -25298, /* The specified callback function is not valid. */
|
||||
ErrSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
|
||||
ErrSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
|
||||
ErrSecBufferTooSmall = -25301, /* There is not enough memory available to use the specified item. */
|
||||
ErrSecDataTooLarge = -25302, /* This item contains information which is too large or in a format that cannot be displayed. */
|
||||
ErrSecNoSuchAttr = -25303, /* The specified attribute does not exist. */
|
||||
ErrSecInvalidItemRef = -25304, /* The specified item is no longer valid. It may have been deleted from the keychain. */
|
||||
ErrSecInvalidSearchRef = -25305, /* Unable to search the current keychain. */
|
||||
ErrSecNoSuchClass = -25306, /* The specified item does not appear to be a valid keychain item. */
|
||||
ErrSecNoDefaultKeychain = -25307, /* A default keychain could not be found. */
|
||||
ErrSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
|
||||
ErrSecReadOnlyAttr = -25309, /* The specified attribute could not be modified. */
|
||||
ErrSecWrongSecVersion = -25310, /* This keychain was created by a different version of the system software and cannot be opened. */
|
||||
ErrSecKeySizeNotAllowed = -25311, /* This item specifies a key size which is too large. */
|
||||
ErrSecNoStorageModule = -25312, /* A required component (data storage module) could not be loaded. You may need to restart your computer. */
|
||||
ErrSecNoCertificateModule = -25313, /* A required component (certificate module) could not be loaded. You may need to restart your computer. */
|
||||
ErrSecNoPolicyModule = -25314, /* A required component (policy module) could not be loaded. You may need to restart your computer. */
|
||||
ErrSecInteractionRequired = -25315, /* User interaction is required, but is currently not allowed. */
|
||||
ErrSecDataNotAvailable = -25316, /* The contents of this item cannot be retrieved. */
|
||||
ErrSecDataNotModifiable = -25317, /* The contents of this item cannot be modified. */
|
||||
ErrSecCreateChainFailed = -25318, /* One or more certificates required to validate this certificate cannot be found. */
|
||||
ErrSecInvalidPrefsDomain = -25319, /* The specified preferences domain is not valid. */
|
||||
ErrSecInDarkWake = -25320, /* In dark wake, no UI possible */
|
||||
|
||||
ErrSecACLNotSimple = -25240, /* The specified access control list is not in standard (simple) form. */
|
||||
ErrSecPolicyNotFound = -25241, /* The specified policy cannot be found. */
|
||||
ErrSecInvalidTrustSetting = -25242, /* The specified trust setting is invalid. */
|
||||
ErrSecNoAccessForItem = -25243, /* The specified item has no access control. */
|
||||
ErrSecInvalidOwnerEdit = -25244, /* Invalid attempt to change the owner of this item. */
|
||||
ErrSecTrustNotAvailable = -25245, /* No trust results are available. */
|
||||
ErrSecUnsupportedFormat = -25256, /* Import/Export format unsupported. */
|
||||
ErrSecUnknownFormat = -25257, /* Unknown format in import. */
|
||||
ErrSecKeyIsSensitive = -25258, /* Key material must be wrapped for export. */
|
||||
ErrSecMultiplePrivKeys = -25259, /* An attempt was made to import multiple private keys. */
|
||||
ErrSecPassphraseRequired = -25260, /* Passphrase is required for import/export. */
|
||||
ErrSecInvalidPasswordRef = -25261, /* The password reference was invalid. */
|
||||
ErrSecInvalidTrustSettings = -25262, /* The Trust Settings Record was corrupted. */
|
||||
ErrSecNoTrustSettings = -25263, /* No Trust Settings were found. */
|
||||
ErrSecPkcs12VerifyFailure = -25264, /* MAC verification failed during PKCS12 import (wrong password?) */
|
||||
ErrSecNotSigner = -26267, /* A certificate was not signed by its proposed parent. */
|
||||
|
||||
ErrSecDecode = -26275, /* Unable to decode the provided data. */
|
||||
|
||||
ErrSecServiceNotAvailable = -67585, /* The required service is not available. */
|
||||
ErrSecInsufficientClientID = -67586, /* The client ID is not correct. */
|
||||
ErrSecDeviceReset = -67587, /* A device reset has occurred. */
|
||||
ErrSecDeviceFailed = -67588, /* A device failure has occurred. */
|
||||
ErrSecAppleAddAppACLSubject = -67589, /* Adding an application ACL subject failed. */
|
||||
ErrSecApplePublicKeyIncomplete = -67590, /* The public key is incomplete. */
|
||||
ErrSecAppleSignatureMismatch = -67591, /* A signature mismatch has occurred. */
|
||||
ErrSecAppleInvalidKeyStartDate = -67592, /* The specified key has an invalid start date. */
|
||||
ErrSecAppleInvalidKeyEndDate = -67593, /* The specified key has an invalid end date. */
|
||||
ErrSecConversionError = -67594, /* A conversion error has occurred. */
|
||||
ErrSecAppleSSLv2Rollback = -67595, /* A SSLv2 rollback error has occurred. */
|
||||
ErrSecDiskFull = -34, /* The disk is full. */
|
||||
ErrSecQuotaExceeded = -67596, /* The quota was exceeded. */
|
||||
ErrSecFileTooBig = -67597, /* The file is too big. */
|
||||
ErrSecInvalidDatabaseBlob = -67598, /* The specified database has an invalid blob. */
|
||||
ErrSecInvalidKeyBlob = -67599, /* The specified database has an invalid key blob. */
|
||||
ErrSecIncompatibleDatabaseBlob = -67600, /* The specified database has an incompatible blob. */
|
||||
ErrSecIncompatibleKeyBlob = -67601, /* The specified database has an incompatible key blob. */
|
||||
ErrSecHostNameMismatch = -67602, /* A host name mismatch has occurred. */
|
||||
ErrSecUnknownCriticalExtensionFlag = -67603, /* There is an unknown critical extension flag. */
|
||||
ErrSecNoBasicConstraints = -67604, /* No basic constraints were found. */
|
||||
ErrSecNoBasicConstraintsCA = -67605, /* No basic CA constraints were found. */
|
||||
ErrSecInvalidAuthorityKeyID = -67606, /* The authority key ID is not valid. */
|
||||
ErrSecInvalidSubjectKeyID = -67607, /* The subject key ID is not valid. */
|
||||
ErrSecInvalidKeyUsageForPolicy = -67608, /* The key usage is not valid for the specified policy. */
|
||||
ErrSecInvalidExtendedKeyUsage = -67609, /* The extended key usage is not valid. */
|
||||
ErrSecInvalidIDLinkage = -67610, /* The ID linkage is not valid. */
|
||||
ErrSecPathLengthConstraintExceeded = -67611, /* The path length constraint was exceeded. */
|
||||
ErrSecInvalidRoot = -67612, /* The root or anchor certificate is not valid. */
|
||||
ErrSecCRLExpired = -67613, /* The CRL has expired. */
|
||||
ErrSecCRLNotValidYet = -67614, /* The CRL is not yet valid. */
|
||||
ErrSecCRLNotFound = -67615, /* The CRL was not found. */
|
||||
ErrSecCRLServerDown = -67616, /* The CRL server is down. */
|
||||
ErrSecCRLBadURI = -67617, /* The CRL has a bad Uniform Resource Identifier. */
|
||||
ErrSecUnknownCertExtension = -67618, /* An unknown certificate extension was encountered. */
|
||||
ErrSecUnknownCRLExtension = -67619, /* An unknown CRL extension was encountered. */
|
||||
ErrSecCRLNotTrusted = -67620, /* The CRL is not trusted. */
|
||||
ErrSecCRLPolicyFailed = -67621, /* The CRL policy failed. */
|
||||
ErrSecIDPFailure = -67622, /* The issuing distribution point was not valid. */
|
||||
ErrSecSMIMEEmailAddressesNotFound = -67623, /* An email address mismatch was encountered. */
|
||||
ErrSecSMIMEBadExtendedKeyUsage = -67624, /* The appropriate extended key usage for SMIME was not found. */
|
||||
ErrSecSMIMEBadKeyUsage = -67625, /* The key usage is not compatible with SMIME. */
|
||||
ErrSecSMIMEKeyUsageNotCritical = -67626, /* The key usage extension is not marked as critical. */
|
||||
ErrSecSMIMENoEmailAddress = -67627, /* No email address was found in the certificate. */
|
||||
ErrSecSMIMESubjAltNameNotCritical = -67628, /* The subject alternative name extension is not marked as critical. */
|
||||
ErrSecSSLBadExtendedKeyUsage = -67629, /* The appropriate extended key usage for SSL was not found. */
|
||||
ErrSecOCSPBadResponse = -67630, /* The OCSP response was incorrect or could not be parsed. */
|
||||
ErrSecOCSPBadRequest = -67631, /* The OCSP request was incorrect or could not be parsed. */
|
||||
ErrSecOCSPUnavailable = -67632, /* OCSP service is unavailable. */
|
||||
ErrSecOCSPStatusUnrecognized = -67633, /* The OCSP server did not recognize this certificate. */
|
||||
ErrSecEndOfData = -67634, /* An end-of-data was detected. */
|
||||
ErrSecIncompleteCertRevocationCheck = -67635, /* An incomplete certificate revocation check occurred. */
|
||||
ErrSecNetworkFailure = -67636, /* A network failure occurred. */
|
||||
ErrSecOCSPNotTrustedToAnchor = -67637, /* The OCSP response was not trusted to a root or anchor certificate. */
|
||||
ErrSecRecordModified = -67638, /* The record was modified. */
|
||||
ErrSecOCSPSignatureError = -67639, /* The OCSP response had an invalid signature. */
|
||||
ErrSecOCSPNoSigner = -67640, /* The OCSP response had no signer. */
|
||||
ErrSecOCSPResponderMalformedReq = -67641, /* The OCSP responder was given a malformed request. */
|
||||
ErrSecOCSPResponderInternalError = -67642, /* The OCSP responder encountered an internal error. */
|
||||
ErrSecOCSPResponderTryLater = -67643, /* The OCSP responder is busy, try again later. */
|
||||
ErrSecOCSPResponderSignatureRequired = -67644, /* The OCSP responder requires a signature. */
|
||||
ErrSecOCSPResponderUnauthorized = -67645, /* The OCSP responder rejected this request as unauthorized. */
|
||||
ErrSecOCSPResponseNonceMismatch = -67646, /* The OCSP response nonce did not match the request. */
|
||||
ErrSecCodeSigningBadCertChainLength = -67647, /* Code signing encountered an incorrect certificate chain length. */
|
||||
ErrSecCodeSigningNoBasicConstraints = -67648, /* Code signing found no basic constraints. */
|
||||
ErrSecCodeSigningBadPathLengthConstraint= -67649, /* Code signing encountered an incorrect path length constraint. */
|
||||
ErrSecCodeSigningNoExtendedKeyUsage = -67650, /* Code signing found no extended key usage. */
|
||||
ErrSecCodeSigningDevelopment = -67651, /* Code signing indicated use of a development-only certificate. */
|
||||
ErrSecResourceSignBadCertChainLength = -67652, /* Resource signing has encountered an incorrect certificate chain length. */
|
||||
ErrSecResourceSignBadExtKeyUsage = -67653, /* Resource signing has encountered an error in the extended key usage. */
|
||||
ErrSecTrustSettingDeny = -67654, /* The trust setting for this policy was set to Deny. */
|
||||
ErrSecInvalidSubjectName = -67655, /* An invalid certificate subject name was encountered. */
|
||||
ErrSecUnknownQualifiedCertStatement = -67656, /* An unknown qualified certificate statement was encountered. */
|
||||
ErrSecMobileMeRequestQueued = -67657, /* The MobileMe request will be sent during the next connection. */
|
||||
ErrSecMobileMeRequestRedirected = -67658, /* The MobileMe request was redirected. */
|
||||
ErrSecMobileMeServerError = -67659, /* A MobileMe server error occurred. */
|
||||
ErrSecMobileMeServerNotAvailable = -67660, /* The MobileMe server is not available. */
|
||||
ErrSecMobileMeServerAlreadyExists = -67661, /* The MobileMe server reported that the item already exists. */
|
||||
ErrSecMobileMeServerServiceErr = -67662, /* A MobileMe service error has occurred. */
|
||||
ErrSecMobileMeRequestAlreadyPending = -67663, /* A MobileMe request is already pending. */
|
||||
ErrSecMobileMeNoRequestPending = -67664, /* MobileMe has no request pending. */
|
||||
ErrSecMobileMeCSRVerifyFailure = -67665, /* A MobileMe CSR verification failure has occurred. */
|
||||
ErrSecMobileMeFailedConsistencyCheck = -67666, /* MobileMe has found a failed consistency check. */
|
||||
ErrSecNotInitialized = -67667, /* A function was called without initializing CSSM. */
|
||||
ErrSecInvalidHandleUsage = -67668, /* The CSSM handle does not match with the service type. */
|
||||
ErrSecPVCReferentNotFound = -67669, /* A reference to the calling module was not found in the list of authorized callers. */
|
||||
ErrSecFunctionIntegrityFail = -67670, /* A function address was not within the verified module. */
|
||||
ErrSecInternalError = -67671, /* An internal error has occurred. */
|
||||
ErrSecMemoryError = -67672, /* A memory error has occurred. */
|
||||
ErrSecInvalidData = -67673, /* Invalid data was encountered. */
|
||||
ErrSecMDSError = -67674, /* A Module Directory Service error has occurred. */
|
||||
ErrSecInvalidPointer = -67675, /* An invalid pointer was encountered. */
|
||||
ErrSecSelfCheckFailed = -67676, /* Self-check has failed. */
|
||||
ErrSecFunctionFailed = -67677, /* A function has failed. */
|
||||
ErrSecModuleManifestVerifyFailed = -67678, /* A module manifest verification failure has occurred. */
|
||||
ErrSecInvalidGUID = -67679, /* An invalid GUID was encountered. */
|
||||
ErrSecInvalidHandle = -67680, /* An invalid handle was encountered. */
|
||||
ErrSecInvalidDBList = -67681, /* An invalid DB list was encountered. */
|
||||
ErrSecInvalidPassthroughID = -67682, /* An invalid passthrough ID was encountered. */
|
||||
ErrSecInvalidNetworkAddress = -67683, /* An invalid network address was encountered. */
|
||||
ErrSecCRLAlreadySigned = -67684, /* The certificate revocation list is already signed. */
|
||||
ErrSecInvalidNumberOfFields = -67685, /* An invalid number of fields were encountered. */
|
||||
ErrSecVerificationFailure = -67686, /* A verification failure occurred. */
|
||||
ErrSecUnknownTag = -67687, /* An unknown tag was encountered. */
|
||||
ErrSecInvalidSignature = -67688, /* An invalid signature was encountered. */
|
||||
ErrSecInvalidName = -67689, /* An invalid name was encountered. */
|
||||
ErrSecInvalidCertificateRef = -67690, /* An invalid certificate reference was encountered. */
|
||||
ErrSecInvalidCertificateGroup = -67691, /* An invalid certificate group was encountered. */
|
||||
ErrSecTagNotFound = -67692, /* The specified tag was not found. */
|
||||
ErrSecInvalidQuery = -67693, /* The specified query was not valid. */
|
||||
ErrSecInvalidValue = -67694, /* An invalid value was detected. */
|
||||
ErrSecCallbackFailed = -67695, /* A callback has failed. */
|
||||
ErrSecACLDeleteFailed = -67696, /* An ACL delete operation has failed. */
|
||||
ErrSecACLReplaceFailed = -67697, /* An ACL replace operation has failed. */
|
||||
ErrSecACLAddFailed = -67698, /* An ACL add operation has failed. */
|
||||
ErrSecACLChangeFailed = -67699, /* An ACL change operation has failed. */
|
||||
ErrSecInvalidAccessCredentials = -67700, /* Invalid access credentials were encountered. */
|
||||
ErrSecInvalidRecord = -67701, /* An invalid record was encountered. */
|
||||
ErrSecInvalidACL = -67702, /* An invalid ACL was encountered. */
|
||||
ErrSecInvalidSampleValue = -67703, /* An invalid sample value was encountered. */
|
||||
ErrSecIncompatibleVersion = -67704, /* An incompatible version was encountered. */
|
||||
ErrSecPrivilegeNotGranted = -67705, /* The privilege was not granted. */
|
||||
ErrSecInvalidScope = -67706, /* An invalid scope was encountered. */
|
||||
ErrSecPVCAlreadyConfigured = -67707, /* The PVC is already configured. */
|
||||
ErrSecInvalidPVC = -67708, /* An invalid PVC was encountered. */
|
||||
ErrSecEMMLoadFailed = -67709, /* The EMM load has failed. */
|
||||
ErrSecEMMUnloadFailed = -67710, /* The EMM unload has failed. */
|
||||
ErrSecAddinLoadFailed = -67711, /* The add-in load operation has failed. */
|
||||
ErrSecInvalidKeyRef = -67712, /* An invalid key was encountered. */
|
||||
ErrSecInvalidKeyHierarchy = -67713, /* An invalid key hierarchy was encountered. */
|
||||
ErrSecAddinUnloadFailed = -67714, /* The add-in unload operation has failed. */
|
||||
ErrSecLibraryReferenceNotFound = -67715, /* A library reference was not found. */
|
||||
ErrSecInvalidAddinFunctionTable = -67716, /* An invalid add-in function table was encountered. */
|
||||
ErrSecInvalidServiceMask = -67717, /* An invalid service mask was encountered. */
|
||||
ErrSecModuleNotLoaded = -67718, /* A module was not loaded. */
|
||||
ErrSecInvalidSubServiceID = -67719, /* An invalid subservice ID was encountered. */
|
||||
ErrSecAttributeNotInContext = -67720, /* An attribute was not in the context. */
|
||||
ErrSecModuleManagerInitializeFailed = -67721, /* A module failed to initialize. */
|
||||
ErrSecModuleManagerNotFound = -67722, /* A module was not found. */
|
||||
ErrSecEventNotificationCallbackNotFound = -67723, /* An event notification callback was not found. */
|
||||
ErrSecInputLengthError = -67724, /* An input length error was encountered. */
|
||||
ErrSecOutputLengthError = -67725, /* An output length error was encountered. */
|
||||
ErrSecPrivilegeNotSupported = -67726, /* The privilege is not supported. */
|
||||
ErrSecDeviceError = -67727, /* A device error was encountered. */
|
||||
ErrSecAttachHandleBusy = -67728, /* The CSP handle was busy. */
|
||||
ErrSecNotLoggedIn = -67729, /* You are not logged in. */
|
||||
ErrSecAlgorithmMismatch = -67730, /* An algorithm mismatch was encountered. */
|
||||
ErrSecKeyUsageIncorrect = -67731, /* The key usage is incorrect. */
|
||||
ErrSecKeyBlobTypeIncorrect = -67732, /* The key blob type is incorrect. */
|
||||
ErrSecKeyHeaderInconsistent = -67733, /* The key header is inconsistent. */
|
||||
ErrSecUnsupportedKeyFormat = -67734, /* The key header format is not supported. */
|
||||
ErrSecUnsupportedKeySize = -67735, /* The key size is not supported. */
|
||||
ErrSecInvalidKeyUsageMask = -67736, /* The key usage mask is not valid. */
|
||||
ErrSecUnsupportedKeyUsageMask = -67737, /* The key usage mask is not supported. */
|
||||
ErrSecInvalidKeyAttributeMask = -67738, /* The key attribute mask is not valid. */
|
||||
ErrSecUnsupportedKeyAttributeMask = -67739, /* The key attribute mask is not supported. */
|
||||
ErrSecInvalidKeyLabel = -67740, /* The key label is not valid. */
|
||||
ErrSecUnsupportedKeyLabel = -67741, /* The key label is not supported. */
|
||||
ErrSecInvalidKeyFormat = -67742, /* The key format is not valid. */
|
||||
ErrSecUnsupportedVectorOfBuffers = -67743, /* The vector of buffers is not supported. */
|
||||
ErrSecInvalidInputVector = -67744, /* The input vector is not valid. */
|
||||
ErrSecInvalidOutputVector = -67745, /* The output vector is not valid. */
|
||||
ErrSecInvalidContext = -67746, /* An invalid context was encountered. */
|
||||
ErrSecInvalidAlgorithm = -67747, /* An invalid algorithm was encountered. */
|
||||
ErrSecInvalidAttributeKey = -67748, /* A key attribute was not valid. */
|
||||
ErrSecMissingAttributeKey = -67749, /* A key attribute was missing. */
|
||||
ErrSecInvalidAttributeInitVector = -67750, /* An init vector attribute was not valid. */
|
||||
ErrSecMissingAttributeInitVector = -67751, /* An init vector attribute was missing. */
|
||||
ErrSecInvalidAttributeSalt = -67752, /* A salt attribute was not valid. */
|
||||
ErrSecMissingAttributeSalt = -67753, /* A salt attribute was missing. */
|
||||
ErrSecInvalidAttributePadding = -67754, /* A padding attribute was not valid. */
|
||||
ErrSecMissingAttributePadding = -67755, /* A padding attribute was missing. */
|
||||
ErrSecInvalidAttributeRandom = -67756, /* A random number attribute was not valid. */
|
||||
ErrSecMissingAttributeRandom = -67757, /* A random number attribute was missing. */
|
||||
ErrSecInvalidAttributeSeed = -67758, /* A seed attribute was not valid. */
|
||||
ErrSecMissingAttributeSeed = -67759, /* A seed attribute was missing. */
|
||||
ErrSecInvalidAttributePassphrase = -67760, /* A passphrase attribute was not valid. */
|
||||
ErrSecMissingAttributePassphrase = -67761, /* A passphrase attribute was missing. */
|
||||
ErrSecInvalidAttributeKeyLength = -67762, /* A key length attribute was not valid. */
|
||||
ErrSecMissingAttributeKeyLength = -67763, /* A key length attribute was missing. */
|
||||
ErrSecInvalidAttributeBlockSize = -67764, /* A block size attribute was not valid. */
|
||||
ErrSecMissingAttributeBlockSize = -67765, /* A block size attribute was missing. */
|
||||
ErrSecInvalidAttributeOutputSize = -67766, /* An output size attribute was not valid. */
|
||||
ErrSecMissingAttributeOutputSize = -67767, /* An output size attribute was missing. */
|
||||
ErrSecInvalidAttributeRounds = -67768, /* The number of rounds attribute was not valid. */
|
||||
ErrSecMissingAttributeRounds = -67769, /* The number of rounds attribute was missing. */
|
||||
ErrSecInvalidAlgorithmParms = -67770, /* An algorithm parameters attribute was not valid. */
|
||||
ErrSecMissingAlgorithmParms = -67771, /* An algorithm parameters attribute was missing. */
|
||||
ErrSecInvalidAttributeLabel = -67772, /* A label attribute was not valid. */
|
||||
ErrSecMissingAttributeLabel = -67773, /* A label attribute was missing. */
|
||||
ErrSecInvalidAttributeKeyType = -67774, /* A key type attribute was not valid. */
|
||||
ErrSecMissingAttributeKeyType = -67775, /* A key type attribute was missing. */
|
||||
ErrSecInvalidAttributeMode = -67776, /* A mode attribute was not valid. */
|
||||
ErrSecMissingAttributeMode = -67777, /* A mode attribute was missing. */
|
||||
ErrSecInvalidAttributeEffectiveBits = -67778, /* An effective bits attribute was not valid. */
|
||||
ErrSecMissingAttributeEffectiveBits = -67779, /* An effective bits attribute was missing. */
|
||||
ErrSecInvalidAttributeStartDate = -67780, /* A start date attribute was not valid. */
|
||||
ErrSecMissingAttributeStartDate = -67781, /* A start date attribute was missing. */
|
||||
ErrSecInvalidAttributeEndDate = -67782, /* An end date attribute was not valid. */
|
||||
ErrSecMissingAttributeEndDate = -67783, /* An end date attribute was missing. */
|
||||
ErrSecInvalidAttributeVersion = -67784, /* A version attribute was not valid. */
|
||||
ErrSecMissingAttributeVersion = -67785, /* A version attribute was missing. */
|
||||
ErrSecInvalidAttributePrime = -67786, /* A prime attribute was not valid. */
|
||||
ErrSecMissingAttributePrime = -67787, /* A prime attribute was missing. */
|
||||
ErrSecInvalidAttributeBase = -67788, /* A base attribute was not valid. */
|
||||
ErrSecMissingAttributeBase = -67789, /* A base attribute was missing. */
|
||||
ErrSecInvalidAttributeSubprime = -67790, /* A subprime attribute was not valid. */
|
||||
ErrSecMissingAttributeSubprime = -67791, /* A subprime attribute was missing. */
|
||||
ErrSecInvalidAttributeIterationCount = -67792, /* An iteration count attribute was not valid. */
|
||||
ErrSecMissingAttributeIterationCount = -67793, /* An iteration count attribute was missing. */
|
||||
ErrSecInvalidAttributeDLDBHandle = -67794, /* A database handle attribute was not valid. */
|
||||
ErrSecMissingAttributeDLDBHandle = -67795, /* A database handle attribute was missing. */
|
||||
ErrSecInvalidAttributeAccessCredentials = -67796, /* An access credentials attribute was not valid. */
|
||||
ErrSecMissingAttributeAccessCredentials = -67797, /* An access credentials attribute was missing. */
|
||||
ErrSecInvalidAttributePublicKeyFormat = -67798, /* A public key format attribute was not valid. */
|
||||
ErrSecMissingAttributePublicKeyFormat = -67799, /* A public key format attribute was missing. */
|
||||
ErrSecInvalidAttributePrivateKeyFormat = -67800, /* A private key format attribute was not valid. */
|
||||
ErrSecMissingAttributePrivateKeyFormat = -67801, /* A private key format attribute was missing. */
|
||||
ErrSecInvalidAttributeSymmetricKeyFormat = -67802, /* A symmetric key format attribute was not valid. */
|
||||
ErrSecMissingAttributeSymmetricKeyFormat = -67803, /* A symmetric key format attribute was missing. */
|
||||
ErrSecInvalidAttributeWrappedKeyFormat = -67804, /* A wrapped key format attribute was not valid. */
|
||||
ErrSecMissingAttributeWrappedKeyFormat = -67805, /* A wrapped key format attribute was missing. */
|
||||
ErrSecStagedOperationInProgress = -67806, /* A staged operation is in progress. */
|
||||
ErrSecStagedOperationNotStarted = -67807, /* A staged operation was not started. */
|
||||
ErrSecVerifyFailed = -67808, /* A cryptographic verification failure has occurred. */
|
||||
ErrSecQuerySizeUnknown = -67809, /* The query size is unknown. */
|
||||
ErrSecBlockSizeMismatch = -67810, /* A block size mismatch occurred. */
|
||||
ErrSecPublicKeyInconsistent = -67811, /* The public key was inconsistent. */
|
||||
ErrSecDeviceVerifyFailed = -67812, /* A device verification failure has occurred. */
|
||||
ErrSecInvalidLoginName = -67813, /* An invalid login name was detected. */
|
||||
ErrSecAlreadyLoggedIn = -67814, /* The user is already logged in. */
|
||||
ErrSecInvalidDigestAlgorithm = -67815, /* An invalid digest algorithm was detected. */
|
||||
ErrSecInvalidCRLGroup = -67816, /* An invalid CRL group was detected. */
|
||||
ErrSecCertificateCannotOperate = -67817, /* The certificate cannot operate. */
|
||||
ErrSecCertificateExpired = -67818, /* An expired certificate was detected. */
|
||||
ErrSecCertificateNotValidYet = -67819, /* The certificate is not yet valid. */
|
||||
ErrSecCertificateRevoked = -67820, /* The certificate was revoked. */
|
||||
ErrSecCertificateSuspended = -67821, /* The certificate was suspended. */
|
||||
ErrSecInsufficientCredentials = -67822, /* Insufficient credentials were detected. */
|
||||
ErrSecInvalidAction = -67823, /* The action was not valid. */
|
||||
ErrSecInvalidAuthority = -67824, /* The authority was not valid. */
|
||||
ErrSecVerifyActionFailed = -67825, /* A verify action has failed. */
|
||||
ErrSecInvalidCertAuthority = -67826, /* The certificate authority was not valid. */
|
||||
ErrSecInvaldCRLAuthority = -67827, /* The CRL authority was not valid. */
|
||||
ErrSecInvalidCRLEncoding = -67828, /* The CRL encoding was not valid. */
|
||||
ErrSecInvalidCRLType = -67829, /* The CRL type was not valid. */
|
||||
ErrSecInvalidCRL = -67830, /* The CRL was not valid. */
|
||||
ErrSecInvalidFormType = -67831, /* The form type was not valid. */
|
||||
ErrSecInvalidID = -67832, /* The ID was not valid. */
|
||||
ErrSecInvalidIdentifier = -67833, /* The identifier was not valid. */
|
||||
ErrSecInvalidIndex = -67834, /* The index was not valid. */
|
||||
ErrSecInvalidPolicyIdentifiers = -67835, /* The policy identifiers are not valid. */
|
||||
ErrSecInvalidTimeString = -67836, /* The time specified was not valid. */
|
||||
ErrSecInvalidReason = -67837, /* The trust policy reason was not valid. */
|
||||
ErrSecInvalidRequestInputs = -67838, /* The request inputs are not valid. */
|
||||
ErrSecInvalidResponseVector = -67839, /* The response vector was not valid. */
|
||||
ErrSecInvalidStopOnPolicy = -67840, /* The stop-on policy was not valid. */
|
||||
ErrSecInvalidTuple = -67841, /* The tuple was not valid. */
|
||||
ErrSecMultipleValuesUnsupported = -67842, /* Multiple values are not supported. */
|
||||
ErrSecNotTrusted = -67843, /* The trust policy was not trusted. */
|
||||
ErrSecNoDefaultAuthority = -67844, /* No default authority was detected. */
|
||||
ErrSecRejectedForm = -67845, /* The trust policy had a rejected form. */
|
||||
ErrSecRequestLost = -67846, /* The request was lost. */
|
||||
ErrSecRequestRejected = -67847, /* The request was rejected. */
|
||||
ErrSecUnsupportedAddressType = -67848, /* The address type is not supported. */
|
||||
ErrSecUnsupportedService = -67849, /* The service is not supported. */
|
||||
ErrSecInvalidTupleGroup = -67850, /* The tuple group was not valid. */
|
||||
ErrSecInvalidBaseACLs = -67851, /* The base ACLs are not valid. */
|
||||
ErrSecInvalidTupleCredendtials = -67852, /* The tuple credentials are not valid. */
|
||||
ErrSecInvalidEncoding = -67853, /* The encoding was not valid. */
|
||||
ErrSecInvalidValidityPeriod = -67854, /* The validity period was not valid. */
|
||||
ErrSecInvalidRequestor = -67855, /* The requestor was not valid. */
|
||||
ErrSecRequestDescriptor = -67856, /* The request descriptor was not valid. */
|
||||
ErrSecInvalidBundleInfo = -67857, /* The bundle information was not valid. */
|
||||
ErrSecInvalidCRLIndex = -67858, /* The CRL index was not valid. */
|
||||
ErrSecNoFieldValues = -67859, /* No field values were detected. */
|
||||
ErrSecUnsupportedFieldFormat = -67860, /* The field format is not supported. */
|
||||
ErrSecUnsupportedIndexInfo = -67861, /* The index information is not supported. */
|
||||
ErrSecUnsupportedLocality = -67862, /* The locality is not supported. */
|
||||
ErrSecUnsupportedNumAttributes = -67863, /* The number of attributes is not supported. */
|
||||
ErrSecUnsupportedNumIndexes = -67864, /* The number of indexes is not supported. */
|
||||
ErrSecUnsupportedNumRecordTypes = -67865, /* The number of record types is not supported. */
|
||||
ErrSecFieldSpecifiedMultiple = -67866, /* Too many fields were specified. */
|
||||
ErrSecIncompatibleFieldFormat = -67867, /* The field format was incompatible. */
|
||||
ErrSecInvalidParsingModule = -67868, /* The parsing module was not valid. */
|
||||
ErrSecDatabaseLocked = -67869, /* The database is locked. */
|
||||
ErrSecDatastoreIsOpen = -67870, /* The data store is open. */
|
||||
ErrSecMissingValue = -67871, /* A missing value was detected. */
|
||||
ErrSecUnsupportedQueryLimits = -67872, /* The query limits are not supported. */
|
||||
ErrSecUnsupportedNumSelectionPreds = -67873, /* The number of selection predicates is not supported. */
|
||||
ErrSecUnsupportedOperator = -67874, /* The operator is not supported. */
|
||||
ErrSecInvalidDBLocation = -67875, /* The database location is not valid. */
|
||||
ErrSecInvalidAccessRequest = -67876, /* The access request is not valid. */
|
||||
ErrSecInvalidIndexInfo = -67877, /* The index information is not valid. */
|
||||
ErrSecInvalidNewOwner = -67878, /* The new owner is not valid. */
|
||||
ErrSecInvalidModifyMode = -67879, /* The modify mode is not valid. */
|
||||
ErrSecMissingRequiredExtension = -67880, /* A required certificate extension is missing. */
|
||||
ErrSecExtendedKeyUsageNotCritical = -67881, /* The extended key usage extension was not marked critical. */
|
||||
ErrSecTimestampMissing = -67882, /* A timestamp was expected but was not found. */
|
||||
ErrSecTimestampInvalid = -67883, /* The timestamp was not valid. */
|
||||
ErrSecTimestampNotTrusted = -67884, /* The timestamp was not trusted. */
|
||||
ErrSecTimestampServiceNotAvailable = -67885, /* The timestamp service is not available. */
|
||||
ErrSecTimestampBadAlg = -67886, /* An unrecognized or unsupported Algorithm Identifier in timestamp. */
|
||||
ErrSecTimestampBadRequest = -67887, /* The timestamp transaction is not permitted or supported. */
|
||||
ErrSecTimestampBadDataFormat = -67888, /* The timestamp data submitted has the wrong format. */
|
||||
ErrSecTimestampTimeNotAvailable = -67889, /* The time source for the Timestamp Authority is not available. */
|
||||
ErrSecTimestampUnacceptedPolicy = -67890, /* The requested policy is not supported by the Timestamp Authority. */
|
||||
ErrSecTimestampUnacceptedExtension = -67891, /* The requested extension is not supported by the Timestamp Authority. */
|
||||
ErrSecTimestampAddInfoNotAvailable = -67892, /* The additional information requested is not available. */
|
||||
ErrSecTimestampSystemFailure = -67893, /* The timestamp request cannot be handled due to system failure. */
|
||||
ErrSecSigningTimeMissing = -67894, /* A signing time was expected but was not found. */
|
||||
ErrSecTimestampRejection = -67895, /* A timestamp transaction was rejected. */
|
||||
ErrSecTimestampWaiting = -67896, /* A timestamp transaction is waiting. */
|
||||
ErrSecTimestampRevocationWarning = -67897, /* A timestamp authority revocation warning was issued. */
|
||||
ErrSecTimestampRevocationNotification = -67898, /* A timestamp authority revocation notification was issued. */
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.OSX
|
||||
{
|
||||
/// <summary>
|
||||
/// OSX implementation of the credential store
|
||||
/// </summary>
|
||||
internal class OSXCredentialStore : ICredentialStore
|
||||
{
|
||||
public bool DeletePassword(string credentialId)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
return DeletePasswordImpl(credentialId);
|
||||
}
|
||||
|
||||
public bool TryGetPassword(string credentialId, out string password)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
return FindPassword(credentialId, out password);
|
||||
}
|
||||
|
||||
public bool Save(Credential credential)
|
||||
{
|
||||
Credential.ValidateForSave(credential);
|
||||
bool result = false;
|
||||
|
||||
// Note: OSX blocks AddPassword if the credential
|
||||
// already exists, so for now we delete the password if already present since we're updating
|
||||
// the value. In the future, we could consider updating but it's low value to solve this
|
||||
DeletePasswordImpl(credential.CredentialId);
|
||||
|
||||
// Now add the password
|
||||
result = AddGenericPassword(credential);
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool AddGenericPassword(Credential credential)
|
||||
{
|
||||
IntPtr passwordPtr = Marshal.StringToCoTaskMemUni(credential.Password);
|
||||
Interop.Security.OSStatus status = Interop.Security.SecKeychainAddGenericPassword(
|
||||
IntPtr.Zero,
|
||||
InteropUtils.GetLengthInBytes(credential.CredentialId),
|
||||
credential.CredentialId,
|
||||
0,
|
||||
null,
|
||||
InteropUtils.GetLengthInBytes(credential.Password),
|
||||
passwordPtr,
|
||||
IntPtr.Zero);
|
||||
|
||||
return status == Interop.Security.OSStatus.ErrSecSuccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first password matching this credential
|
||||
/// </summary>
|
||||
private bool FindPassword(string credentialId, out string password)
|
||||
{
|
||||
password = null;
|
||||
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
|
||||
{
|
||||
if( handle == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
password = handle.Password;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private KeyChainItemHandle LookupKeyChainItem(string credentialId)
|
||||
{
|
||||
UInt32 passwordLength;
|
||||
IntPtr passwordPtr;
|
||||
IntPtr item;
|
||||
Interop.Security.OSStatus status = Interop.Security.SecKeychainFindGenericPassword(
|
||||
IntPtr.Zero,
|
||||
InteropUtils.GetLengthInBytes(credentialId),
|
||||
credentialId,
|
||||
0,
|
||||
null,
|
||||
out passwordLength,
|
||||
out passwordPtr,
|
||||
out item);
|
||||
|
||||
if(status == Interop.Security.OSStatus.ErrSecSuccess)
|
||||
{
|
||||
return new KeyChainItemHandle(item, passwordPtr, passwordLength);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool DeletePasswordImpl(string credentialId)
|
||||
{
|
||||
// Find password, then Delete, then cleanup
|
||||
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
|
||||
{
|
||||
if (handle == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Interop.Security.OSStatus status = Interop.Security.SecKeychainItemDelete(handle);
|
||||
return status == Interop.Security.OSStatus.ErrSecSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyChainItemHandle : SafeCreateHandle
|
||||
{
|
||||
private IntPtr passwordPtr;
|
||||
private int passwordLength;
|
||||
|
||||
public KeyChainItemHandle() : base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public KeyChainItemHandle(IntPtr itemPtr) : this(itemPtr, IntPtr.Zero, 0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public KeyChainItemHandle(IntPtr itemPtr, IntPtr passwordPtr, UInt32 passwordLength)
|
||||
: base(itemPtr)
|
||||
{
|
||||
this.passwordPtr = passwordPtr;
|
||||
this.passwordLength = (int) passwordLength;
|
||||
}
|
||||
|
||||
public string Password
|
||||
{
|
||||
get {
|
||||
if (IsInvalid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return InteropUtils.CopyToString(passwordPtr, passwordLength);
|
||||
}
|
||||
}
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (passwordPtr != IntPtr.Zero)
|
||||
{
|
||||
Interop.Security.SecKeychainItemFreeContent(IntPtr.Zero, passwordPtr);
|
||||
}
|
||||
base.ReleaseHandle();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is a wrapper around the Create pattern in OS X where
|
||||
/// if a Create* function is called, the caller must also CFRelease
|
||||
/// on the same pointer in order to correctly free the memory.
|
||||
/// </summary>
|
||||
[System.Security.SecurityCritical]
|
||||
internal partial class SafeCreateHandle : SafeHandle
|
||||
{
|
||||
internal SafeCreateHandle() : base(IntPtr.Zero, true) { }
|
||||
|
||||
internal SafeCreateHandle(IntPtr ptr) : base(IntPtr.Zero, true)
|
||||
{
|
||||
this.SetHandle(ptr);
|
||||
}
|
||||
|
||||
[System.Security.SecurityCritical]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Interop.CoreFoundation.CFRelease(handle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool IsInvalid
|
||||
{
|
||||
[System.Security.SecurityCritical]
|
||||
get
|
||||
{
|
||||
return handle == IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
internal static class SecureStringHelper
|
||||
{
|
||||
// Methods
|
||||
internal static SecureString CreateSecureString(string plainString)
|
||||
{
|
||||
SecureString str = new SecureString();
|
||||
if (!string.IsNullOrEmpty(plainString))
|
||||
{
|
||||
foreach (char c in plainString)
|
||||
{
|
||||
str.AppendChar(c);
|
||||
}
|
||||
}
|
||||
str.MakeReadOnly();
|
||||
return str;
|
||||
}
|
||||
|
||||
internal static string CreateString(SecureString value)
|
||||
{
|
||||
IntPtr ptr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(value);
|
||||
try
|
||||
{
|
||||
return Marshal.PtrToStringUni(ptr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
// TODO Replace this strings class with a resx file
|
||||
internal class CredentialResources
|
||||
{
|
||||
public const string PasswordLengthExceeded = "The password has exceeded 512 bytes.";
|
||||
public const string TargetRequiredForDelete = "Target must be specified to delete a credential.";
|
||||
public const string TargetRequiredForLookup = "Target must be specified to check existance of a credential.";
|
||||
public const string CredentialDisposed = "Win32Credential object is already disposed.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public class CredentialSet: List<Win32Credential>, IDisposable
|
||||
{
|
||||
bool _disposed;
|
||||
|
||||
public CredentialSet()
|
||||
{
|
||||
}
|
||||
|
||||
public CredentialSet(string target)
|
||||
: this()
|
||||
{
|
||||
if (string.IsNullOrEmpty(target))
|
||||
{
|
||||
throw new ArgumentNullException("target");
|
||||
}
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public string Target { get; set; }
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
// Prevent GC Collection since we have already disposed of this object
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~CredentialSet()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (Count > 0)
|
||||
{
|
||||
ForEach(cred => cred.Dispose());
|
||||
}
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public CredentialSet Load()
|
||||
{
|
||||
LoadInternal();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void LoadInternal()
|
||||
{
|
||||
uint count;
|
||||
|
||||
IntPtr pCredentials = IntPtr.Zero;
|
||||
bool result = NativeMethods.CredEnumerateW(Target, 0, out count, out pCredentials);
|
||||
if (!result)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, string.Format("Win32Exception: {0}", new Win32Exception(Marshal.GetLastWin32Error()).ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read in all of the pointers first
|
||||
IntPtr[] ptrCredList = new IntPtr[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ptrCredList[i] = Marshal.ReadIntPtr(pCredentials, IntPtr.Size*i);
|
||||
}
|
||||
|
||||
// Now let's go through all of the pointers in the list
|
||||
// and create our Credential object(s)
|
||||
List<NativeMethods.CriticalCredentialHandle> credentialHandles =
|
||||
ptrCredList.Select(ptrCred => new NativeMethods.CriticalCredentialHandle(ptrCred)).ToList();
|
||||
|
||||
IEnumerable<Win32Credential> existingCredentials = credentialHandles
|
||||
.Select(handle => handle.GetCredential())
|
||||
.Select(nativeCredential =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential();
|
||||
credential.LoadInternal(nativeCredential);
|
||||
return credential;
|
||||
});
|
||||
AddRange(existingCredentials);
|
||||
|
||||
// The individual credentials should not be free'd
|
||||
credentialHandles.ForEach(handle => handle.SetHandleAsInvalid());
|
||||
|
||||
// Clean up memory to the Enumeration pointer
|
||||
NativeMethods.CredFree(pCredentials);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public enum CredentialType: uint
|
||||
{
|
||||
None = 0,
|
||||
Generic = 1,
|
||||
DomainPassword = 2,
|
||||
DomainCertificate = 3,
|
||||
DomainVisiblePassword = 4
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
internal class NativeMethods
|
||||
{
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CREDENTIAL
|
||||
{
|
||||
public int Flags;
|
||||
public int Type;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string TargetName;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Comment;
|
||||
public long LastWritten;
|
||||
public int CredentialBlobSize;
|
||||
public IntPtr CredentialBlob;
|
||||
public int Persist;
|
||||
public int AttributeCount;
|
||||
public IntPtr Attributes;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string TargetAlias;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string UserName;
|
||||
}
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr CredentialPtr);
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern bool CredWrite([In] ref CREDENTIAL userCredential, [In] UInt32 flags);
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
|
||||
internal static extern bool CredFree([In] IntPtr cred);
|
||||
|
||||
[DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)]
|
||||
internal static extern bool CredDelete(StringBuilder target, CredentialType type, int flags);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
internal static extern bool CredEnumerateW(string filter, int flag, out uint count, out IntPtr pCredentials);
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
internal static extern void CoTaskMemFree(IntPtr ptr);
|
||||
|
||||
|
||||
internal abstract class CriticalHandleZeroOrMinusOneIsInvalid : CriticalHandle
|
||||
{
|
||||
protected CriticalHandleZeroOrMinusOneIsInvalid() : base(IntPtr.Zero)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsInvalid
|
||||
{
|
||||
get { return handle == new IntPtr(0) || handle == new IntPtr(-1); }
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
// Set the handle.
|
||||
internal CriticalCredentialHandle(IntPtr preexistingHandle)
|
||||
{
|
||||
SetHandle(preexistingHandle);
|
||||
}
|
||||
|
||||
internal CREDENTIAL GetCredential()
|
||||
{
|
||||
if (!IsInvalid)
|
||||
{
|
||||
// Get the Credential from the mem location
|
||||
return (CREDENTIAL)Marshal.PtrToStructure<CREDENTIAL>(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Invalid CriticalHandle!");
|
||||
}
|
||||
}
|
||||
|
||||
// Perform any specific actions to release the handle in the ReleaseHandle method.
|
||||
// Often, you need to use Pinvoke to make a call into the Win32 API to release the
|
||||
// handle. In this case, however, we can use the Marshal class to release the unmanaged memory.
|
||||
|
||||
override protected bool ReleaseHandle()
|
||||
{
|
||||
// If the handle was set, free it. Return success.
|
||||
if (!IsInvalid)
|
||||
{
|
||||
// NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it
|
||||
// so there are no traces of the sensitive data left in memory.
|
||||
CredFree(handle);
|
||||
// Mark the handle as invalid for future users.
|
||||
SetHandleAsInvalid();
|
||||
return true;
|
||||
}
|
||||
// Return false.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public enum PersistanceType : uint
|
||||
{
|
||||
Session = 1,
|
||||
LocalComputer = 2,
|
||||
Enterprise = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
public class Win32Credential: IDisposable
|
||||
{
|
||||
bool disposed;
|
||||
|
||||
CredentialType type;
|
||||
string target;
|
||||
SecureString password;
|
||||
string username;
|
||||
string description;
|
||||
DateTime lastWriteTime;
|
||||
PersistanceType persistanceType;
|
||||
|
||||
public Win32Credential()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username)
|
||||
: this(username, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username, string password)
|
||||
: this(username, password, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username, string password, string target)
|
||||
: this(username, password, target, CredentialType.Generic)
|
||||
{
|
||||
}
|
||||
|
||||
public Win32Credential(string username, string password, string target, CredentialType type)
|
||||
{
|
||||
Username = username;
|
||||
Password = password;
|
||||
Target = target;
|
||||
Type = type;
|
||||
PersistanceType = PersistanceType.Session;
|
||||
lastWriteTime = DateTime.MinValue;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
// Prevent GC Collection since we have already disposed of this object
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
SecurePassword.Clear();
|
||||
SecurePassword.Dispose();
|
||||
}
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private void CheckNotDisposed()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(CredentialResources.CredentialDisposed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string Username {
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return username;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
username = value;
|
||||
}
|
||||
}
|
||||
public string Password
|
||||
{
|
||||
get
|
||||
{
|
||||
return SecureStringHelper.CreateString(SecurePassword);
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
SecurePassword = SecureStringHelper.CreateSecureString(string.IsNullOrEmpty(value) ? string.Empty : value);
|
||||
}
|
||||
}
|
||||
public SecureString SecurePassword
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return null == password ? new SecureString() : password.Copy();
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
if (null != password)
|
||||
{
|
||||
password.Clear();
|
||||
password.Dispose();
|
||||
}
|
||||
password = null == value ? new SecureString() : value.Copy();
|
||||
}
|
||||
}
|
||||
public string Target
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return target;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
target = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return description;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
description = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime LastWriteTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return LastWriteTimeUtc.ToLocalTime();
|
||||
}
|
||||
}
|
||||
public DateTime LastWriteTimeUtc
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return lastWriteTime;
|
||||
}
|
||||
private set { lastWriteTime = value; }
|
||||
}
|
||||
|
||||
public CredentialType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return type;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
type = value;
|
||||
}
|
||||
}
|
||||
|
||||
public PersistanceType PersistanceType
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNotDisposed();
|
||||
return persistanceType;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckNotDisposed();
|
||||
persistanceType = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Save()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
byte[] passwordBytes = Encoding.Unicode.GetBytes(Password);
|
||||
if (Password.Length > (512))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(CredentialResources.PasswordLengthExceeded);
|
||||
}
|
||||
|
||||
NativeMethods.CREDENTIAL credential = new NativeMethods.CREDENTIAL();
|
||||
credential.TargetName = Target;
|
||||
credential.UserName = Username;
|
||||
credential.CredentialBlob = Marshal.StringToCoTaskMemUni(Password);
|
||||
credential.CredentialBlobSize = passwordBytes.Length;
|
||||
credential.Comment = Description;
|
||||
credential.Type = (int)Type;
|
||||
credential.Persist = (int) PersistanceType;
|
||||
|
||||
bool result = NativeMethods.CredWrite(ref credential, 0);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LastWriteTimeUtc = DateTime.UtcNow;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Delete()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(Target))
|
||||
{
|
||||
throw new InvalidOperationException(CredentialResources.TargetRequiredForDelete);
|
||||
}
|
||||
|
||||
StringBuilder target = string.IsNullOrEmpty(Target) ? new StringBuilder() : new StringBuilder(Target);
|
||||
bool result = NativeMethods.CredDelete(target, Type, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool Load()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
IntPtr credPointer;
|
||||
|
||||
bool result = NativeMethods.CredRead(Target, Type, 0, out credPointer);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
using (NativeMethods.CriticalCredentialHandle credentialHandle = new NativeMethods.CriticalCredentialHandle(credPointer))
|
||||
{
|
||||
LoadInternal(credentialHandle.GetCredential());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Exists()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
if (string.IsNullOrEmpty(Target))
|
||||
{
|
||||
throw new InvalidOperationException(CredentialResources.TargetRequiredForLookup);
|
||||
}
|
||||
|
||||
using (Win32Credential existing = new Win32Credential { Target = Target, Type = Type })
|
||||
{
|
||||
return existing.Load();
|
||||
}
|
||||
}
|
||||
|
||||
internal void LoadInternal(NativeMethods.CREDENTIAL credential)
|
||||
{
|
||||
Username = credential.UserName;
|
||||
if (credential.CredentialBlobSize > 0)
|
||||
{
|
||||
Password = Marshal.PtrToStringUni(credential.CredentialBlob, credential.CredentialBlobSize / 2);
|
||||
}
|
||||
Target = credential.TargetName;
|
||||
Type = (CredentialType)credential.Type;
|
||||
PersistanceType = (PersistanceType)credential.Persist;
|
||||
Description = credential.Comment;
|
||||
LastWriteTimeUtc = DateTime.FromFileTimeUtc(credential.LastWritten);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||
{
|
||||
/// <summary>
|
||||
/// Win32 implementation of the credential store
|
||||
/// </summary>
|
||||
internal class Win32CredentialStore : ICredentialStore
|
||||
{
|
||||
private const string AnyUsername = "*";
|
||||
|
||||
public bool DeletePassword(string credentialId)
|
||||
{
|
||||
using (Win32Credential cred = new Win32Credential() { Target = credentialId, Username = AnyUsername })
|
||||
{
|
||||
return cred.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetPassword(string credentialId, out string password)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||
password = null;
|
||||
|
||||
using (CredentialSet set = new CredentialSet(credentialId).Load())
|
||||
{
|
||||
// Note: Credentials are disposed on disposal of the set
|
||||
Win32Credential foundCred = null;
|
||||
if (set.Count > 0)
|
||||
{
|
||||
foundCred = set[0];
|
||||
}
|
||||
|
||||
if (foundCred != null)
|
||||
{
|
||||
password = foundCred.Password;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Save(Credential credential)
|
||||
{
|
||||
Credential.ValidateForSave(credential);
|
||||
|
||||
using (Win32Credential cred =
|
||||
new Win32Credential(AnyUsername, credential.Password, credential.CredentialId, CredentialType.Generic)
|
||||
{ PersistanceType = PersistanceType.LocalComputer })
|
||||
{
|
||||
return cred.Save();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
@@ -13,17 +16,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||
/// </summary>
|
||||
public interface IProtocolEndpoint : IMessageSender
|
||||
{
|
||||
void SetRequestHandler<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
void SetRequestHandler<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
Func<TParams, RequestContext<TResult>, Task> requestHandler);
|
||||
|
||||
void SetEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
void SetEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
Func<TParams, EventContext, Task> eventHandler);
|
||||
|
||||
void SetEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
Func<TParams, EventContext, Task> eventHandler,
|
||||
void SetEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
Func<TParams, EventContext, Task> eventHandler,
|
||||
bool overrideExisting);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Main application class for SQL Tools API Service Host executable
|
||||
/// </summary>
|
||||
@@ -50,6 +48,7 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
AutoCompleteService.Instance.InitializeService(serviceHost);
|
||||
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
|
||||
ConnectionService.Instance.InitializeService(serviceHost);
|
||||
CredentialService.Instance.InitializeService(serviceHost);
|
||||
QueryExecutionService.Instance.InitializeService(serviceHost);
|
||||
|
||||
serviceHost.Initialize();
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// Credential Service tests that should pass on all platforms, regardless of backing store.
|
||||
/// These tests run E2E, storing values in the native credential store for whichever platform
|
||||
/// tests are being run on
|
||||
/// </summary>
|
||||
public class CredentialServiceTests : IDisposable
|
||||
{
|
||||
private static readonly LinuxCredentialStore.StoreConfig config = new LinuxCredentialStore.StoreConfig()
|
||||
{
|
||||
CredentialFolder = ".testsecrets",
|
||||
CredentialFile = "sqltestsecrets.json",
|
||||
IsRelativeToUserHomeDir = true
|
||||
};
|
||||
|
||||
const string credentialId = "Microsoft_SqlToolsTest_TestId";
|
||||
const string password1 = "P@ssw0rd1";
|
||||
const string password2 = "2Pass2Furious";
|
||||
|
||||
const string otherCredId = credentialId + "2345";
|
||||
const string otherPassword = credentialId + "2345";
|
||||
|
||||
// Test-owned credential store used to clean up before/after tests to ensure code works as expected
|
||||
// even if previous runs stopped midway through
|
||||
private ICredentialStore credStore;
|
||||
private CredentialService service;
|
||||
/// <summary>
|
||||
/// Constructor called once for every test
|
||||
/// </summary>
|
||||
public CredentialServiceTests()
|
||||
{
|
||||
credStore = CredentialService.GetStoreForOS(config);
|
||||
service = new CredentialService(credStore, config);
|
||||
DeleteDefaultCreds();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DeleteDefaultCreds();
|
||||
}
|
||||
|
||||
private void DeleteDefaultCreds()
|
||||
{
|
||||
credStore.DeletePassword(credentialId);
|
||||
credStore.DeletePassword(otherCredId);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
string credsFolder = ((LinuxCredentialStore)credStore).CredentialFolderPath;
|
||||
if (Directory.Exists(credsFolder))
|
||||
{
|
||||
Directory.Delete(credsFolder, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialThrowsIfCredentialIdMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<bool>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
await service.HandleSaveCredentialRequest(new Credential(null), contextMock.Object);
|
||||
VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialThrowsIfPasswordMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<bool>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
await service.HandleSaveCredentialRequest(new Credential(credentialId), contextMock.Object);
|
||||
VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialWorksForSingleCredential()
|
||||
{
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialSupportsSavingCredentialMultipleTimes()
|
||||
{
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialWorksForSingleCredential()
|
||||
{
|
||||
// Given we have saved the credential
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
|
||||
|
||||
|
||||
// Expect read of the credential to return the password
|
||||
await RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(password1, actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialWorksForMultipleCredentials()
|
||||
{
|
||||
|
||||
// Given we have saved multiple credentials
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(otherCredId, otherPassword), requestContext),
|
||||
verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
|
||||
|
||||
|
||||
// Expect read of the credentials to return the right password
|
||||
await RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(password1, actual.Password);
|
||||
}));
|
||||
await RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(otherCredId, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(otherPassword, actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialHandlesPasswordUpdate()
|
||||
{
|
||||
// Given we have saved twice with a different password
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password2), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
// When we read the value for this credential
|
||||
// Then we expect only the last saved password to be found
|
||||
await RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(password2, actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialThrowsIfCredentialIsNull()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<Credential>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
// Verify throws on null, and this is sent as an error
|
||||
await service.HandleReadCredentialRequest(null, contextMock.Object);
|
||||
VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentNullException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialThrowsIfIdMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<Credential>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
// Verify throws with no ID
|
||||
await service.HandleReadCredentialRequest(new Credential(), contextMock.Object);
|
||||
VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialReturnsNullPasswordForMissingCredential()
|
||||
{
|
||||
// Given a credential whose password doesn't exist
|
||||
string credWithNoPassword = "Microsoft_SqlTools_CredThatDoesNotExist";
|
||||
|
||||
// When reading the credential
|
||||
// Then expect the credential to be returned but password left blank
|
||||
await RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credWithNoPassword, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(credWithNoPassword, actual.CredentialId);
|
||||
Assert.Null(actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteCredentialThrowsIfIdMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<bool>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
// Verify throws with no ID
|
||||
await service.HandleDeleteCredentialRequest(new Credential(), contextMock.Object);
|
||||
VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteCredentialReturnsTrueOnlyIfCredentialExisted()
|
||||
{
|
||||
// Save should be true
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
// Then delete - should return true
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
// Then delete - should return false as no longer exists
|
||||
await RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext),
|
||||
verify: (actual => Assert.False(actual)));
|
||||
}
|
||||
|
||||
private async Task RunAndVerify<T>(Func<RequestContext<T>, Task> test, Action<T> verify)
|
||||
{
|
||||
T result = default(T);
|
||||
var contextMock = RequestContextMocks.Create<T>(r => result = r).AddErrorHandling(null);
|
||||
await test(contextMock.Object);
|
||||
VerifyResult(contextMock, verify, result);
|
||||
}
|
||||
|
||||
private void VerifyErrorSent<T>(Mock<RequestContext<T>> contextMock)
|
||||
{
|
||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Never);
|
||||
contextMock.Verify(c => c.SendError(It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
private void VerifyResult<T, U>(Mock<RequestContext<T>> contextMock, U expected, U actual)
|
||||
{
|
||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
|
||||
Assert.Equal(expected, actual);
|
||||
contextMock.Verify(c => c.SendError(It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
private void VerifyResult<T>(Mock<RequestContext<T>> contextMock, Action<T> verify, T actual)
|
||||
{
|
||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
|
||||
contextMock.Verify(c => c.SendError(It.IsAny<string>()), Times.Never);
|
||||
verify(actual);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Credentials
|
||||
{
|
||||
public class LinuxInteropTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetEUidReturnsInt()
|
||||
{
|
||||
TestUtils.RunIfLinux(() =>
|
||||
{
|
||||
Assert.NotNull(Interop.Sys.GetEUid());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHomeDirectoryFromPwFindsHomeDir()
|
||||
{
|
||||
|
||||
TestUtils.RunIfLinux(() =>
|
||||
{
|
||||
string userDir = LinuxCredentialStore.GetHomeDirectoryFromPw();
|
||||
Assert.StartsWith("/", userDir);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Credentials
|
||||
{
|
||||
public class CredentialSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void CredentialSetCreate()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new CredentialSet());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetCreateWithTarget()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new CredentialSet("target"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetShouldBeIDisposable()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.True(new CredentialSet() is IDisposable, "CredentialSet needs to implement IDisposable Interface.");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetLoad()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential
|
||||
{
|
||||
Username = "username",
|
||||
Password = "password",
|
||||
Target = "target",
|
||||
Type = CredentialType.Generic
|
||||
};
|
||||
credential.Save();
|
||||
|
||||
CredentialSet set = new CredentialSet();
|
||||
set.Load();
|
||||
Assert.NotNull(set);
|
||||
Assert.NotEmpty(set);
|
||||
|
||||
credential.Delete();
|
||||
|
||||
set.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetLoadShouldReturnSelf()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
CredentialSet set = new CredentialSet();
|
||||
Assert.IsType<CredentialSet>(set.Load());
|
||||
|
||||
set.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetLoadWithTargetFilter()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential
|
||||
{
|
||||
Username = "filteruser",
|
||||
Password = "filterpassword",
|
||||
Target = "filtertarget"
|
||||
};
|
||||
credential.Save();
|
||||
|
||||
CredentialSet set = new CredentialSet("filtertarget");
|
||||
Assert.Equal(1, set.Load().Count);
|
||||
set.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Credentials
|
||||
{
|
||||
public class Win32CredentialTests
|
||||
{
|
||||
[Fact]
|
||||
public void Credential_Create_ShouldNotThrowNull()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Create_With_Username_ShouldNotThrowNull()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential("username"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Create_With_Username_And_Password_ShouldNotThrowNull()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential("username", "password"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Create_With_Username_Password_Target_ShouldNotThrowNull()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential("username", "password", "target"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_ShouldBe_IDisposable()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.True(new Win32Credential() is IDisposable, "Credential should implement IDisposable Interface.");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Dispose_ShouldNotThrowException()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
new Win32Credential().Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_ShouldThrowObjectDisposedException()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential disposed = new Win32Credential { Password = "password" };
|
||||
disposed.Dispose();
|
||||
Assert.Throws<ObjectDisposedException>(() => disposed.Username = "username");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Save()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential saved = new Win32Credential("username", "password", "target", CredentialType.Generic);
|
||||
saved.PersistanceType = PersistanceType.LocalComputer;
|
||||
Assert.True(saved.Save());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Delete()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
new Win32Credential("username", "password", "target").Save();
|
||||
Assert.True(new Win32Credential("username", "password", "target").Delete());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Delete_NullTerminator()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential((string)null, (string)null, "\0", CredentialType.None);
|
||||
credential.Description = (string)null;
|
||||
Assert.False(credential.Delete());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Load()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential setup = new Win32Credential("username", "password", "target", CredentialType.Generic);
|
||||
setup.Save();
|
||||
|
||||
Win32Credential credential = new Win32Credential { Target = "target", Type = CredentialType.Generic };
|
||||
Assert.True(credential.Load());
|
||||
|
||||
Assert.NotEmpty(credential.Username);
|
||||
Assert.NotNull(credential.Password);
|
||||
Assert.Equal("username", credential.Username);
|
||||
Assert.Equal("password", credential.Password);
|
||||
Assert.Equal("target", credential.Target);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Exists_Target_ShouldNotBeNull()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
new Win32Credential { Username = "username", Password = "password", Target = "target" }.Save();
|
||||
|
||||
Win32Credential existingCred = new Win32Credential { Target = "target" };
|
||||
Assert.True(existingCred.Exists());
|
||||
|
||||
existingCred.Delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -21,7 +22,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// ... I request a query (doesn't matter what kind) and execute it
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
|
||||
|
||||
@@ -47,7 +49,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// ... I request a query (doesn't matter what kind) and wait for execution
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams {QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// ... And then I request to cancel the query
|
||||
|
||||
@@ -3,24 +3,19 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
@@ -215,46 +210,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Request Mocking
|
||||
|
||||
public static Mock<RequestContext<QueryExecuteResult>> GetQueryExecuteResultContextMock(
|
||||
Action<QueryExecuteResult> resultCallback,
|
||||
Action<EventType<QueryExecuteCompleteParams>, QueryExecuteCompleteParams> eventCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
var requestContext = new Mock<RequestContext<QueryExecuteResult>>();
|
||||
|
||||
// Setup the mock for SendResult
|
||||
var sendResultFlow = requestContext
|
||||
.Setup(rc => rc.SendResult(It.IsAny<QueryExecuteResult>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (resultCallback != null)
|
||||
{
|
||||
sendResultFlow.Callback(resultCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendEvent
|
||||
var sendEventFlow = requestContext.Setup(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteCompleteParams>>(m => m == QueryExecuteCompleteEvent.Type),
|
||||
It.IsAny<QueryExecuteCompleteParams>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (eventCallback != null)
|
||||
{
|
||||
sendEventFlow.Callback(eventCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendError
|
||||
var sendErrorFlow = requestContext.Setup(rc => rc.SendError(It.IsAny<object>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (errorCallback != null)
|
||||
{
|
||||
sendErrorFlow.Callback(errorCallback);
|
||||
}
|
||||
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -21,7 +22,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// ... I request a query (doesn't matter what kind)
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams {QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// ... And then I dispose of the query
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
@@ -399,7 +400,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, (et, cp) => completeParams = cp, null);
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(
|
||||
resultCallback: qer => result = qer,
|
||||
expectedEvent: QueryExecuteCompleteEvent.Type,
|
||||
eventCallback: (et, cp) => completeParams = cp,
|
||||
errorCallback: null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -426,7 +432,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, (et, cp) => completeParams = cp, null);
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(
|
||||
resultCallback: qer => result = qer,
|
||||
expectedEvent: QueryExecuteCompleteEvent.Type,
|
||||
eventCallback: (et, cp) => completeParams = cp,
|
||||
errorCallback: null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -453,7 +464,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QueryText = Common.StandardQuery };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, null, null);
|
||||
var requestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -476,13 +487,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait();
|
||||
|
||||
// ... And then I request another query without waiting for the first to complete
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Simulate query hasn't finished
|
||||
QueryExecuteResult result = null;
|
||||
var secondRequestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, null, null);
|
||||
var secondRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, secondRequestContext.Object).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -505,13 +516,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
|
||||
queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait();
|
||||
|
||||
// ... And then I request another query after waiting for the first to complete
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
var secondRequestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, (et, qecp) => complete = qecp, null);
|
||||
var secondRequestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null);
|
||||
queryService.HandleExecuteRequest(queryParams, secondRequestContext.Object).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -535,7 +548,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = query };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, null, null);
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -560,7 +574,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, (et, qecp) => complete = qecp, null);
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
|
||||
// Then:
|
||||
|
||||
@@ -9,6 +9,7 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -95,7 +96,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryService =Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true);
|
||||
var executeParams = new QueryExecuteParams {QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// ... And I then ask for a valid set of results from it
|
||||
@@ -141,7 +142,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryService = Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false;
|
||||
|
||||
@@ -168,7 +169,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryService = Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// ... And I then ask for a set of results from it
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
|
||||
{
|
||||
public static class RequestContextMocks
|
||||
{
|
||||
|
||||
public static Mock<RequestContext<TResponse>> Create<TResponse>(Action<TResponse> resultCallback)
|
||||
{
|
||||
var requestContext = new Mock<RequestContext<TResponse>>();
|
||||
|
||||
// Setup the mock for SendResult
|
||||
var sendResultFlow = requestContext
|
||||
.Setup(rc => rc.SendResult(It.IsAny<TResponse>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (resultCallback != null)
|
||||
{
|
||||
sendResultFlow.Callback(resultCallback);
|
||||
}
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
public static Mock<RequestContext<TResponse>> AddEventHandling<TResponse, TParams>(
|
||||
this Mock<RequestContext<TResponse>> mock,
|
||||
EventType<TParams> expectedEvent,
|
||||
Action<EventType<TParams>, TParams> eventCallback)
|
||||
{
|
||||
var flow = mock.Setup(rc => rc.SendEvent(
|
||||
It.Is<EventType<TParams>>(m => m == expectedEvent),
|
||||
It.IsAny<TParams>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (eventCallback != null)
|
||||
{
|
||||
flow.Callback(eventCallback);
|
||||
}
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
public static Mock<RequestContext<TResponse>> AddErrorHandling<TResponse>(
|
||||
this Mock<RequestContext<TResponse>> mock,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
|
||||
// Setup the mock for SendError
|
||||
var sendErrorFlow = mock.Setup(rc => rc.SendError(It.IsAny<object>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (mock != null && errorCallback != null)
|
||||
{
|
||||
sendErrorFlow.Callback(errorCallback);
|
||||
}
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
public static Mock<RequestContext<TResponse>> SetupRequestContextMock<TResponse, TParams>(
|
||||
Action<TResponse> resultCallback,
|
||||
EventType<TParams> expectedEvent,
|
||||
Action<EventType<TParams>, TParams> eventCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
return Create(resultCallback)
|
||||
.AddEventHandling(expectedEvent, eventCallback)
|
||||
.AddErrorHandling(errorCallback);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,20 +6,13 @@
|
||||
//#define USE_LIVE_CONNECTION
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.Test.Utility
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
|
||||
{
|
||||
public static class TestUtils
|
||||
{
|
||||
|
||||
public static void RunIfLinux(Action test)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
test();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunIfWindows(Action test)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
test();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// //
|
||||
// // Copyright (c) Microsoft. All rights reserved.
|
||||
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
// //
|
||||
|
||||
// using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
// using Microsoft.SqlTools.ServiceLayer.WorkspaceServices.Contracts;
|
||||
// using Microsoft.SqlTools.Test.Utility;
|
||||
// using Xunit;
|
||||
|
||||
// namespace Microsoft.SqlTools.ServiceLayer.Test.Workspace
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Tests for the ServiceHost Language Service tests
|
||||
// /// </summary>
|
||||
// public class WorkspaceServiceTests
|
||||
// {
|
||||
|
||||
// [Fact]
|
||||
// public async Task ServiceLoadsProfilesOnDemand()
|
||||
// {
|
||||
// // Given an event detailing
|
||||
|
||||
// // when
|
||||
// // Send the configuration change to cause profiles to be loaded
|
||||
// await this.languageServiceClient.SendEvent(
|
||||
// DidChangeConfigurationNotification<LanguageServerSettingsWrapper>.Type,
|
||||
// new DidChangeConfigurationParams<LanguageServerSettingsWrapper>
|
||||
// {
|
||||
// Settings = new LanguageServerSettingsWrapper
|
||||
// {
|
||||
// Powershell = new LanguageServerSettings
|
||||
// {
|
||||
// EnableProfileLoading = true,
|
||||
// ScriptAnalysis = new ScriptAnalysisSettings
|
||||
// {
|
||||
// Enable = false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// OutputReader outputReader = new OutputReader(this.protocolClient);
|
||||
|
||||
// Task<EvaluateResponseBody> evaluateTask =
|
||||
// this.SendRequest(
|
||||
// EvaluateRequest.Type,
|
||||
// new EvaluateRequestArguments
|
||||
// {
|
||||
// Expression = "\"PROFILE: $(Assert-ProfileLoaded)\"",
|
||||
// Context = "repl"
|
||||
// });
|
||||
|
||||
// // Try reading up to 10 lines to find the expected output line
|
||||
// string outputString = null;
|
||||
// for (int i = 0; i < 10; i++)
|
||||
// {
|
||||
// outputString = await outputReader.ReadLine();
|
||||
|
||||
// if (outputString.StartsWith("PROFILE"))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Delete the test profile before any assert failures
|
||||
// // cause the function to exit
|
||||
// File.Delete(currentUserCurrentHostPath);
|
||||
|
||||
// // Wait for the selection to appear as output
|
||||
// await evaluateTask;
|
||||
// Assert.Equal("PROFILE: True", outputString);
|
||||
// }
|
||||
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user