[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/auth/ -> ad.class.php (source)

   1  <?php
   2  /**
   3   * Active Directory authentication backend for DokuWiki
   4   *
   5   * This makes authentication with a Active Directory server much easier
   6   * than when using the normal LDAP backend by utilizing the adLDAP library
   7   *
   8   * Usage:
   9   *   Set DokuWiki's local.protected.php auth setting to read
  10   *
  11   *   $conf['useacl']         = 1;
  12   *   $conf['disableactions'] = 'register';
  13   *   $conf['autopasswd']     = 0;
  14   *   $conf['authtype']       = 'ad';
  15   *   $conf['passcrypt']      = 'ssha';
  16   *
  17   *   $conf['auth']['ad']['account_suffix']     = '@my.domain.org';
  18   *   $conf['auth']['ad']['base_dn']            = 'DC=my,DC=domain,DC=org';
  19   *   $conf['auth']['ad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
  20   *
  21   *   //optional:
  22   *   $conf['auth']['ad']['sso']                = 1;
  23   *   $conf['auth']['ad']['ad_username']        = 'root';
  24   *   $conf['auth']['ad']['ad_password']        = 'pass';
  25   *   $conf['auth']['ad']['real_primarygroup']  = 1;
  26   *   $conf['auth']['ad']['use_ssl']            = 1;
  27   *   $conf['auth']['ad']['use_tls']            = 1;
  28   *   $conf['auth']['ad']['debug']              = 1;
  29   *   // warn user about expiring password this many days in advance:
  30   *   $conf['auth']['ad']['expirywarn']         = 5;
  31   *
  32   *   // get additional information to the userinfo array
  33   *   // add a list of comma separated ldap contact fields.
  34   *   $conf['auth']['ad']['additional'] = 'field1,field2';
  35   *
  36   * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  37   * @author  James Van Lommel <jamesvl@gmail.com>
  38   * @link    http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
  39   * @author  Andreas Gohr <andi@splitbrain.org>
  40   */
  41  
  42  require_once (DOKU_INC.'inc/adLDAP.php');
  43  
  44  class auth_ad extends auth_basic {
  45      var $cnf = null;
  46      var $opts = null;
  47      var $adldap = null;
  48      var $users = null;
  49      var $msgshown = false;
  50  
  51      /**
  52       * Constructor
  53       */
  54      function __construct() {
  55          global $conf;
  56          $this->cnf = $conf['auth']['ad'];
  57  
  58          // additional information fields
  59          if (isset($this->cnf['additional'])) {
  60              $this->cnf['additional'] = str_replace(' ', '', $this->cnf['additional']);
  61              $this->cnf['additional'] = explode(',', $this->cnf['additional']);
  62          } else $this->cnf['additional'] = array();
  63  
  64          // ldap extension is needed
  65          if (!function_exists('ldap_connect')) {
  66              if ($this->cnf['debug'])
  67                  msg("AD Auth: PHP LDAP extension not found.",-1);
  68              $this->success = false;
  69              return;
  70          }
  71  
  72          // Prepare SSO
  73          if(!utf8_check($_SERVER['REMOTE_USER'])){
  74              $_SERVER['REMOTE_USER'] = utf8_encode($_SERVER['REMOTE_USER']);
  75          }
  76          if($_SERVER['REMOTE_USER'] && $this->cnf['sso']){
  77              // remove possible NTLM domain
  78              list($dom,$usr) = explode('\\',$_SERVER['REMOTE_USER'],2);
  79              if(!$usr) $usr = $dom;
  80  
  81              // remove possible Kerberos domain
  82              list($usr,$dom) = explode('@',$usr);
  83  
  84              $dom = strtolower($dom);
  85              $_SERVER['REMOTE_USER'] = $usr;
  86  
  87              // we need to simulate a login
  88              if(empty($_COOKIE[DOKU_COOKIE])){
  89                  $_REQUEST['u'] = $_SERVER['REMOTE_USER'];
  90                  $_REQUEST['p'] = 'sso_only';
  91              }
  92          }
  93  
  94          // prepare adLDAP standard configuration
  95          $this->opts = $this->cnf;
  96  
  97          // add possible domain specific configuration
  98          if($dom && is_array($this->cnf[$dom])) foreach($this->cnf[$dom] as $key => $val){
  99              $this->opts[$key] = $val;
 100          }
 101  
 102          // handle multiple AD servers
 103          $this->opts['domain_controllers'] = explode(',',$this->opts['domain_controllers']);
 104          $this->opts['domain_controllers'] = array_map('trim',$this->opts['domain_controllers']);
 105          $this->opts['domain_controllers'] = array_filter($this->opts['domain_controllers']);
 106  
 107          // we can change the password if SSL is set
 108          if($this->opts['use_ssl'] || $this->opts['use_tls']){
 109              $this->cando['modPass'] = true;
 110          }
 111          $this->cando['modName'] = true;
 112          $this->cando['modMail'] = true;
 113      }
 114  
 115      /**
 116       * Check user+password [required auth function]
 117       *
 118       * Checks if the given user exists and the given
 119       * plaintext password is correct by trying to bind
 120       * to the LDAP server
 121       *
 122       * @author  James Van Lommel <james@nosq.com>
 123       * @return  bool
 124       */
 125      function checkPass($user, $pass){
 126          if($_SERVER['REMOTE_USER'] &&
 127             $_SERVER['REMOTE_USER'] == $user &&
 128             $this->cnf['sso']) return true;
 129  
 130          if(!$this->_init()) return false;
 131          return $this->adldap->authenticate($user, $pass);
 132      }
 133  
 134      /**
 135       * Return user info [required auth function]
 136       *
 137       * Returns info about the given user needs to contain
 138       * at least these fields:
 139       *
 140       * name string  full name of the user
 141       * mail string  email address of the user
 142       * grps array   list of groups the user is in
 143       *
 144       * This LDAP specific function returns the following
 145       * addional fields:
 146       *
 147       * dn   string  distinguished name (DN)
 148       * uid  string  Posix User ID
 149       *
 150       * @author  James Van Lommel <james@nosq.com>
 151       */
 152      function getUserData($user){
 153          global $conf;
 154          global $lang;
 155          global $ID;
 156          if(!$this->_init()) return false;
 157  
 158          if($user == '') return array();
 159  
 160          $fields = array('mail','displayname','samaccountname','lastpwd','pwdlastset','useraccountcontrol');
 161  
 162          // add additional fields to read
 163          $fields = array_merge($fields, $this->cnf['additional']);
 164          $fields = array_unique($fields);
 165  
 166          //get info for given user
 167          $result = $this->adldap->user_info($user, $fields);
 168          if($result == false){
 169              return array();
 170          }
 171  
 172          //general user info
 173          $info['name']    = $result[0]['displayname'][0];
 174          $info['mail']    = $result[0]['mail'][0];
 175          $info['uid']     = $result[0]['samaccountname'][0];
 176          $info['dn']      = $result[0]['dn'];
 177          //last password set (Windows counts from January 1st 1601)
 178          $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10000000 - 11644473600;
 179          //will it expire?
 180          $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD
 181  
 182          // additional information
 183          foreach ($this->cnf['additional'] as $field) {
 184              if (isset($result[0][strtolower($field)])) {
 185                  $info[$field] = $result[0][strtolower($field)][0];
 186              }
 187          }
 188  
 189          // handle ActiveDirectory memberOf
 190          $info['grps'] = $this->adldap->user_groups($user,(bool) $this->opts['recursive_groups']);
 191  
 192          if (is_array($info['grps'])) {
 193              foreach ($info['grps'] as $ndx => $group) {
 194                  $info['grps'][$ndx] = $this->cleanGroup($group);
 195              }
 196          }
 197  
 198          // always add the default group to the list of groups
 199          if(!is_array($info['grps']) || !in_array($conf['defaultgroup'],$info['grps'])){
 200              $info['grps'][] = $conf['defaultgroup'];
 201          }
 202  
 203          // check expiry time
 204          if($info['expires'] && $this->cnf['expirywarn']){
 205              $result   = $this->adldap->domain_info(array('maxpwdage')); // maximum pass age
 206              $maxage   = -1 * $result['maxpwdage'][0] / 10000000; // negative 100 nanosecs
 207              $timeleft = $maxage - (time() - $info['lastpwd']);
 208              $timeleft = round($timeleft/(24*60*60));
 209              $info['expiresin'] = $timeleft;
 210  
 211              // if this is the current user, warn him (once per request only)
 212              if( ($_SERVER['REMOTE_USER'] == $user) &&
 213                  ($timeleft <= $this->cnf['expirywarn']) &&
 214                  !$this->msgshown
 215              ){
 216                  $msg = sprintf($lang['authpwdexpire'],$timeleft);
 217                  if($this->canDo('modPass')){
 218                      $url = wl($ID,array('do'=>'profile'));
 219                      $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>';
 220                  }
 221                  msg($msg);
 222                  $this->msgshown = true;
 223              }
 224          }
 225  
 226          return $info;
 227      }
 228  
 229      /**
 230       * Make AD group names usable by DokuWiki.
 231       *
 232       * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
 233       *
 234       * @author  James Van Lommel (jamesvl@gmail.com)
 235       */
 236      function cleanGroup($name) {
 237          $sName = str_replace('\\', '', $name);
 238          $sName = str_replace('#', '', $sName);
 239          $sName = preg_replace('[\s]', '_', $sName);
 240          return $sName;
 241      }
 242  
 243      /**
 244       * Sanitize user names
 245       */
 246      function cleanUser($name) {
 247          return $this->cleanGroup($name);
 248      }
 249  
 250      /**
 251       * Most values in LDAP are case-insensitive
 252       */
 253      function isCaseSensitive(){
 254          return false;
 255      }
 256  
 257      /**
 258       * Bulk retrieval of user data
 259       *
 260       * @author  Dominik Eckelmann <dokuwiki@cosmocode.de>
 261       * @param   start     index of first user to be returned
 262       * @param   limit     max number of users to be returned
 263       * @param   filter    array of field/pattern pairs, null for no filter
 264       * @return  array of userinfo (refer getUserData for internal userinfo details)
 265       */
 266      function retrieveUsers($start=0,$limit=-1,$filter=array()) {
 267          if(!$this->_init()) return false;
 268  
 269          if ($this->users === null) {
 270              //get info for given user
 271              $result = $this->adldap->all_users();
 272              if (!$result) return array();
 273              $this->users = array_fill_keys($result, false);
 274          }
 275  
 276          $i = 0;
 277          $count = 0;
 278          $this->_constructPattern($filter);
 279          $result = array();
 280  
 281          foreach ($this->users as $user => &$info) {
 282              if ($i++ < $start) {
 283                  continue;
 284              }
 285              if ($info === false) {
 286                  $info = $this->getUserData($user);
 287              }
 288              if ($this->_filter($user, $info)) {
 289                  $result[$user] = $info;
 290                  if (($limit >= 0) && (++$count >= $limit)) break;
 291              }
 292          }
 293          return $result;
 294      }
 295  
 296      /**
 297       * Modify user data
 298       *
 299       * @param   $user      nick of the user to be changed
 300       * @param   $changes   array of field/value pairs to be changed
 301       * @return  bool
 302       */
 303      function modifyUser($user, $changes) {
 304          $return = true;
 305  
 306          // password changing
 307          if(isset($changes['pass'])){
 308              try {
 309                  $return = $this->adldap->user_password($user,$changes['pass']);
 310              } catch (adLDAPException $e) {
 311                  if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
 312                  $return = false;
 313              }
 314              if(!$return) msg('AD Auth: failed to change the password. Maybe the password policy was not met?',-1);
 315          }
 316  
 317          // changing user data
 318          $adchanges = array();
 319          if(isset($changes['name'])){
 320              // get first and last name
 321              $parts = explode(' ',$changes['name']);
 322              $adchanges['surname']   = array_pop($parts);
 323              $adchanges['firstname'] = join(' ',$parts);
 324              $adchanges['display_name'] = $changes['name'];
 325          }
 326          if(isset($changes['mail'])){
 327              $adchanges['email'] = $changes['mail'];
 328          }
 329          if(count($adchanges)){
 330              try {
 331                  $return = $return & $this->adldap->user_modify($user,$adchanges);
 332              } catch (adLDAPException $e) {
 333                  if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
 334                  $return = false;
 335              }
 336          }
 337  
 338          return $return;
 339      }
 340  
 341      /**
 342       * Initialize the AdLDAP library and connect to the server
 343       */
 344      function _init(){
 345          if(!is_null($this->adldap)) return true;
 346  
 347          // connect
 348          try {
 349              $this->adldap = new adLDAP($this->opts);
 350              if (isset($this->opts['ad_username']) && isset($this->opts['ad_password'])) {
 351                  $this->canDo['getUsers'] = true;
 352              }
 353              return true;
 354          } catch (adLDAPException $e) {
 355              if ($this->cnf['debug']) {
 356                  msg('AD Auth: '.$e->getMessage(), -1);
 357              }
 358              $this->success = false;
 359              $this->adldap  = null;
 360          }
 361          return false;
 362      }
 363  
 364      /**
 365       * return 1 if $user + $info match $filter criteria, 0 otherwise
 366       *
 367       * @author   Chris Smith <chris@jalakai.co.uk>
 368       */
 369      function _filter($user, $info) {
 370          foreach ($this->_pattern as $item => $pattern) {
 371              if ($item == 'user') {
 372                  if (!preg_match($pattern, $user)) return 0;
 373              } else if ($item == 'grps') {
 374                  if (!count(preg_grep($pattern, $info['grps']))) return 0;
 375              } else {
 376                  if (!preg_match($pattern, $info[$item])) return 0;
 377              }
 378          }
 379          return 1;
 380      }
 381  
 382      function _constructPattern($filter) {
 383          $this->_pattern = array();
 384          foreach ($filter as $item => $pattern) {
 385              $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i';    // allow regex characters
 386          }
 387      }
 388  }
 389  
 390  //Setup VIM: ex: et ts=4 :


Generated: Sun Feb 3 03:00:06 2013 Cross-referenced by PHPXref 0.7
WikiForumIRCBugsGitXRefTranslate