Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenChangePasswordDialog new rules #328 #714

Merged
merged 14 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading