From 3ad2b2312a9e381c71c68c44dbe7f5cc959d545a Mon Sep 17 00:00:00 2001 From: Aasim Khan Date: Tue, 4 May 2021 06:51:45 +0000 Subject: [PATCH] Adding windows account validation api for sql migration extensions. (#1202) * Adding validate file share api * Making message endpoint camel case * Adding validation for windows username --- .../Contracts/ValidateWindowsAccount.cs | 26 +++++++ .../Migration/MigrationService.cs | 76 ++++++++++++++++--- 2 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs new file mode 100644 index 00000000..7d7c80dc --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/ValidateWindowsAccount.cs @@ -0,0 +1,26 @@ +// +// 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 ValidateWindowsAccountRequestParams + { + public string Username { get; set; } + 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"); + } +} \ 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 9e340650..2d3a1b6a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs @@ -11,7 +11,6 @@ using Microsoft.SqlServer.Management.Assessment; using Microsoft.SqlServer.Management.Assessment.Checks; using Microsoft.SqlServer.Migration.Assessment.Common.Contracts.Models; using Microsoft.SqlServer.Migration.Assessment.Common.Engine; -using Microsoft.SqlServer.Migration.Assessment.Common.Models; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; @@ -19,7 +18,10 @@ using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.Migration.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlAssessment; -using Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts; +using Microsoft.Win32.SafeHandles; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; namespace Microsoft.SqlTools.ServiceLayer.Migration { @@ -27,9 +29,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration /// Main class for Migration Service functionality /// public sealed class MigrationService : IDisposable - { + { private static ConnectionService connectionService = null; - + private static readonly Lazy instance = new Lazy(() => new MigrationService()); private bool disposed; @@ -90,13 +92,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration { this.ServiceHost = serviceHost; this.ServiceHost.SetRequestHandler(MigrationAssessmentsRequest.Type, HandleMigrationAssessmentsRequest); + this.ServiceHost.SetRequestHandler(ValidateWindowsAccountRequest.Type, HandleValidateWindowsAccountRequest); } /// /// Handle request to start a migration session /// internal async Task HandleMigrationAssessmentsRequest( - MigrationAssessmentsParams parameters, + MigrationAssessmentsParams parameters, RequestContext requestContext) { string randomUri = Guid.NewGuid().ToString(); @@ -141,7 +144,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration result.Items.AddRange(results); await requestContext.SendResult(result); } - catch(Exception e){ + catch (Exception e) + { await requestContext.SendError(e.ToString()); } finally @@ -150,7 +154,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration } } - + internal class AssessmentRequest : IAssessmentRequest { private readonly Check[] checks = null; @@ -182,7 +186,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration { DmaEngine engine = new DmaEngine(connectionString); var assessmentResults = await engine.GetTargetAssessmentResultsList(); - + var result = new List(); foreach (var r in assessmentResults) { @@ -194,13 +198,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration var targetName = !string.IsNullOrWhiteSpace(migrationResult.DatabaseName) ? $"{target.ServerName}:{migrationResult.DatabaseName}" - : target.Name; + : target.Name; var ruleId = migrationResult.FeatureId.ToString(); var item = new MigrationAssessmentInfo() { CheckId = r.Check.Id, - Description = r.Check.Description, + Description = r.Check.Description, DisplayName = r.Check.DisplayName, HelpLink = r.Check.HelpLink, Level = r.Check.Level.ToString(), @@ -248,5 +252,57 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration disposed = true; } } + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, + int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken); + + internal async Task HandleValidateWindowsAccountRequest( + ValidateWindowsAccountRequestParams parameters, + 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) + { + await requestContext.SendResult(new ValidateWindowsAccountResult() + { + Success = false, + ErrorMessage = "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; + + const int LOGON32_PROVIDER_DEFAULT = 0; + const int LOGON32_LOGON_INTERACTIVE = 2; + + SafeAccessTokenHandle safeAccessTokenHandle; + bool returnValue = LogonUser(userName, domainName, parameters.Password, + LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, + out safeAccessTokenHandle); + + if (false == returnValue) + { + int ret = Marshal.GetLastWin32Error(); + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + await requestContext.SendResult(new ValidateWindowsAccountResult() + { + Success = returnValue, + ErrorMessage = errorMessage + }); + return; + } + } + await requestContext.SendResult(new ValidateWindowsAccountResult() + { + Success = true, + ErrorMessage = "" + }); + } } }