Add change password function and handling (#1771)

* added ChangePassword to connectionService

* added changepasswordparams

* added more code

* added more changes to connectionservice

* added more changes

* added small test

* added changepasswordrequest

* added different ServerConnection constructor

* consolidated changepassword

* added exception catch

* added passwordChangeFail params

* added changePassword to it's own function

* simplified changePassword

* made fixes to test

* added new test

* added one additional connection test

* added response callback

* removed unnecessary SendError

* added localized empty password error

* added updated error messages

* added small fix to check

* added changes based on feedback

* added minor change

* fix tests

* renamed messages to errorDetails

* simplified error message

* small change to connectionservice message

* error message change

* added environment newline

* added error retry messages to STS

* added regex

* added newline handling
This commit is contained in:
Alex Ma
2022-12-07 14:24:26 -08:00
committed by GitHub
parent d761e56354
commit 7eee5180a2
9 changed files with 322 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace Microsoft.SqlTools.ServiceLayer.Connection
{
@@ -1060,6 +1061,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
// Register request and event handlers with the Service Host
serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest);
serviceHost.SetRequestHandler(CancelConnectRequest.Type, HandleCancelConnectRequest);
serviceHost.SetRequestHandler(ChangePasswordRequest.Type, HandleChangePasswordRequest);
serviceHost.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequest);
serviceHost.SetRequestHandler(ListDatabasesRequest.Type, HandleListDatabasesRequest);
serviceHost.SetRequestHandler(ChangeDatabaseRequest.Type, HandleChangeDatabaseRequest);
@@ -1137,6 +1139,65 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
}).ContinueWithOnFaulted(null);
}
/// <summary>
/// Handle new change password requests
/// </summary>
/// <param name="connectParams"></param>
/// <param name="requestContext"></param>
/// <returns></returns>
protected async Task HandleChangePasswordRequest(
ChangePasswordParams changePasswordParams,
RequestContext<PasswordChangeResponse> requestContext)
{
Logger.Write(TraceEventType.Verbose, "HandleChangePasswordRequest");
PasswordChangeResponse newResponse = new PasswordChangeResponse();
try
{
ChangePassword(changePasswordParams);
newResponse.Result = true;
}
catch (Exception ex)
{
newResponse.Result = false;
newResponse.ErrorMessage = ex.InnerException != null ? (ex.Message + Environment.NewLine + Environment.NewLine + ex.InnerException.Message) : ex.Message;
newResponse.ErrorMessage = Regex.Replace(newResponse.ErrorMessage, @"\r?\nChanged database context to '\w+'\.", "");
newResponse.ErrorMessage = Regex.Replace(newResponse.ErrorMessage, @"\r?\nChanged language setting to \w+\.", "");
if (newResponse.ErrorMessage.Equals(SR.PasswordChangeEmptyPassword))
{
newResponse.ErrorMessage += Environment.NewLine + Environment.NewLine + SR.PasswordChangeEmptyPasswordRetry;
}
else if (newResponse.ErrorMessage.Contains(SR.PasswordChangeDNMReqs))
{
newResponse.ErrorMessage += Environment.NewLine + Environment.NewLine + SR.PasswordChangeDNMReqsRetry;
}
else if (newResponse.ErrorMessage.Contains(SR.PasswordChangePWCannotBeUsed))
{
newResponse.ErrorMessage += Environment.NewLine + Environment.NewLine + SR.PasswordChangePWCannotBeUsedRetry;
}
}
await requestContext.SendResult(newResponse);
}
public void ChangePassword(ChangePasswordParams changePasswordParams)
{
// Empty passwords are not valid.
if (string.IsNullOrEmpty(changePasswordParams.NewPassword))
{
throw new Exception(SR.PasswordChangeEmptyPassword);
}
// result is null if the ConnectParams was successfully validated
ConnectionCompleteParams result = ValidateConnectParams(changePasswordParams);
if (result != null)
{
throw new Exception(result.ErrorMessage, new Exception(result.Messages));
}
// Change the password of the connection
ServerConnection serverConnection = new ServerConnection(changePasswordParams.Connection.ServerName, changePasswordParams.Connection.UserName, changePasswordParams.Connection.Password);
serverConnection.ChangePassword(changePasswordParams.NewPassword);
}
/// <summary>
/// Handle cancel connect requests
/// </summary>

View File

@@ -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.
//
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
{
/// <summary>
/// Parameters for the Change Password Request.
/// </summary>
public class ChangePasswordParams : ConnectParams
{
/// <summary>
/// The password to change the account of the connection to.
/// </summary>
public string NewPassword { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
//
// 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.Connection.Contracts
{
/// <summary>
/// Connect request mapping entry
/// </summary>
public class ChangePasswordRequest
{
public static readonly
RequestType<ChangePasswordParams, PasswordChangeResponse> Type =
RequestType<ChangePasswordParams, PasswordChangeResponse>.Create("connection/changepassword");
}
}

View File

@@ -0,0 +1,23 @@
//
// 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.Connection.Contracts
{
/// <summary>
/// Parameters to be sent back after a password change attempt.
/// </summary>
public class PasswordChangeResponse
{
/// <summary>
/// Status indicating if password change was successful or not.
/// </summary>
public bool Result { get; set; }
/// <summary>
/// Error message for the password change, if an error occured.
/// </summary>
public string? ErrorMessage { get; set; }
}
}

View File

@@ -61,6 +61,54 @@ namespace Microsoft.SqlTools.ServiceLayer
}
}
public static string PasswordChangeEmptyPassword
{
get
{
return Keys.GetString(Keys.PasswordChangeEmptyPassword);
}
}
public static string PasswordChangeEmptyPasswordRetry
{
get
{
return Keys.GetString(Keys.PasswordChangeEmptyPasswordRetry);
}
}
public static string PasswordChangeDNMReqs
{
get
{
return Keys.GetString(Keys.PasswordChangeDNMReqs);
}
}
public static string PasswordChangeDNMReqsRetry
{
get
{
return Keys.GetString(Keys.PasswordChangeDNMReqsRetry);
}
}
public static string PasswordChangePWCannotBeUsed
{
get
{
return Keys.GetString(Keys.PasswordChangePWCannotBeUsed);
}
}
public static string PasswordChangePWCannotBeUsedRetry
{
get
{
return Keys.GetString(Keys.PasswordChangePWCannotBeUsedRetry);
}
}
public static string ConnectionParamsValidateNullOwnerUri
{
get
@@ -10082,6 +10130,24 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string ConnectionServiceConnectionCanceled = "ConnectionServiceConnectionCanceled";
public const string PasswordChangeEmptyPassword = "PasswordChangeEmptyPassword";
public const string PasswordChangeEmptyPasswordRetry = "PasswordChangeEmptyPasswordRetry";
public const string PasswordChangeDNMReqs = "PasswordChangeDNMReqs";
public const string PasswordChangeDNMReqsRetry = "PasswordChangeDNMReqsRetry";
public const string PasswordChangePWCannotBeUsed = "PasswordChangePWCannotBeUsed";
public const string PasswordChangePWCannotBeUsedRetry = "PasswordChangePWCannotBeUsedRetry";
public const string ConnectionParamsValidateNullOwnerUri = "ConnectionParamsValidateNullOwnerUri";

View File

@@ -168,6 +168,30 @@
<value>Connection canceled</value>
<comment></comment>
</data>
<data name="PasswordChangeEmptyPassword" xml:space="preserve">
<value>New password cannot be empty</value>
<comment></comment>
</data>
<data name="PasswordChangeEmptyPasswordRetry" xml:space="preserve">
<value>Press OK to input a new password that is not empty.</value>
<comment></comment>
</data>
<data name="PasswordChangeDNMReqs" xml:space="preserve">
<value>password does not meet operating system policy requirements</value>
<comment></comment>
</data>
<data name="PasswordChangeDNMReqsRetry" xml:space="preserve">
<value>Press OK to input a new password that meets operating system policy requirements.</value>
<comment></comment>
</data>
<data name="PasswordChangePWCannotBeUsed" xml:space="preserve">
<value>password cannot be used at this time</value>
<comment></comment>
</data>
<data name="PasswordChangePWCannotBeUsedRetry" xml:space="preserve">
<value>Press OK to input a different password.</value>
<comment></comment>
</data>
<data name="ConnectionParamsValidateNullOwnerUri" xml:space="preserve">
<value>OwnerUri cannot be null or empty</value>
<comment></comment>

View File

@@ -45,6 +45,20 @@ ConnectionServiceConnStringInvalidIntent(string intent) = Invalid value '{0}' fo
ConnectionServiceConnectionCanceled = Connection canceled
### Password Change
PasswordChangeEmptyPassword = New password cannot be empty
PasswordChangeEmptyPasswordRetry = Press OK to input a new password that is not empty.
PasswordChangeDNMReqs = password does not meet operating system policy requirements
PasswordChangeDNMReqsRetry = Press OK to input a new password that meets operating system policy requirements.
PasswordChangePWCannotBeUsed = password cannot be used at this time
PasswordChangePWCannotBeUsedRetry = Press OK to input a different password.
### Connection Params Validation Errors
ConnectionParamsValidateNullOwnerUri = OwnerUri cannot be null or empty

View File

@@ -6505,6 +6505,36 @@ The Query Processor estimates that implementing the following index could improv
<target state="new">Actual CPU Cost</target>
<note></note>
</trans-unit>
<trans-unit id="PasswordChangeEmptyPassword">
<source>New password cannot be empty</source>
<target state="new">New password cannot be empty</target>
<note></note>
</trans-unit>
<trans-unit id="PasswordChangeEmptyPasswordRetry">
<source>Press OK to input a new password that is not empty.</source>
<target state="new">Press OK to input a new password that is not empty.</target>
<note></note>
</trans-unit>
<trans-unit id="PasswordChangeDNMReqs">
<source>password does not meet operating system policy requirements</source>
<target state="new">password does not meet operating system policy requirements</target>
<note></note>
</trans-unit>
<trans-unit id="PasswordChangeDNMReqsRetry">
<source>Press OK to input a new password that meets operating system policy requirements.</source>
<target state="new">Press OK to input a new password that meets operating system policy requirements.</target>
<note></note>
</trans-unit>
<trans-unit id="PasswordChangePWCannotBeUsed">
<source>password cannot be used at this time</source>
<target state="new">password cannot be used at this time</target>
<note></note>
</trans-unit>
<trans-unit id="PasswordChangePWCannotBeUsedRetry">
<source>Press OK to input a different password.</source>
<target state="new">Press OK to input a different password.</target>
<note></note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -1763,5 +1763,72 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
Assert.IsFalse(ConnectionService.IsDbPool("db"));
Assert.IsFalse(ConnectionService.IsDbPool(null));
}
/// <summary>
/// Verify that providing an empty password to change password will fire an error.
/// </summary>
[Test]
public async Task ConnectionEmptyPasswordChange()
{
var serviceHostMock = new Mock<IProtocolEndpoint>();
var connectionService = ConnectionService.Instance;
connectionService.ServiceHost = serviceHostMock.Object;
// Set up an initial connection
const string ownerUri = "file://my/sample/file.sql";
ChangePasswordParams testConnectionParams = new ChangePasswordParams()
{
OwnerUri = ownerUri,
Connection = TestObjects.GetTestConnectionDetails(),
NewPassword = ""
};
Assert.Throws<Exception>(() => connectionService.ChangePassword(testConnectionParams));
}
/// <summary>
/// Verify that providing an invalid connection parameter value to change password will fire an error.
/// </summary>
[Test]
public async Task ConnectionInvalidParamPasswordChange()
{
var serviceHostMock = new Mock<IProtocolEndpoint>();
var connectionService = ConnectionService.Instance;
connectionService.ServiceHost = serviceHostMock.Object;
// Set up an initial connection
const string ownerUri = "file://my/sample/file.sql";
ChangePasswordParams testConnectionParams = new ChangePasswordParams()
{
OwnerUri = ownerUri,
Connection = { },
NewPassword = "TestPassword"
};
Assert.Throws<Exception>(() => connectionService.ChangePassword(testConnectionParams));
}
/// <summary>
/// Verify that providing a non actual connection and a fake password to change password will throw an error.
/// </summary>
[Test]
public async Task InvalidConnectionPasswordChange()
{
var serviceHostMock = new Mock<IProtocolEndpoint>();
var connectionService = ConnectionService.Instance;
connectionService.ServiceHost = serviceHostMock.Object;
// Set up an initial connection
const string ownerUri = "file://my/sample/file.sql";
ChangePasswordParams testConnectionParams = new ChangePasswordParams()
{
OwnerUri = ownerUri,
Connection = TestObjects.GetTestConnectionDetails(),
NewPassword = "TestPassword"
};
Assert.Throws<Microsoft.SqlServer.Management.Common.ChangePasswordFailureException>(
() => connectionService.ChangePassword(testConnectionParams));
}
}
}