<?php
/**
 * eGroupWare API - LDAP Authentication with fallback to SQL
 *
 * @link http://www.egroupware.org
 * @author Ralf Becker <ralfbecker@outdoor-training.de>
 * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License
 * @package api
 * @subpackage auth
 * @version $Id$
 */

namespace EGroupware\Api\Auth;

use EGroupware\Api;

/**
 * Authentication against an LDAP Server (or other authentication type) with fallback to SQL
 */
class Fallback implements Backend
{
	/**
	 * Primary auth backend
	 *
	 * @var Backend
	 */
	private $primary_backend;

	/**
	 * Fallback auth backend
	 *
	 * @var Backend
	 */
	private $fallback_backend;

	/**
	 * Constructor
	 *
	 * @param string $primary ='ldap'
	 * @param string $fallback ='sql'
	 */
	function __construct($primary='ldap',$fallback='sql')
	{
		// do NOT save our backends in session, as we want "fallback" to be saved
		$this->primary_backend = Api\Auth::backend(str_replace('auth_', '', $primary ?: 'ldap'), false);

		$this->fallback_backend = Api\Auth::backend(str_replace('auth_', '', $fallback ?: 'sql'), false);
	}

	/**
	 * authentication against LDAP with fallback to SQL
	 *
	 * @param string $username username of account to authenticate
	 * @param string $passwd corresponding password
	 * @return boolean true if successful authenticated, false otherwise
	 */
	function authenticate($username, $passwd, $passwd_type='text')
	{
		$ret = $this->primary_backend->authenticate($username, $passwd, $passwd_type);
		Api\Auth::log(__METHOD__."('$username', ...) primary_backend(".get_class($this->primary_backend).
			")->authenticate('$username', '".str_repeat('*', strlen($passwd))."', ...) returned ".json_encode($ret));
		if ($ret)
		{
			Api\Cache::setInstance(__CLASS__,'backend_used-'.$username,'primary');
			// check if fallback has correct password, if not update it
			if (($account_id = $GLOBALS['egw']->accounts->name2id($username)) &&
				!$this->fallback_backend->authenticate($username,$passwd, $passwd_type))
			{
				$backup_currentapp = $GLOBALS['egw_info']['flags']['currentapp'];
				$GLOBALS['egw_info']['flags']['currentapp'] = 'admin';	// otherwise
				$ret = $this->fallback_backend->change_password('', $passwd, $account_id);
				Api\Auth::log(__METHOD__."('$username', ...) fallback_backend(".get_class($this->fallback_backend).
					")->change_password('', '".str_repeat('*', strlen($passwd))."', $account_id) returned ".json_encode($ret));
				$GLOBALS['egw_info']['flags']['currentapp'] = $backup_currentapp;
				//error_log(__METHOD__."('$username', \$passwd) updated password for #$account_id on fallback ".($ret ? 'successfull' : 'failed!'));
			}
			return true;
		}
		if (($ret = $this->fallback_backend->authenticate($username,$passwd, $passwd_type)))
		{
			Api\Cache::setInstance(__CLASS__,'backend_used-'.$username,'fallback');
		}
		Api\Auth::log(__METHOD__."('$username', ...) fallback_backend(".get_class($this->fallback_backend).
			")->authenticate('$username', '".str_repeat('*', strlen($passwd))."', ...) returned ".json_encode($ret));
		return $ret;
	}

	/**
	 * changes password in LDAP
	 *
	 * If $old_passwd is given, the password change is done binded as user and NOT with the
	 * "root" dn given in the configurations.
	 *
	 * @param string $old_passwd must be cleartext or empty to not to be checked
	 * @param string $new_passwd must be cleartext
	 * @param int $account_id account id of user whose passwd should be changed
	 * @return boolean true if password successful changed, false otherwise
	 */
	function change_password($old_passwd, $new_passwd, $account_id=0)
	{
		if(!$account_id)
		{
			$account_id = $GLOBALS['egw_info']['user']['account_id'];
			$username = $GLOBALS['egw_info']['user']['account_lid'];
		}
		else
		{
			$username = $GLOBALS['egw']->accounts->id2name($account_id);
		}
		if (Api\Cache::getInstance(__CLASS__,'backend_used-'.$username) == 'primary')
		{
			if (($ret = $this->primary_backend->change_password($old_passwd, $new_passwd, $account_id)))
			{
				// if password successfully changed on primary, also update fallback
				$change_pwd = $this->fallback_backend->change_password($old_passwd, $new_passwd, $account_id);
				Api\Auth::log(__METHOD__."(..., $account_id) fallback_backend(".get_class($this->fallback_backend).
					")->change_password('', '".str_repeat('*', strlen($new_passwd))."', $account_id) returned ".json_encode($change_pwd));
			}
		}
		else
		{
			$ret = $this->fallback_backend->change_password($old_passwd, $new_passwd, $account_id);
			Api\Auth::log(__METHOD__."(..., $account_id) fallback_backend(".get_class($this->fallback_backend).
				")->change_password('', '".str_repeat('*', strlen($new_passwd))."', $account_id) returned ".json_encode($ret));
		}
		//error_log(__METHOD__."('$old_passwd', '$new_passwd', $account_id) username='$username', backend=".Api\Cache::getInstance(__CLASS__,'backend_used-'.$username)." returning ".array2string($ret));
		return $ret;
	}

	/**
	 * fetch the last pwd change for the user
	 *
	 * @param string $username username of account to authenticate
	 * @return mixed false or account_lastpwd_change
	 */
	function getLastPwdChange($username)
	{
		if (Api\Cache::getInstance(__CLASS__,'backend_used-'.$username) == 'primary')
		{
			if (method_exists($this->primary_backend,'getLastPwdChange'))
			{
				return $this->primary_backend->getLastPwdChange($username);
			}
		}
		if (method_exists($this->fallback_backend,'getLastPwdChange'))
		{
			return $this->fallback_backend->getLastPwdChange($username);
		}
		return false;
	}

	/**
	 * changes account_lastpwd_change in sql datababse
	 *
	 * @param int $account_id account id of user whose passwd should be changed
	 * @param string $passwd must be cleartext, usually not used, but may be used to authenticate as user to do the change -> ldap
	 * @param int $lastpwdchange must be a unixtimestamp
	 * @return boolean true if account_lastpwd_change successful changed, false otherwise
	 */
	function setLastPwdChange($account_id=0, $passwd=NULL, $lastpwdchange=NULL)
	{
		if(!$account_id || $GLOBALS['egw_info']['flags']['currentapp'] == 'login')
		{
			$account_id = $GLOBALS['egw_info']['user']['account_id'];
			$username = $GLOBALS['egw_info']['user']['account_lid'];
		}
		else
		{
			$username = $GLOBALS['egw']->accounts->id2name($account_id);
		}
		if (Api\Cache::getInstance(__CLASS__,'backend_used-'.$username) == 'primary')
		{
			if (method_exists($this->primary_backend,'setLastPwdChange'))
			{
				return $this->primary_backend->setLastPwdChange($username);
			}
		}
		if (method_exists($this->fallback_backend,'setLastPwdChange'))
		{
			return $this->fallback_backend->setLastPwdChange($account_id, $passwd, $lastpwdchange);
		}
		return false;
	}
}