Skip to content

Commit

Permalink
OpenChangePasswordDialog new rules #328 (#714)
Browse files Browse the repository at this point in the history
<!-- Thank you for submitting a Pull Request. If you're new to
contributing to BCApps please read our pull request guideline below
* https://github.com/microsoft/BCApps/Contributing.md
-->
#### Summary <!-- Provide a general summary of your changes -->

If OldPassword is passed, we check that the new password should match
the OldPassword
Also in this case we check that the old password matches the entered old
password
New test

#### Work Item(s) <!-- Add the issue number here after the #. The issue
needs to be open and approved. Submitting PRs with no linked issues or
unapproved issues is highly discouraged. -->
Fixes #328 





Fixes
[AB#506715](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/506715)

---------

Co-authored-by: Jesper Schulz-Wedde <[email protected]>
  • Loading branch information
Drakonian and JesperSchulz authored May 24, 2024
1 parent f5cb5a2 commit 6e0b1ac
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ report 9810 "Change Password"
trigger OnInitReport()
var
User: Record User;
PasswordDialogManagement: Codeunit "Password Dialog Management";
PasswordDialogImpl: Codeunit "Password Dialog Impl.";
Password: SecretText;
OldPassword: SecretText;
begin
PasswordDialogManagement.OpenChangePasswordDialog(OldPassword, Password);
PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
if Password.IsEmpty() then
exit;

Expand Down
17 changes: 17 additions & 0 deletions src/System Application/App/Password/src/PasswordDialog.Page.al
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ page 9810 "Password Dialog"
ExtendedDatatype = Masked;
ToolTip = 'Specifies the current password, before the user defines a new one.';
Visible = ShowOldPassword;
trigger OnValidate()
begin
PasswordDialogImpl.ValidateOldPasswordMatch(CurrentPasswordToCompare, OldPasswordValue);
end;
}
field(Password; PasswordValue)
{
Expand All @@ -39,6 +43,8 @@ page 9810 "Password Dialog"
begin
if RequiresPasswordValidation then
PasswordDialogImpl.ValidatePasswordStrength(PasswordValue);

PasswordDialogImpl.ValidateNewPasswordUniqueness(CurrentPasswordToCompare, PasswordValue);
end;
}
field(ConfirmPassword; ConfirmPasswordValue)
Expand Down Expand Up @@ -91,6 +97,7 @@ page 9810 "Password Dialog"
ConfirmPasswordValue: Text;
[NonDebuggable]
OldPasswordValue: Text;
CurrentPasswordToCompare: SecretText;
ShowOldPassword: Boolean;
ValidPassword: Boolean;
RequiresPasswordValidation: Boolean;
Expand Down Expand Up @@ -150,6 +157,16 @@ page 9810 "Password Dialog"
Password := OldPasswordValue;
end;

/// <summary>
/// Set the old password value to compare with typed on the page.
/// </summary>
/// <param name="OldPasswordSecret">Old password to compare.</param>
[Scope('OnPrem')]
procedure SetCurrentPasswordToCompareSecretValue(CurrentPasswordSecret: SecretText)
begin
CurrentPasswordToCompare := CurrentPasswordSecret;
end;

/// <summary>
/// Enables the Change password mode, it makes the old password field on the page visible.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ codeunit 9811 "Password Dialog Impl."
PasswordMismatchErr: Label 'The passwords that you entered do not match.';
PasswordTooSimpleErr: Label 'The password that you entered does not meet the minimum requirements. It must be at least %1 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character. It must not have a sequence of 3 or more ascending, descending or repeating characters.', Comment = '%1: The minimum number of characters required in the password';
ConfirmBlankPasswordQst: Label 'Do you want to exit without entering a password?';
PasswordSameAsNewErr: Label 'The new password cannot be the same as the current password.';
CurrentPasswordMismatchErr: Label 'The current password does not match the entered password.';

procedure ValidatePasswordStrength(Password: SecretText)
var
Expand Down Expand Up @@ -79,6 +81,16 @@ codeunit 9811 "Password Dialog Impl."
end;
end;

procedure OpenPasswordChangeDialog(CurrentPassword: SecretText; var NewPassword: SecretText)
var
PasswordDialog: Page "Password Dialog";
begin
PasswordDialog.EnableChangePassword();
PasswordDialog.SetCurrentPasswordToCompareSecretValue(CurrentPassword);
if PasswordDialog.RunModal() = Action::OK then
NewPassword := PasswordDialog.GetPasswordSecretValue();
end;

[NonDebuggable]
procedure ValidatePassword(RequiresPasswordConfirmation: Boolean; RequiresPasswordValidation: Boolean; Password: SecretText; ConfirmPassword: SecretText): Boolean
begin
Expand All @@ -92,5 +104,23 @@ codeunit 9811 "Password Dialog Impl."
exit(false);
exit(true);
end;

[NonDebuggable]
procedure ValidateOldPasswordMatch(CurrentPasswordToCompare: SecretText; OldPasswordEntered: SecretText)
begin
if CurrentPasswordToCompare.IsEmpty() then
exit;
if CurrentPasswordToCompare.Unwrap() <> OldPasswordEntered.Unwrap() then
Error(CurrentPasswordMismatchErr);
end;

[NonDebuggable]
procedure ValidateNewPasswordUniqueness(CurrentPasswordToCompare: SecretText; NewPassword: SecretText)
begin
if CurrentPasswordToCompare.IsEmpty() then
exit;
if CurrentPasswordToCompare.Unwrap() = NewPassword.Unwrap() then
Error(PasswordSameAsNewErr);
end;
}

Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ codeunit 9810 "Password Dialog Management"
PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
#pragma warning restore AL0432
end;

/// <summary>
/// Opens a dialog for the user to change a password and returns the old and new typed passwords if there is no validation error,
/// otherwise an empty text are returned.
/// </summary>
/// <param name="OldPassword">Out parameter, the old password user typed on the dialog.</param>
/// <param name="Password">Out parameter, the new password user typed on the dialog.</param>
[Obsolete('Replaced by OpenPasswordChangeDialog without out OldPassword param', '25.0')]
procedure OpenChangePasswordDialog(var OldPassword: SecretText; var Password: SecretText)
begin
PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
end;
#endif

/// <summary>
Expand Down Expand Up @@ -113,14 +125,14 @@ codeunit 9810 "Password Dialog Management"
end;

/// <summary>
/// Opens a dialog for the user to change a password and returns the old and new typed passwords if there is no validation error,
/// Opens a dialog for the user to change a password and returns the new typed password if there is no validation error,
/// otherwise an empty text are returned.
/// </summary>
/// <param name="OldPassword">Out parameter, the old password user typed on the dialog.</param>
/// <param name="Password">Out parameter, the new password user typed on the dialog.</param>
procedure OpenChangePasswordDialog(var OldPassword: SecretText; var Password: SecretText)
/// <param name="CurrentPassword">In parameter, the current password to compare with the password user typed on the dialog.</param>
/// <param name="NewPassword">Out parameter, the new password user typed on the dialog.</param>
procedure OpenPasswordChangeDialog(CurrentPassword: SecretText; var NewPassword: SecretText)
begin
PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
PasswordDialogImpl.OpenPasswordChangeDialog(CurrentPassword, NewPassword);
end;

/// <summary>
Expand Down
8 changes: 6 additions & 2 deletions src/System Application/Test Library/Password/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@
],
"idRanges": [
{
"from": 135033,
"to": 135033
"from": 132528,
"to": 132528
},
{
"from": 135033,
"to": 135033
}
],
"platform": "25.0.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace System.TestLibraries.Security.AccessControl;

using System.Security.AccessControl;

codeunit 132528 "Library - Password"
{
var
PasswordDialogImpl: Codeunit "Password Dialog Impl.";

/// <summary>
/// Opens a dialog for the user to change a password and returns the old and new typed passwords if there is no validation error,
/// otherwise an empty text are returned. Used for OnPrem report "Change Password"
/// </summary>
/// <param name="OldPassword">Out parameter, the old password user typed on the dialog.</param>
/// <param name="Password">Out parameter, the new password user typed on the dialog.</param>
procedure OpenChangePasswordDialog(var OldPassword: SecretText; var Password: SecretText)
begin
PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ codeunit 135033 "Password Dialog Test"
DisablePasswordValidation: Boolean;
PasswordMissmatch: Boolean;
ValidPasswordTxt: Label 'Some Password 2!';
AnotherValidPasswordTxt: Label 'Some Password 3!';
InValidPasswordTxt: Label 'Some Password';
AnotherPasswordTxt: Label 'Another Password';
PasswordSameAsNewErr: Label 'The new password cannot be the same as the current password.';
CurrentPasswordMismatchErr: Label 'The current password does not match the entered password.';

[Test]
[HandlerFunctions('PasswordDialogModalPageHandler')]
Expand Down Expand Up @@ -207,20 +210,64 @@ codeunit 135033 "Password Dialog Test"
[HandlerFunctions('ChangePasswordDialogModalPageHandler')]
procedure OpenChangePasswordDialogTest();
var
LibraryPassword: Codeunit "Library - Password";
Password: SecretText;
OldPassword: SecretText;
begin
// [SCENARIO] Open Password dialog in change password mode.
// Used for "Change Password" OnPrem report.
// Internal implementation to support OnPrem Database.ChangeUserPassword
PermissionsMock.Set('All Objects');

// [WHEN] The password dialog is opened in change password mode.
PasswordDialogManagement.OpenChangePasswordDialog(OldPassword, Password);
LibraryPassword.OpenChangePasswordDialog(OldPassword, Password);

// [THEN] The Old and New passwords are retrieved.
Assert.AreEqual(InValidPasswordTxt, GetPasswordValue(OldPassword), 'A diferrent password was expected.');
Assert.AreEqual(ValidPasswordTxt, GetPasswordValue(Password), 'A diferrent password was expected.')
end;

[Test]
[HandlerFunctions('ChangePasswordDialogModalPageHandler')]
procedure OpenPasswordChangeDialogTest();
var
Password: SecretText;
CurrentPassword: SecretText;
begin
// [SCENARIO] Open Password dialog in change password mode.
PermissionsMock.Set('All Objects');

// [WHEN] The password dialog is opened in change password mode.
PasswordDialogManagement.OpenPasswordChangeDialog(CurrentPassword, Password);

// [THEN] The New passwords was retrieved.
Assert.AreEqual('', GetPasswordValue(CurrentPassword), 'A diferrent password was expected.');
Assert.AreEqual(ValidPasswordTxt, GetPasswordValue(Password), 'A diferrent password was expected.')
end;

[Test]
[HandlerFunctions('ChangePasswordDialogWithCurrentPasswordModalPageHandler')]
procedure OpenPasswordChangeDialogWithCurrentPasswordTest();
var
Password: SecretText;
CurrentPassword: SecretText;
begin
// [SCENARIO] Open Password dialog in change password mode.
// The old password has been passed on.
PermissionsMock.Set('All Objects');

// [GIVEN] The old password is for comparison that the old password matches the entered user and
// the new password does not match the old password.
CurrentPassword := SecretStrSubstNo(ValidPasswordTxt);

// [WHEN] The password dialog is opened in change password mode.
PasswordDialogManagement.OpenPasswordChangeDialog(CurrentPassword, Password);

// [THEN] The Old and New passwords are retrieved.
Assert.AreEqual(ValidPasswordTxt, GetPasswordValue(CurrentPassword), 'A diferrent password was expected.');
Assert.AreEqual(AnotherValidPasswordTxt, GetPasswordValue(Password), 'A diferrent password was expected.')
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Password Dialog Management", 'OnSetMinPasswordLength', '', true, true)]
local procedure OnSetMinimumPAsswordLength(var MinPasswordLength: Integer);
begin
Expand All @@ -243,14 +290,29 @@ codeunit 135033 "Password Dialog Test"
[ModalPageHandler]
procedure ChangePasswordDialogModalPageHandler(var PasswordDialog: TestPage "Password Dialog");
begin
Assert.IsTrue(PasswordDialog.OldPassword.Visible(), 'Old Password Field should not be visible.');
Assert.IsTrue(PasswordDialog.ConfirmPassword.Visible(), 'Confirm Password Field should not be visible.');
Assert.IsTrue(PasswordDialog.OldPassword.Visible(), 'Old Password Field should be visible.');
Assert.IsTrue(PasswordDialog.ConfirmPassword.Visible(), 'Confirm Password Field should be visible.');
PasswordDialog.OldPassword.SetValue(InValidPasswordTxt);
PasswordDialog.Password.SetValue(ValidPasswordTxt);
PasswordDialog.ConfirmPassword.SetValue(ValidPasswordTxt);
PasswordDialog.OK().Invoke();
end;

[ModalPageHandler]
procedure ChangePasswordDialogWithCurrentPasswordModalPageHandler(var PasswordDialog: TestPage "Password Dialog");
begin
Assert.IsTrue(PasswordDialog.OldPassword.Visible(), 'Old Password Field should be visible.');
Assert.IsTrue(PasswordDialog.ConfirmPassword.Visible(), 'Confirm Password Field should be visible.');
asserterror PasswordDialog.OldPassword.SetValue(InValidPasswordTxt);
Assert.ExpectedError(CurrentPasswordMismatchErr);
PasswordDialog.OldPassword.SetValue(ValidPasswordTxt);
asserterror PasswordDialog.Password.SetValue(ValidPasswordTxt);
Assert.ExpectedError(PasswordSameAsNewErr);
PasswordDialog.Password.SetValue(AnotherValidPasswordTxt);
PasswordDialog.ConfirmPassword.SetValue(AnotherValidPasswordTxt);
PasswordDialog.OK().Invoke();
end;

[ConfirmHandler]
procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean);
begin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ codeunit 130045 "System Initialization Test"
CompanyTriggers: Codeunit "Company Triggers";
PasswordDialogManagement: Codeunit "Password Dialog Management";
PermissionsMock: Codeunit "Permissions Mock";
OldPassword: SecretText;
CurrentPassword: SecretText;
NewPassword: SecretText;
begin
PermissionsMock.Set('System Init Exec');
// [WHEN] Calling CompanyTriggers.OnCompanyOpen()
CompanyTriggers.OnCompanyOpenCompleted();

// [THEN] Calling PasswordDialog.OpenChangePasswordDialog should NOT results in an error
PasswordDialogManagement.OpenChangePasswordDialog(OldPassword, NewPassword);
// [THEN] Calling PasswordDialog.OpenPasswordChangeDialog should NOT results in an error
PasswordDialogManagement.OpenPasswordChangeDialog(CurrentPassword, NewPassword);
end;

[ModalPageHandler]
Expand Down

0 comments on commit 6e0b1ac

Please sign in to comment.