1<?php 2/** 3 * Federated Login for DokuWiki - helper class 4 * 5 * Enables your DokuWiki to provide users with 6 * Hybrid OAuth + OpenId federated login. 7 * 8 * @copyright 2012 Aoi Karasu 9 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 10 * @link http://www.dokuwiki.org/plugin:fedauth 11 * @author Aoi Karasu <aoikarasu@gmail.com> 12 */ 13 14// must be run within Dokuwiki 15if (!defined('DOKU_INC')) die(); 16 17if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 18if (!defined('FEDAUTH_PLUGIN')) define('FEDAUTH_PLUGIN', DOKU_PLUGIN . 'fedauth/'); 19 20require_once(DOKU_PLUGIN.'action.php'); 21 22global $conf; 23 24// define cookie and session id, append server port when securecookie is configured 25if (!defined('FEDAUTH_COOKIE')) define('FEDAUTH_COOKIE', 'DWFA'.md5(DOKU_REL.(($conf['securecookie'])?$_SERVER['SERVER_PORT']:''))); 26 27if (!defined('USER_CMD_SCOPE')) define('USER_CMD_SCOPE', 'usr'); 28 29class action_plugin_fedauth extends DokuWiki_Action_Plugin { 30 31 var $provid = ''; 32 var $cmd = ''; 33 var $handler = null; 34 35 var $cookie = null; 36 37 var $providers = null; 38 39 var $functions = array('select','signin','signedin','remove'); // require a provider id 40 var $commands = array('add','login','logout','manage','register'); // don't require a provider id 41 42 /** 43 * Returns the plugin meta information. 44 */ 45 function getInfo() { 46 return array( 47 'author' => 'Aoi Karasu', 48 'email' => 'aoikarasu@gmail.com', 49 'date' => '2012-06-09', 50 'name' => 'Federated Login Plugin', 51 'desc' => 'Functions to handle user identity related DokuWiki actions', 52 'url' => 'http://www.dokuwiki.org/plugin:fedauth', 53 ); 54 } 55 56 /** 57 * Registers the event handlers. 58 */ 59 function register(&$controller) 60 { 61 $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'AFTER', $this, 'handle_login_form', array()); 62 $controller->register_hook('HTML_UPDATEPROFILEFORM_OUTPUT', 'AFTER', $this, 'handle_profile_form', array()); 63 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_act_preprocess', array()); 64 $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handle_act_unknown', array()); 65 $controller->register_hook('AUTH_LOGIN_CHECK', 'BEFORE', $this, 'handle_login_check', array()); 66 } 67 68 /** 69 * Validates federated login credentials. 70 * 71 * This method is crucial to keep the user logged in, when authorized via fedauth. 72 * If the fedauth cookie is found, it is validated against the session data 73 * and timeout value. When valid, user stays logged in. Otherwise a request 74 * is issued to the authorization service stored in the cookie, to authorize 75 * the user again. The default authorization check via auth_login() is suppressed 76 * and $_SESSION[DOKU_COOKIE]['auth'] is never set - there is no need for it. 77 * 78 * If the cookie is not found, the default authorization is not suppressed and 79 * will proceed as usual (unless other plugins influence it). 80 * 81 * IMPORTANT: 82 * There is an uresolved problem with AJAX calls, however. For typical GET and POST 83 * calls the timestamp of last successful authorization is validated against the 84 * $conf['auth_security_timeout'] variable. On timeout an authorization request 85 * is reissued to the authorization service. This involves HTTP redirections that 86 * don't work with AJAX calls that eventually hang. 87 * 88 * For the time beeing the timeout check is skipped for AJAX calls and the user 89 * stays logged in as long as other fedauth cookie data is valid. 90 * 91 * Dokuwiki built-in AJAX handler 'lib/exe/ajax.php' expects $_GET['call'] or 92 * $_POST['call'] to be set. Thus the action_plugin_fedauth::isAjaxCall() use 93 * this condition to recognize an AJAX call. Custom AJAX handlers should be 94 * implemented the same way as the built-in one, or at least implment setting 95 * of a dummy value before any invocations of Dokuwiki functions are made, eg. 96 * $_GET['call'] = 'dummy'; 97 * 98 * @param object $event event data 99 * @param array $param additional parameters 100 */ 101 function handle_login_check(&$event, $param) { 102 global $conf, $USERINFO; 103 104 // standard dokuwiki auhtorization in progress, escape 105 if (!empty($event->data['user'])) return; 106 107 require_once(FEDAUTH_PLUGIN . "classes/fa_cookie.class.php"); 108 $this->cookie = new fa_cookie(); 109 110 // fedauth signed-in complete or logout in progress; requires $this->cookie to be set, 111 // however an escape is a must to prevent unwanted behavior (auth loop, blocked logout) 112 if ((!empty($_REQUEST['fa']['signedin']) && ($_REQUEST['mode'] != 'add')) || 113 ($_REQUEST['do'] == 'logout')) return; 114 115 if ($cdata = $this->cookie->get()) { 116// msg( "<pre>".print_r($cdata, true)."</pre>"); 117 $user = $cdata['user']; 118 $session = $_SESSION[DOKU_COOKIE]['fedauth']; 119 // remove temp data, if any 120 if (isset($_SESSION[DOKU_COOKIE]['fedauth']['tmpr'])) { 121 unset($_SESSION[DOKU_COOKIE]['fedauth']['tmpr']); 122 } 123// msg( "<pre>".print_r($session, true)."</pre>"); 124 // refer to Dokuwiki's 'inc/auth/basic.class.php' for detailed information 125 // on useSessionCache() method and the purpose of @filemtime() condition 126 if (isset($session) && 127 ($session['time'] >= @filemtime($conf['cachedir'] . '/sessionpurge')) && 128 (($session['time'] >= time() - $conf['auth_security_timeout']) || $this->isLibExe() || $this->isAjaxCall()) && 129 ($session['user'] == $user) && 130 ($session['prid'] == $cdata['prid']) && 131 ($session['stok'] == $cdata['stok']) && 132 ($session['buid'] == auth_browseruid()) 133 ) { 134 // cookie and session ok - keep user logged-in 135// msg( "<pre>".print_r($session['rq'], true)."</pre>"); 136 $_SERVER['REMOTE_USER'] = $user; 137 $USERINFO = $session['info']; 138 $event->preventDefault(); 139 if (isset($session['sgin'])) { 140 // redirected from authorization service, display welcome message 141 msg('login successful'); 142 unset($_SESSION[DOKU_COOKIE]['fedauth']['sgin']); 143 } 144 if ($session['stor']) { 145 // restore request values from saved before authorization interception 146 $this->_restoreRequestData(); 147 } 148 return; 149 } 150//print('<pre>'.print_r($GLOBALS, true).'</pre>'); return; 151 // perform fedauth auhtorization based on cookie data 152 $this->_ensure_providers_loaded(); 153 if ($pro = $this->providers->get($cdata['prid'])) { 154 global $ID; 155 $ID = getID(); 156 $this->_require_user_infrastructure(); 157 // load command class and process the signin command 158 $this->handler =& load_handler_class($this, 'signin', USER_CMD_SCOPE, $cdata['prid'], 'login'); 159 $result = $this->handler->callService($pro, $cdata['svcd'], true); 160 if (is_array($result) && empty($_REQUEST['ajax'])) { 161 msg($result['msg'], $result['code']); 162 } 163 return; 164 } 165 // provider not found 166 msg('cookie found but provider is unknown or disabled'); 167 $this->cookie->clean(); 168 } 169 else if (isset($_SESSION[DOKU_COOKIE]['fedauth']['tmpr'])) { 170 // temporary fedauth data set, user authenticated but does not have local account 171 if ($_REQUEST['do'] == 'register') { 172 // if user navigated to standard register page, redirect to fedauth one 173 send_redirect(wl(getID(), 'do=fedauth', true, '&') . '&fa[register]'); 174 } 175 if (!isset($_REQUEST['fa']['register'])) { 176 // display account creation reminder 177 $msg = $this->getLang('registernow'); 178 $msg = str_replace('@PROVID@', $_SESSION[DOKU_COOKIE]['fedauth']['tmpr']['prnm'], $msg); 179 $msg = str_replace('@REGURL@', wl(getID(), 'do=fedauth', true, '&').'&fa[register]', $msg); 180 msg($msg, 2); 181 } 182 } 183 // no fedauth cookie nor temp login, do nothing fedauth related 184 // unless any other plugin takes over, auth_login() comes into play 185 } 186 187 /** 188 * Handles federated login action preprocess for fedauth, login, logout 189 * and profile actions. Loads required classes and authoriation providers 190 * configuration, processes request variables to route commands and 191 * optionally parameters to proper classes. Finally performs process() 192 * method for the selected command and prints result message (if any) 193 * using Dokuwiki message system, the msg() function. 194 */ 195 function handle_act_preprocess(&$event, $param) { 196 if ($event->data != 'login' && $event->data != 'logout' && 197 $event->data != 'profile' && $event->data != 'fedauth') { 198 return; 199 } 200 201 // require infrastructure classes 202 $this->_require_user_infrastructure(); 203 $this->_ensure_no_temp_data(); 204 $user = $_SERVER['REMOTE_USER']; 205 206 // load providers configuration 207 $this->_ensure_providers_loaded(); 208 209 // get command array and emulate logout command for dokuwiki logout action 210 $fa = ($event->data != 'logout') ? $_REQUEST['fa'] : array('logout' => null); 211 // read command arrray 212 if (is_array($fa)) { 213 $this->cmd = key($fa); 214 $this->provid = is_array($fa[$this->cmd]) ? key($fa[$this->cmd]) : null; 215 } else { 216 $this->cmd = $fa; 217 $this->provid = null; 218 } 219 220 $defaultcmd = empty($user) ? 'login' : 'manage'; 221 222 // validate command array 223 if (in_array($this->cmd, $this->commands)) { 224 $this->provid = ''; 225 } else if (!in_array($this->cmd, $this->functions) || !$this->providers->get($this->provid)) { 226 // NOTE: would be nicer if redirected to wiki's login page on empty username 227 $this->cmd = $defaultcmd; 228 $this->provid = ''; 229 } else if ($this->cmd == 'select') { 230 $this->cmd = 'login'; 231 } 232 233 // takeover fedauth action handling 234 if ($event->data == 'fedauth') { 235 $event->stopPropagation(); 236 $event->preventDefault(); 237 238 // manage and signedin commands do not require checking the security token 239 if ($this->cmd != 'manage' && $this->cmd != 'signedin') { 240 if (($this->cmd != 'login' || $this->provid != '') && !checkSecurityToken()) { 241 $this->cmd = $defaultcmd; 242 $this->provid = ''; 243 } 244 } 245 } 246 247 // load command class and process the command 248 $this->handler =& load_handler_class($this, $this->cmd, USER_CMD_SCOPE, $this->provid, 'login'); 249 $result = $this->handler->process(); 250 if (is_array($result) && empty($_REQUEST['ajax'])) { 251 msg($result['msg'], $result['code']); 252 } 253 } 254 255 /** 256 * Handles unknown action preprocess. 257 */ 258 function handle_act_unknown(&$event, $param) { 259 // mandatory check since other plugins' actions trigger this as well 260 if ($event->data != 'fedauth') { 261 return; 262 } 263 264 // enable direct access to language strings 265 $this->setupLocale(); 266 $event->stopPropagation(); 267 $event->preventDefault(); 268 $this->handler->html(); 269 } 270 271 /** 272 * Handles the login form rendering. 273 */ 274 function handle_login_form(&$event, $param) { 275 $this->setupLocale(); 276 $this->handler->html(); 277 } 278 279 /** 280 * Handles the profile form rendering. 281 */ 282 function handle_profile_form(&$event, $param) { 283 global $ID; 284 285 print '<p>' . '<a href="' . wl($ID, 'do=fedauth', true, '&') . '">' . $this->getLang('mylogins') . '</a></p>'; 286 } 287 288 /** 289 * Removes temporary fedauth data in case user authenticated with unassigned 290 * identity while not being logged in, and later on he did log in using 291 * different credentials instead of creating a new account. 292 */ 293 function _ensure_no_temp_data() { 294 if (!empty($_SERVER['REMOTE_USER']) && isset($_SESSION[DOKU_COOKIE]['fedauth']['tmpr'])) { 295 @session_start(); // make session writable 296 unset($_SESSION[DOKU_COOKIE]['fedauth']['tmpr']); 297 } 298 } 299 300 function _ensure_providers_loaded() { 301 if ($this->providers == null) { 302 if ($helper =& plugin_load('helper', 'fedauth')) { 303 $this->providers = $helper->getProviders(); 304 } 305 } 306 } 307 308 function _require_user_infrastructure() { 309 require_once(FEDAUTH_PLUGIN . 'common.php'); 310 require_once(FEDAUTH_PLUGIN . "classes/fa_base.class.php"); 311 require_once(FEDAUTH_PLUGIN . "classes/fa_service.class.php"); 312 require_once(FEDAUTH_PLUGIN . "classes/usr/fa_login.usr.class.php"); 313 } 314 315 /** 316 * Restores arbitrarily selected variables from the pre-authorization 317 * reqest and stored in a session variable. 318 */ 319 function _restoreRequestData() { 320 global $ACT; 321 322 $_REQUEST = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['rq']; 323 $_GET = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['gt']; 324 $_POST = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['pt']; 325 $_FILES = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['fs']; 326 $HTTP_RAW_POST_DATA = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['rp']; 327 // deprecated vars are subject to remove 328 $HTTP_GET_VARS = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['hg']; 329 $HTTP_POST_VARS = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['hp']; 330 $HTTP_POST_FILES = $_SESSION[DOKU_COOKIE]['fedauth']['stor']['hf']; 331 332 unset($_SESSION[DOKU_COOKIE]['fedauth']['stor']); 333 334 if (isset($_REQUEST['do'])) { 335 $ACT = $_REQUEST['do']; 336 } 337 } 338 339 /** 340 * Detects standard Dokuwiki AJAX call. 341 */ 342 function isAjaxCall() { 343 return (isset($_POST['call']) || isset($_GET['call'])); 344 } 345 346 /** 347 * Detects Dokuwiki PHP script files that do not route thru doku.php 348 */ 349 function isLibExe() { 350 return (strpos($_SERVER['PHP_SELF'], DOKU_REL . 'lib/exe/') === 0); 351 } 352 353} /* action_plugin_federate */ 354 355/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 356