Translations of this page?:

Chained Auth Backend

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.

Installation

Copy The Code to dokuwiki/inc/auth/chained.class.php

Configuration

$conf['authtype']     = 'chained';
$conf['chained_authtypes'] = 'ldap:plain';       // ":" separated list of auth backends.
$conf['chained_usermanager_authtype'] = 'plain'; // Which authtype to use with userManager

TODO

  • Confirm this works with the ldap backend (I tested with htaccess:plain)
  • Confirm this works with more than two auth backends in the chain
  • Confirm with latest version of dokuwiki (tested with 2006-11-06)
  • trustExternal is not supported. Could possibly work if ALL chained backends support external but needs changes to the main auth mechanism to handle mixed support of trustExternal from the chain.

The Code

<?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

Hmm - no problem getting ldap:plain to work with dokuwiki-2008-05-05 - just something I found that might be of importance: it seems that plain authorisation adds the default group for every ldap user, so the ACLs should respect that every successfully authenticated LDAP user is part of the default group - i.e. “user”. So be sure to do a ”?do=check” to list the groups you are authenticated as.

Christian Marg 2008/09/22 12:10

I also can't get ldap:plain to work in dokuwiki-2008-05-05. ldap works by itself, and plain too, but not chained.

Francois Malric 2008/09/22 9:35

2008/10/10 dokuwiki-2008-05-05: works great for me with ldap:plain. Thanks for this very usefull tip!
Etienne M.
2008/10/10 dokuwiki-2008-05-05: I've made some modifications to the usermanager plugin so that you can easily manage each authentication methods.
See this page.
 
tips/chainedauth.txt · Last modified: 2008/10/10 17:45 by 145.242.11.4
 

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Noncommercial-Share Alike 3.0 Unported

Imprint Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki
WikiForumIRCBugsTranslate