Changeset 500 for trunk/lib/Auth_SQL.inc.php
- Timestamp:
- Nov 15, 2014 9:34:39 PM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/lib/Auth_SQL.inc.php
r497 r500 39 39 const ENCRYPT_MD5 = 5; 40 40 const ENCRYPT_MD5_HARDENED = 6; 41 const ENCRYPT_PASSWORD_BCRYPT = 7; 42 const ENCRYPT_PASSWORD_DEFAULT = 8; 41 43 42 44 // Namespace of this auth object. … … 174 176 " . $this->getParam('db_username_column') . " varchar(255) NOT NULL default '', 175 177 userpass VARCHAR(255) NOT NULL DEFAULT '', 178 userpass_hashtype TINYINT UNSIGNED NOT NULL DEFAULT '0', 176 179 first_name VARCHAR(255) NOT NULL DEFAULT '', 177 180 last_name VARCHAR(255) NOT NULL DEFAULT '', … … 260 263 $params['login_abuse_exempt_usernames'] = array_map('strtolower', $params['login_abuse_exempt_usernames']); 261 264 } 265 if (isset($params['encryption_type']) && version_compare(PHP_VERSION, '5.5.0', '<') && in_array($params['encryption_type'], array(self::ENCRYPT_PASSWORD_BCRYPT, self::ENCRYPT_PASSWORD_DEFAULT))) { 266 // These hash types require the password_* userland lib in PHP < 5.5.0 267 $pw_compat_lib = 'vendor/ircmaxell/password-compat/lib/password.php'; 268 if (false !== stream_resolve_include_path($pw_compat_lib)) { 269 include_once $pw_compat_lib; 270 } else { 271 $app =& App::getInstance(); 272 $app->logMsg(sprintf('Encryption type %s requires password-compat lib in PHP < 5.5.0; falling back to ENCRYPT_SHA1', $params['encryption_type']), LOG_ERR, __FILE__, __LINE__); 273 $params['encryption_type'] = self::ENCRYPT_SHA1; 274 } 275 } 262 276 if (isset($params) && is_array($params)) { 263 277 // Merge new parameters with old overriding only those passed. … … 353 367 354 368 /** 355 * Find out if a set of login credentials are valid.369 * Retrieve and verify the given username and password against a matching user record in the database. 356 370 * 357 371 * @access private … … 367 381 $this->initDB(); 368 382 369 switch ($this->_params['encryption_type']) { 370 case self::ENCRYPT_CRYPT : 371 // Query DB for user matching credentials. Compare cyphertext with salted-encrypted password. 372 $qid = $db->query(" 373 SELECT *, " . $this->_params['db_primary_key'] . " AS user_id 374 FROM " . $this->_params['db_table'] . " 375 WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "' 376 AND BINARY userpass = ENCRYPT('" . $db->escapeString($password) . "', LEFT(userpass, 2))) 377 "); 378 break; 379 case self::ENCRYPT_PLAINTEXT : 380 case self::ENCRYPT_MD5 : 381 case self::ENCRYPT_SHA1 : 382 default : 383 // Query DB for user matching credentials. Directly compare cyphertext with result from encryptPassword(). 384 $qid = $db->query(" 385 SELECT *, " . $this->_params['db_primary_key'] . " AS user_id 386 FROM " . $this->_params['db_table'] . " 387 WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "' 388 AND BINARY userpass = '" . $db->escapeString($this->encryptPassword($password)) . "' 389 "); 390 break; 391 } 392 393 // Return user data if found. 394 if ($user_data = mysql_fetch_assoc($qid)) { 395 // Don't return password value. 396 unset($user_data['userpass']); 397 $app->logMsg(sprintf('Authentication successful for user_id %s (%s)', $user_data['user_id'], $username), LOG_INFO, __FILE__, __LINE__); 383 // Get user data for specified username. 384 // Query DB for user matching credentials. Compare cyphertext with salted-encrypted password. 385 $qid = $db->query(" 386 SELECT *, " . $this->_params['db_primary_key'] . " AS user_id 387 FROM " . $this->_params['db_table'] . " 388 WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "' 389 "); 390 if (!$user_data = mysql_fetch_assoc($qid)) { 391 $app->logMsg(sprintf('Username %s not found for authentication', $username), LOG_NOTICE, __FILE__, __LINE__); 392 return false; 393 } 394 395 if ($this->verifyPassword($password, $user_data['userpass'])) { 396 $app->logMsg(sprintf('Authentication successful for %s (user_id=%s)', $username, $user_data['user_id']), LOG_INFO, __FILE__, __LINE__); 397 unset($user_data['userpass']); // Avoid revealing the encrypted password in the $user_data. 398 398 return $user_data; 399 } else {400 $app->logMsg(sprintf('Authentication failed for username %s (encrypted attempted password: %s)', $username, $this->encryptPassword($password)), LOG_NOTICE, __FILE__, __LINE__); 401 return false;402 }399 } 400 401 $app->logMsg(sprintf('Authentication failed for %s (user_id=%s)', $username, $user_data['user_id']), LOG_NOTICE, __FILE__, __LINE__); 402 return false; 403 403 } 404 404 … … 553 553 SELECT 1 FROM " . $this->_params['db_table'] . " 554 554 WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "' 555 AND DATE_ADD(last_login_datetime, INTERVAL '" . $this->_params['login_timeout'] . "' SECOND) > NOW()556 AND DATE_ADD(last_access_datetime, INTERVAL '" . $this->_params['idle_timeout'] . "' SECOND) > NOW()555 AND last_login_datetime > DATE_SUB(NOW(), INTERVAL '" . $this->_params['login_timeout'] . "' SECOND) 556 AND last_access_datetime > DATE_SUB(NOW(), INTERVAL '" . $this->_params['idle_timeout'] . "' SECOND) 557 557 "); 558 558 $login_status = (mysql_num_rows($qid) > 0); … … 809 809 } 810 810 811 /** 812 * Returns a randomly generated password based on $pattern. The pattern is any 813 * sequence of 'x', 'V', 'C', 'v', 'c', or 'd' and if it is something like 'cvccv' this 814 * function will generate a pronounceable password. Recommend using more complex 815 * patterns, at minimum the US State Department standard: cvcddcvc. 816 * 817 * - x A random upper or lower character, digit, or punctuation. 818 * - C A random upper or lower consonant. 819 * - V A random upper or lower vowel. 820 * - c A random lowercase consonant. 821 * - v A random lowercase vowel. 822 * - d A random digit. 823 * 824 * @param string $pattern a sequence of character types, above. 825 * @return string a password 826 */ 827 public function generatePassword($pattern='CvcdCvc') 828 { 829 $app =& App::getInstance(); 830 if (preg_match('/[^xCVcvd]/', $pattern)) { 831 $app->logMsg(sprintf('Invalid pattern: %s', $pattern), LOG_WARNING, __FILE__, __LINE__); 832 $pattern='CvcdCvc'; 833 } 834 $str = ''; 835 for ($i=0; $i<mb_strlen($pattern); $i++) { 836 $x = mb_substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZaeiouyAEIOUY0123456789!@#%&*-=+.?', (mt_rand() % 71), 1); 837 $c = mb_substr('bcdfghjklmnprstvwxz', (mt_rand() % 19), 1); 838 $C = mb_substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZ', (mt_rand() % 38), 1); 839 $v = mb_substr('aeiouy', (mt_rand() % 6), 1); 840 $V = mb_substr('aeiouyAEIOUY', (mt_rand() % 12), 1); 841 $d = mb_substr('0123456789', (mt_rand() % 10), 1); 842 $str .= $$pattern[$i]; 843 } 844 return $str; 811 /* 812 * Generate a cryptographically secure, random password. 813 * 814 * @access public 815 * @param int $bytes Length of password (in bytes) 816 * @return string Random string of characters. 817 * @author Quinn Comendant <quinn@strangecode.com> 818 * @version 1.0 819 * @since 15 Nov 2014 20:30:27 820 */ 821 public function generatePassword($bytes=10) 822 { 823 $app =& App::getInstance(); 824 825 $bytes = is_numeric($bytes) ? $bytes : 10; 826 $string = strtok(base64_encode(openssl_random_pseudo_bytes($bytes, $strong)), '='); 827 if (!$strong) { 828 $app->logMsg(sprintf('Password generated was not "cryptographically strong"; check your openssl.', null), LOG_NOTICE, __FILE__, __LINE__); 829 } 830 831 return $string; 845 832 } 846 833 … … 851 838 { 852 839 $app =& App::getInstance(); 840 841 $password = (string)$password; 853 842 854 843 // Existing password hashes rely on the same key/salt being used to compare encryptions. … … 858 847 switch ($this->_params['encryption_type']) { 859 848 case self::ENCRYPT_PLAINTEXT : 860 return$password;849 $encrypted_password = $password; 861 850 break; 862 851 863 852 case self::ENCRYPT_CRYPT : 864 // If comparing p laintext password with a hash, provide first two chars of the hashas the salt.865 return isset($salt) ? crypt($password, mb_substr($salt, 0, 2)) : crypt($password);853 // If comparing password with an existing hashed password, provide the hashed password as the salt. 854 $encrypted_password = isset($salt) ? crypt($password, $salt) : crypt($password); 866 855 break; 867 856 868 857 case self::ENCRYPT_SHA1 : 869 returnsha1($password);858 $encrypted_password = sha1($password); 870 859 break; 871 860 872 861 case self::ENCRYPT_SHA1_HARDENED : 873 $hash = sha1($app->getParam('signing_key') . $password . $more_salt); 874 // Increase key strength by 12 bits. 875 for ($i=0; $i < 4096; $i++) { 876 $hash = sha1($hash); 877 } 878 return $hash; 862 $encrypted_password = sha1($app->getParam('signing_key') . $password . $more_salt); 863 for ($i=0; $i < pow(2, 20); $i++) { 864 $encrypted_password = sha1($password . $encrypted_password); 865 } 879 866 break; 880 867 881 868 case self::ENCRYPT_MD5 : 882 returnmd5($password);869 $encrypted_password = md5($password); 883 870 break; 884 871 885 872 case self::ENCRYPT_MD5_HARDENED : 886 // Include salt to improve hash 887 $hash = md5($app->getParam('signing_key') . $password . $more_salt); 888 // Increase key strength by 12 bits. 889 for ($i=0; $i < 4096; $i++) { 890 $hash = md5($hash); 891 } 892 return $hash; 873 $encrypted_password = md5($app->getParam('signing_key') . $password . $more_salt); 874 for ($i=0; $i < pow(2, 20); $i++) { 875 $encrypted_password = md5($password . $encrypted_password); 876 } 877 break; 878 879 case self::ENCRYPT_PASSWORD_BCRYPT : 880 $encrypted_password = password_hash($password, PASSWORD_BCRYPT, array('cost' => 12)); 881 break; 882 883 case self::ENCRYPT_PASSWORD_DEFAULT : 884 $encrypted_password = password_hash($password, PASSWORD_DEFAULT, array('cost' => 12)); 893 885 break; 894 886 … … 897 889 return false; 898 890 break; 891 } 892 893 // In case our hashing function returns 'false' or another empty value, bail out. 894 if ('' == trim((string)$encrypted_password)) { 895 $app->logMsg(sprintf('Invalid password hash returned; check yo crypto!', null), LOG_ALERT, __FILE__, __LINE__); 896 return false; 897 } 898 899 return $encrypted_password; 900 } 901 902 /* 903 * 904 * 905 * @access public 906 * @param 907 * @return 908 * @author Quinn Comendant <quinn@strangecode.com> 909 * @version 1.0 910 * @since 15 Nov 2014 21:37:28 911 */ 912 public function verifyPassword($password, $encrypted_password) 913 { 914 switch ($this->_params['encryption_type']) { 915 case self::ENCRYPT_CRYPT : 916 return $this->encryptPassword($password, $encrypted_password) == $encrypted_password; 917 918 case self::ENCRYPT_PLAINTEXT : 919 case self::ENCRYPT_MD5 : 920 case self::ENCRYPT_MD5_HARDENED : 921 case self::ENCRYPT_SHA1 : 922 case self::ENCRYPT_SHA1_HARDENED : 923 default : 924 return $this->encryptPassword($password) == $encrypted_password; 925 926 case self::ENCRYPT_PASSWORD_BCRYPT : 927 case self::ENCRYPT_PASSWORD_DEFAULT : 928 return password_verify($password, $encrypted_password); 899 929 } 900 930 } … … 920 950 "); 921 951 if (!list($old_encrypted_password) = mysql_fetch_row($qid)) { 922 $app->logMsg(sprintf('Cannot set password for nonexistent user_id %s', $user_id), LOG_ NOTICE, __FILE__, __LINE__);952 $app->logMsg(sprintf('Cannot set password for nonexistent user_id %s', $user_id), LOG_WARNING, __FILE__, __LINE__); 923 953 return false; 924 954 } 925 955 926 956 // Compare old with new to ensure we're actually *changing* the password. 927 $encrypted_password = $this->encryptPassword($password); 928 if ($old_encrypted_password == $encrypted_password) { 957 if ($this->verifyPassword($password, $old_encrypted_password)) { 929 958 $app->logMsg(sprintf('Not setting password: new is the same as old.', null), LOG_INFO, __FILE__, __LINE__); 930 return false; 959 return null; 960 } 961 962 // Save the hash method used if a table exists for it. 963 $userpass_hashtype = ''; 964 if ($db->columnExists($this->_params['db_table'], 'userpass_hashtype', false)) { 965 $userpass_hashtype = ", userpass_hashtype = '" . $db->escapeString($this->getParam('encryption_type')) . "'"; 931 966 } 932 967 … … 934 969 $db->query(" 935 970 UPDATE " . $this->_params['db_table'] . " 936 SET userpass = '" . $db->escapeString($encrypted_password) . "' 971 SET userpass = '" . $db->escapeString($this->encryptPassword($password)) . "' 972 $userpass_hashtype 937 973 WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "' 938 974 "); … … 943 979 } 944 980 981 $app->logMsg(sprintf('Password change successful for user_id %s', $user_id), LOG_INFO, __FILE__, __LINE__); 945 982 return true; 946 983 } … … 1008 1045 } 1009 1046 1010 /**1011 * If the current user has access to the specified $security_zone, return true.1012 * If the optional $user_type is supplied, test that against the zone.1013 *1014 * NOTE: "user_type" used to be called "priv" in some older implementations.1015 *1016 * @param constant $security_zone string of comma delimited privileges for the zone1017 * @param string $user_type a privilege that might be found in a zone1018 * @return bool true if user is a member of security zone, false otherwise1019 */1020 public function inClearanceZone($security_zone, $user_type='')1021 {1022 $zone_members = preg_split('/,\s*/', $security_zone);1023 $user_type = empty($user_type) ? $this->get('user_type') : $user_type;1024 1025 // If the current user's privilege level is NOT in that array or if the1026 // user has no privilege, return false. Otherwise the user is clear.1027 if (!in_array($user_type, $zone_members) || empty($user_type)) {1028 return false;1029 } else {1030 return true;1031 }1032 }1033 1034 /**1035 * This function tests a list of arguments $security_zone against the priv that the current user has.1036 * If the user doesn't have one of the supplied privs, die.1037 *1038 * NOTE: "user_type" used to be called "priv" in some older implementations.1039 *1040 * @param constant $security_zone string of comma delimited privileges for the zone1041 */1042 public function requireAccessClearance($security_zone, $message='')1043 {1044 $app =& App::getInstance();1045 1046 $zone_members = preg_split('/,\s*/', $security_zone);1047 1048 /* If the current user's privilege level is NOT in that array or if the1049 * user has no privilege, DIE with a message. */1050 if (!in_array($this->get('user_type'), $zone_members) || !$this->get('user_type')) {1051 $message = empty($message) ? _("You have insufficient privileges to view that page.") : $message;1052 $app->raiseMsg($message, MSG_NOTICE, __FILE__, __LINE__);1053 $app->dieBoomerangURL();1054 }1055 }1056 1057 1047 } // end class 1058 1059 // CIDR cheat-sheet1060 //1061 // Netmask Netmask (binary) CIDR Notes1062 // _____________________________________________________________________________1063 // 255.255.255.255 11111111.11111111.11111111.11111111 /32 Host (single addr)1064 // 255.255.255.254 11111111.11111111.11111111.11111110 /31 Unusable1065 // 255.255.255.252 11111111.11111111.11111111.11111100 /30 2 useable1066 // 255.255.255.248 11111111.11111111.11111111.11111000 /29 6 useable1067 // 255.255.255.240 11111111.11111111.11111111.11110000 /28 14 useable1068 // 255.255.255.224 11111111.11111111.11111111.11100000 /27 30 useable1069 // 255.255.255.192 11111111.11111111.11111111.11000000 /26 62 useable1070 // 255.255.255.128 11111111.11111111.11111111.10000000 /25 126 useable1071 // 255.255.255.0 11111111.11111111.11111111.00000000 /24 "Class C" 254 useable1072 //1073 // 255.255.254.0 11111111.11111111.11111110.00000000 /23 2 Class C's1074 // 255.255.252.0 11111111.11111111.11111100.00000000 /22 4 Class C's1075 // 255.255.248.0 11111111.11111111.11111000.00000000 /21 8 Class C's1076 // 255.255.240.0 11111111.11111111.11110000.00000000 /20 16 Class C's1077 // 255.255.224.0 11111111.11111111.11100000.00000000 /19 32 Class C's1078 // 255.255.192.0 11111111.11111111.11000000.00000000 /18 64 Class C's1079 // 255.255.128.0 11111111.11111111.10000000.00000000 /17 128 Class C's1080 // 255.255.0.0 11111111.11111111.00000000.00000000 /16 "Class B"1081 //1082 // 255.254.0.0 11111111.11111110.00000000.00000000 /15 2 Class B's1083 // 255.252.0.0 11111111.11111100.00000000.00000000 /14 4 Class B's1084 // 255.248.0.0 11111111.11111000.00000000.00000000 /13 8 Class B's1085 // 255.240.0.0 11111111.11110000.00000000.00000000 /12 16 Class B's1086 // 255.224.0.0 11111111.11100000.00000000.00000000 /11 32 Class B's1087 // 255.192.0.0 11111111.11000000.00000000.00000000 /10 64 Class B's1088 // 255.128.0.0 11111111.10000000.00000000.00000000 /9 128 Class B's1089 // 255.0.0.0 11111111.00000000.00000000.00000000 /8 "Class A"1090 //1091 // 254.0.0.0 11111110.00000000.00000000.00000000 /71092 // 252.0.0.0 11111100.00000000.00000000.00000000 /61093 // 248.0.0.0 11111000.00000000.00000000.00000000 /51094 // 240.0.0.0 11110000.00000000.00000000.00000000 /41095 // 224.0.0.0 11100000.00000000.00000000.00000000 /31096 // 192.0.0.0 11000000.00000000.00000000.00000000 /21097 // 128.0.0.0 10000000.00000000.00000000.00000000 /11098 // 0.0.0.0 00000000.00000000.00000000.00000000 /0 IP space
Note: See TracChangeset
for help on using the changeset viewer.