* @version 2.2.0-ul-2 */ /** * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. * * 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. * * The license for this software can likely be found here: * http://www.gnu.org/licenses/gpl-2.0.html */ /** * This program also use the PHP OpenID library by JanRain, Inc. * which is licensed under the Apache license 2.0: * http://www.apache.org/licenses/LICENSE-2.0 */ // 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_openid extends DokuWiki_Action_Plugin { /** * Return some info */ function getInfo() { return array( 'author' => 'h6e.net / 7usr7local / tzzee', 'email' => 'pjw-git-2018@usr-local.org', 'date' => '2023-02-18', 'name' => 'OpenID plugin', 'desc' => 'Authenticate on a DokuWiki with OpenID (Vers. 2.2.0-ul-2)', 'url' => 'https://github.com/usr-local/dokuwiki-openid', ); } /** * Register the eventhandlers */ function register(Doku_Event_Handler $controller) { $controller->register_hook('FORM_LOGIN_OUTPUT', 'BEFORE', $this, 'handle_login_form', array()); $controller->register_hook('FORM_UPDATEPROFILE_OUTPUT', 'AFTER', $this, 'handle_profile_form', array()); $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_act_preprocess', array()); $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handle_act_unknown', array()); } /** * Returns the Consumer URL */ function _self($do) { global $ID; return wl($ID, 'do=' . $do, true, '&'); } /** * Redirect the user */ function _redirect($url) { header('Location: ' . $url); exit; } /** * Return an OpenID Consumer */ function getConsumer() { global $conf; if (isset($this->consumer)) { return $this->consumer; } define('Auth_OpenID_RAND_SOURCE', null); set_include_path( get_include_path() . PATH_SEPARATOR . dirname(__FILE__) ); require_once "Auth/OpenID/Consumer.php"; require_once "Auth/OpenID/FileStore.php"; // start session (needed for YADIS) session_start(); // create file storage area for OpenID data $store = new Auth_OpenID_FileStore($conf['tmpdir'] . '/openid'); // create OpenID consumer $this->consumer = new Auth_OpenID_Consumer($store); return $this->consumer; } /** * Handles the openid action */ function handle_act_preprocess(&$event, $param) { global $ID, $conf, $auth; $disabled = explode(',', $conf['disableactions']); if ($this->getConf('openid_disable_registration')) { $disabled[] = 'register'; } if ($this->getConf('openid_disable_update_profile')) { $disabled[] = 'resendpwd'; $disabled[] = 'profile'; } $conf['disableactions'] = implode(',', $disabled); $user = $_SERVER['REMOTE_USER']; // Do not ask the user a password he didn't set if ($event->data == 'profile') { $conf['profileconfirm'] = 0; if (preg_match('!^https?://!', $user)) { $this->_redirect( $this->_self('openid') ); } } if ($event->data != 'openid' && $event->data != 'logout') { // Warn the user to register an account if he's using a not registered OpenID // and if registration is possible if (preg_match('!^https?://!', $user)) { if ($auth && $auth->canDo('addUser') && actionOK('register')) { $message = sprintf($this->getLang('complete_registration_notice'), $this->_self('openid')); msg($message, 2); } } } if ($event->data == 'openid') { // not sure this if it's useful there $event->stopPropagation(); $event->preventDefault(); if (isset($_POST['mode']) && ($_POST['mode'] == 'login' || $_POST['mode'] == 'add')) { // we try to login with the OpenID submited $consumer = $this->getConsumer(); $auth = $consumer->begin($_POST['openid_identifier']); if (!$auth) { msg($this->getLang('enter_valid_openid_error'), -1); return; } // add an attribute query extension if we've never seen this OpenID before. $associations = $this->get_associations(); if (!isset($associations[$openid])) { require_once('Auth/OpenID/SReg.php'); $e = Auth_OpenID_SRegRequest::build(array(),array('nickname','email','fullname')); $auth->addExtension($e); } // redirect to OpenID provider for authentication // this fix an issue with mod_rewrite with JainRain library // when a parameter seems to be non existing in the query $return_to = $this->_self('openid') . '&id=' . $ID; $url = $auth->redirectURL(DOKU_URL, $return_to); $this->_redirect($url); } else if (isset($_POST['mode']) && $_POST['mode'] == 'extra') { // we register the user on the wiki and associate the account with his OpenID $this->register_user(); } else if (isset($_POST['mode']) && $_POST['mode'] == 'delete') { foreach ($_POST['delete'] as $identity => $state) { $this->remove_openid_association($user, $identity); } } else if ($_GET['openid_mode'] == 'id_res') { $consumer = $this->getConsumer(); $response = $consumer->complete($this->_self('openid')); // set session variable depending on authentication result if ($response->status == Auth_OpenID_SUCCESS) { $openid = isset($_GET['openid1_claimed_id']) ? $_GET['openid1_claimed_id'] : $_GET['openid_claimed_id']; if (empty($openid)) { msg("Can't find OpenID claimed ID.", -1); return false; } if (isset($user) && !preg_match('!^https?://!', $user)) { $result = $this->register_openid_association($user, $openid); if ($result) { msg($this->getLang('openid_identity_added'), 1); } } else { $authenticate = $this->login_user($openid); if ($authenticate) { $log = array('message' => 'logged in temporarily', 'user' => $user); trigger_event('PLUGIN_LOGLOG_LOG', $log); // redirect to the page itself (without do=openid) $this->_redirect(wl($ID)); } } } else { msg($this->getLang('openid_authentication_failed') . ': ' . $response->message, -1); $log = array('message' => 'failed login attempt', 'user' => $user); trigger_event('PLUGIN_LOGLOG_LOG', $log); return; } } else if ($_GET['openid_mode'] == 'cancel') { // User cancelled the authentication msg($this->getLang('openid_authentication_canceled'), 0); return; // fall through to what ever action was called } } if ($this->getConf('openid_disable_registration') && $event->data == 'register') { $event->stopPropagation(); $event->preventDefault(); msg($this->getLang('openid_registration_denied'), -1); return; } if ($this->getConf('openid_disable_update_profile') && ($event->data == 'profile'||$event->data == 'resendpwd')) { $event->stopPropagation(); $event->preventDefault(); msg($this->getLang('openid_update_profile_denied'), -1); return; } return; // fall through to what ever action was called } /** * Create the OpenID login/complete forms */ function handle_act_unknown(&$event, $param) { global $auth, $ID; if ($event->data != 'openid') { return; } $event->stopPropagation(); $event->preventDefault(); $user = $_SERVER['REMOTE_USER']; if (empty($user)) { print $this->locale_xhtml('intro'); print '
'.NL; $form = $this->get_openid_form('login'); html_form('register', $form); print '
'.NL; } else if (preg_match('!^https?://!', $user)) { echo '

', $this->getLang('openid_account_fieldset'), '

', NL; if ($auth && $auth->canDo('addUser') && actionOK('register')) { echo '

', $this->getLang('openid_complete_text'), '

', NL; print '
'.NL; $form = $this->get_openid_form('extra'); html_form('complete', $form); print '
'.NL; } else { echo '

', sprintf($this->getLang('openid_complete_disabled_text'), wl($ID)), '

', NL; } } else if (!$this->getConf('openid_disable_update_profile')) { echo '

', $this->getLang('openid_identities_title'), '

', NL; $identities = $this->get_associations($_SERVER['REMOTE_USER']); if (!empty($identities)) { echo '
'; echo ''; foreach ($identities as $identity => $user) { echo ''; echo ''; echo ''; echo ''; } echo '
' . $identity . '
'; echo ''; echo ''; echo '
'; } else { echo '

' . $this->getLang('none') . '

'; } echo '

' . $this->getLang('add_openid_title') . '

'; print '
'.NL; $form = new Doku_Form('openid__login', script()); $form->addHidden('do', 'openid'); $form->addHidden('mode', 'add'); $form->addElement( form_makeTextField( 'openid_identifier', isset($_POST['openid_identifier']) ? $_POST['openid_identifier'] : '', $this->getLang('openid_url_label'), 'openid__url', 'block', array('size'=>'50') ) ); $form->addElement(form_makeButton('submit', '', $this->getLang('add_button'))); html_form('add', $form); print '
'.NL; } else { msg($this->getLang('openid_update_profile_denied'), -1); } } /** * Generate the OpenID login/complete forms */ function get_openid_form($mode) { global $USERINFO, $lang; $c = 'block'; $p = array('size'=>'50'); $form = new Doku_Form('openid__login', script()); $form->addHidden('id', $_GET['id']); $form->addHidden('do', 'openid'); if ($mode == 'extra') { $form->startFieldset($this->getLang('openid_account_fieldset')); $form->addHidden('mode', 'extra'); if($this->getConf('openid_disable_update_profile')){ $form->addHidden('nickname', $_REQUEST['nickname']); $form->addHidden('email', $_REQUEST['email']); $form->addHidden('fullname', $_REQUEST['fullname']); }else{ $form->addElement(form_makeTextField('nickname', $_REQUEST['nickname'], $lang['user'], null, $c, $p)); $form->addElement(form_makeTextField('email', $_REQUEST['email'], $lang['email'], '', $c, $p)); $form->addElement(form_makeTextField('fullname', $_REQUEST['fullname'], $lang['fullname'], '', $c, $p)); } $form->addElement(form_makeButton('submit', '', $this->getLang('complete_button'))); } else { $form->startFieldset($this->getLang('openid_login_fieldset')); $form->addHidden('mode', 'login'); if (!empty($this->getConf('openid_identifier'))){ $form->addHidden('openid_identifier', $this->getConf('openid_identifier')); }else{ $form->addElement(form_makeTextField('openid_identifier', $_REQUEST['openid_identifier'], $this->getLang('openid_url_label'), 'openid__url', $c, $p)); } $form->addElement(form_makeButton('submit', '', $lang['btn_login'])); } $form->endFieldset(); return $form; } /** * Insert link to OpenID into usual login form */ function handle_login_form(&$event, $param) { $msg = $this->getLang('login_link'); $msg = sprintf("

$msg

", $this->_self('openid')); $pos = $event->data->findPositionByAttribute('type', 'submit'); $event->data->addHTML($msg, $pos+2); } function handle_profile_form(&$event, $param) { echo '

', sprintf($this->getLang('manage_link'), $this->_self('openid')), '

'; } /** * Gets called when a OpenID login was succesful * * We store available userinfo in Session and Cookie */ function login_user($openid) { global $USERINFO, $auth, $conf; // look for associations passed from an auth backend in user infos $users = $auth->retrieveUsers(); foreach ($users as $id => $user) { if (isset($user['openids'])) { foreach ($user['openids'] as $identity) { if ($identity == $openid) { return $this->update_session($id); } } } } $associations = $this->get_associations(); // this openid is associated with a real wiki user account if (isset($associations[$openid])) { $user = $associations[$openid]; return $this->update_session($user); } // no real wiki user account associated // note that the generated cookie is invalid and will be invalided // when the 'auth_security_timeout' expire $this->update_session($openid); $redirect_url = $this->_self('openid'); $sregs = array('email', 'nickname', 'fullname'); foreach ($sregs as $sreg) { if (!empty($_GET["openid_sreg_$sreg"])) { $redirect_url .= "&$sreg=" . urlencode($_GET["openid_sreg_$sreg"]); } } // we will advice the user to register a real user account $this->_redirect($redirect_url); } /** * Register the user in DokuWiki user conf, * write the OpenID association in the OpenID conf */ function register_user() { global $ID, $lang, $conf, $auth, $openid_associations; if(!$auth->canDo('addUser')) return false; $_POST['login'] = $_POST['nickname']; // clean username $_POST['login'] = preg_replace('/.*:/','',$_POST['login']); $_POST['login'] = cleanID($_POST['login']); // clean fullname and email $_POST['fullname'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['fullname'])); $_POST['email'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['email'])); if (empty($_POST['login']) || empty($_POST['fullname']) || empty($_POST['email'])) { msg($lang['regmissing'], -1); return false; } else if (!mail_isvalid($_POST['email'])) { msg($lang['regbadmail'], -1); return false; } // okay try to create the user if (!$auth->createUser($_POST['login'], auth_pwgen(), $_POST['fullname'], $_POST['email'])) { msg($lang['reguexists'], -1); return false; } $user = $_POST['login']; $openid = $_SERVER['REMOTE_USER']; // we update the OpenID associations array $this->register_openid_association($user, $openid); $this->update_session($user); $log = array('message' => 'logged in permanently', 'user' => $user); trigger_event('PLUGIN_LOGLOG_LOG', $log); // account created, everything OK $this->_redirect(wl($ID)); } /** * Update user sessions * * Note that this doesn't play well with DokuWiki 'auth_security_timeout' configuration. * * So, you better set it to an high value, like '60*60*24', the user being disconnected * in that case one day after authentication */ function update_session($user) { session_start(); global $USERINFO, $INFO, $conf, $auth; $_SERVER['REMOTE_USER'] = $user; $USERINFO = $auth->getUserData($user); if (empty($USERINFO)) { $USERINFO['pass'] = 'invalid'; $USERINFO['name'] = 'OpenID'; $USERINFO['grps'] = array($conf['defaultgroup'], 'openid'); } $pass = auth_encrypt($USERINFO['pass'], auth_cookiesalt()); auth_setCookie($user, $pass, false); // auth data has changed, reinit the $INFO array $INFO = pageinfo(); return true; } function register_openid_association($user, $openid) { $associations = $this->get_associations(); if (isset($associations[$openid])) { msg($this->getLang('openid_already_user_error'), -1); return false; } $associations[$openid] = $user; $this->write_openid_associations($associations); return true; } function remove_openid_association($user, $openid) { $associations = $this->get_associations(); if (isset($associations[$openid]) && $associations[$openid] == $user) { unset($associations[$openid]); $this->write_openid_associations($associations); return true; } return false; } function write_openid_associations($associations) { $cfg = ' $login) { $cfg .= '$openid_associations["' . addslashes($id) . '"] = "' . addslashes($login) . '"' . ";\n"; } file_put_contents(DOKU_CONF.'openid.php', $cfg); $this->openid_associations = $associations; } function get_associations($username = null) { if (isset($this->openid_associations)) { $openid_associations = $this->openid_associations; } else if (file_exists(DOKU_CONF.'openid.php')) { // load OpenID associations array $openid_associations = array(); include(DOKU_CONF.'openid.php'); $this->openid_associations = $openid_associations; } else { $this->openid_associations = $openid_associations = $openid_associations = array(); } // Maybe is there a better way to filter the array if (!empty($username)) { $user_openid_associations = array(); foreach ((array)$openid_associations as $openid => $login) { if ($username == $login) { $user_openid_associations[$openid] = $login; } } return $user_openid_associations; } return $openid_associations; } }