1<?php
2/**
3 * DokuWiki SSO CAS Plugin
4 *
5 * @licence	GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author	Iain Hallam <iain@nineworlds.net>
7 */
8
9/**
10 * Copyright (C) 2012 Iain Hallam, Andreas Gohr
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
25 */
26
27// must be run within DokuWiki
28if(!defined('DOKU_INC')) die();
29
30if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
31require_once(DOKU_PLUGIN.'action.php');
32
33class action_plugin_ssocas extends DokuWiki_Action_Plugin {
34	function getInfo() {
35		return array (
36			'author' => 'Iain Hallam',
37			'email' => 'iain@nineworlds.net',
38			'date' => '2012-06-16',
39			'name' => 'SSO CAS Plugin',
40			'desc' => 'Authenticate DokuWiki users via CAS',
41			'url' => 'http://www.dokuwiki.org/plugin:ssocas',
42		);
43	}
44
45	function register (&$controller) {
46		if ($this->getConf('server') != '') {
47			$controller->register_hook ('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_login_form');
48			$controller->register_hook ('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action');
49			$controller->register_hook ('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handle_template');
50		}
51	}
52
53	function _self () {
54		global $ID;
55		return wl($ID, '', true, '');
56	}
57
58	function _selfdo ($do) {
59		global $ID;
60		return wl($ID, 'do=' . $do, true, '&');
61	}
62
63	function _redirect ($url) {
64		header ('Location: ' . $url);
65		exit;
66	}
67
68	// Log function copied from Andreas Gohr's loglog plugin
69	function _log ($msg) {
70		global $conf;
71		$t = time();
72		$log = $t."\t".strftime($conf['dformat'],$t)."\t".$_SERVER['REMOTE_ADDR']."\t".$_SERVER['REMOTE_USER']."\t".$msg;
73		io_saveFile($conf['cachedir'].'/ssocas.log',"$log\n",true);
74	}
75
76	function handle_login_form (&$event, $param) {
77		global $auth;
78		global $conf;
79		global $lang;
80		global $ID;
81
82		// Remove the register and resendpwd links, if they exist.
83		for ($formPosition = 0; $formPosition < count($event->data->_content); $formPosition++) {
84			$formElement = $event->data->getElementAt($formPosition);
85			if ((! is_array($formElement)) and (substr($formElement, 0, 2) == '<p')) {
86				$event->data->replaceElement ($formPosition, NULL);
87			}
88		}
89
90		$insertElement = 5;
91
92		if($auth && $auth->canDo('addUser') && actionOK('register')){
93			$event->data->insertElement($insertElement,'<p>'.$lang['reghere'].': <a href="'.wl($ID,'do=register').'" rel="nofollow" class="wikilink1">'.$lang['register'].'</a></p>');
94			$insertElement = 6;
95		}
96		if ($auth && $auth->canDo('modPass') && actionOK('resendpwd')) {
97			$event->data->insertElement($insertElement,'<p>'.$lang['pwdforget'].': <a href="'.wl($ID,'do=resendpwd').'" rel="nofollow" class="wikilink1">'.$lang['btn_resendpwd'].'</a></p>');
98		}
99
100		if ($this->getConf('logourl') != '') {
101			$caslogo = '<img src="'.$this->getConf('logourl').'" alt="" style="vertical-align: middle;" /> ';
102		} else {
103			$caslogo = '';
104		}
105
106		$event->data->insertElement(0,'<fieldset><legend>'.$this->getConf('name').'</legend>');
107		$event->data->insertElement(1,'<p style="text-align: center;">'.$caslogo.'<a href="'.$this->_selfdo('caslogin').'">Login</a></p>');
108		$event->data->insertElement(2,'</fieldset>');
109		if ($this->getConf('jshidelocal')) {
110			$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>');
111			$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>');
112		} else {
113			$event->data->replaceElement(3,'<fieldset><legend>'.$this->getConf('localname').'</legend>');
114		}
115	}
116
117	function handle_caslogin () {
118		global $ACT, $auth, $conf, $INFO, $USERINFO;
119		$cas_auth_client = phpCAS::forceAuthentication();
120		if ($cas_auth_client) {
121			// Successful
122			$casuser = phpCAS::getUser();
123			if ($this->getConf('logging')) {$this->_log('user '.$casuser.' successfully authenticated');}
124			$USERINFO = $auth->getUserData($casuser);
125
126			if (empty($USERINFO)) {
127				// Logged in, but no valid account
128				if ($this->getConf('logging')) {$this->_log('user '.$casuser.' has no local account');}
129				$ACT = act_permcheck('show');
130				msg ('Sorry; your login to '.$this->getConf('name').' succeeded but you don\'t have a valid account here.',-1);
131			} else {
132				// Populate the session variables
133				$_SERVER['REMOTE_USER'] = $casuser;
134				if ($this->getConf('stickysession')) {
135					$stickysession = true;
136				} else {
137					$stickysession = false;
138				}
139				auth_setCookie($casuser,'CAS',$stickysession);
140
141				// Authentication info has changed: reset the page info
142				$INFO = pageinfo();
143
144				// Now logged in, so show the page
145				$ACT = act_permcheck('show');
146
147				// Log action
148				if ($this->getConf('logging')) {
149					if ($stickysession) {
150						$this->_log('logged in permanently');
151					}else{
152						$this->_log('logged in temporarily');
153					}
154				}
155			}
156		} else {
157			// Failed, but this should never be reached, because the CAS server shouldn't redirect back to us without a successful login
158			if ($this->getConf('logging')) {$this->_log('forceAuthentication failed');}
159			$ACT = act_permcheck('show');
160			msg ('Sorry; your login to '.$this->getConf('name').' failed.',-1);
161		}
162	}
163
164	function handle_caslogout () {
165		// Check CAS authentication and whether to log out of CAS completely, and do a phpCAS::logout if so.
166		if ((isset($_SERVER['REMOTE_USER'])) && ($_SESSION[DOKU_COOKIE]['auth']['pass'] == 'CAS')) {
167			if (($this->getConf('caslogout')) && (phpCAS::checkAuthentication())) {
168				phpCAS::logoutWithRedirectServiceAndUrl($this->_self(), $this->_self());
169
170				// Log action
171				if ($this->getConf('logging')) {
172					$this->_log('logged off');
173				}
174			}
175		}
176		auth_logoff();
177	}
178
179	function handle_action (&$event, $param) {
180		global $ACT;
181		require_once ('CAS.php');
182		phpCAS::client($this->getConf('version').'.0',$this->getConf('server'),(integer) $this->getConf('port'),$this->getConf('uri'));
183		if (! ($this->getConf('servercert') or $this->getConf('servercacert'))) {
184			// Neither Server or CA certificate is set
185			phpCAS::setNoCasServerValidation();
186		} else {
187			if ($this->getConf('servercert')) {
188				// Server certificate is set
189				phpCAS::setCasServerCert($this->getConf('servercert'));
190			}
191			if ($this->getConf('servercacert')) {
192				// CA certificate is set
193				phpCAS::setCasServerCACert($this->getConf('servercacert'));
194			}
195		}
196
197		// Handle the case where the CAS session is finished but the user is still logged in to DokuWiki
198		if (! $this->getConf('stickysession')) {
199			if ((isset($_SERVER['REMOTE_USER'])) && ($_SESSION[DOKU_COOKIE]['auth']['pass'] == 'CAS')) {
200				if (! phpCAS::checkAuthentication()) {
201					// Authentication failed
202					$event->preventDefault();
203					$this->handle_caslogout();
204					$this->_redirect($this->_self());
205				}
206			}
207		}
208
209		if ($event->data == 'caslogin') {
210			$event->preventDefault();
211			$this->handle_caslogin();
212		}
213		if ($event->data == 'logout') {
214			$this->handle_caslogout();
215		}
216	}
217
218	function handle_template (&$event, $param) {
219		if ($event->data == 'caslogin') {
220			$event->preventDefault();
221		}
222	}
223}
224