<?php defined('SYSPATH') or die('No direct access allowed.');

/**
 * OSB Auth driver.
 *
 * @package    OSB
 * @category   Classes
 * @author     Deon George
 * @copyright  (c) 2009-2013 Open Source Billing
 * @license    http://dev.osbill.net/license.html
 */
class Auth_OSB extends Auth_ORM {
	/**
	 * We need to override Kohana's __construct(), for tasks, which attempt to open a session
	 * and probably dont have access to PHP sessions path.
	 * Tasks dont need sessions anyway?
	 */
	public function __construct($config = array()) {
		// Save the config in the object
		$this->_config = $config;

		if (PHP_SAPI !== 'cli')
			parent::__construct($config);
	}

	/**
	 * Get the user that a token applies to
	 *
	 * This will check that the token is valid (not expired and for the request)
	 *
	 * @param $token The token
	 * @return Model_Account|NULL The user that the token is valid for.
	 */
	private function _get_token_user($token) {
		// This has been implemented, as we sometimes we seem to come here twice
		static $uo = NULL;

		if (! is_null($uo))
			return $uo;

		$mmto = ORM::factory('Module_Method_Token',array('token'=>$token));

		// Ignore the token if it doesnt exist.
		if ($mmto->loaded()) {
			// Check that the token is for this URI
			$mo = ORM::factory('Module',array('name'=>Request::current()->controller()));
			$mmo = $mo->module_method
				->where_open()
				->where('name','=',strtolower(Request::current()->directory() ? sprintf('%s:%s',Request::current()->directory(),Request::current()->action()) : Request::current()->action()))
				// @todo No longer required after all method names have been colon delimited
				->or_where('name','=',strtolower(Request::current()->directory() ? sprintf('%s_%s',Request::current()->directory(),Request::current()->action()) : Request::current()->action()))
				->where_close()
				->find();

			// Ignore the token if this is not the right method.
			if ($mmo->id == $mmto->method_id) {
				if (! is_null($mmto->date_expire) AND $mmto->date_expire < time()) {
					SystemMessage::add(array(
						'title'=>_('Token Not Valid'),
						'type'=>'warning',
						'body'=>_('Token expired')));

					Session::instance()->delete('token');
					$mmto->delete();

				} elseif (! is_null($mmto->uses) AND $mmto->uses < 1) {
					SystemMessage::add(array(
						'title'=>_('Token Not Valid'),
						'type'=>'warning',
						'body'=>_('Token expired')));

					Session::instance()->delete('token');
					$mmto->delete();

				} else {
					// If this is a usage count token, reduce the count.
					if (! is_null($mmto->uses))
						$mmto->uses -= 1;

					// Record the date this token was used
					$mmto->date_last = time();
					$mmto->save();

					Session::instance()->set('token',$token);

					$uo = ORM::factory('Account',$mmto->account_id);
					$uo->log(sprintf('Token %s used for method %s [%s]',$mmto->token,$mmto->module_method->id,Request::current()->param('id')));
				}
			}
		}

		return $uo;
	}

	/**
	 * Logs a user in.
	 *
	 * @param   string   username
	 * @param   string   password
	 * @param   boolean  enable autologin
	 * @return  boolean
	 */
	protected function _login($user,$password,$remember) {
		if (! is_object($user)) {
			$username = $user;

			// Load the user
			$user = ORM::factory('Account');
			$user->where('username','=',$username)->find();

			// If no user loaded, return
			if (! $user->loaded())
				return FALSE;
		}

		// Create a hashed password
		if (is_string($password))
			$password = $this->hash($password);

		// If the passwords match, perform a login
		if ($user->status AND $user->has_any('group',ORM::factory('Group',array('name'=>'Registered Users'))->list_childgrps(TRUE)) AND $user->password === $password) {

			// @todo This is not currently used.
			if ($remember === TRUE) {
				// Create a new autologin token
				$token = ORM::factory('User_Token');

				// Set token data
				$token->user_id = $user->id;
				$token->expires = time() + $this->_config['lifetime'];
				$token->save();

				// Set the autologin cookie
				Cookie::set('authautologin', $token->token, $this->_config['lifetime']);
			}

			// Record our session ID, we may need to update our DB when we get a new ID
			$oldsess = session_id();

			// Finish the login
			$this->complete_login($user);

			// Do we need to update databases with our new sesion ID
			$sct = Kohana::$config->load('config')->session_change_trigger;
			if (session_id() != $oldsess AND count($sct))
				foreach ($sct as $t => $c)
					if (Config::module_exist($t))
						foreach (ORM::factory(ucwords($t))->where($c,'=',$oldsess)->find_all() as $o)
							$o->set('session_id',session_id())
							->update();

			return TRUE;
		}

		// Login failed
		return FALSE;
	}

	/**
	 * Determine if a user is authorised to view an account
	 *
	 * @param Model_Account Account Ojbect to validate if the current user has access
	 * @return boolean TRUE if authorised, FALSE if not.
	 */
	public function authorised(Model_Account $ao) {
		return (($uo = $this->get_user()) AND $uo->loaded() AND ($uo == $ao OR in_array($ao->id,$uo->RTM->customers($uo->RTM))));
	}

	/**
	 * Gets the currently logged in user from the session.
	 * Returns NULL if no user is currently logged in.
	 *
	 * @param boolean Check token users too
	 * @return  mixed
	 */
	public function get_user($default=NULL,$tokenuser=TRUE) {
		// If we are a CLI, we are not logged in
		if (PHP_SAPI === 'cli')
			throw new Kohana_Exception('Calling :method from the CLI is not allowed!',array(':method'=>__METHOD__));

		// Get the current user
		$uo = parent::get_user($default);

		// If we are not logged in, see if there is token for the user
		if (is_null($uo) AND $tokenuser AND ($token=Session::instance()->get('token')) OR (! empty($_REQUEST['token']) AND $token=$_REQUEST['token']))
			$uo = $this->_get_token_user($token);

		return $uo;
	}

	public function get_groups() {
		return is_null($x=$this->get_user()) ? ORM::factory('Group')->where('id','=',0)->find_all() : $x->groups();
	}

	/**
	 * OSB authentication is controlled via database queries.
	 *
	 * This method can be used to test two situations:
	 * 1) Is the user logged in? ($role == FALSE)
	 * 2) Can the user run the current controller->action ($role == TRUE)
	 *
	 * @param   boolean If authentication should be done for this module:method (ie: controller:action).
	 * @return  boolean
	 */
	public function logged_in($role=NULL,$debug=NULL) {
		$status = FALSE;

		// If we are a CLI, we are not logged in
		if (PHP_SAPI === 'cli')
			return $status;

		// Get the user from the session
		$uo = $this->get_user();

		// If we are not a valid user object, then we are not logged in
		if (is_object($uo) AND ($uo instanceof Model_Account) AND $uo->loaded())
			if (! empty($role)) {
				if (($x = Request::current()->mmo()) instanceof Model)
					// If the role has the authorisation to run the method
					foreach ($x->group->find_all() as $go)
						if ($go->id == 0 OR $uo->has_any('group',$go->list_childgrps(TRUE))) {
							$status = TRUE;
							break;
						}

			// There is no role, so the method should be allowed to run as anonymous
			} else
				$status = TRUE;

		return $status;
	}
}
?>
