Index: branches/5.2.x/core/kernel/utility/formatters/password_formatter.php
===================================================================
--- branches/5.2.x/core/kernel/utility/formatters/password_formatter.php	(revision 16477)
+++ branches/5.2.x/core/kernel/utility/formatters/password_formatter.php	(revision 16478)
@@ -1,337 +1,338 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kPasswordFormatter extends kFormatter
 {
 	/**
 	 * Instance of PHPPass
 	 *
 	 * @var PasswordHash
 	 * @access protected
 	 */
 	protected $_phpPass;
 
 	/**
 	 * Creates formatter instance
 	 *
 	 * @access public
 	 */
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->_phpPass = $this->Application->makeClass('PasswordHash', Array (8, false));
 	}
 
 	/**
 	 * The method is supposed to alter config options or configure object in some way based on its usage of formatters
 	 * The methods is called for every field with formatter defined when configuring item.
 	 * Could be used for adding additional VirtualFields to an object required by some special Formatter
 	 *
 	 * @param string $field_name
 	 * @param array $field_options
 	 * @param kDBBase $object
 	 */
 	function PrepareOptions($field_name, &$field_options, &$object)
 	{
 		if ( !isset($field_options['verify_field']) ) {
 			return;
 		}
 
 		$add_fields = Array ();
 		$options = Array (
 			'master_field' => $field_name,
 //			'error_field' => $field_name,
 			'formatter' => 'kPasswordFormatter'
 		);
 
 		$copy_options = Array ('hashing_method', 'hashing_method_field', 'salt', 'required', 'skip_empty');
 
 		foreach ($copy_options as $copy_option) {
 			if ( array_key_exists($copy_option, $field_options) ) {
 				$options[$copy_option] = $field_options[$copy_option];
 			}
 		}
 
 		$add_fields[$field_options['verify_field']] = $options;
 
 		$add_fields[$field_name . '_plain'] = Array ('type' => 'string', 'error_field' => $field_name);
 		$add_fields[$field_options['verify_field'] . '_plain'] = Array ('type' => 'string', 'error_field' => $field_options['verify_field']);
 
 		$virtual_fields = $object->getVirtualFields();
 		$add_fields = kUtil::array_merge_recursive($add_fields, $virtual_fields);
 		$object->setVirtualFields($add_fields);
 	}
 
 	/**
 	 * Formats value of a given field
 	 *
 	 * @param string $value
 	 * @param string $field_name
 	 * @param kDBItem|kDBList $object
 	 * @param string $format
 	 * @return string
 	 */
 	function Format($value, $field_name, &$object, $format = null)
 	{
 		return $value;
 	}
 
 	/**
 	 * Performs password & verify password field validation
 	 *
 	 * @param mixed $value
 	 * @param string $field_name
 	 * @param kDBItem $object
 	 * @return mixed
 	 * @access public
 	 */
 	public function Parse($value, $field_name, &$object)
 	{
 		list ($password_field, $verify_field) = $this->_getPasswordFields($value, $field_name, $object);
 
 		$options = $object->GetFieldOptions($field_name);
 		$salt = $object->GetFieldOption($password_field, 'salt', false, '');
 		$hashing_method = isset($options['hashing_method']) ? $options['hashing_method'] : $object->GetDBField($options['hashing_method_field']);
 
 		if ( $object->GetFieldOption($password_field, 'verify_field_set') && $object->GetFieldOption($verify_field, 'master_field_set') ) {
 			$new_password = $object->GetDBField($password_field . '_plain');
 			$verify_password = $object->GetDBField($verify_field . '_plain');
 
 			if ( $new_password == '' && $verify_password == '' ) {
 				$stored_hash = $object->GetDBField($password_field);
 
 				if ( !$this->checkPassword('', $stored_hash, $hashing_method) ) {
 					// return empty string causing password from database to stay
 					return $value;
 				}
 				else {
 					return $this->hashPassword($value, $salt, $hashing_method);
 				}
 			}
 
 			// determine admin or front
 			$phrase_error_prefix = $this->Application->isAdmin ? 'la' : 'lu';
 
 			if ( $new_password != $verify_password ) {
 				// passwords don't match (no matter what is their length)
 				$object->SetError($verify_field, 'passwords_do_not_match', $phrase_error_prefix . '_passwords_do_not_match');
 			}
 
 			$min_length = $this->Application->ConfigValue('Min_Password'); // for error message too
 			$min_length = $object->GetFieldOption($password_field, 'min_length', false, $min_length);
 
 			if ( mb_strlen($new_password) < $min_length ) {
 				$error_msg = '+' . sprintf($this->Application->Phrase($phrase_error_prefix . '_passwords_too_short', false), $min_length); // + -> not phrase
 				$object->SetError($password_field, 'passwords_min_length', $error_msg);
 			}
 		}
 
 		if ( $value == '' ) {
 			// new value is empty - return hash from database
 			return $object->GetDBField($field_name);
 		}
 
 		return $this->hashPassword($value, $salt, $hashing_method);
 	}
 
 	/**
 	 * Finds out names of password and verify password fields and updates "_plain" virtual field
 	 *
 	 * @param string $value
 	 * @param string $field_name
 	 * @param kDBItem $object
 	 * @return Array
 	 * @access protected
 	 */
 	protected function _getPasswordFields($value, $field_name, &$object)
 	{
 		$options = $object->GetFieldOptions($field_name);
 
 		$flip_count = 0;
 		$password_field = $verify_field = '';
 		$fields = Array ('master_field', 'verify_field');
 
 		// 1. collect values from both Password and VerifyPassword fields
 		while ($flip_count < 2) {
 			if ( getArrayValue($options, $fields[0]) ) {
 				$tmp_field = $options[$fields[0]];
 				$object->SetDBField($field_name . '_plain', $value);
 
 				if ( !$object->GetFieldOption($tmp_field, $fields[1] . '_set') ) {
 					$object->SetFieldOption($tmp_field, $fields[1] . '_set', true);
 				}
 
 				$password_field = $options[$fields[0]];
 				$verify_field = $field_name;
 			}
 
 			$fields = array_reverse($fields);
 			$flip_count++;
 		}
 
 		return Array ($password_field, $verify_field);
 	}
 
 	/**
 	 * Creates hash from given password and salt
 	 *
 	 * @param string $password
 	 * @param string $salt
 	 * @param int $hashing_method
 	 * @return string
 	 * @throws InvalidArgumentException
 	 * @access public
 	 */
 	public function hashPassword($password, $salt = null, $hashing_method = PasswordHashingMethod::PHPPASS)
 	{
 		switch ( $hashing_method ) {
 			case PasswordHashingMethod::NONE:
 				return $password;
 				break;
 
 			case PasswordHashingMethod::MD5:
 				return $this->_md5hash($password, $salt, false);
 				break;
 
 			case PasswordHashingMethod::MD5_AND_PHPPASS:
-				return $this->_phpPass->hashPassword($this->_md5hash($password, $salt, true));
+				$password_hashed = preg_match('/^[a-f0-9]{32}$/', $password);
+				return $this->_phpPass->hashPassword($this->_md5hash($password, $salt, $password_hashed));
 				break;
 
 			case PasswordHashingMethod::PHPPASS:
 				return $this->_phpPass->hashPassword($password);
 				break;
 
 			default:
 				throw new InvalidArgumentException('Unknown password hashing method "' . $hashing_method . '"');
 				break;
 		}
 	}
 
 	/**
 	 * Checks, that user password is valid
 	 *
 	 * @param string $password Non-hashed password provided by user
 	 * @param string $stored_hash Hash, calculated before from correct user password (must have salt inside)
 	 * @param int $hashing_method Hash generation method
 	 * @return bool
 	 * @access public
 	 * @throws InvalidArgumentException
 	 */
 	public function checkPassword($password, $stored_hash = null, $hashing_method = PasswordHashingMethod::PHPPASS)
 	{
 		$salt = '';
 
 		if ( $hashing_method != PasswordHashingMethod::PHPPASS && strpos($stored_hash, ':') !== false ) {
 			list ($salt, $stored_hash) = explode(':', $stored_hash, 2);
 		}
 
 		switch ( $hashing_method ) {
 			case PasswordHashingMethod::NONE:
 				return $password == $stored_hash;
 				break;
 
 			case PasswordHashingMethod::MD5:
 				return $this->_md5hash($password, $salt, false) == $stored_hash;
 				break;
 
 			case PasswordHashingMethod::MD5_AND_PHPPASS:
 				$password_hashed = preg_match('/^[a-f0-9]{32}$/', $password);
 				return $this->_phpPass->checkPassword($this->_md5hash($password, $salt, $password_hashed), $stored_hash);
 				break;
 
 			case PasswordHashingMethod::PHPPASS:
 				return $this->_phpPass->checkPassword($password, $stored_hash);
 				break;
 
 			default:
 				throw new InvalidArgumentException('Unknown password hashing method "' . $hashing_method . '"');
 				break;
 		}
 	}
 
 	/**
 	 * Checks a password stored as system setting using phppass with fallback to salted md5
 	 *
 	 * @param string $setting_name
 	 * @param string $password
 	 * @param int $hashing_method
 	 * @return bool
 	 * @access public
 	 */
 	public function checkPasswordFromSetting($setting_name, $password, $hashing_method = PasswordHashingMethod::PHPPASS)
 	{
 		$stored_hash = $this->Application->ConfigValue($setting_name);
 		$stored_hash = $this->prepareHash($stored_hash, 'b38', $hashing_method);
 
 		if ( $this->checkPassword($password, $stored_hash, $hashing_method) ) {
 			return true;
 		}
 
 		if ( $hashing_method != PasswordHashingMethod::MD5 ) {
 			if ( $this->checkPasswordFromSetting($setting_name, $password, PasswordHashingMethod::MD5) ) {
 				// rehash password on the go using more secure algorithm
 				$this->Application->SetConfigValue($setting_name, $this->hashPassword($password));
 
 				return true;
 			}
 		}
 
 		return false;
 	}
 
 	/**
 	 * Ensures, that salt is always present in the hash
 	 *
 	 * @param string $stored_hash
 	 * @param string $salt
 	 * @param int $hashing_method
 	 * @return string
 	 * @access public
 	 */
 	public function prepareHash($stored_hash, $salt = '', $hashing_method = PasswordHashingMethod::PHPPASS)
 	{
 		if ( $hashing_method == PasswordHashingMethod::PHPPASS ) {
 			return $stored_hash;
 		}
 
 		// embed salt into hash generated not by phppass
 		return $salt . ':' . $stored_hash;
 	}
 
 	/**
 	 * Hashes password using MD5 algorithm
 	 *
 	 * @param string $password
 	 * @param string $salt
 	 * @param bool $password_hashed
 	 * @return string
 	 * @access protected
 	 */
 	protected function _md5hash($password, $salt = null, $password_hashed = false)
 	{
 		if ( !$password_hashed ) {
 			$password = md5($password);
 		}
 
 		if ( isset($salt) && $salt ) {
 			return md5($password . $salt);
 		}
 
 		// if empty salt, assume, that it's not passed at all
 		return $password;
 	}
 }