[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> auth.php (source)

   1  <?php
   2  /**
   3   * Authentication library
   4   *
   5   * Including this file will automatically try to login
   6   * a user by calling auth_login()
   7   *
   8   * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
   9   * @author     Andreas Gohr <andi@splitbrain.org>
  10   */
  11  
  12    if(!defined('DOKU_INC')) define('DOKU_INC',fullpath(dirname(__FILE__).'/../').'/');
  13    require_once (DOKU_INC.'inc/common.php');
  14    require_once (DOKU_INC.'inc/io.php');
  15  
  16    // some ACL level defines
  17    define('AUTH_NONE',0);
  18    define('AUTH_READ',1);
  19    define('AUTH_EDIT',2);
  20    define('AUTH_CREATE',4);
  21    define('AUTH_UPLOAD',8);
  22    define('AUTH_DELETE',16);
  23    define('AUTH_ADMIN',255);
  24  
  25    global $conf;
  26  
  27    if($conf['useacl']){
  28      require_once (DOKU_INC.'inc/blowfish.php');
  29      require_once (DOKU_INC.'inc/mail.php');
  30  
  31      global $auth;
  32  
  33      // load the the backend auth functions and instantiate the auth object
  34      if (@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) {
  35        require_once (DOKU_INC.'inc/auth/basic.class.php');
  36        require_once(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php');
  37  
  38        $auth_class = "auth_".$conf['authtype'];
  39        if (class_exists($auth_class)) {
  40          $auth = new $auth_class();
  41          if ($auth->success == false) {
  42            // degrade to unauthenticated user
  43            unset($auth);
  44            auth_logoff();
  45            msg($lang['authtempfail'], -1);
  46          }
  47        } else {
  48          nice_die($lang['authmodfailed']);
  49        }
  50      } else {
  51        nice_die($lang['authmodfailed']);
  52      }
  53    }
  54  
  55    // do the login either by cookie or provided credentials
  56    if($conf['useacl']){
  57      if($auth){
  58        if (!isset($_REQUEST['u'])) $_REQUEST['u'] = '';
  59        if (!isset($_REQUEST['p'])) $_REQUEST['p'] = '';
  60        if (!isset($_REQUEST['r'])) $_REQUEST['r'] = '';
  61        $_REQUEST['http_credentials'] = false;
  62        if (!$conf['rememberme']) $_REQUEST['r'] = false;
  63  
  64        // if no credentials were given try to use HTTP auth (for SSO)
  65        if(empty($_REQUEST['u']) && empty($_COOKIE[DOKU_COOKIE]) && !empty($_SERVER['PHP_AUTH_USER'])){
  66          $_REQUEST['u'] = $_SERVER['PHP_AUTH_USER'];
  67          $_REQUEST['p'] = $_SERVER['PHP_AUTH_PW'];
  68          $_REQUEST['http_credentials'] = true;
  69        }
  70  
  71        if($_REQUEST['authtok']){
  72          // when an authentication token is given, trust the session
  73          auth_validateToken($_REQUEST['authtok']);
  74        }elseif(!is_null($auth) && $auth->canDo('external')){
  75          // external trust mechanism in place
  76          $auth->trustExternal($_REQUEST['u'],$_REQUEST['p'],$_REQUEST['r']);
  77        }else{
  78          auth_login($_REQUEST['u'],$_REQUEST['p'],$_REQUEST['r'],$_REQUEST['http_credentials']);
  79        }
  80      }
  81  
  82      //load ACL into a global array
  83      global $AUTH_ACL;
  84      if(is_readable(DOKU_CONF.'acl.auth.php')){
  85        $AUTH_ACL = file(DOKU_CONF.'acl.auth.php');
  86        if(isset($_SERVER['REMOTE_USER'])){
  87          $AUTH_ACL = str_replace('@USER@',$_SERVER['REMOTE_USER'],$AUTH_ACL);
  88        }
  89      }else{
  90        $AUTH_ACL = array();
  91      }
  92    }
  93  
  94  /**
  95   * This tries to login the user based on the sent auth credentials
  96   *
  97   * The authentication works like this: if a username was given
  98   * a new login is assumed and user/password are checked. If they
  99   * are correct the password is encrypted with blowfish and stored
 100   * together with the username in a cookie - the same info is stored
 101   * in the session, too. Additonally a browserID is stored in the
 102   * session.
 103   *
 104   * If no username was given the cookie is checked: if the username,
 105   * crypted password and browserID match between session and cookie
 106   * no further testing is done and the user is accepted
 107   *
 108   * If a cookie was found but no session info was availabe the
 109   * blowfish encrypted password from the cookie is decrypted and
 110   * together with username rechecked by calling this function again.
 111   *
 112   * On a successful login $_SERVER[REMOTE_USER] and $USERINFO
 113   * are set.
 114   *
 115   * @author  Andreas Gohr <andi@splitbrain.org>
 116   *
 117   * @param   string  $user    Username
 118   * @param   string  $pass    Cleartext Password
 119   * @param   bool    $sticky  Cookie should not expire
 120   * @param   bool    $silent  Don't show error on bad auth
 121   * @return  bool             true on successful auth
 122  */
 123  function auth_login($user,$pass,$sticky=false,$silent=false){
 124    global $USERINFO;
 125    global $conf;
 126    global $lang;
 127    global $auth;
 128    $sticky ? $sticky = true : $sticky = false; //sanity check
 129  
 130    if(!empty($user)){
 131      //usual login
 132      if ($auth->checkPass($user,$pass)){
 133        // make logininfo globally available
 134        $_SERVER['REMOTE_USER'] = $user;
 135        auth_setCookie($user,PMA_blowfish_encrypt($pass,auth_cookiesalt()),$sticky);
 136        return true;
 137      }else{
 138        //invalid credentials - log off
 139        if(!$silent) msg($lang['badlogin'],-1);
 140        auth_logoff();
 141        return false;
 142      }
 143    }else{
 144      // read cookie information
 145      $cookie = base64_decode($_COOKIE[DOKU_COOKIE]);
 146      list($user,$sticky,$pass) = split('\|',$cookie,3);
 147      // get session info
 148      $session = $_SESSION[DOKU_COOKIE]['auth'];
 149      if($user && $pass){
 150        // we got a cookie - see if we can trust it
 151        if(isset($session) &&
 152          $auth->useSessionCache($user) &&
 153          ($session['time'] >= time()-$conf['auth_security_timeout']) &&
 154          ($session['user'] == $user) &&
 155          ($session['pass'] == $pass) &&  //still crypted
 156          ($session['buid'] == auth_browseruid()) ){
 157          // he has session, cookie and browser right - let him in
 158          $_SERVER['REMOTE_USER'] = $user;
 159          $USERINFO = $session['info']; //FIXME move all references to session
 160          return true;
 161        }
 162        // no we don't trust it yet - recheck pass but silent
 163        $pass = PMA_blowfish_decrypt($pass,auth_cookiesalt());
 164        return auth_login($user,$pass,$sticky,true);
 165      }
 166    }
 167    //just to be sure
 168    auth_logoff();
 169    return false;
 170  }
 171  
 172  /**
 173   * Checks if a given authentication token was stored in the session
 174   *
 175   * Will setup authentication data using data from the session if the
 176   * token is correct. Will exit with a 401 Status if not.
 177   *
 178   * @author Andreas Gohr <andi@splitbrain.org>
 179   * @param  string $token The authentication token
 180   * @return boolean true (or will exit on failure)
 181   */
 182  function auth_validateToken($token){
 183      if(!$token || $token != $_SESSION[DOKU_COOKIE]['auth']['token']){
 184          // bad token
 185          header("HTTP/1.0 401 Unauthorized");
 186          print 'Invalid auth token - maybe the session timed out';
 187          unset($_SESSION[DOKU_COOKIE]['auth']['token']); // no second chance
 188          exit;
 189      }
 190      // still here? trust the session data
 191      global $USERINFO;
 192      $_SERVER['REMOTE_USER'] = $_SESSION[DOKU_COOKIE]['auth']['user'];
 193      $USERINFO = $_SESSION[DOKU_COOKIE]['auth']['info'];
 194      return true;
 195  }
 196  
 197  /**
 198   * Create an auth token and store it in the session
 199   *
 200   * NOTE: this is completely unrelated to the getSecurityToken() function
 201   *
 202   * @author Andreas Gohr <andi@splitbrain.org>
 203   * @return string The auth token
 204   */
 205  function auth_createToken(){
 206      $token = md5(mt_rand());
 207      @session_start(); // reopen the session if needed
 208      $_SESSION[DOKU_COOKIE]['auth']['token'] = $token;
 209      session_write_close();
 210      return $token;
 211  }
 212  
 213  /**
 214   * Builds a pseudo UID from browser and IP data
 215   *
 216   * This is neither unique nor unfakable - still it adds some
 217   * security. Using the first part of the IP makes sure
 218   * proxy farms like AOLs are stil okay.
 219   *
 220   * @author  Andreas Gohr <andi@splitbrain.org>
 221   *
 222   * @return  string  a MD5 sum of various browser headers
 223   */
 224  function auth_browseruid(){
 225    $uid  = '';
 226    $uid .= $_SERVER['HTTP_USER_AGENT'];
 227    $uid .= $_SERVER['HTTP_ACCEPT_ENCODING'];
 228    $uid .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
 229    $uid .= $_SERVER['HTTP_ACCEPT_CHARSET'];
 230    $uid .= substr($_SERVER['REMOTE_ADDR'],0,strpos($_SERVER['REMOTE_ADDR'],'.'));
 231    return md5($uid);
 232  }
 233  
 234  /**
 235   * Creates a random key to encrypt the password in cookies
 236   *
 237   * This function tries to read the password for encrypting
 238   * cookies from $conf['metadir'].'/_htcookiesalt'
 239   * if no such file is found a random key is created and
 240   * and stored in this file.
 241   *
 242   * @author  Andreas Gohr <andi@splitbrain.org>
 243   *
 244   * @return  string
 245   */
 246  function auth_cookiesalt(){
 247    global $conf;
 248    $file = $conf['metadir'].'/_htcookiesalt';
 249    $salt = io_readFile($file);
 250    if(empty($salt)){
 251      $salt = uniqid(rand(),true);
 252      io_saveFile($file,$salt);
 253    }
 254    return $salt;
 255  }
 256  
 257  /**
 258   * This clears all authenticationdata and thus log the user
 259   * off
 260   *
 261   * @author  Andreas Gohr <andi@splitbrain.org>
 262   */
 263  function auth_logoff(){
 264    global $conf;
 265    global $USERINFO;
 266    global $INFO, $ID;
 267    global $auth;
 268  
 269    // reopen session
 270    @session_start();
 271  
 272    if(isset($_SESSION[DOKU_COOKIE]['auth']['user']))
 273      unset($_SESSION[DOKU_COOKIE]['auth']['user']);
 274    if(isset($_SESSION[DOKU_COOKIE]['auth']['pass']))
 275      unset($_SESSION[DOKU_COOKIE]['auth']['pass']);
 276    if(isset($_SESSION[DOKU_COOKIE]['auth']['info']))
 277      unset($_SESSION[DOKU_COOKIE]['auth']['info']);
 278    if(isset($_SESSION[DOKU_COOKIE]['bc']))
 279      unset($_SESSION[DOKU_COOKIE]['bc']);
 280    if(isset($_SERVER['REMOTE_USER']))
 281      unset($_SERVER['REMOTE_USER']);
 282    $USERINFO=null; //FIXME
 283  
 284    if (version_compare(PHP_VERSION, '5.2.0', '>')) {
 285      setcookie(DOKU_COOKIE,'',time()-600000,DOKU_REL,($conf['securecookie'] && is_ssl()),true);
 286    }else{
 287      setcookie(DOKU_COOKIE,'',time()-600000,DOKU_REL,($conf['securecookie'] && is_ssl()));
 288    }
 289  
 290    if($auth && $auth->canDo('logoff')){
 291      $auth->logOff();
 292    }
 293  
 294    // close session again
 295    session_write_close();
 296  }
 297  
 298  /**
 299   * Check if a user is a manager
 300   *
 301   * Should usually be called without any parameters to check the current
 302   * user.
 303   *
 304   * The info is available through $INFO['ismanager'], too
 305   *
 306   * @author Andreas Gohr <andi@splitbrain.org>
 307   * @see    auth_isadmin
 308   * @param  string user      - Username
 309   * @param  array  groups    - List of groups the user is in
 310   * @param  bool   adminonly - when true checks if user is admin
 311   */
 312  function auth_ismanager($user=null,$groups=null,$adminonly=false){
 313    global $conf;
 314    global $USERINFO;
 315  
 316    if(!$conf['useacl']) return false;
 317    if(is_null($user))   $user   = $_SERVER['REMOTE_USER'];
 318    if(is_null($groups)) $groups = (array) $USERINFO['grps'];
 319    $user   = auth_nameencode($user);
 320  
 321    // check username against superuser and manager
 322    $superusers = explode(',', $conf['superuser']);
 323    $superusers = array_unique($superusers);
 324    $superusers = array_map('trim', $superusers);
 325    // prepare an array containing only true values for array_map call
 326    $alltrue = array_fill(0, count($superusers), true);
 327    $superusers = array_map('auth_nameencode', $superusers, $alltrue);
 328    if(in_array($user, $superusers)) return true;
 329  
 330    if(!$adminonly){
 331      $managers = explode(',', $conf['manager']);
 332      $managers = array_unique($managers);
 333      $managers = array_map('trim', $managers);
 334      // prepare an array containing only true values for array_map call
 335      $alltrue = array_fill(0, count($managers), true);
 336      $managers = array_map('auth_nameencode', $managers, $alltrue);
 337      if(in_array($user, $managers)) return true;
 338    }
 339  
 340    // check user's groups against superuser and manager
 341    if (!empty($groups)) {
 342  
 343      //prepend groups with @ and nameencode
 344      $cnt = count($groups);
 345      for($i=0; $i<$cnt; $i++){
 346        $groups[$i] = '@'.auth_nameencode($groups[$i]);
 347      }
 348  
 349      // check groups against superuser and manager
 350      foreach($superusers as $supu)
 351        if(in_array($supu, $groups)) return true;
 352      if(!$adminonly){
 353        foreach($managers as $mana)
 354          if(in_array($mana, $groups)) return true;
 355      }
 356    }
 357  
 358    return false;
 359  }
 360  
 361  /**
 362   * Check if a user is admin
 363   *
 364   * Alias to auth_ismanager with adminonly=true
 365   *
 366   * The info is available through $INFO['isadmin'], too
 367   *
 368   * @author Andreas Gohr <andi@splitbrain.org>
 369   * @see auth_ismanager
 370   */
 371  function auth_isadmin($user=null,$groups=null){
 372    return auth_ismanager($user,$groups,true);
 373  }
 374  
 375  /**
 376   * Convinience function for auth_aclcheck()
 377   *
 378   * This checks the permissions for the current user
 379   *
 380   * @author  Andreas Gohr <andi@splitbrain.org>
 381   *
 382   * @param  string  $id  page ID
 383   * @return int          permission level
 384   */
 385  function auth_quickaclcheck($id){
 386    global $conf;
 387    global $USERINFO;
 388    # if no ACL is used always return upload rights
 389    if(!$conf['useacl']) return AUTH_UPLOAD;
 390    return auth_aclcheck($id,$_SERVER['REMOTE_USER'],$USERINFO['grps']);
 391  }
 392  
 393  /**
 394   * Returns the maximum rights a user has for
 395   * the given ID or its namespace
 396   *
 397   * @author  Andreas Gohr <andi@splitbrain.org>
 398   *
 399   * @param  string  $id     page ID
 400   * @param  string  $user   Username
 401   * @param  array   $groups Array of groups the user is in
 402   * @return int             permission level
 403   */
 404  function auth_aclcheck($id,$user,$groups){
 405    global $conf;
 406    global $AUTH_ACL;
 407  
 408    // if no ACL is used always return upload rights
 409    if(!$conf['useacl']) return AUTH_UPLOAD;
 410  
 411    //make sure groups is an array
 412    if(!is_array($groups)) $groups = array();
 413  
 414    //if user is superuser or in superusergroup return 255 (acl_admin)
 415    if(auth_isadmin($user,$groups)) { return AUTH_ADMIN; }
 416  
 417    $user = auth_nameencode($user);
 418  
 419    //prepend groups with @ and nameencode
 420    $cnt = count($groups);
 421    for($i=0; $i<$cnt; $i++){
 422      $groups[$i] = '@'.auth_nameencode($groups[$i]);
 423    }
 424  
 425    $ns    = getNS($id);
 426    $perm  = -1;
 427  
 428    if($user || count($groups)){
 429      //add ALL group
 430      $groups[] = '@ALL';
 431      //add User
 432      if($user) $groups[] = $user;
 433      //build regexp
 434      $regexp   = join('|',$groups);
 435    }else{
 436      $regexp = '@ALL';
 437    }
 438  
 439    //check exact match first
 440    $matches = preg_grep('/^'.preg_quote($id,'/').'\s+('.$regexp.')\s+/',$AUTH_ACL);
 441    if(count($matches)){
 442      foreach($matches as $match){
 443        $match = preg_replace('/#.*$/','',$match); //ignore comments
 444        $acl   = preg_split('/\s+/',$match);
 445        if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
 446        if($acl[2] > $perm){
 447          $perm = $acl[2];
 448        }
 449      }
 450      if($perm > -1){
 451        //we had a match - return it
 452        return $perm;
 453      }
 454    }
 455  
 456    //still here? do the namespace checks
 457    if($ns){
 458      $path = $ns.':\*';
 459    }else{
 460      $path = '\*'; //root document
 461    }
 462  
 463    do{
 464      $matches = preg_grep('/^'.$path.'\s+('.$regexp.')\s+/',$AUTH_ACL);
 465      if(count($matches)){
 466        foreach($matches as $match){
 467          $match = preg_replace('/#.*$/','',$match); //ignore comments
 468          $acl   = preg_split('/\s+/',$match);
 469          if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
 470          if($acl[2] > $perm){
 471            $perm = $acl[2];
 472          }
 473        }
 474        //we had a match - return it
 475        return $perm;
 476      }
 477  
 478      //get next higher namespace
 479      $ns   = getNS($ns);
 480  
 481      if($path != '\*'){
 482        $path = $ns.':\*';
 483        if($path == ':\*') $path = '\*';
 484      }else{
 485        //we did this already
 486        //looks like there is something wrong with the ACL
 487        //break here
 488        msg('No ACL setup yet! Denying access to everyone.');
 489        return AUTH_NONE;
 490      }
 491    }while(1); //this should never loop endless
 492  
 493    //still here? return no permissions
 494    return AUTH_NONE;
 495  }
 496  
 497  /**
 498   * Encode ASCII special chars
 499   *
 500   * Some auth backends allow special chars in their user and groupnames
 501   * The special chars are encoded with this function. Only ASCII chars
 502   * are encoded UTF-8 multibyte are left as is (different from usual
 503   * urlencoding!).
 504   *
 505   * Decoding can be done with rawurldecode
 506   *
 507   * @author Andreas Gohr <gohr@cosmocode.de>
 508   * @see rawurldecode()
 509   */
 510  function auth_nameencode($name,$skip_group=false){
 511    global $cache_authname;
 512    $cache =& $cache_authname;
 513    $name  = (string) $name;
 514  
 515    if (!isset($cache[$name][$skip_group])) {
 516      if($skip_group && $name{0} =='@'){
 517        $cache[$name][$skip_group] = '@'.preg_replace('/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
 518                                                      "'%'.dechex(ord(substr('\\1',-1)))",substr($name,1));
 519      }else{
 520        $cache[$name][$skip_group] = preg_replace('/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
 521                                                  "'%'.dechex(ord(substr('\\1',-1)))",$name);
 522      }
 523    }
 524  
 525    return $cache[$name][$skip_group];
 526  }
 527  
 528  /**
 529   * Create a pronouncable password
 530   *
 531   * @author  Andreas Gohr <andi@splitbrain.org>
 532   * @link    http://www.phpbuilder.com/annotate/message.php3?id=1014451
 533   *
 534   * @return string  pronouncable password
 535   */
 536  function auth_pwgen(){
 537    $pw = '';
 538    $c  = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
 539    $v  = 'aeiou';              //vowels
 540    $a  = $c.$v;                //both
 541  
 542    //use two syllables...
 543    for($i=0;$i < 2; $i++){
 544      $pw .= $c[rand(0, strlen($c)-1)];
 545      $pw .= $v[rand(0, strlen(