diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index b6d19896..57a7ba6e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -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; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Contracts/Credential.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Contracts/Credential.cs new file mode 100644 index 00000000..be595ec8 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Contracts/Credential.cs @@ -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 +{ + /// + /// A Credential containing information needed to log into a resource. This is primarily + /// defined as a unique with an associated + /// that's linked to it. + /// + public class Credential + { + /// + /// A unique ID to identify the credential being saved. + /// + public string CredentialId { get; set; } + + /// + /// The Password stored for this credential. + /// + public string Password { get; set; } + + /// + /// Default Constructor + /// + public Credential() + { + } + + /// + /// Constructor used when only is known + /// + /// + public Credential(string credentialId) + : this(credentialId, null) + { + + } + + /// + /// Constructor + /// + /// + /// + 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 + }; + } + + /// + /// Validates the credential has all the properties needed to look up the password + /// + public static void ValidateForLookup(Credential credential) + { + Validate.IsNotNull("credential", credential); + Validate.IsNotNullOrEmptyString("credential.CredentialId", credential.CredentialId); + } + + + /// + /// Validates the credential has all the properties needed to save a password + /// + public static void ValidateForSave(Credential credential) + { + ValidateForLookup(credential); + Validate.IsNotNullOrEmptyString("credential.Password", credential.Password); + } + } + + /// + /// Read Credential request mapping entry. Expects a Credential with CredentialId, + /// and responds with the filled in if found + /// + public class ReadCredentialRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("credential/read"); + } + + /// + /// Save Credential request mapping entry + /// + public class SaveCredentialRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("credential/save"); + } + + /// + /// Delete Credential request mapping entry + /// + public class DeleteCredentialRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("credential/delete"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs new file mode 100644 index 00000000..f1a80807 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs @@ -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 +{ + /// + /// Service responsible for securing credentials in a platform-neutral manner. This provides + /// a generic API for read, save and delete credentials + /// + public class CredentialService + { + internal static string DefaultSecretsFolder = ".sqlsecrets"; + internal const string DefaultSecretsFile = "sqlsecrets.json"; + + + /// + /// Singleton service instance + /// + private static Lazy instance + = new Lazy(() => new CredentialService()); + + /// + /// Gets the singleton service instance + /// + public static CredentialService Instance + { + get + { + return instance.Value; + } + } + + private ICredentialStore credStore; + + /// + /// Default constructor is private since it's a singleton class + /// + private CredentialService() + : this(null, new LinuxCredentialStore.StoreConfig() + { CredentialFolder = DefaultSecretsFolder, CredentialFile = DefaultSecretsFile, IsRelativeToUserHomeDir = true}) + { + } + + /// + /// Internal for testing purposes only + /// + internal CredentialService(ICredentialStore store, LinuxCredentialStore.StoreConfig config) + { + this.credStore = store != null ? store : GetStoreForOS(config); + } + + /// + /// Internal for testing purposes only + /// + 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 requestContext) + { + Func 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 requestContext) + { + Func doSave = () => + { + Credential.ValidateForSave(credential); + return credStore.Save(credential); + }; + await HandleRequest(doSave, requestContext, "HandleSaveCredentialRequest"); + } + + public async Task HandleDeleteCredentialRequest(Credential credential, RequestContext requestContext) + { + Func doDelete = () => + { + Credential.ValidateForLookup(credential); + return credStore.DeletePassword(credential.CredentialId); + }; + await HandleRequest(doDelete, requestContext, "HandleDeleteCredentialRequest"); + } + + private async Task HandleRequest(Func handler, RequestContext requestContext, string requestType) + { + Logger.Write(LogLevel.Verbose, requestType); + + try + { + T result = handler(); + await requestContext.SendResult(result); + } + catch (Exception ex) + { + await requestContext.SendError(ex.ToString()); + } + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/ICredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/ICredentialStore.cs new file mode 100644 index 00000000..0fa51cdd --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/ICredentialStore.cs @@ -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 +{ + /// + /// An support securely saving and retrieving passwords + /// + public interface ICredentialStore + { + /// + /// Saves a Password linked to a given Credential + /// + /// + /// A to be saved. + /// and are required + /// + /// True if successful, false otherwise + bool Save(Credential credential); + + /// + /// Gets a Password and sets it into a object + /// + /// The name of the credential to find the password for. This is required + /// Out value + /// true if password was found, false otherwise + bool TryGetPassword(string credentialId, out string password); + + /// + /// Deletes a password linked to a given credential + /// + /// The name of the credential to find the password for. This is required + /// True if password existed and was deleted, false otherwise + bool DeletePassword(string credentialId); + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/InteropUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/InteropUtils.cs new file mode 100644 index 00000000..fdb5343e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/InteropUtils.cs @@ -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 + { + + /// + /// Gets the length in bytes for a Unicode string, for use in interop where length must be defined + /// + 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); + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/CredentialsWrapper.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/CredentialsWrapper.cs new file mode 100644 index 00000000..3deab819 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/CredentialsWrapper.cs @@ -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 +{ + /// + /// Simplified class to enable writing a set of credentials to/from disk + /// + public class CredentialsWrapper + { + public List Credentials { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/FileTokenStorage.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/FileTokenStorage.cs new file mode 100644 index 00000000..ef2c2a67 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/FileTokenStorage.cs @@ -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 newEntries, IEnumerable existingEntries) + { + var allEntries = existingEntries.Concat(newEntries); + this.SaveEntries(allEntries); + } + + public void Clear() + { + this.SaveEntries(new List()); + } + + public IEnumerable LoadEntries() + { + if(!File.Exists(this.fileName)) + { + return Enumerable.Empty(); + } + + string serializedCreds; + lock (lockObject) + { + serializedCreds = File.ReadAllText(this.fileName); + } + + CredentialsWrapper creds = JsonConvert.DeserializeObject(serializedCreds, Constants.JsonSerializerSettings); + if(creds != null) + { + return creds.Credentials; + } + return Enumerable.Empty(); + } + + public void SaveEntries(IEnumerable 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); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Errors.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Errors.cs new file mode 100644 index 00000000..f3b1d5f5 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Errors.cs @@ -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 + { + /// Common Unix errno error codes. + 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); + } + } + +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Sys.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Sys.cs new file mode 100644 index 00000000..8777ab0c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/Interop.Sys.cs @@ -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"; + } + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/LinuxCredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/LinuxCredentialStore.cs new file mode 100644 index 00000000..6d6b5908 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Linux/LinuxCredentialStore.cs @@ -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 +{ + /// + /// Linux implementation of the credential store. + /// + /// + /// This entire implementation may need to be revised to support encryption of + /// passwords and protection of them when loaded into memory. + /// + /// + 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 creds; + if (LoadCredentialsAndFilterById(credentialId, out creds)) + { + storage.SaveEntries(creds); + return true; + } + + return false; + } + + /// + /// Gets filtered credentials with a specific ID filtered out + /// + /// True if the credential to filter was removed, false if it was not found + private bool LoadCredentialsAndFilterById(string idToFilter, out IEnumerable 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 creds; + LoadCredentialsAndFilterById(credential.CredentialId, out creds); + storage.SaveEntries(creds.Append(credential)); + + return true; + } + + + /// + /// Internal for testing purposes only + /// + internal string CredentialFolderPath + { + get { return this.credentialFolderPath; } + } + + /// + /// Concatenates a directory to the user home directory's path + /// + internal static string GetUserScopedDirectory(string userPath) + { + string homeDir = GetHomeDirectory() ?? string.Empty; + return Path.Combine(homeDir, userPath); + } + + + /// Gets the current user's home directory. + /// The path to the home directory, or null if it could not be determined. + 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; + } + } + } + + /// Wrapper for getpwuid_r. + /// The length of the buffer to use when storing the password result. + /// The resulting path; null if the user didn't have an entry. + /// true if the call was successful (path may still be null); false is a larger buffer is needed. + 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); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.CoreFoundation.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.CoreFoundation.cs new file mode 100644 index 00000000..140dfc63 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.CoreFoundation.cs @@ -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 + { + /// + /// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework. + /// + 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 + } + + /// + /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. + /// + /// Should be IntPtr.Zero + /// The string to get a CFStringRef for + /// The encoding of the str variable. This should be UTF 8 for OS X + /// Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero + /// For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that + [DllImport(Interop.Libraries.CoreFoundationLibrary, CharSet = CharSet.Ansi)] + private static extern SafeCreateHandle CFStringCreateWithCString( + IntPtr allocator, + string str, + CFStringBuiltInEncodings encoding); + + /// + /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. + /// + /// The string to get a CFStringRef for + /// Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle + internal static SafeCreateHandle CFStringCreateWithCString(string str) + { + return CFStringCreateWithCString(IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8); + } + + /// + /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. + /// + /// Should be IntPtr.Zero + /// The values to put in the array + /// The number of values in the array + /// Should be IntPtr.Zero + /// Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + private static extern SafeCreateHandle CFArrayCreate( + IntPtr allocator, + [MarshalAs(UnmanagedType.LPArray)] + IntPtr[] values, + ulong numValues, + IntPtr callbacks); + + /// + /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. + /// + /// The values to put in the array + /// The number of values in the array + /// Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle + internal static SafeCreateHandle CFArrayCreate(IntPtr[] values, ulong numValues) + { + return CFArrayCreate(IntPtr.Zero, values, numValues, IntPtr.Zero); + } + + /// + /// 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 + /// + /// The CFType object to retain. This value must not be NULL + /// The input value + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + internal extern static IntPtr CFRetain(IntPtr ptr); + + /// + /// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object. + /// + /// The pointer on which to decrement the reference count. + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + internal extern static void CFRelease(IntPtr ptr); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Libraries.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Libraries.cs new file mode 100644 index 00000000..7ad5b639 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Libraries.cs @@ -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"; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Security.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Security.cs new file mode 100644 index 00000000..0a6209e8 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/Interop.Security.cs @@ -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); + + /// + /// Find a generic password based on the attributes passed + /// + /// + /// A reference to an array of keychains to search, a single keychain, or NULL to search the user's default keychain search list. + /// + /// The length of the buffer pointed to by serviceName. + /// A pointer to a string containing the service name. + /// The length of the buffer pointed to by accountName. + /// A pointer to a string containing the account name. + /// On return, the length of the buffer pointed to by passwordData. + /// + /// 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. + /// + /// On return, a reference to the keychain item which was found. + /// A result code that should be in + /// + /// 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. + /// + [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); + + /// + /// Releases the memory used by the keychain attribute list and the keychain data retrieved in a previous call to SecKeychainItemCopyContent. + /// + /// A pointer to the attribute list to release. Pass NULL to ignore this parameter. + /// A pointer to the data buffer to release. Pass NULL to ignore this parameter. + /// A result code that should be in + [DllImport(Libraries.SecurityLibrary, SetLastError = true)] + internal static extern OSStatus SecKeychainItemFreeContent([In] IntPtr attrList, [In] IntPtr data); + + /// + /// Deletes a keychain item from the default keychain's permanent data store. + /// + /// A keychain item reference of the item to delete. + /// A result code that should be in + /// + /// 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. + /// + [DllImport(Libraries.SecurityLibrary, SetLastError = true)] + internal static extern OSStatus SecKeychainItemDelete(SafeHandle itemRef); + + #region OSStatus Codes + /// Common Unix errno error codes. + 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 + } + } +} + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/OSXCredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/OSXCredentialStore.cs new file mode 100644 index 00000000..dc868040 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/OSXCredentialStore.cs @@ -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 +{ + /// + /// OSX implementation of the credential store + /// + 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; + } + + /// + /// Finds the first password matching this credential + /// + 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; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/SafeCreateHandle.OSX.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/SafeCreateHandle.OSX.cs new file mode 100644 index 00000000..5beaaf26 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/OSX/SafeCreateHandle.OSX.cs @@ -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 + +{ + /// + /// 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. + /// + [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; + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/SecureStringHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/SecureStringHelper.cs new file mode 100644 index 00000000..070e0b20 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/SecureStringHelper.cs @@ -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); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialResources.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialResources.cs new file mode 100644 index 00000000..d3834c42 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialResources.cs @@ -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."; + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialSet.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialSet.cs new file mode 100644 index 00000000..f94a0f57 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialSet.cs @@ -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, 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 credentialHandles = + ptrCredList.Select(ptrCred => new NativeMethods.CriticalCredentialHandle(ptrCred)).ToList(); + + IEnumerable 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); + } + + } + +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialType.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialType.cs new file mode 100644 index 00000000..edc16d0d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/CredentialType.cs @@ -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 + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/GlobalSuppressions.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/GlobalSuppressions.cs new file mode 100644 index 00000000..3ee40dc8 Binary files /dev/null and b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/GlobalSuppressions.cs differ diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/NativeMethods.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/NativeMethods.cs new file mode 100644 index 00000000..1e43205c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/NativeMethods.cs @@ -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(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; + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/PersistanceType.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/PersistanceType.cs new file mode 100644 index 00000000..b08eff08 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/PersistanceType.cs @@ -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 + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32Credential.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32Credential.cs new file mode 100644 index 00000000..21c1c8b9 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32Credential.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32CredentialStore.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32CredentialStore.cs new file mode 100644 index 00000000..8a219854 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/Win32/Win32CredentialStore.cs @@ -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 +{ + /// + /// Win32 implementation of the credential store + /// + 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(); + } + + } + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs index b688d3d5..496e3d56 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/IProtocolEndpoint.cs @@ -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 /// public interface IProtocolEndpoint : IMessageSender { - void SetRequestHandler( - RequestType requestType, + void SetRequestHandler( + RequestType requestType, Func, Task> requestHandler); - void SetEventHandler( - EventType eventType, + void SetEventHandler( + EventType eventType, Func eventHandler); - void SetEventHandler( - EventType eventType, - Func eventHandler, + void SetEventHandler( + EventType eventType, + Func eventHandler, bool overrideExisting); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs index 17d4b5e0..e70f33f4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageReader.cs @@ -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 diff --git a/src/Microsoft.SqlTools.ServiceLayer/Program.cs b/src/Microsoft.SqlTools.ServiceLayer/Program.cs index c0f547c2..f0d2d6e8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Program.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Program.cs @@ -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 -{ +{ /// /// Main application class for SQL Tools API Service Host executable /// @@ -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(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs new file mode 100644 index 00000000..7adbdebe --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs @@ -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 +{ + /// + /// 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 + /// + 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; + /// + /// Constructor called once for every test + /// + 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(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(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( + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), + verify: (actual => Assert.True(actual))); + } + + [Fact] + public async Task SaveCredentialSupportsSavingCredentialMultipleTimes() + { + await RunAndVerify( + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), + verify: (actual => Assert.True(actual))); + + await RunAndVerify( + 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( + 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( + 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( + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), + verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully"))); + await RunAndVerify( + 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( + test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext), + verify: (actual => + { + Assert.Equal(password1, actual.Password); + })); + await RunAndVerify( + 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( + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), + verify: (actual => Assert.True(actual))); + + await RunAndVerify( + 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( + 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(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(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( + 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(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( + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), + verify: (actual => Assert.True(actual))); + + // Then delete - should return true + await RunAndVerify( + test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext), + verify: (actual => Assert.True(actual))); + + // Then delete - should return false as no longer exists + await RunAndVerify( + test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext), + verify: (actual => Assert.False(actual))); + } + + private async Task RunAndVerify(Func, Task> test, Action verify) + { + T result = default(T); + var contextMock = RequestContextMocks.Create(r => result = r).AddErrorHandling(null); + await test(contextMock.Object); + VerifyResult(contextMock, verify, result); + } + + private void VerifyErrorSent(Mock> contextMock) + { + contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Never); + contextMock.Verify(c => c.SendError(It.IsAny()), Times.Once); + } + + private void VerifyResult(Mock> contextMock, U expected, U actual) + { + contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); + Assert.Equal(expected, actual); + contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); + } + + private void VerifyResult(Mock> contextMock, Action verify, T actual) + { + contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); + contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); + verify(actual); + } + + } +} + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Linux/LinuxInteropTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Linux/LinuxInteropTests.cs new file mode 100644 index 00000000..1dcff8e6 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Linux/LinuxInteropTests.cs @@ -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); + }); + } + + } +} + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/CredentialSetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/CredentialSetTests.cs new file mode 100644 index 00000000..19edb663 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/CredentialSetTests.cs @@ -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(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(); + }); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/Win32CredentialTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/Win32CredentialTests.cs new file mode 100644 index 00000000..ae3f18b2 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/Win32/Win32CredentialTests.cs @@ -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(() => 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(); + }); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs index 5cf54b90..a1587015 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Properties/AssemblyInfo.cs @@ -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 diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs index 05df93b9..7e0e5a4d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs @@ -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(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(null, QueryExecuteCompleteEvent.Type, null, null); queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); // ... And then I request to cancel the query diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs index 9d2c1749..2437dc30 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs @@ -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> GetQueryExecuteResultContextMock( - Action resultCallback, - Action, QueryExecuteCompleteParams> eventCallback, - Action errorCallback) - { - var requestContext = new Mock>(); - - // Setup the mock for SendResult - var sendResultFlow = requestContext - .Setup(rc => rc.SendResult(It.IsAny())) - .Returns(Task.FromResult(0)); - if (resultCallback != null) - { - sendResultFlow.Callback(resultCallback); - } - - // Setup the mock for SendEvent - var sendEventFlow = requestContext.Setup(rc => rc.SendEvent( - It.Is>(m => m == QueryExecuteCompleteEvent.Type), - It.IsAny())) - .Returns(Task.FromResult(0)); - if (eventCallback != null) - { - sendEventFlow.Callback(eventCallback); - } - - // Setup the mock for SendError - var sendErrorFlow = requestContext.Setup(rc => rc.SendError(It.IsAny())) - .Returns(Task.FromResult(0)); - if (errorCallback != null) - { - sendErrorFlow.Callback(errorCallback); - } - - return requestContext; - } - - #endregion + } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs index 8ff0affd..8c79296d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs @@ -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(null, QueryExecuteCompleteEvent.Type, null, null); queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); // ... And then I dispose of the query diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs index ee98f8a2..49b6c76c 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs @@ -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( + 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( + 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(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(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(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(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(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(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(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null); queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); // Then: diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs index ad0f7075..a6f5e9fe 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs @@ -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(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(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(null, QueryExecuteCompleteEvent.Type, null, null); queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); // ... And I then ask for a set of results from it diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/RequestContextMocks.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/RequestContextMocks.cs new file mode 100644 index 00000000..91e05a76 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/RequestContextMocks.cs @@ -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> Create(Action resultCallback) + { + var requestContext = new Mock>(); + + // Setup the mock for SendResult + var sendResultFlow = requestContext + .Setup(rc => rc.SendResult(It.IsAny())) + .Returns(Task.FromResult(0)); + if (resultCallback != null) + { + sendResultFlow.Callback(resultCallback); + } + return requestContext; + } + + public static Mock> AddEventHandling( + this Mock> mock, + EventType expectedEvent, + Action, TParams> eventCallback) + { + var flow = mock.Setup(rc => rc.SendEvent( + It.Is>(m => m == expectedEvent), + It.IsAny())) + .Returns(Task.FromResult(0)); + if (eventCallback != null) + { + flow.Callback(eventCallback); + } + + return mock; + } + + public static Mock> AddErrorHandling( + this Mock> mock, + Action errorCallback) + { + + // Setup the mock for SendError + var sendErrorFlow = mock.Setup(rc => rc.SendError(It.IsAny())) + .Returns(Task.FromResult(0)); + if (mock != null && errorCallback != null) + { + sendErrorFlow.Callback(errorCallback); + } + + return mock; + } + + public static Mock> SetupRequestContextMock( + Action resultCallback, + EventType expectedEvent, + Action, TParams> eventCallback, + Action errorCallback) + { + return Create(resultCallback) + .AddEventHandling(expectedEvent, eventCallback) + .AddErrorHandling(errorCallback); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs index da079ea0..23ff7260 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs @@ -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 { diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestUtils.cs new file mode 100644 index 00000000..9a5f8ce1 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestUtils.cs @@ -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(); + } + } + } +} diff --git a/test/ServiceHost.Test/Workspace/WorkspaceServiceTests.cs b/test/ServiceHost.Test/Workspace/WorkspaceServiceTests.cs deleted file mode 100644 index dcdce257..00000000 --- a/test/ServiceHost.Test/Workspace/WorkspaceServiceTests.cs +++ /dev/null @@ -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 -// { -// /// -// /// Tests for the ServiceHost Language Service tests -// /// -// 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.Type, -// new DidChangeConfigurationParams -// { -// Settings = new LanguageServerSettingsWrapper -// { -// Powershell = new LanguageServerSettings -// { -// EnableProfileLoading = true, -// ScriptAnalysis = new ScriptAnalysisSettings -// { -// Enable = false -// } -// } -// } -// }); - -// OutputReader outputReader = new OutputReader(this.protocolClient); - -// Task 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); -// } - - -// } -// } -