Skip to content

Implement password policy with hook#5419

Merged
lippserd merged 6 commits into
mainfrom
password-policy
Jul 3, 2026
Merged

Implement password policy with hook#5419
lippserd merged 6 commits into
mainfrom
password-policy

Conversation

@JolienTrog

@JolienTrog JolienTrog commented Aug 26, 2025

Copy link
Copy Markdown
Contributor

Add password policy support for database-backed user accounts. Administrators can select a policy under Configuration > Application > General. It is then enforced on every form that sets or resets a password. The account change-password form, the admin user create and edit form, and the setup wizard admin account page.

Two built-in policies ship with this change. None (the default) accepts any password without validation. Common enforces a baseline: minimum 12 characters, at least one digit, one special character, one lowercase letter, and one uppercase letter. The active policy's requirements are shown as an info callout below the password field so users know what to enter before submitting. A configuration error falls back to an error callout rather than breaking the form.

Third-party modules can provide additional policies by implementing PasswordPolicyHook. The new section in doc/05-Authentication.md covers the configuration UI and a worked example of a custom policy module.

resolve #4401

@JolienTrog JolienTrog requested a review from Al2Klimov August 26, 2025 09:22
@cla-bot cla-bot Bot added the cla/signed label Aug 26, 2025
Al2Klimov

This comment was marked as resolved.

Al2Klimov

This comment was marked as resolved.

@JolienTrog JolienTrog requested a review from Al2Klimov August 26, 2025 15:21
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
@JolienTrog JolienTrog requested a review from Al2Klimov August 27, 2025 07:53
Comment thread library/Icinga/Authentication/PasswordValidator.php Outdated
Comment thread application/forms/Account/ChangePasswordForm.php Outdated
Comment thread application/forms/Config/GeneralConfigForm.php Outdated
Comment thread application/forms/Account/ChangePasswordForm.php Outdated
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread application/forms/Config/User/UserForm.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Authentication/PasswordValidator.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Authentication/PasswordValidator.php Outdated
Comment thread library/Icinga/Authentication/PasswordValidator.php Outdated
Comment thread application/forms/Account/ChangePasswordForm.php Outdated
@JolienTrog JolienTrog requested a review from Al2Klimov August 28, 2025 08:35
Comment thread application/forms/Account/ChangePasswordForm.php Outdated
Comment thread library/Icinga/Application/Hook/PasswordPolicyHook.php
Comment thread library/Icinga/Application/Hook/PasswordPolicyHook.php Outdated
Comment thread library/Icinga/Application/Hook/PasswordPolicyHook.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread application/forms/Account/ChangePasswordForm.php
Comment thread application/forms/Config/User/UserForm.php
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Authentication/PasswordValidator.php Outdated
@JolienTrog JolienTrog requested a review from Al2Klimov August 28, 2025 11:05
Comment thread application/forms/Account/ChangePasswordForm.php Outdated
Comment thread application/forms/Account/ChangePasswordForm.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
@JolienTrog JolienTrog requested a review from Al2Klimov August 28, 2025 13:15
Comment thread library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/NonePasswordPolicy.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/NonePasswordPolicy.php
@JolienTrog JolienTrog requested a review from Al2Klimov August 28, 2025 14:05
Comment thread application/forms/Account/ChangePasswordForm.php Outdated
Comment thread application/forms/Config/User/UserForm.php Outdated
@JolienTrog JolienTrog requested a review from Al2Klimov August 28, 2025 15:26

@Al2Klimov Al2Klimov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During setup, the wizard prompts me for a username and password "to configure your first administrative account". This should enforce the chosen password policy.

@JolienTrog JolienTrog force-pushed the password-policy branch 2 times, most recently from 97e07ad to 39bbc53 Compare September 2, 2025 10:24
Comment thread test/php/library/Icinga/Application/CommonPasswordPolicyTest.php Outdated
Comment thread test/php/library/Icinga/Application/CommonPasswordPolicyTest.php Outdated
Comment thread test/php/library/Icinga/Application/CommonPasswordPolicyTest.php Outdated
Comment thread test/php/library/Icinga/Application/CommonPasswordPolicyTest.php Outdated
Comment thread library/Icinga/Application/Hook/PasswordPolicyHook.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/CommonPasswordPolicy.php Outdated
Comment thread library/Icinga/Application/ProvidedHook/CommonPasswordPolicy.php Outdated
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread test/php/library/Icinga/Application/NoPasswordPolicyTest.php
Comment thread test/php/library/Icinga/Application/CommonPasswordPolicyTest.php Outdated
Comment thread test/php/library/Icinga/Application/CommonPasswordPolicyTest.php Outdated
@JolienTrog JolienTrog requested a review from lippserd January 30, 2026 08:43
public static function register(): void
{
Hook::register(self::HOOK_NAME, static::class, static::class);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Wait until Introduce HookEssentials #5474 is merged
  2. Use that trait

Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread doc/05-Authentication.md Outdated
Comment thread doc/05-Authentication.md

class PasswordPolicy extends PasswordPolicyHook
{
use Translation;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use the Translation trait only but never call $this->translate() on any of the strings.

Comment thread doc/05-Authentication.md
Comment thread doc/05-Authentication.md
return $passwordPolicy;
}

public static function addPasswordPolicyDescription(Form $form, PasswordPolicy $passwordPolicy): void

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPDoc missing.

Comment thread library/Icinga/Authentication/PasswordPolicyValidator.php Outdated
Comment thread library/Icinga/Authentication/PasswordPolicyValidator.php Outdated
Comment thread library/Icinga/Authentication/PasswordPolicyValidator.php Outdated
Comment thread test/php/library/Icinga/Application/AnyPasswordPolicyTest.php Outdated
@JolienTrog JolienTrog requested a review from lippserd March 24, 2026 10:46
@lippserd lippserd added this to the 2.13 milestone Mar 24, 2026
@lippserd lippserd requested a review from Al2Klimov March 25, 2026 12:19
@lippserd lippserd removed this from the 2.13 milestone Mar 25, 2026
@Al2Klimov Al2Klimov requested a review from TheSyscall March 30, 2026 10:59
Comment thread library/Icinga/Application/Hook/PasswordPolicyHook.php Outdated
Comment thread library/Icinga/Application/Hook/PasswordPolicyHook.php Outdated
Comment thread library/Icinga/Authentication/PasswordPolicyHelper.php Outdated
Comment thread library/Icinga/Authentication/PasswordPolicyHelper.php Outdated
Comment thread library/Icinga/Authentication/PasswordPolicyHelper.php Outdated
const DEFAULT_PASSWORD_POLICY = AnyPasswordPolicy::class;

/** @var string INI configuration section for password policy */
const CONFIG_SECTION = 'global';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving password policy to the security section.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you also made a separate permission for the CSP stuff, didn't you?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mostly talking about the [security] section of the config.ini file here.

But yes, I added 'config/security' in #5477 for all security related configuration.
The only thing that is currently in this section is CSP, but it seems password policy would fit well there.

*
* This form is not used directly but as subform for the {@link GeneralConfigForm}.
*/
class PasswordPolicyConfigForm extends Form

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There already was some discussion with @lippserd to move this form into a new Security section in IW2.
We should consider converting this form into an ipl-web based form instead during this process.

Comment thread application/forms/Config/User/UserForm.php
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread application/forms/Config/General/PasswordPolicyConfigForm.php Outdated
Comment thread library/Icinga/Authentication/PasswordPolicyHelper.php Outdated
Comment thread library/Icinga/Application/Hook/PasswordPolicyHook.php Outdated
@jrauh01

jrauh01 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Since we now have the new callouts (Icinga/ipl-web#358) consider using them in the PasswordPolicyHelper instead of the custom callout.

@TheSyscall

Copy link
Copy Markdown
Contributor

Changed class loading to ask the hook by a canonical name instead of storing the class path and instantiating a class from that.

Since we now have the new callouts (Icinga/ipl-web#358) consider using them in the PasswordPolicyHelper instead of the custom callout.

Used callouts

@jrauh01 jrauh01 force-pushed the password-policy branch 4 times, most recently from fd2bca3 to 358a20d Compare July 3, 2026 09:26
@raviks789 raviks789 added this to the 2.14.0 milestone Jul 3, 2026
JolienTrog and others added 6 commits July 3, 2026 13:00
`PasswordPolicy` defines the contract every implementation must fulfill.
A display name, an optional description shown to users, and a `validate()`
method that returns a list of violation strings.

`PasswordPolicyHook` implements the interface and adds `loadConfigured()`,
a static factory that reads the selected policy name from application
configuration and returns the matching hook implementation, falling back to
`AnyPasswordPolicy` when none is configured.

`PasswordPolicyValidator` wraps a `PasswordPolicy` in a Zend form validator so
it can be attached to a password element without additional glue.
`PasswordPolicyHelper` handles form-level plumbing: attaching the validator,
rendering an info callout with the policy description, and falling back to an
error callout when loading the configured policy fails.

Co-authored-by: Johannes Rauh <johannes.rauh@icinga.com>
Co-authored-by: Alexander Rieß <alexander.riess@icinga.com>
`AnyPasswordPolicy` is the permissive default. It accepts every password without
validation. It exists so the policy dropdown always has a valid selection when
no enforcement is wanted.

`CommonPasswordPolicy` enforces a baseline. A minimum of 12 characters, at least
one digit, one special character, one uppercase letter, and one lowercase
letter. Its `getDescription()` return value is rendered in an info callout
before the password field so users know the requirements before submitting.

Co-authored-by: Johannes Rauh <johannes.rauh@icinga.com>
Co-authored-by: Alexander Rieß <alexander.riess@icinga.com>
Registers `CommonPasswordPolicy` and `AnyPasswordPolicy` so they are available
when `PasswordPolicyHook::loadConfigured()` resolves the active policy at
runtime.

Co-authored-by: Johannes Rauh <johannes.rauh@icinga.com>
Co-authored-by: Alexander Rieß <alexander.riess@icinga.com>
Adds a policy selector to Configuration > Application > General. The dropdown
lists all registered `PasswordPolicyHook` implementations by their display
name. The selected value is persisted under `[authentication]` in `config.ini`
and read back by `PasswordPolicyHook::loadConfigured()` at runtime.

Co-authored-by: Johannes Rauh <johannes.rauh@icinga.com>
Co-authored-by: Alexander Rieß <alexander.riess@icinga.com>
`PasswordPolicyHelper::apply()` is wired into every form that sets or resets a
database-backed password. The user account change-password form, the admin user
create and edit form, and the setup wizard admin account page.

`UserForm` previously shared element creation between insert and update by
having `createUpdateElements` call `createInsertElements`. That gave the update
path a password element identical to insert's, making it impossible for each
path to attach the validator with the correct `$oldPasswordElementName`
argument. Extracting the shared fields into `createCommonElements` gives each
path independent control over its own password element.

Co-authored-by: Johannes Rauh <johannes.rauh@icinga.com>
Co-authored-by: Alexander Rieß <alexander.riess@icinga.com>
Adds a new section to the authentication chapter covering the built-in policies
and their configuration under Configuration > Application > General, and a
worked example of a custom policy module with the full hook implementation,
`module.info`, and `run.php` registration.

Co-authored-by: Johannes Rauh <johannes.rauh@icinga.com>
Co-authored-by: Alexander Rieß <alexander.riess@icinga.com>
@jrauh01 jrauh01 force-pushed the password-policy branch from 358a20d to 46c047e Compare July 3, 2026 11:02
@lippserd lippserd merged commit 53d83a2 into main Jul 3, 2026
13 checks passed
@lippserd lippserd deleted the password-policy branch July 3, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Password Policies

7 participants