<?php

/**
 * Subclass of mysql.class.php to integrate with a Flyspray instance
 * that is using mysql. 
 * See http://discerning.com/hacks/flyspray/
 *
 * @license MIT
 * @author  Mark D. Anderson <mda@discerning.com>
 */

// sigh, both DW and FS have a global $conf variable
global $conf;

// do DW stuff first, as we are loaded by DW 
$dw_conf = $conf;
define('DOKU_AUTH_DIR', dirname(__FILE__));
require_once(DOKU_AUTH_DIR.'/mysql.class.php');

// FS stuff
define('FS_ROOT', dirname(__FILE__).'/../../../flyspray');
// allow loading of header.php, but don't load the dokuwiki syntax_plugin (done in constants.inc.php)
define('IN_FS',true);
// prevent startSession in class.flyspray.php
define('IN_FEED',true);
define('FS_NO_PLUGINS',true);
define('FS_NO_TPL',true);
define('FS_ERROR_REPORTING', E_ALL ^ E_NOTICE);
require_once(FS_ROOT.'/header.php');
global $fs_conf;
$fs_conf = $conf;

// FS sets error reporting to E_ALL, but DW isn't ready for that
error_reporting((E_ALL ^ E_NOTICE) ^ E_USER_NOTICE);

// merge $conf 
$conf = array_merge($dw_conf, $fs_conf);

// copy database connection info over from flyspray
$conf['auth']['mysql']['server']   = $fs_conf['database']['dbhost'];
$conf['auth']['mysql']['user']     = $fs_conf['database']['dbuser'];
$conf['auth']['mysql']['password'] = $fs_conf['database']['dbpass'];
$conf['auth']['mysql']['database'] = $fs_conf['database']['dbname'];

// used for our debug output
//define(DW_AUTH_MSG_LEVEL, E_USER_ERROR); 
define(DW_AUTH_MSG_LEVEL, E_USER_NOTICE); 

class auth_flyspray_mysql extends auth_mysql {
    /**
     * indicate that we can check whether user is already authenticated,
     * so auth.php can call trustExternal()
     */
    function auth_flyspray_mysql(){
	$this->cando['external'] = true;
	$this->cando['logoff'] = true;
    }

    // See flyspray/index.php
    function _fs_user_if_cookie() {
	if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
	    $_uid = Cookie::val('flyspray_userid');
	    if (!is_numeric($_uid)) trigger_error("non-numeric flyspray_userid = $_uid", DW_AUTH_MSG_LEVEL);
	    if ($_uid == '0') trigger_error("flyspray_userid set to $_uid", DW_AUTH_MSG_LEVEL);
	    global $proj; // not really even used in constructor....
	    $_user = new User($_uid, $proj);
	    // we've only restored from the cookie, now we validate the cookie.
	    if ($this->_fs_is_authenticated($_user))
		return $_user;
	}
	else {
	    // trigger_error("no flyspray_userid and flyspray_passhash cookie", DW_AUTH_MSG_LEVEL);
	}
	return NULL;
    }

    // See check_account_ok() in FS class.user.php 
    function _fs_is_authenticated($user) {
	global $fs_conf;
	$ok = true;
	if (!$user->infos['account_enabled']) {
	    trigger_error("user '" . $user->infos['real_name'] . "' exists but account is disabled uid=" . $user->id, DW_AUTH_MSG_LEVEL);
	    $ok = false;
	}
	else if (!$user->perms('group_open', 0)) {
	    trigger_error("user exists but does not have 'group_open' perm", DW_AUTH_MSG_LEVEL);
	    $ok = false;
	}
	else {
	    $cookie_hash = Cookie::val('flyspray_passhash');
	    $salt = $fs_conf['general']['cookiesalt'];
	    if (!$salt) trigger_error("no salt in FS config, to check cookie!", E_USER_ERROR);
	    // the encrypted password in the database
	    $correct_encrypted_pass = $user->infos['user_pass'];
	    $correct_hash = crypt($correct_encrypted_pass, $salt);
	    $ok = ($cookie_hash == $correct_hash);
	    if (!$ok) trigger_error("cookie hash $cookie_hash != correct hash $correct_hash, from database pass $correct_encrypted_pass, salt '$salt'", DW_AUTH_MSG_LEVEL);
	}
	if (!$ok)
	    // clear out the bogus FS cookies
	    $user->logout();
	return $ok;
    }

    // See FS scripts/authenticate.php
    function _fs_set_authenticated($user_id, $clear_pass, $sticky) {
	global $fs_conf;
	$cookie_time = $sticky ? time() + (60*60*24*30) : -1;
	Flyspray::setCookie('flyspray_userid', $user_id, $cookie_time);
	$salt = $fs_conf['general']['cookiesalt'];
	if (!$salt) trigger_error("no salt in FS config, to set cookie!", E_USER_ERROR);
	// regardless of the fact that the database value is already encrypted,
	// we use crypt with a salt to hash things in the cookie.
	// We are passed in the clear text password; we could encrypt that but
	// we only know the current value of FS passwdcrypt (md5, etc.), not the
	// one used in the database. So we have to ignore $clear_pass and fetch
	// from db.
	$_user = new User($user_id);
	$encrypted_pass = $_user->infos['user_pass'];
	$cookie_hash = crypt($encrypted_pass, $salt);
	Flyspray::setCookie('flyspray_passhash', $cookie_hash, $cookie_time);
    }

    // See FS class.flyspray.php
    // returns user id.
    // -1 means fails account_enabled and group_open
    // 0 means passes those, but not authentication
    // > 0 means pass those and authentication
    function _fs_authenticate($username, $clear_pass) {
	$user_id = Flyspray::checkLogin($username, $clear_pass);
	return $user_id;
    }

    // see auth_login in DW auth.php
    function _dw_set_authenticated($username, $clear_pass, $sticky=false) {
	global $USERINFO;
	global $auth;
	$_SERVER['REMOTE_USER'] = $username;
	$USERINFO = $auth->getUserData($username);

	// we don't need the DOKU_COOKIE, since FS cookie is being used
	if (false) {
	    $pass   = PMA_blowfish_encrypt($clear_pass,auth_cookiesalt());
	    $cookie = base64_encode("$user|$sticky|$pass");
	    if($sticky) $time = time()+60*60*24*365; //one year
	    setcookie(DOKU_COOKIE,$cookie,$time,'/');
	}

	$_SESSION[DOKU_COOKIE]['auth']['user'] = $username;
	$_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass;
	$_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid();
	$_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
    }

    /**
     * Check for whether user is authenticated using FS cookie (via FS api).
     * If user is authenticated, make sure DW cookies and session variables are set up.
     */
    function trustExternal($username,$clear_pass,$sticky=false) {
	// $username is set, so trying an explicit login
	// see auth_login() in DW auth.php
	if (!empty($username)) {
	    $user_id = $this->_fs_authenticate($username, $clear_pass);
	    if ($user_id > 0) {
		$this->_fs_set_authenticated($user_id, $clear_pass, $sticky);
		$this->_dw_set_authenticated($username, $clear_pass, $sticky);
		return true;
	    }
	    else {
		global $lang;
		msg($lang['badlogin'],-1);
		auth_logoff();
		return false;
	    }
	}

	// $username is not set, so look at FS cookie.
	$user = $this->_fs_user_if_cookie();
	if ($user) {
	    $username = $user->infos['user_name'];
	    // NOTE we don't have the clear password, but we don't actually need it
	    // because it would only be used to set DOKU_COOKIE
	    $clear_pass = NULL; 
	    $this->_dw_set_authenticated($username, $clear_pass, $sticky);
	    return true;
	}

	// make sure not authenticated in DW
	auth_logoff();
	return false;
    }

    /**
     * Explicit logout from DW, make it happen in FS also.
     */
    function logOff() {
	// FS
	$user = $this->_fs_user_if_cookie();
	if ($user) {
	    trigger_error("logging out FS user", DW_AUTH_MSG_LEVEL);
	    $user->logout();
	}
	else {
	    // debug_print_backtrace();
	    trigger_error("no FS user to log out", DW_AUTH_MSG_LEVEL);
	}
	// DW
	global $dw_in_logOff; // recursion protection
	if (!$dw_in_logOff) {
	    $dw_in_logOff = 1; 
	    auth_logoff();
	}
    }
}

?>
