Browse Source

Rewrite generate password and check password - requires composer update

- Added ircmaxell/random-lib lib to improve security.
- Add 'password_requirements' setting see BT#12571 to customize min
requirements.
jmontoyaa 8 years ago
parent
commit
807cf5697b

+ 2 - 1
composer.json

@@ -104,7 +104,8 @@
         "kigkonsult/icalcreator" : "0.1.0",
         "essence/essence": "2.6.1",
         "pclzip/pclzip": "2.8.2",
-        "chamilo/chash": "dev-master"
+        "chamilo/chash": "dev-master",
+        "ircmaxell/random-lib": "^1.2"
     },
     "require-dev": {
         "behat/behat": "@stable",

+ 84 - 35
main/inc/lib/api.lib.php

@@ -1953,22 +1953,49 @@ function api_format_course_array($course_data)
  */
 function api_generate_password($length = 8)
 {
-    $characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
-    $numbers = '23456789';
-
     if ($length < 2) {
         $length = 2;
     }
+
+    $charactersLowerCase = 'abcdefghijkmnopqrstuvwxyz';
+    $charactersUpperCase = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+    $minNumbers = 2;
+    $length = $length - $minNumbers;
+    $minLowerCase = round($length/2);
+    $minUpperCase = $length - $minLowerCase;
+
     $password = '';
-    for ($i = 0; $i < $length; $i ++) {
-        $password .= $characters[rand() % strlen($characters)];
+    $passwordRequirements = api_get_configuration_value('password_requirements');
+
+    $factory = new RandomLib\Factory();
+    $generator = $factory->getGenerator(new SecurityLib\Strength(SecurityLib\Strength::MEDIUM));
+
+    if (!empty($passwordRequirements)) {
+        $length = $passwordRequirements['min']['length'];
+        $minNumbers = $passwordRequirements['min']['numeric'];
+        $minLowerCase = $passwordRequirements['min']['lowercase'];
+        $minUpperCase = $passwordRequirements['min']['uppercase'];
+
+        $rest = $length - $minNumbers - $minLowerCase - $minUpperCase;
+        // Add the rest to fill the length requirement
+        if ($rest > 0) {
+            $password .= $generator->generateString($rest, $charactersLowerCase.$charactersUpperCase);
+        }
     }
 
-    // At least 2 digits
-    for ($i = 0; $i < 2; $i ++) {
-        $password .= $numbers[rand() % strlen($numbers)];
+    // Min digits default 2
+    for ($i = 0; $i < $minNumbers; $i ++) {
+        $password .= $generator->generateInt(2, 9);
     }
 
+    // Min lowercase
+    $password .= $generator->generateString($minLowerCase, $charactersLowerCase);
+
+    // Min uppercase
+    $password .= $generator->generateString($minUpperCase, $charactersUpperCase);
+
+    $password = str_shuffle($password);
+
     return $password;
 }
 
@@ -1982,37 +2009,57 @@ function api_generate_password($length = 8)
  * 3. The password should contain at least 3 letters.
  * 4. It should contain at least 2 digits.
  * 5. It should not contain 3 or more consequent (according to ASCII table) characters.
- */
-function api_check_password($password) {
-    $password_length = api_strlen($password);
-    if ($password_length < 5) {
+ * Settings will change if the configuration value is set: password_requirements
+ */
+function api_check_password($password)
+{
+    $passwordRequirements = api_get_configuration_value('password_requirements');
+
+    $minLength = 5;
+    $minLetters = 3;
+    $minNumbers = 2;
+    $minLowerCase = 0; // Is only use if  password_requirements is set
+    $minUpperCase = 0; // Is only use if  password_requirements is set
+    if (!empty($passwordRequirements)) {
+        $minLength = $passwordRequirements['min']['length'];
+        $minNumbers = $passwordRequirements['min']['numeric'];
+        $minLowerCase = $passwordRequirements['min']['lowercase'];
+        $minUpperCase = $passwordRequirements['min']['uppercase'];
+        $minLetters = $minLowerCase + $minUpperCase;
+    }
+    $passwordLength = api_strlen($password);
+    if ($passwordLength < $minLength) {
         return false;
     }
-    $password = api_strtolower($password);
-    $letters = 0;
+
     $digits = 0;
-    $consequent_characters = 0;
-    $previous_character_code = 0;
-    for ($i = 0; $i < $password_length; $i ++) {
-        $current_character_code = api_ord(api_substr($password, $i, 1));
-        if ($i && abs($current_character_code - $previous_character_code) <= 1) {
-            $consequent_characters ++;
-            if ($consequent_characters == 3) {
-                return false;
-            }
-        } else {
-            $consequent_characters = 1;
+    $lowerCase = 0;
+    $upperCase = 0;
+
+    for ($i = 0; $i < $passwordLength; $i++) {
+        $currentCharacterCode = api_ord(api_substr($password, $i, 1));
+        if ($currentCharacterCode >= 65 && $currentCharacterCode <= 90) {
+            $upperCase++;
         }
-        if ($current_character_code >= 97 && $current_character_code <= 122) {
-            $letters ++;
-        } elseif ($current_character_code >= 48 && $current_character_code <= 57) {
-            $digits ++;
-        } else {
-            return false;
+
+        if ($currentCharacterCode >= 97 && $currentCharacterCode <= 122) {
+            $lowerCase++;
+        }
+        if ($currentCharacterCode >= 48 && $currentCharacterCode <= 57) {
+            $digits++;
         }
-        $previous_character_code = $current_character_code;
     }
-    return ($letters >= 3 && $digits >= 2);
+    $letters = $upperCase + $lowerCase;
+
+    if (!empty($passwordRequirements)) {
+        return (
+            $upperCase >= $minUpperCase &&
+            $lowerCase >= $minLowerCase &&
+            $digits >= $minNumbers
+        );
+    }
+
+    return $letters >= $minLetters && $digits >= $minNumbers;
 }
 
 /**
@@ -2040,7 +2087,8 @@ function api_clear_anonymous($db_check = false)
  * @author Noel Dieschburg
  * @param the int status code
  */
-function get_status_from_code($status_code) {
+function get_status_from_code($status_code)
+{
     switch ($status_code) {
         case STUDENT:
             return get_lang('Student', '');
@@ -2062,7 +2110,8 @@ function get_status_from_code($status_code) {
  * time we get on a course homepage or on a neutral page (index, admin, my space)
  * @return bool     true if set user as anonymous, false if user was already logged in or anonymous id could not be found
  */
-function api_set_anonymous() {
+function api_set_anonymous()
+{
     global $_user;
 
     if (!empty($_user['user_id'])) {

+ 2 - 1
main/inc/lib/internationalization.lib.php

@@ -1092,7 +1092,8 @@ function api_transliterate($string, $unknown = '?', $from_encoding = null)
  * @link http://php.net/manual/en/function.ord.php
  * Note the difference with the original funtion ord(): ord('') returns 0, api_ord('') returns 0xFFFD (unknown character).
  */
-function api_ord($character, $encoding = null) {
+function api_ord($character, $encoding = 'UTF-8')
+{
     return Utf8::ord(api_utf8_encode($character, $encoding));
 }
 

+ 10 - 1
main/install/configuration.dist.php

@@ -309,4 +309,13 @@ $_configuration['system_stable'] = NEW_VERSION_STABLE;
 // Hide LP time in reports.
 // $_configuration['hide_lp_time'] = false;
 // Hide rating elements in pages ("Courses catalog" & "Most Popular courses")
-// $_configuration['hide_course_rating'] = false;
+// $_configuration['hide_course_rating'] = false;
+// Customize password generation and verification
+/*$_configuration['password_requirements'] = [
+    'min' => [
+        'lowercase' => 2,
+        'uppercase' => 2,
+        'numeric' => 2,
+        'length' => 8
+    ]
+];*/