A post and feature request on the mailing list suggested chained auth might be useful, especially if there are segregated user groups, eg internal to a campus who might be in an ldap directory plus a select set of external users managed in a dokuwiki auth.users file.
This is a crude attempt to implement that.
Copy The Code to dokuwiki/inc/auth/chained.class.php
$conf['authtype'] = 'chained'; $conf['chained_authtypes'] = 'ldap:plain'; // ":" separated list of auth backends. $conf['chained_usermanager_authtype'] = 'plain'; // Which authtype to use with userManager
<?php /** * Chained authentication backend. * Will check a list of auth backends in turn for the supplied user. * Duplicate userIds will cause issues. * * Profile can be updated for any backend with "Profile" capability. * We detect profile by finding the auth for the current user * as specified by $_SERVER['REMOTE_USER'] * * UserManager can only be used with one of the backends because * it assumes the backend capabilities are set at construction * but in this case they would be user specific. * * Another approach would be to have a capability for UserManager only if supported * by ALL backends. * * I suspect that a number of auth backends don't play nice enough to work 100% * eg. * Multiple auth backends may use cleanID on the request attributes * which may render some userids invalid for other auth backends. * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Grant Gardner <grant@lastweekend.com.au> */ define('DOKU_AUTH', dirname(__FILE__)); require_once(DOKU_AUTH.'/basic.class.php'); class auth_chained extends auth_basic { //Chain of other auths.. var $chain = array(); //Backend to use with usermanager var $user_manager_auth = false; //Traversing the chain is expensive. Cache some userinfo var $user_cache = array(); /** * Constructor * * Carry out sanity checks to ensure the object is * able to operate. Set capabilities. * */ function auth_chained() { global $conf; // Create the auth chain... split string, create auths. $link_names = split(":", $conf['chained_authtypes']); $count = count($link_names); for ($i=0;$i<$count;$i++) { $link_name=$link_names[$i]; $link_classfile=DOKU_INC.'inc/auth/'.$link_name.'.class.php'; if (file_exists($link_classfile)) { require_once($link_classfile); } else { continue; nice_die($lang['authmodfailed'].' for '.$link_name); } $auth_class = "auth_".$link_name; if (class_exists($auth_class)) { $link_auth = new $auth_class(); if ($link_auth->success) { $this->chain[$link_name]=$link_auth; if ($link_name == $conf['chained_usermanager_authtype']) { $this->user_manager_auth = $link_auth; } } else { $this->success = false; return; } } else { nice_die($lang['authmodfailed']); } } $this->success = true; } /** * OK so we're not supposed to override this * but because our capabilities are context specific * we'll create this dirty hack */ function canDo($cap) { switch($cap) { case 'Profile': //Depends on current user. $user_auth = $this->getAuthFromUser(); if ($user_auth != false) { return $user_auth->canDo('Profile'); } return false; case 'UserMod': case 'addUser': case 'delUser': case 'getUsers': case 'getUserCount': case 'getGroups': //Depends on the auth for use with user manager if ($this->user_manager_auth != false) { return $this->user_manager_auth->canDo($cap); } return false; case 'modPass': case 'modName': case 'modLogin': case 'modGroups': case 'modMail': //Depends whether we are looking at the profile (Current user) //or User Manager $capability_auth = $this->getAuthFromRequest(); if ($capability_auth != false) { return $capability_auth->canDo($cap); } default: //Everything else (false) //'external' //'logoff' return parent::canDo($cap); } } /** * Use request attributes to guess whether we are in the Profile or UserManager * and return the appropriate auth backend */ function getAuthFromRequest() { global $ACT; if ($ACT == "admin" && $_REQUEST['page']=="usermanager") { return $this->user_manager_auth; } else { // assume we want profile info. return $this->getAuthFromUser(); } } function getAuthFromUser($user = null) { global $USERINFO; if (empty($user) || $user == $_SERVER['REMOTE_USER']) { //Use current user. See if we already have the global $USERINFO, if not use REMOTE_USER if (isset($USERINFO) && !empty($USERINFO['authtype'])) { $userinfo = $USERINFO; } else { $userinfo = $this->getUserData($_SERVER['REMOTE_USER']); } } else { $userinfo = $this->getUserData($user); } if ($userinfo != false && isset($userinfo['authtype'])) { return $this->chain[$userinfo['authtype']]; } return false; } /** * Return user info * * Delegate to the first link in the chain that has this user * * We also include which auth backend the user is associated with * so we can retrieve it later. * * and cache the result.. */ function getUserData($user){ if (empty($user)) { return false; } $cache_result = $this->user_cache[$user]; if (!empty($cache_result)) { return $cache_result; } $userinfo = false; foreach ($this->chain as $link_name => $link_auth) { //msg("Searching for $user in $link_name>",-1,__LINE__,__FILE__); $userinfo = $link_auth->getUserData($user); if ($userinfo != false) { $userinfo['authtype'] = $link_name; break; } } $this->user_cache[$user] = $userinfo; return $userinfo; } function checkPass($user,$pass) { $user_auth = $this->getAuthFromUser($user); if ($user_auth != false) { return $user_auth->checkPass($user,$pass); } return false; } function modifyUser($user, $changes) { $user_auth = $this->getAuthFromUser($user); if ($user_auth != false) { if ($user_auth->modifyUser($user,$changes)) { //Update the cache entry with changes. Keep authtype. $auth_name = $this->user_cache[$user]['authtype']; $this->user_cache[$user] = $user_auth->getUserData($user); $this->user_cache[$user]['authtype'] = $auth_name; //msg("Updated $user mail=".$this->user_cache[$user]['mail']); return true; } } return false; } function createUser($user,$pass,$name,$mail,$grps=null){ return $this->user_manager_auth->createUser($user,$pass,$name,$mail,$grps); } function deleteUsers($users) { //TODO. Remove users from cache. Probably not necessary on a per request basis. return $this->user_manager_auth->deleteUsers($users); } function getUserCount($filter=array()) { return $this->user_manager_auth->getUserCount($filter); } function retrieveUsers($start=0,$limit=-1,$filter=null) { return $this->user_manager_auth->retrieveUsers($start,$limit,$filter); } function addGroup($group) { return $this->user_manager_auth->addGroup($group); } function retrieveGroups($start=0,$limit=0) { return $this->user_manager_auth->retrieveGroups($start,$limit); } }
— Grant Gardner 2007-03-17 09:01
I can confirm that it also works in the combination mysql,plain.
— A Loonstra 2008-04-18 12:24
I can confirm that it works in the combination ldap:plain.
— Thomas Poindessous 2008-08-22 16:48
For me, ldap:plain doesn't work, because I use the email for login and plain backend “sanitizes” the login
It doesn't work for dokuwiki-2008-05-05 : plain auth and ldap auth work separately, but not ldap:plain with chained auth.
— Pierre Maurice 2008-09-03