[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
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 :
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Feb 3 03:00:06 2013 | Cross-referenced by PHPXref 0.7 |