<?php
/**
 * DokuWiki SSO CAS Plugin
 * 
 * @licence	GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author	Iain Hallam <iain@nineworlds.net>
 */

/**
 * Copyright (C) 2012 Iain Hallam, Andreas Gohr
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

// must be run within DokuWiki
if(!defined('DOKU_INC')) die();
 
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'action.php');

class action_plugin_ssocas extends DokuWiki_Action_Plugin {
	function getInfo() {
		return array (
			'author' => 'Iain Hallam',
			'email' => 'iain@nineworlds.net',
			'date' => '2012-06-16',
			'name' => 'SSO CAS Plugin',
			'desc' => 'Authenticate DokuWiki users via CAS',
			'url' => 'http://www.dokuwiki.org/plugin:ssocas',
		);
	}

	function register (&$controller) {
		if ($this->getConf('server') != '') {
			$controller->register_hook ('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_login_form');
			$controller->register_hook ('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action');
			$controller->register_hook ('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handle_template');
		}
	}

	function _self () {
		global $ID;
		return wl($ID, '', true, '');
	}

	function _selfdo ($do) {
		global $ID;
		return wl($ID, 'do=' . $do, true, '&');
	}

	function _redirect ($url) {
		header ('Location: ' . $url);
		exit;
	}
	
	// Log function copied from Andreas Gohr's loglog plugin
	function _log ($msg) {
		global $conf;
		$t = time();
		$log = $t."\t".strftime($conf['dformat'],$t)."\t".$_SERVER['REMOTE_ADDR']."\t".$_SERVER['REMOTE_USER']."\t".$msg;
		io_saveFile($conf['cachedir'].'/ssocas.log',"$log\n",true);
	}

	function handle_login_form (&$event, $param) {
		global $auth;
		global $conf;
		global $lang;
		global $ID;
		
		// Remove the register and resendpwd links, if they exist.
		for ($formPosition = 0; $formPosition < count($event->data->_content); $formPosition++) {
			$formElement = $event->data->getElementAt($formPosition);
			if ((! is_array($formElement)) and (substr($formElement, 0, 2) == '<p')) {
				$event->data->replaceElement ($formPosition, NULL);
			}
		}

		$insertElement = 5;

		if($auth && $auth->canDo('addUser') && actionOK('register')){
			$event->data->insertElement($insertElement,'<p>'.$lang['reghere'].': <a href="'.wl($ID,'do=register').'" rel="nofollow" class="wikilink1">'.$lang['register'].'</a></p>');
			$insertElement = 6;
		}
		if ($auth && $auth->canDo('modPass') && actionOK('resendpwd')) {
			$event->data->insertElement($insertElement,'<p>'.$lang['pwdforget'].': <a href="'.wl($ID,'do=resendpwd').'" rel="nofollow" class="wikilink1">'.$lang['btn_resendpwd'].'</a></p>');
		}

		if ($this->getConf('logourl') != '') {
			$caslogo = '<img src="'.$this->getConf('logourl').'" alt="" style="vertical-align: middle;" /> ';
		} else {
			$caslogo = '';
		}

		$event->data->insertElement(0,'<fieldset><legend>'.$this->getConf('name').'</legend>');
		$event->data->insertElement(1,'<p style="text-align: center;">'.$caslogo.'<a href="'.$this->_selfdo('caslogin').'">Login</a></p>');
		$event->data->insertElement(2,'</fieldset>');
		if ($this->getConf('jshidelocal')) {
			$event->data->insertElement(3,'<p id="normalLoginToggle" style="display: none; text-align: center;"><a href="#" onClick="javascript:document.getElementById(\'normalLogin\').style.display = \'block\'; document.getElementById(\'normalLoginToggle\').style.display = \'none\'; return false;">Show '.$this->getConf('localname').'</a></p><p style="text-align: center;">Only use this if you cannot use the '.$this->getConf('name').' above.</p>');
			$event->data->replaceElement(4,'<fieldset id="normalLogin" style="display: block;"><legend>'.$this->getConf('localname').'</legend><script type="text/javascript">document.getElementById(\'normalLoginToggle\').style.display = \'block\'; document.getElementById(\'normalLogin\').style.display = \'none\';</script>');
		} else {
			$event->data->replaceElement(3,'<fieldset><legend>'.$this->getConf('localname').'</legend>');
		}
	}

	function handle_caslogin () {
		global $ACT, $auth, $conf, $INFO, $USERINFO;
		$cas_auth_client = phpCAS::forceAuthentication();
		if ($cas_auth_client) {
			// Successful
			$casuser = phpCAS::getUser();
			if ($this->getConf('logging')) {$this->_log('user '.$casuser.' successfully authenticated');}
			$USERINFO = $auth->getUserData($casuser);
			
			if (empty($USERINFO)) {
				// Logged in, but no valid account
				if ($this->getConf('logging')) {$this->_log('user '.$casuser.' has no local account');}
				$ACT = act_permcheck('show');
				msg ('Sorry; your login to '.$this->getConf('name').' succeeded but you don\'t have a valid account here.',-1);
			} else {
				// Populate the session variables
				$_SERVER['REMOTE_USER'] = $casuser;
				if ($this->getConf('stickysession')) {
					$stickysession = true;
				} else {
					$stickysession = false;
				}
				auth_setCookie($casuser,'CAS',$stickysession);
			
				// Authentication info has changed: reset the page info
				$INFO = pageinfo();
			
				// Now logged in, so show the page
				$ACT = act_permcheck('show');
				
				// Log action
				if ($this->getConf('logging')) {
					if ($stickysession) {
						$this->_log('logged in permanently');
					}else{
						$this->_log('logged in temporarily');
					}
				}
			}
		} else {
			// Failed, but this should never be reached, because the CAS server shouldn't redirect back to us without a successful login
			if ($this->getConf('logging')) {$this->_log('forceAuthentication failed');}
			$ACT = act_permcheck('show');
			msg ('Sorry; your login to '.$this->getConf('name').' failed.',-1);
		}
	}

	function handle_caslogout () {
		// Check CAS authentication and whether to log out of CAS completely, and do a phpCAS::logout if so.
		if ((isset($_SERVER['REMOTE_USER'])) && ($_SESSION[DOKU_COOKIE]['auth']['pass'] == 'CAS')) {
			if (($this->getConf('caslogout')) && (phpCAS::checkAuthentication())) {
				phpCAS::logoutWithRedirectServiceAndUrl($this->_self(), $this->_self());
				
				// Log action
				if ($this->getConf('logging')) {
					$this->_log('logged off');
				}
			}
		}
		auth_logoff();
	}

	function handle_action (&$event, $param) {
		global $ACT;
		require_once ('CAS.php');
		phpCAS::client($this->getConf('version').'.0',$this->getConf('server'),(integer) $this->getConf('port'),$this->getConf('uri'));
		if (! ($this->getConf('servercert') or $this->getConf('servercacert'))) {
			// Neither Server or CA certificate is set
			phpCAS::setNoCasServerValidation();
		} else {
			if ($this->getConf('servercert')) {
				// Server certificate is set
				phpCAS::setCasServerCert($this->getConf('servercert'));
			}
			if ($this->getConf('servercacert')) {
				// CA certificate is set
				phpCAS::setCasServerCACert($this->getConf('servercacert'));
			}
		}
		
		// Handle the case where the CAS session is finished but the user is still logged in to DokuWiki
		if (! $this->getConf('stickysession')) {
			if ((isset($_SERVER['REMOTE_USER'])) && ($_SESSION[DOKU_COOKIE]['auth']['pass'] == 'CAS')) {
				if (! phpCAS::checkAuthentication()) {
					// Authentication failed
					$event->preventDefault();
					$this->handle_caslogout();
					$this->_redirect($this->_self());
				}
			}
		}
		
		if ($event->data == 'caslogin') {
			$event->preventDefault();
			$this->handle_caslogin();
		}
		if ($event->data == 'logout') {
			$this->handle_caslogout();
		}
	}

	function handle_template (&$event, $param) {
		if ($event->data == 'caslogin') {
			$event->preventDefault();
		}
	}
}
