User password policy
What's new
Regex by default for all iTops, (new and upgrade)
No password expiration by default (new and iTop upgrade, user creation and change user)
XML configuration, over-writtable in Configuration file, not in Configuration file by default
Default behavior
-
The UserLocal has a custom behaviour during the DoCheckToWrite preventing the object persistence if the password does not respect the configured policies.
-
The existing passwords are not affected.
-
The admin acount created during the setup is not affected
-
All UI are affected, even the webservices.
-
Admin can't bypass the configuration.
Configuration
$MyModuleSettings = array( 'authent-local' => array ( 'password_validation.pattern' => '', ), );
Configure the password value policy
using
-
password_validation.pattern
: this let you define a pattern used to validate new passwords
default ''password_validation.pattern''
the default password_validation.pattern
is
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$
.
It means :
-
at least one lower cased letter
-
at least one upper cased letter
-
at least one digit
-
at least one special character
-
more than 8 characters long
translation
If you change the pattern, you MUST change also the “password
does not match the password_validation.pattern
” using
this translation key:
Error:UserLocalPasswordValidator:UserPasswordPolicyRegex/ValidationFailed
Example of configuration
The configuration can either be defined using the XML or
conf/production/config-itop.php
. The example will make
use of conf/production/config-itop.php
because they
are simpler to write/read, but our customers should be encourage to
use the XML because doing so let them be sure to have the same
configuration across their instances.
No policy at all
if you want to disable the pattern matching policy, just set an empty string.
$MyModuleSettings = array( 'authent-local' => array( 'password_validation.pattern' => '', ), );
custom regex
Just use the standard PHP syntax (without the delimiters). This example force the password to be between 6 and 15 chars long: you should use the excellent https://regex101.com/ to test your regex.
$MyModuleSettings = array( 'authent-local' => array( 'password_validation.pattern' => '.{6,15}', ), );
Tests
Unit test exists here :
test/coreExtensions/UserLocalTest.php
.
Possible extensions
Here are some ideas of possible extensions. They are not available by defaut, by may (or may not) have already been coded by combodo.
Blacklist
It is quite easy to code. so easy that an example implementation will be proposed as a cookbook in a next chapter.
Previous password
This one, is a little more complicated, because :
-
iTop store only passwords hash,
-
and the salt is both random and hard to extract
-
(it is stored alongside the hash, and how to separate the salt and the hash depends upon the hash algorithm used.
-
So we write here a summary on own to write this validator:
-
The validator must implements
iApplicationObjectExtension::OnDBUpdate()
in addition to the\UserLocalPasswordValidator
interface-
in order to store the previous passwords hashes into a custom classe dedicated to this role
-
-
The check must be performed using a clear text password against this list of previous hashes.
-
In other words, it cannot be done by comparing two hases.
-
-
it seem reasonable to limit the number of previous stored hashes.
-
it can be made configurable
-
How to write an extension
You can add as many validator as you want, by making them
implements the UserLocalPasswordValidator
interface.
class MyCustomPasswordValidator implements UserLocalPasswordValidator { // your code here ... }
To perform the validation, just return a
\ValidatePassword
instance.
-
if the password is valid return
new \UserLocalPasswordValidity(true)
-
if the password is not valid, return
new \UserLocalPasswordValidity(false, $sMessage)
.-
$sMessage
must be an already translated message that will be displayed to the end user. Per convention, please use this form'Error:UserLocalPasswordValidator:<yourClassName>/ValidationFailed
' replacing<yourClassName>
with your class name.
-
Cookbook : write a Blacklist validator
Create an extension with this class:
class UserPasswordPolicyBlacklist implements UserLocalPasswordValidator { public function __construct() { } /** * @param string $proposedValue * @param UserLocal $oUserLocal * @param Config $config * * @return UserLocalPasswordValidity */ public function ValidatePassword($proposedValue, UserLocal $oUserLocal, $config) { $sBlacklistFile = APPROOT.$config->GetModuleSetting('password-validation.blacklist_file', 'password_validation.blacklist_file'); $handle = @fopen($sBlacklistFile, "r"); if (! $handle) { return new UserLocalPasswordValidity( false, 'Unknown error : Failed to read the password blacklist.' ); } try { while (($sBlackListPwd = fgets($handle, 4096)) !== false) { if (trim($sBlackListPwd) == trim($proposedValue)) { $sMessage = Dict::S('Error:UserLocalPasswordValidator:UserPasswordPolicyBlacklist/ValidationFailed'); return new UserLocalPasswordValidity( false, $sMessage ); } } if (!feof($handle)) { return new UserLocalPasswordValidity( false, 'Unknown error : Failed to read the password blacklist until the end.' ); } } finally { fclose($handle); } return new UserLocalPasswordValidity(true); } }
then, use this configuration to enable it :
$MyModuleSettings = array( 'password_validation.classes' => array( 'UserPasswordPolicyBlacklist ' => array( 'blacklist_file' => 'data/passwordBlacklist.txt', ), ), );
data/passwordBlacklist.txt
have to be composed by
one forbidden password per line. you may per example use this file
: https://github.com/danielmiessler/SecLists/blob/master/Passwords/darkweb2017-top10000.txt
it contains the 10.000 most frequent passwords.