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