diff --git a/src/System Application/App/Password/src/ChangePassword.Report.al b/src/System Application/App/Password/src/ChangePassword.Report.al
index 2ca90d7cfa..07bf125c4d 100644
--- a/src/System Application/App/Password/src/ChangePassword.Report.al
+++ b/src/System Application/App/Password/src/ChangePassword.Report.al
@@ -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;
diff --git a/src/System Application/App/Password/src/PasswordDialog.Page.al b/src/System Application/App/Password/src/PasswordDialog.Page.al
index 6a6a659802..02b91c2664 100644
--- a/src/System Application/App/Password/src/PasswordDialog.Page.al
+++ b/src/System Application/App/Password/src/PasswordDialog.Page.al
@@ -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)
{
@@ -39,6 +43,8 @@ page 9810 "Password Dialog"
begin
if RequiresPasswordValidation then
PasswordDialogImpl.ValidatePasswordStrength(PasswordValue);
+
+ PasswordDialogImpl.ValidateNewPasswordUniqueness(CurrentPasswordToCompare, PasswordValue);
end;
}
field(ConfirmPassword; ConfirmPasswordValue)
@@ -91,6 +97,7 @@ page 9810 "Password Dialog"
ConfirmPasswordValue: Text;
[NonDebuggable]
OldPasswordValue: Text;
+ CurrentPasswordToCompare: SecretText;
ShowOldPassword: Boolean;
ValidPassword: Boolean;
RequiresPasswordValidation: Boolean;
@@ -150,6 +157,16 @@ page 9810 "Password Dialog"
Password := OldPasswordValue;
end;
+ ///
+ /// Set the old password value to compare with typed on the page.
+ ///
+ /// Old password to compare.
+ [Scope('OnPrem')]
+ procedure SetCurrentPasswordToCompareSecretValue(CurrentPasswordSecret: SecretText)
+ begin
+ CurrentPasswordToCompare := CurrentPasswordSecret;
+ end;
+
///
/// Enables the Change password mode, it makes the old password field on the page visible.
///
diff --git a/src/System Application/App/Password/src/PasswordDialogImpl.Codeunit.al b/src/System Application/App/Password/src/PasswordDialogImpl.Codeunit.al
index 3b700c7e76..afecdde6c4 100644
--- a/src/System Application/App/Password/src/PasswordDialogImpl.Codeunit.al
+++ b/src/System Application/App/Password/src/PasswordDialogImpl.Codeunit.al
@@ -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
@@ -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
@@ -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;
}
diff --git a/src/System Application/App/Password/src/PasswordDialogManagement.Codeunit.al b/src/System Application/App/Password/src/PasswordDialogManagement.Codeunit.al
index a660d45df9..54dd0e9d26 100644
--- a/src/System Application/App/Password/src/PasswordDialogManagement.Codeunit.al
+++ b/src/System Application/App/Password/src/PasswordDialogManagement.Codeunit.al
@@ -77,6 +77,18 @@ codeunit 9810 "Password Dialog Management"
PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
#pragma warning restore AL0432
end;
+
+ ///
+ /// 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.
+ ///
+ /// Out parameter, the old password user typed on the dialog.
+ /// Out parameter, the new password user typed on the dialog.
+ [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
///
@@ -113,14 +125,14 @@ codeunit 9810 "Password Dialog Management"
end;
///
- /// 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.
///
- /// Out parameter, the old password user typed on the dialog.
- /// Out parameter, the new password user typed on the dialog.
- procedure OpenChangePasswordDialog(var OldPassword: SecretText; var Password: SecretText)
+ /// In parameter, the current password to compare with the password user typed on the dialog.
+ /// Out parameter, the new password user typed on the dialog.
+ procedure OpenPasswordChangeDialog(CurrentPassword: SecretText; var NewPassword: SecretText)
begin
- PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
+ PasswordDialogImpl.OpenPasswordChangeDialog(CurrentPassword, NewPassword);
end;
///
diff --git a/src/System Application/Test Library/Password/app.json b/src/System Application/Test Library/Password/app.json
index fe513ed383..333fdba0c2 100644
--- a/src/System Application/Test Library/Password/app.json
+++ b/src/System Application/Test Library/Password/app.json
@@ -23,8 +23,12 @@
],
"idRanges": [
{
- "from": 135033,
- "to": 135033
+ "from": 132528,
+ "to": 132528
+ },
+ {
+ "from": 135033,
+ "to": 135033
}
],
"platform": "25.0.0.0",
diff --git a/src/System Application/Test Library/Password/src/LibraryPassword.Codeunit.al b/src/System Application/Test Library/Password/src/LibraryPassword.Codeunit.al
new file mode 100644
index 0000000000..183bb8e616
--- /dev/null
+++ b/src/System Application/Test Library/Password/src/LibraryPassword.Codeunit.al
@@ -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.";
+
+ ///
+ /// 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"
+ ///
+ /// Out parameter, the old password user typed on the dialog.
+ /// Out parameter, the new password user typed on the dialog.
+ procedure OpenChangePasswordDialog(var OldPassword: SecretText; var Password: SecretText)
+ begin
+ PasswordDialogImpl.OpenChangePasswordDialog(OldPassword, Password);
+ end;
+}
\ No newline at end of file
diff --git a/src/System Application/Test/Password/src/PasswordDialogTest.Codeunit.al b/src/System Application/Test/Password/src/PasswordDialogTest.Codeunit.al
index 1753ef9bee..63265da1ca 100644
--- a/src/System Application/Test/Password/src/PasswordDialogTest.Codeunit.al
+++ b/src/System Application/Test/Password/src/PasswordDialogTest.Codeunit.al
@@ -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')]
@@ -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
@@ -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
diff --git a/src/System Application/Test/System Initialization/src/SystemInitializationTest.Codeunit.al b/src/System Application/Test/System Initialization/src/SystemInitializationTest.Codeunit.al
index 20188ee021..b98fb2a202 100644
--- a/src/System Application/Test/System Initialization/src/SystemInitializationTest.Codeunit.al
+++ b/src/System Application/Test/System Initialization/src/SystemInitializationTest.Codeunit.al
@@ -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]