From add0724c214eb9ccaae172f9e323807f40fe86d6 Mon Sep 17 00:00:00 2001 From: Aasim Khan Date: Tue, 4 May 2021 22:16:31 +0000 Subject: [PATCH] Adding network share validation in sql migration. (#1203) * adding network file validator contract * Adding UNC path validation * Using helper methods for sanity checks * Fixed the validation variable type * Fixing if condition --- .../Contracts/ValidateNetworkFileShare.cs | 21 +++ .../Contracts/ValidateWindowsAccount.cs | 8 +- .../Migration/MigrationService.cs | 121 ++++++++++++++---- 3 files changed, 120 insertions(+), 30 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateNetworkFileShare.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateNetworkFileShare.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateNetworkFileShare.cs new file mode 100644 index 00000000..7ff7412f --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateNetworkFileShare.cs @@ -0,0 +1,21 @@ +// +// 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.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts +{ + public class ValidateNetworkFileShareRequestParams + { + public string Path { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } + + public class ValidateNetworkFileShareRequest + { + public static readonly RequestType Type = RequestType.Create("migration/validateNetworkFileShare"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs index 7d7c80dc..1a549b48 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs @@ -13,14 +13,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts public string Password { get; set; } } - public class ValidateWindowsAccountResult - { - public bool Success { get; set; } - public string ErrorMessage { get; set; } - } - public class ValidateWindowsAccountRequest { - public static readonly RequestType Type = RequestType.Create("migration/validateWindowsAccount"); + public static readonly RequestType Type = RequestType.Create("migration/validateWindowsAccount"); } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs index 2d3a1b6a..d734930f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs @@ -22,7 +22,8 @@ using Microsoft.Win32.SafeHandles; using System.ComponentModel; using System.Runtime.InteropServices; using System.Text.RegularExpressions; - +using System.Security.Principal; +using System.IO; namespace Microsoft.SqlTools.ServiceLayer.Migration { /// @@ -93,6 +94,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration this.ServiceHost = serviceHost; this.ServiceHost.SetRequestHandler(MigrationAssessmentsRequest.Type, HandleMigrationAssessmentsRequest); this.ServiceHost.SetRequestHandler(ValidateWindowsAccountRequest.Type, HandleValidateWindowsAccountRequest); + this.ServiceHost.SetRequestHandler(ValidateNetworkFileShareRequest.Type, HandleValidateNetworkFileShareRequest); } /// @@ -259,24 +261,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration internal async Task HandleValidateWindowsAccountRequest( ValidateWindowsAccountRequestParams parameters, - RequestContext requestContext) + RequestContext requestContext) { - var domainUserRegex = new Regex(@"^[A-Za-z0-9\\\._-]{7,}$"); - // Checking if the username string is in 'domain\name' format - if (!domainUserRegex.Match(parameters.Username).Success) + if (!ValidateWindowsDomainUsername(parameters.Username)) { - await requestContext.SendResult(new ValidateWindowsAccountResult() - { - Success = false, - ErrorMessage = "Invalid user name format. Example: Domain\\username" - }); + await requestContext.SendError("Invalid user name format. Example: Domain\\username"); return; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { int separator = parameters.Username.IndexOf("\\"); - string domainName = (separator > -1) ? parameters.Username.Substring(0, separator) : string.Empty; - string userName = (separator > -1) ? parameters.Username.Substring(separator + 1, parameters.Username.Length - separator - 1) : string.Empty; + string domainName = parameters.Username.Substring(0, separator); + string userName = parameters.Username.Substring(separator + 1, parameters.Username.Length - separator - 1); const int LOGON32_PROVIDER_DEFAULT = 0; const int LOGON32_LOGON_INTERACTIVE = 2; @@ -286,23 +282,102 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeAccessTokenHandle); - if (false == returnValue) + if (!returnValue) { int ret = Marshal.GetLastWin32Error(); string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; - await requestContext.SendResult(new ValidateWindowsAccountResult() - { - Success = returnValue, - ErrorMessage = errorMessage - }); + await requestContext.SendError(errorMessage); + } + else + { + await requestContext.SendResult(true); + } + } + else + { + await requestContext.SendResult(true); + } + } + + internal async Task HandleValidateNetworkFileShareRequest( + ValidateNetworkFileShareRequestParams parameters, + RequestContext requestContext) + { + if (!ValidateWindowsDomainUsername(parameters.Username)) + { + await requestContext.SendError("Invalid user name format. Example: Domain\\username"); + return; + } + + if (!ValidateUNCPath(parameters.Path)) + { + await requestContext.SendError("Invalid network share path. Example: \\\\Servername.domainname.com\\Backupfolder"); + return; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + int separator = parameters.Username.IndexOf("\\"); + string domainName = parameters.Username.Substring(0, separator); + string userName = parameters.Username.Substring(separator + 1, parameters.Username.Length - separator - 1); + + const int LOGON32_PROVIDER_WINNT50 = 3; + const int LOGON32_LOGON_NEW_CREDENTIALS = 9; + + SafeAccessTokenHandle safeAccessTokenHandle; + bool returnValue = LogonUser( + userName, + domainName, + parameters.Password, + LOGON32_LOGON_NEW_CREDENTIALS, + LOGON32_PROVIDER_WINNT50, + out safeAccessTokenHandle); + + if (!returnValue) + { + int ret = Marshal.GetLastWin32Error(); + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + await requestContext.SendError(errorMessage); return; } - } - await requestContext.SendResult(new ValidateWindowsAccountResult() + await WindowsIdentity.RunImpersonated( + safeAccessTokenHandle, + // User action + async () => + { + if(!Directory.Exists(parameters.Path)){ + await requestContext.SendError("Cannot connect to file share"); + } else { + await requestContext.SendResult(true); + } + } + ); + } + else { - Success = true, - ErrorMessage = "" - }); + await requestContext.SendResult(true); + } + } + + /// + /// Check if the username is in 'domain\username' format. + /// + /// + internal bool ValidateWindowsDomainUsername(string username) + { + var domainUserRegex = new Regex(@"^(?[A-Za-z0-9\._-]*)\\(?[A-Za-z0-9\._-]*)$"); + return domainUserRegex.IsMatch(username); + } + + + /// + /// Checks if the file path is in UNC format '\\Servername.domainname.com\Backupfolder' + /// + /// + /// + internal bool ValidateUNCPath(string path) + { + return new Uri(path).IsUnc; } } }