summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorTimotheus Pokorra <tp@tbits.net>2015-04-17 10:17:49 +0200
committerTimotheus Pokorra <tp@tbits.net>2015-04-17 10:20:55 +0200
commit87d05c91840bac6ebb9d965a95d18d9896dc23a7 (patch)
treea316f755d4a41adb436b75819c35f2b41cb2f53e /lib
parent981a19e863bac41ef0c9e2deabe2e6ddfc23cc77 (diff)
downloadwebadmin-87d05c91840bac6ebb9d965a95d18d9896dc23a7.tar.gz
implement Password complexity policy for WAP (#4988)
Diffstat (limited to 'lib')
-rw-r--r--lib/Password.php142
-rw-r--r--lib/api/kolab_api_service_form_value.php4
-rw-r--r--lib/api/kolab_api_service_user.php4
-rw-r--r--lib/locale/de_DE.php6
-rw-r--r--lib/locale/en_US.php6
5 files changed, 159 insertions, 3 deletions
diff --git a/lib/Password.php b/lib/Password.php
new file mode 100644
index 0000000..765b3d1
--- /dev/null
+++ b/lib/Password.php
@@ -0,0 +1,142 @@
+<?php
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab Web Admin Panel |
+ | |
+ | Copyright (C) 2011-2015, Kolab Systems AG |
+ | |
+ | This program is free software: you can redistribute it and/or modify |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 of the License, or |
+ | (at your option) any later version. |
+ | |
+ | This program is distributed in the hope that it will be useful, |
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+ | GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/> |
+ +--------------------------------------------------------------------------+
+ | Author: Timotheus Pokorra <tp@tbits.net> |
+ +--------------------------------------------------------------------------+
+*/
+
+class Password {
+
+ private static function get_password_policy()
+ {
+ // TODO: get the password policy from LDAP instead?
+ $conf = Conf::get_instance();
+ return $conf->get('kolab', 'password_policy', Conf::AUTO);
+ }
+
+ /**
+ * validate if password matches the password policy
+ * @throws an exception if password is not secure enough
+ */
+ public static function validate_password($password)
+ {
+ $passwordpolicy = self::get_password_policy();
+
+ if (!isset($passwordpolicy)) {
+ return true;
+ }
+
+ $password = trim($password);
+
+ $errors = '';
+
+ if (strlen($password) < $passwordpolicy['minLength']) {
+ $errors .= kolab_api_controller::translate("error").": ".
+ kolab_api_controller::translate("user.password.tooshort", $passwordpolicy['minLength']).'<br/>';
+ }
+
+ $countUpperCaseChars = strlen(preg_replace('![^A-Z]+!', '', $password));
+ if ($countUpperCaseChars < $passwordpolicy['minUpper']) {
+ $errors .= kolab_api_controller::translate("error").": ".
+ kolab_api_controller::translate("user.password.moreupper", $passwordpolicy['minUpper']).'<br/>';
+ }
+
+ $countLowerCaseChars = strlen(preg_replace('![^a-z]+!', '', $password));
+ if ($countLowerCaseChars < $passwordpolicy['minLower']) {
+ $errors .= kolab_api_controller::translate("error").": ".
+ kolab_api_controller::translate("user.password.morelower", $passwordpolicy['minLower']).'<br/>';
+ }
+
+ $countDigits = strlen(preg_replace('![^0-9]+!', '', $password));
+ if ($countDigits < $passwordpolicy['minNumeric']) {
+ $errors .= kolab_api_controller::translate("error").": ".
+ kolab_api_controller::translate("user.password.moredigits", $passwordpolicy['minNumeric']).'<br/>';
+ }
+
+ $sourceCharacters = str_split($passwordpolicy['specialChars']);
+ $countSpecialChars = 0;
+ foreach($sourceCharacters as $currentCharacter) {
+ $countSpecialChars += substr_count($password, $currentCharacter);
+ }
+
+ if ($countSpecialChars < $passwordpolicy['minSpecial']) {
+ $errors .= kolab_api_controller::translate("error").": ".
+ kolab_api_controller::translate("user.password.morespecial", $passwordpolicy['minSpecial'], $passwordpolicy['specialChars']).'<br/>';
+ }
+
+ if ($countUpperCaseChars + $countLowerCaseChars + $countDigits + $countSpecialChars < strlen($password)) {
+ $errors .= kolab_api_controller::translate("error").": ".
+ kolab_api_controller::translate("user.password.notallowed").'<br/>';
+ }
+
+ if ($errors != "") {
+ throw new Exception($errors);
+ }
+ }
+
+ /**
+ * generate a password that meets the given password complexity policy
+ *
+ * @return password
+ */
+ public static function generate_password()
+ {
+ $passwordpolicy = self::get_password_policy();
+
+ if (!isset($passwordpolicy)) {
+ # default behaviour, if no policy has been configured
+ exec("head -c 200 /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c15", $userpassword_plain);
+ return $userpassword_plain[0];
+ }
+
+ $password = '';
+
+ if ($passwordpolicy['minNumeric'] > 0) {
+ unset($pwd_snippet);
+ exec("head -c 200 /dev/urandom | tr -dc 0-9 | head -c".$passwordpolicy['minNumeric'], $pwd_snippet);
+ $password .= $pwd_snippet[0];
+ }
+
+ if ($passwordpolicy['minUpper'] > 0) {
+ unset($pwd_snippet);
+ exec("head -c 200 /dev/urandom | tr -dc 'A-Z' | head -c".$passwordpolicy['minUpper'], $pwd_snippet);
+ $password .= $pwd_snippet[0];
+ }
+
+ if ($passwordpolicy['minSpecial'] > 0) {
+ # using rand instead of /dev/urandom to avoid problems with special characters on bash command line
+ for ($countSpecialChar = 0; $countSpecialChar < $passwordpolicy['minSpecial']; $countSpecialChar++) {
+ $password .= $passwordpolicy['specialChars'][(rand() % strlen($passwordpolicy['specialChars']))];
+ }
+ }
+
+ $numberLowerCase = $passwordpolicy['minLower'];
+
+ if ($passwordpolicy['minLength'] > strlen($password) + $passwordpolicy['minLower']) {
+ $numberLowerCase = $passwordpolicy['minLength'] - strlen($password);
+ }
+
+ unset($pwd_snippet);
+ exec("head -c 200 /dev/urandom | tr -dc a-z | head -c".$numberLowerCase, $pwd_snippet);
+ $password .= $pwd_snippet[0];
+
+ return str_shuffle($password);
+ }
+}
diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php
index 10dd39c..bd16c5f 100644
--- a/lib/api/kolab_api_service_form_value.php
+++ b/lib/api/kolab_api_service_form_value.php
@@ -673,9 +673,7 @@ class kolab_api_service_form_value extends kolab_api_service
private function generate_password($postdata, $attribs = array())
{
- // TODO: Password complexity policy.
- exec("head -c 200 /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c15", $userpassword_plain);
- return $userpassword_plain[0];
+ return Password::generate_password();
}
private function generate_userpassword($postdata, $attribs = array())
diff --git a/lib/api/kolab_api_service_user.php b/lib/api/kolab_api_service_user.php
index 5d29d52..b8b14b2 100644
--- a/lib/api/kolab_api_service_user.php
+++ b/lib/api/kolab_api_service_user.php
@@ -83,6 +83,8 @@ class kolab_api_service_user extends kolab_api_service
$attributes = $this->parse_input_attributes('user', $postdata);
+ Password::validate_password($attributes['userpassword']);
+
Log::trace("user_add()", $attributes);
$auth = Auth::get_instance();
@@ -133,6 +135,8 @@ class kolab_api_service_user extends kolab_api_service
Log::trace("\$user_attributes as result from parse_input_attributes", $user_attributes);
+ Password::validate_password($user_attributes['userpassword']);
+
$auth = Auth::get_instance();
$result = $auth->user_edit($postdata['id'], $user_attributes, $postdata['type_id']);
diff --git a/lib/locale/de_DE.php b/lib/locale/de_DE.php
index 8689456..1727518 100644
--- a/lib/locale/de_DE.php
+++ b/lib/locale/de_DE.php
@@ -420,7 +420,13 @@ $LANG['user.org'] = 'Organisation';
$LANG['user.orgunit'] = 'Organisationseinheit';
$LANG['user.ou'] = 'Organisationseinheit';
$LANG['user.pager'] = 'Pager Nummer';
+$LANG['user.password.notallowed'] = 'Passwort enthält Zeichen (z.B. Umlaute), die nicht erlaubt sind';
$LANG['user.password.mismatch'] = 'Passwörter stimmen nicht überein!';
+$LANG['user.password.moreupper'] = 'Passwort muss mindestens $1 Großbuchstaben enthalten';
+$LANG['user.password.morelower'] = 'Passwort muss mindestens $1 Kleinbuchstaben enthalten';
+$LANG['user.password.moredigits'] = 'Passwort muss mindestens $1 Ziffer enthalten';
+$LANG['user.password.morespecial'] = 'Passwort muss mindestens $1 Sonderzeichen enthalten: $2';
+$LANG['user.password.tooshort'] = 'Passwort ist zu kurz, es muss mindestens $1 Zeichen angeben';
$LANG['user.personal'] = 'Persönlich';
$LANG['user.phone'] = 'Telefonnummer';
$LANG['user.postalcode'] = 'Postleitzahl';
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 1d77fd3..620843a 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -418,7 +418,13 @@ $LANG['user.org'] = 'Organization';
$LANG['user.orgunit'] = 'Organizational Unit';
$LANG['user.ou'] = 'Organizational Unit';
$LANG['user.pager'] = 'Pager Number';
+$LANG['user.password.notallowed'] = 'Password contains characters (eg. Umlaut) that are not permitted';
$LANG['user.password.mismatch'] = 'Passwords do not match!';
+$LANG['user.password.moreupper'] = 'Password needs to contain at least $1 uppercase character(s)';
+$LANG['user.password.morelower'] = 'Password needs to contain at least $1 lowercase character(s)';
+$LANG['user.password.moredigits'] = 'Password needs to contain at least $1 digit(s)';
+$LANG['user.password.morespecial'] = 'Password needs to contain at least $1 special character(s): $2';
+$LANG['user.password.tooshort'] = 'Password is too short, it must have at least $1 characters';
$LANG['user.personal'] = 'Personal';
$LANG['user.phone'] = 'Phone number';
$LANG['user.postalcode'] = 'Postal Code';