Conversation
Al2Klimov
left a comment
There was a problem hiding this comment.
Fix your code style as told by the failing Github actions.
Al2Klimov
left a comment
There was a problem hiding this comment.
Please push without -f for now. This eases it for me to keep track of your changes.
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/DefaultPasswordPolicy.php
Outdated
Show resolved
Hide resolved
Al2Klimov
left a comment
There was a problem hiding this comment.
During setup, the wizard prompts me for a username and password "to configure your first administrative account". This should enforce the chosen password policy.
97e07ad to
39bbc53
Compare
test/php/library/Icinga/Application/CommonPasswordPolicyTest.php
Outdated
Show resolved
Hide resolved
test/php/library/Icinga/Application/CommonPasswordPolicyTest.php
Outdated
Show resolved
Hide resolved
test/php/library/Icinga/Application/CommonPasswordPolicyTest.php
Outdated
Show resolved
Hide resolved
test/php/library/Icinga/Application/CommonPasswordPolicyTest.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/CommonPasswordPolicy.php
Outdated
Show resolved
Hide resolved
library/Icinga/Application/ProvidedHook/CommonPasswordPolicy.php
Outdated
Show resolved
Hide resolved
test/php/library/Icinga/Application/CommonPasswordPolicyTest.php
Outdated
Show resolved
Hide resolved
test/php/library/Icinga/Application/CommonPasswordPolicyTest.php
Outdated
Show resolved
Hide resolved
59ff429 to
71090ae
Compare
Al2Klimov
left a comment
There was a problem hiding this comment.
Not done yet:
Also, regarding #5419 (review): Situations where you push -f anyway, such as #5419 (comment), are good opportunities to squash at least commits which address only reviews and/or GitHub action failures.
|
|
||
| namespace Icinga\Application\Hook; | ||
|
|
||
| interface PasswordPolicyHook |
There was a problem hiding this comment.
Given PHP has no multiple inheritance, I wanted to preventively avoid unnecessary base classes and instructed you to make an interface.
See how tables have turned: #5442 (comment) 🙈
There was a problem hiding this comment.
This comment from Alex doesn't read like an instruction, so here's the explanation:
We use arbitrary strings such as PasswordPolicy to register and load hooks, which makes it difficult to actually find hook usages. I would suggest making PasswordPolicyHook an abstract class that also provides the methods all() and register() so that the string PasswordPolicy only appears in the hook itself. Please then also adjust the documentation.
There was a problem hiding this comment.
I think I would also do the following to make it clean:
- Rename
PasswordPolicyHooktoPasswordPolicy(and move it to theAuthenticationnamespace). - Use the
PasswordPolicyinterface everywhere in the code where typing is required. This is better than using the hook as a type, since the hook is used to load the functionality. - Have the new abstract
PasswordPolicyHookimplementPasswordPolicy.
There was a problem hiding this comment.
@lippserd But the framework requires hooks to extend PasswordPolicyHook anyway. Doesn't an abstract base class AND an interface rather provide more confusion than benefits?
- Fix validator handeling - Adjust hook registration name
| $helper = new PasswordPolicyHelper(); | ||
| $validators[] = $helper->getPasswordValidator(); | ||
| $helper->addPasswordPolicyDescription($this); | ||
| } catch (\Throwable $e) { |
| $validators[] = $helper->getPasswordValidator(); | ||
| $helper->addPasswordPolicyDescription($this); | ||
| } catch (\Throwable $e) { | ||
| Logger::error($e); |
There was a problem hiding this comment.
I would adjust the log message so that it includes the topic, e.g., 'The configured password policy could not be loaded: %s', $e
| $helper->addPasswordPolicyDescription($this); | ||
| } catch (\Throwable $e) { | ||
| Logger::error($e); | ||
| Notification::error("The configured password policy could not be loaded."); |
There was a problem hiding this comment.
I want this information to be displayed permanently, so Notification should be avoided. If styling is required, there is a form element called Note that can be used instead. You can search for 'note' in the Icinga web codebase to find examples of how it is used. addDescription() can also be used. The message could then be changed to something like this: There was a problem loading the configured password policy. Please contact your administrator.
| @@ -0,0 +1,51 @@ | |||
| <?php | |||
| { | ||
| $message = $this->passwordPolicyObject->validatePassword($value); | ||
|
|
||
| if (!empty($message)) { |
| use Icinga\Application\Hook\PasswordPolicyHook; | ||
| use Zend_Validate_Abstract; | ||
|
|
||
| class PasswordValidator extends Zend_Validate_Abstract |
There was a problem hiding this comment.
I would rename the class to PasswordPolicyValidator.
| * @return string[] Returns an empty array if the password is valid, | ||
| * otherwise returns error messages describing the violations | ||
| */ | ||
| public function validatePassword(string $password): array; |
|
|
||
| namespace Icinga\Application\Hook; | ||
|
|
||
| interface PasswordPolicyHook |
There was a problem hiding this comment.
I think I would also do the following to make it clean:
- Rename
PasswordPolicyHooktoPasswordPolicy(and move it to theAuthenticationnamespace). - Use the
PasswordPolicyinterface everywhere in the code where typing is required. This is better than using the hook as a type, since the hook is used to load the functionality. - Have the new abstract
PasswordPolicyHookimplementPasswordPolicy.
|
|
||
| public function testValidatePasswordTooShort(): void | ||
| { | ||
| $expectedResults = ['Password must be at least 12 characters long']; |
There was a problem hiding this comment.
Do we really need those variables? The signature of assert*() is $expected, $actual, so having the following is perfectly fine:
$this->assertSame(
expected,
actual
);And if you really need such variables, please use $expected and $actual.
💡@lippserd What do you think about this: ChangePasswordForm already has the old and new password. So PasswordPolicy can take both. It would cost us nothing, but admins could implement #5417 by themselves. They could also use e.g Levenshtein distance and/or A.I to prevent the "smartest" users from using passwords similar to their current ones. |
That's a good idea! @JolienTrog Could you please adjust the implementation so that the validation function accepts both |
| // if($oldPassword !== null) { | ||
| $form->getElement($oldPassword); | ||
| // } |
There was a problem hiding this comment.
Sorry I forgot to delete this part.
There was a problem hiding this comment.
It is indeed necessary to do something with the element for the old password, as your introduced validator expects the old password element to be named old_password, which may not be the case. This shows that it also makes sense to introduce test cases for an actual form that implements password elements, a policy, and the validator.
doc/05-Authentication.md
Outdated
| By default, no password policy is enforced ('Any'). | ||
| Icinga Web provides a built-in policy called 'Common' with the following requirements: |
There was a problem hiding this comment.
Please annotate Any and Common as code.
| use Icinga\Authentication\PasswordPolicy; | ||
|
|
||
| /** | ||
| * This class connects with the PasswordPolicy interface to use it as hook |
There was a problem hiding this comment.
| * This class connects with the PasswordPolicy interface to use it as hook | |
| * Base class for hookable password policies |
| abstract class PasswordPolicyHook implements PasswordPolicy | ||
| { | ||
| /** | ||
| * Registers Hook as Password Policy |
There was a problem hiding this comment.
| * Registers Hook as Password Policy | |
| * Register password policy |
| */ | ||
| public static function register(): void | ||
| { | ||
| Hook::register('PasswordPolicy', static::class, static::class); |
There was a problem hiding this comment.
PasswordPolicy could be a protected const, e.g., HOOK_NAME.
| } | ||
|
|
||
| /** | ||
| * Returns all registered password policies sorted by |
There was a problem hiding this comment.
| * Returns all registered password policies sorted by | |
| * Return all registered password policies sorted by name |
|
|
||
| class AnyPasswordPolicyTest extends TestCase | ||
| { | ||
| private PasswordPolicy $instance; |
There was a problem hiding this comment.
Please rename to passwordPolicy, as everywhere else in the code.
|
|
||
| class CommonPasswordPolicyTest extends TestCase | ||
| { | ||
| protected PasswordPolicy $instance; |
There was a problem hiding this comment.
Please rename to passwordPolicy, as everywhere else in the code.
| ); | ||
| } | ||
|
|
||
| public function testMethodValidatePasswordAlwaysReturnAnEmptyArray(): void |
There was a problem hiding this comment.
| public function testMethodValidatePasswordAlwaysReturnAnEmptyArray(): void | |
| public function testValidatePasswordValid(): void |
| $expectedResult = [ | ||
| 'Password must be at least 12 characters long', | ||
| 'Password must contain at least one number', | ||
| 'Password must contain at least one special character', | ||
| 'Password must contain at least one lowercase letter', | ||
| ]; | ||
| $res = | ||
| $this->assertSame( | ||
| $expectedResult, |
There was a problem hiding this comment.
| $expectedResult = [ | |
| 'Password must be at least 12 characters long', | |
| 'Password must contain at least one number', | |
| 'Password must contain at least one special character', | |
| 'Password must contain at least one lowercase letter', | |
| ]; | |
| $res = | |
| $this->assertSame( | |
| $expectedResult, | |
| $expected = [ | |
| 'Password must be at least 12 characters long', | |
| 'Password must contain at least one number', | |
| 'Password must contain at least one special character', | |
| 'Password must contain at least one lowercase letter', | |
| ]; | |
| $this->assertSame( | |
| $expected, |
|
|
||
| public function testValidatePasswordWithManyCharacters(): void | ||
| { | ||
| $longPassword = str_repeat('a', 1000); | ||
| $this->assertCount(3, $this->instance->validate($longPassword, 'null')); | ||
| } |
There was a problem hiding this comment.
This test is not very useful.
| public function testValidatePasswordWithManyCharacters(): void | |
| { | |
| $longPassword = str_repeat('a', 1000); | |
| $this->assertCount(3, $this->instance->validate($longPassword, 'null')); | |
| } |
Ref #4401