diff --git a/application/forms/Account/ChangePasswordForm.php b/application/forms/Account/ChangePasswordForm.php index 5bca11cc8..10298dc19 100644 --- a/application/forms/Account/ChangePasswordForm.php +++ b/application/forms/Account/ChangePasswordForm.php @@ -3,11 +3,15 @@ namespace Icinga\Forms\Account; +use Icinga\Application\Config; +use Icinga\Authentication\PasswordValidator; use Icinga\Authentication\User\DbUserBackend; use Icinga\Data\Filter\Filter; use Icinga\User; use Icinga\Web\Form; use Icinga\Web\Notification; +use ipl\Html\Text; +use Icinga\Forms\Config\GeneralConfigForm; /** * Form for changing user passwords @@ -29,40 +33,50 @@ class ChangePasswordForm extends Form $this->setSubmitLabel($this->translate('Update Account')); } + /** * {@inheritdoc} + * @throws \Zend_Validate_Exception */ public function createElements(array $formData) { + $passwordPolicy = Config::app()->get('global', 'password_policy'); + if(isset($passwordPolicy) && class_exists($passwordPolicy)) { + $passwordPolicyObject = new $passwordPolicy(); + $this->addDescription($passwordPolicyObject->displayPasswordPolicy()); + } + $this->addElement( 'password', 'old_password', array( - 'label' => $this->translate('Old Password'), - 'required' => true + 'label' => $this->translate('Old Password'), + 'required' => true ) ); $this->addElement( 'password', 'new_password', array( - 'label' => $this->translate('New Password'), - 'required' => true + 'label' => $this->translate('New Password'), + 'required' => true, + 'validators' => array(new PasswordValidator()) ) ); $this->addElement( 'password', 'new_password_confirmation', array( - 'label' => $this->translate('Confirm New Password'), - 'required' => true, - 'validators' => array( + 'label' => $this->translate('Confirm New Password'), + 'required' => true, + 'validators' => array( array('identical', false, array('new_password')) ) ) ); } + /** * {@inheritdoc} */ @@ -83,13 +97,13 @@ class ChangePasswordForm extends Form public function isValid($formData) { $valid = parent::isValid($formData); - if (! $valid) { + if (!$valid) { return false; } $oldPasswordEl = $this->getElement('old_password'); - if (! $this->backend->authenticate($this->Auth()->getUser(), $oldPasswordEl->getValue())) { + if (!$this->backend->authenticate($this->Auth()->getUser(), $oldPasswordEl->getValue())) { $oldPasswordEl->addError($this->translate('Old password is invalid')); $this->markAsError(); return false; @@ -111,7 +125,7 @@ class ChangePasswordForm extends Form /** * Set the user backend * - * @param DbUserBackend $backend + * @param DbUserBackend $backend * * @return $this */ diff --git a/application/forms/Config/General/PasswordPolicyConfigForm.php b/application/forms/Config/General/PasswordPolicyConfigForm.php new file mode 100644 index 000000000..ba7960e6a --- /dev/null +++ b/application/forms/Config/General/PasswordPolicyConfigForm.php @@ -0,0 +1,66 @@ +setName('form_config_general_password_policy'); + } + + /** + * {@inheritdoc} + * + * @return $this + */ + public function createElements(array $formData) + { + $this->addElement( + 'checkbox', + 'global_password_policy', + array( + 'label' => $this->translate('Password Policy'), + 'value' => true, + 'description' => $this->translate( + 'Enforce strong password requirements for new passwords' + ), + ) + ); + + $passwordPolicies = []; + + foreach (Hook::all('passwordpolicy') as $class => $policy) { + $passwordPolicies[$class] = $policy->getName(); + } + + asort($passwordPolicies); + $this->addElement( + 'select', + 'global_password_policy', + array( + 'description' => $this->translate('Enforce strong '. + 'password requirements for new passwords'), + 'label' => $this->translate('Password Policy'), + 'multiOptions' => array_merge( + ['' => sprintf(' - %s - ', + $this->translate('No Password Policy'))], + $passwordPolicies + ), + ) + ); + + return $this; + } +} diff --git a/application/forms/Config/GeneralConfigForm.php b/application/forms/Config/GeneralConfigForm.php index 5f15512a5..9ec5ad552 100644 --- a/application/forms/Config/GeneralConfigForm.php +++ b/application/forms/Config/GeneralConfigForm.php @@ -7,6 +7,7 @@ use Icinga\Forms\Config\General\ApplicationConfigForm; use Icinga\Forms\Config\General\DefaultAuthenticationDomainConfigForm; use Icinga\Forms\Config\General\LoggingConfigForm; use Icinga\Forms\Config\General\ThemingConfigForm; +use Icinga\Forms\Config\General\PasswordPolicyConfigForm; use Icinga\Forms\ConfigForm; /** @@ -32,9 +33,11 @@ class GeneralConfigForm extends ConfigForm $loggingConfigForm = new LoggingConfigForm(); $themingConfigForm = new ThemingConfigForm(); $domainConfigForm = new DefaultAuthenticationDomainConfigForm(); + $passwordPolicyConfigForm = new PasswordPolicyConfigForm(); $this->addSubForm($appConfigForm->create($formData)); $this->addSubForm($loggingConfigForm->create($formData)); $this->addSubForm($themingConfigForm->create($formData)); $this->addSubForm($domainConfigForm->create($formData)); + $this->addSubForm($passwordPolicyConfigForm->create($formData)); } } diff --git a/application/forms/Config/User/UserForm.php b/application/forms/Config/User/UserForm.php index fb2ef4dc3..a2db3ab20 100644 --- a/application/forms/Config/User/UserForm.php +++ b/application/forms/Config/User/UserForm.php @@ -3,7 +3,9 @@ namespace Icinga\Forms\Config\User; +use Icinga\Application\Config; use Icinga\Application\Hook\ConfigFormEventsHook; +use Icinga\Authentication\PasswordValidator; use Icinga\Data\Filter\Filter; use Icinga\Forms\RepositoryForm; use Icinga\Web\Notification; @@ -15,8 +17,15 @@ class UserForm extends RepositoryForm * * @param array $formData The data sent by the user */ + protected function createInsertElements(array $formData) { + $passwordPolicy = Config::app()->get('global', 'password_policy'); + if(isset($passwordPolicy) && class_exists($passwordPolicy)) { + $passwordPolicyObject = new $passwordPolicy(); + $this->addDescription($passwordPolicyObject->displayPasswordPolicy()); + } + $this->addElement( 'checkbox', 'is_active', @@ -39,7 +48,8 @@ class UserForm extends RepositoryForm 'password', array( 'required' => true, - 'label' => $this->translate('Password') + 'label' => $this->translate('Password'), + 'validators' => array(new PasswordValidator()) ) ); diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index 32abeaabb..1537917df 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -7,6 +7,7 @@ use DirectoryIterator; use ErrorException; use Exception; use Icinga\Application\ProvidedHook\DbMigration; +use Icinga\Application\ProvidedHook\DefaultPasswordPolicy; use ipl\I18n\GettextTranslator; use ipl\I18n\StaticTranslator; use LogicException; @@ -740,6 +741,7 @@ abstract class ApplicationBootstrap protected function registerApplicationHooks(): self { Hook::register('DbMigration', DbMigration::class, DbMigration::class); + Hook::register('passwordpolicy', DefaultPasswordPolicy::class, DefaultPasswordPolicy::class); return $this; } diff --git a/library/Icinga/Application/Hook/PasswordPolicyHook.php b/library/Icinga/Application/Hook/PasswordPolicyHook.php new file mode 100644 index 000000000..86b7db081 --- /dev/null +++ b/library/Icinga/Application/Hook/PasswordPolicyHook.php @@ -0,0 +1,30 @@ +_messages = []; + $passwordPolicy = Config::app() + ->get('global', 'password_policy'); + + if (! isset($passwordPolicy) || ! class_exists($passwordPolicy)) { + return true; + } + + $passwordPolicyObject = new $passwordPolicy(); + $errorMessage = $passwordPolicyObject->validatePassword($value); + + if ($errorMessage != null) { + $this->_messages[] = $errorMessage; + return false; + } + + return true; + } +}