auth.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. <?php
  2. use dokuwiki\Utf8\Sort;
  3. use dokuwiki\Logger;
  4. /**
  5. * Active Directory authentication backend for DokuWiki
  6. *
  7. * This makes authentication with a Active Directory server much easier
  8. * than when using the normal LDAP backend by utilizing the adLDAP library
  9. *
  10. * Usage:
  11. * Set DokuWiki's local.protected.php auth setting to read
  12. *
  13. * $conf['authtype'] = 'authad';
  14. *
  15. * $conf['plugin']['authad']['account_suffix'] = '@my.domain.org';
  16. * $conf['plugin']['authad']['base_dn'] = 'DC=my,DC=domain,DC=org';
  17. * $conf['plugin']['authad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
  18. *
  19. * //optional:
  20. * $conf['plugin']['authad']['sso'] = 1;
  21. * $conf['plugin']['authad']['admin_username'] = 'root';
  22. * $conf['plugin']['authad']['admin_password'] = 'pass';
  23. * $conf['plugin']['authad']['real_primarygroup'] = 1;
  24. * $conf['plugin']['authad']['use_ssl'] = 1;
  25. * $conf['plugin']['authad']['use_tls'] = 1;
  26. * $conf['plugin']['authad']['debug'] = 1;
  27. * // warn user about expiring password this many days in advance:
  28. * $conf['plugin']['authad']['expirywarn'] = 5;
  29. *
  30. * // get additional information to the userinfo array
  31. * // add a list of comma separated ldap contact fields.
  32. * $conf['plugin']['authad']['additional'] = 'field1,field2';
  33. *
  34. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  35. * @author James Van Lommel <jamesvl@gmail.com>
  36. * @link http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
  37. * @author Andreas Gohr <andi@splitbrain.org>
  38. * @author Jan Schumann <js@schumann-it.com>
  39. */
  40. class auth_plugin_authad extends DokuWiki_Auth_Plugin
  41. {
  42. /**
  43. * @var array hold connection data for a specific AD domain
  44. */
  45. protected $opts = array();
  46. /**
  47. * @var array open connections for each AD domain, as adLDAP objects
  48. */
  49. protected $adldap = array();
  50. /**
  51. * @var bool message state
  52. */
  53. protected $msgshown = false;
  54. /**
  55. * @var array user listing cache
  56. */
  57. protected $users = array();
  58. /**
  59. * @var array filter patterns for listing users
  60. */
  61. protected $pattern = array();
  62. protected $grpsusers = array();
  63. /**
  64. * Constructor
  65. */
  66. public function __construct()
  67. {
  68. global $INPUT;
  69. parent::__construct();
  70. require_once(DOKU_PLUGIN.'authad/adLDAP/adLDAP.php');
  71. require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php');
  72. // we load the config early to modify it a bit here
  73. $this->loadConfig();
  74. // additional information fields
  75. if (isset($this->conf['additional'])) {
  76. $this->conf['additional'] = str_replace(' ', '', $this->conf['additional']);
  77. $this->conf['additional'] = explode(',', $this->conf['additional']);
  78. } else $this->conf['additional'] = array();
  79. // ldap extension is needed
  80. if (!function_exists('ldap_connect')) {
  81. if ($this->conf['debug'])
  82. msg("AD Auth: PHP LDAP extension not found.", -1);
  83. $this->success = false;
  84. return;
  85. }
  86. // Prepare SSO
  87. if (!empty($INPUT->server->str('REMOTE_USER'))) {
  88. // make sure the right encoding is used
  89. if ($this->getConf('sso_charset')) {
  90. $INPUT->server->set('REMOTE_USER',
  91. iconv($this->getConf('sso_charset'), 'UTF-8', $INPUT->server->str('REMOTE_USER')));
  92. } elseif (!\dokuwiki\Utf8\Clean::isUtf8($INPUT->server->str('REMOTE_USER'))) {
  93. $INPUT->server->set('REMOTE_USER', utf8_encode($INPUT->server->str('REMOTE_USER')));
  94. }
  95. // trust the incoming user
  96. if ($this->conf['sso']) {
  97. $INPUT->server->set('REMOTE_USER', $this->cleanUser($INPUT->server->str('REMOTE_USER')));
  98. // we need to simulate a login
  99. if (empty($_COOKIE[DOKU_COOKIE])) {
  100. $INPUT->set('u', $INPUT->server->str('REMOTE_USER'));
  101. $INPUT->set('p', 'sso_only');
  102. }
  103. }
  104. }
  105. // other can do's are changed in $this->_loadServerConfig() base on domain setup
  106. $this->cando['modName'] = (bool)$this->conf['update_name'];
  107. $this->cando['modMail'] = (bool)$this->conf['update_mail'];
  108. $this->cando['getUserCount'] = true;
  109. }
  110. /**
  111. * Load domain config on capability check
  112. *
  113. * @param string $cap
  114. * @return bool
  115. */
  116. public function canDo($cap)
  117. {
  118. global $INPUT;
  119. //capabilities depend on config, which may change depending on domain
  120. $domain = $this->getUserDomain($INPUT->server->str('REMOTE_USER'));
  121. $this->loadServerConfig($domain);
  122. return parent::canDo($cap);
  123. }
  124. /**
  125. * Check user+password [required auth function]
  126. *
  127. * Checks if the given user exists and the given
  128. * plaintext password is correct by trying to bind
  129. * to the LDAP server
  130. *
  131. * @author James Van Lommel <james@nosq.com>
  132. * @param string $user
  133. * @param string $pass
  134. * @return bool
  135. */
  136. public function checkPass($user, $pass)
  137. {
  138. global $INPUT;
  139. if ($INPUT->server->str('REMOTE_USER') == $user &&
  140. $this->conf['sso']
  141. ) return true;
  142. $adldap = $this->initAdLdap($this->getUserDomain($user));
  143. if (!$adldap) return false;
  144. try {
  145. return $adldap->authenticate($this->getUserName($user), $pass);
  146. } catch (adLDAPException $e) {
  147. // shouldn't really happen
  148. return false;
  149. }
  150. }
  151. /**
  152. * Return user info [required auth function]
  153. *
  154. * Returns info about the given user needs to contain
  155. * at least these fields:
  156. *
  157. * name string full name of the user
  158. * mail string email address of the user
  159. * grps array list of groups the user is in
  160. *
  161. * This AD specific function returns the following
  162. * addional fields:
  163. *
  164. * dn string distinguished name (DN)
  165. * uid string samaccountname
  166. * lastpwd int timestamp of the date when the password was set
  167. * expires true if the password expires
  168. * expiresin int seconds until the password expires
  169. * any fields specified in the 'additional' config option
  170. *
  171. * @author James Van Lommel <james@nosq.com>
  172. * @param string $user
  173. * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin
  174. * @return array
  175. */
  176. public function getUserData($user, $requireGroups = true)
  177. {
  178. global $conf;
  179. global $lang;
  180. global $ID;
  181. global $INPUT;
  182. $adldap = $this->initAdLdap($this->getUserDomain($user));
  183. if (!$adldap) return array();
  184. if ($user == '') return array();
  185. $fields = array('mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol');
  186. // add additional fields to read
  187. $fields = array_merge($fields, $this->conf['additional']);
  188. $fields = array_unique($fields);
  189. $fields = array_filter($fields);
  190. //get info for given user
  191. $result = $adldap->user()->info($this->getUserName($user), $fields);
  192. if ($result == false) {
  193. return array();
  194. }
  195. //general user info
  196. $info = array();
  197. $info['name'] = $result[0]['displayname'][0];
  198. $info['mail'] = $result[0]['mail'][0];
  199. $info['uid'] = $result[0]['samaccountname'][0];
  200. $info['dn'] = $result[0]['dn'];
  201. //last password set (Windows counts from January 1st 1601)
  202. $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10000000 - 11644473600;
  203. //will it expire?
  204. $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD
  205. // additional information
  206. foreach ($this->conf['additional'] as $field) {
  207. if (isset($result[0][strtolower($field)])) {
  208. $info[$field] = $result[0][strtolower($field)][0];
  209. }
  210. }
  211. // handle ActiveDirectory memberOf
  212. $info['grps'] = $adldap->user()->groups($this->getUserName($user), (bool) $this->opts['recursive_groups']);
  213. if (is_array($info['grps'])) {
  214. foreach ($info['grps'] as $ndx => $group) {
  215. $info['grps'][$ndx] = $this->cleanGroup($group);
  216. }
  217. }
  218. // always add the default group to the list of groups
  219. if (!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) {
  220. $info['grps'][] = $conf['defaultgroup'];
  221. }
  222. // add the user's domain to the groups
  223. $domain = $this->getUserDomain($user);
  224. if ($domain && !in_array("domain-$domain", (array) $info['grps'])) {
  225. $info['grps'][] = $this->cleanGroup("domain-$domain");
  226. }
  227. // check expiry time
  228. if ($info['expires'] && $this->conf['expirywarn']) {
  229. try {
  230. $expiry = $adldap->user()->passwordExpiry($user);
  231. if (is_array($expiry)) {
  232. $info['expiresat'] = $expiry['expiryts'];
  233. $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60));
  234. // if this is the current user, warn him (once per request only)
  235. if (($INPUT->server->str('REMOTE_USER') == $user) &&
  236. ($info['expiresin'] <= $this->conf['expirywarn']) &&
  237. !$this->msgshown
  238. ) {
  239. $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']);
  240. if ($this->canDo('modPass')) {
  241. $url = wl($ID, array('do'=> 'profile'));
  242. $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>';
  243. }
  244. msg($msg);
  245. $this->msgshown = true;
  246. }
  247. }
  248. } catch (adLDAPException $e) {
  249. // ignore. should usually not happen
  250. }
  251. }
  252. return $info;
  253. }
  254. /**
  255. * Make AD group names usable by DokuWiki.
  256. *
  257. * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
  258. *
  259. * @author James Van Lommel (jamesvl@gmail.com)
  260. * @param string $group
  261. * @return string
  262. */
  263. public function cleanGroup($group)
  264. {
  265. $group = str_replace('\\', '', $group);
  266. $group = str_replace('#', '', $group);
  267. $group = preg_replace('[\s]', '_', $group);
  268. $group = \dokuwiki\Utf8\PhpString::strtolower(trim($group));
  269. return $group;
  270. }
  271. /**
  272. * Sanitize user names
  273. *
  274. * Normalizes domain parts, does not modify the user name itself (unlike cleanGroup)
  275. *
  276. * @author Andreas Gohr <gohr@cosmocode.de>
  277. * @param string $user
  278. * @return string
  279. */
  280. public function cleanUser($user)
  281. {
  282. $domain = '';
  283. // get NTLM or Kerberos domain part
  284. list($dom, $user) = sexplode('\\', $user, 2, '');
  285. if (!$user) $user = $dom;
  286. if ($dom) $domain = $dom;
  287. list($user, $dom) = sexplode('@', $user, 2, '');
  288. if ($dom) $domain = $dom;
  289. // clean up both
  290. $domain = \dokuwiki\Utf8\PhpString::strtolower(trim($domain));
  291. $user = \dokuwiki\Utf8\PhpString::strtolower(trim($user));
  292. // is this a known, valid domain or do we work without account suffix? if not discard
  293. if ((!isset($this->conf[$domain]) || !is_array($this->conf[$domain])) &&
  294. $this->conf['account_suffix'] !== '') {
  295. $domain = '';
  296. }
  297. // reattach domain
  298. if ($domain) $user = "$user@$domain";
  299. return $user;
  300. }
  301. /**
  302. * Most values in LDAP are case-insensitive
  303. *
  304. * @return bool
  305. */
  306. public function isCaseSensitive()
  307. {
  308. return false;
  309. }
  310. /**
  311. * Create a Search-String useable by adLDAPUsers::all($includeDescription = false, $search = "*", $sorted = true)
  312. *
  313. * @param array $filter
  314. * @return string
  315. */
  316. protected function constructSearchString($filter)
  317. {
  318. if (!$filter) {
  319. return '*';
  320. }
  321. $adldapUtils = new adLDAPUtils($this->initAdLdap(null));
  322. $result = '*';
  323. if (isset($filter['name'])) {
  324. $result .= ')(displayname=*' . $adldapUtils->ldapSlashes($filter['name']) . '*';
  325. unset($filter['name']);
  326. }
  327. if (isset($filter['user'])) {
  328. $result .= ')(samAccountName=*' . $adldapUtils->ldapSlashes($filter['user']) . '*';
  329. unset($filter['user']);
  330. }
  331. if (isset($filter['mail'])) {
  332. $result .= ')(mail=*' . $adldapUtils->ldapSlashes($filter['mail']) . '*';
  333. unset($filter['mail']);
  334. }
  335. return $result;
  336. }
  337. /**
  338. * Return a count of the number of user which meet $filter criteria
  339. *
  340. * @param array $filter $filter array of field/pattern pairs, empty array for no filter
  341. * @return int number of users
  342. */
  343. public function getUserCount($filter = array())
  344. {
  345. $adldap = $this->initAdLdap(null);
  346. if (!$adldap) {
  347. Logger::debug("authad/auth.php getUserCount(): _adldap not set.");
  348. return -1;
  349. }
  350. if ($filter == array()) {
  351. $result = $adldap->user()->all();
  352. } else {
  353. $searchString = $this->constructSearchString($filter);
  354. $result = $adldap->user()->all(false, $searchString);
  355. if (isset($filter['grps'])) {
  356. $this->users = array_fill_keys($result, false);
  357. /** @var admin_plugin_usermanager $usermanager */
  358. $usermanager = plugin_load("admin", "usermanager", false);
  359. $usermanager->setLastdisabled(true);
  360. if (!isset($this->grpsusers[$this->filterToString($filter)])) {
  361. $this->fillGroupUserArray($filter, $usermanager->getStart() + 3*$usermanager->getPagesize());
  362. } elseif (count($this->grpsusers[$this->filterToString($filter)]) <
  363. $usermanager->getStart() + 3*$usermanager->getPagesize()
  364. ) {
  365. $this->fillGroupUserArray(
  366. $filter,
  367. $usermanager->getStart() +
  368. 3*$usermanager->getPagesize() -
  369. count($this->grpsusers[$this->filterToString($filter)])
  370. );
  371. }
  372. $result = $this->grpsusers[$this->filterToString($filter)];
  373. } else {
  374. /** @var admin_plugin_usermanager $usermanager */
  375. $usermanager = plugin_load("admin", "usermanager", false);
  376. $usermanager->setLastdisabled(false);
  377. }
  378. }
  379. if (!$result) {
  380. return 0;
  381. }
  382. return count($result);
  383. }
  384. /**
  385. *
  386. * create a unique string for each filter used with a group
  387. *
  388. * @param array $filter
  389. * @return string
  390. */
  391. protected function filterToString($filter)
  392. {
  393. $result = '';
  394. if (isset($filter['user'])) {
  395. $result .= 'user-' . $filter['user'];
  396. }
  397. if (isset($filter['name'])) {
  398. $result .= 'name-' . $filter['name'];
  399. }
  400. if (isset($filter['mail'])) {
  401. $result .= 'mail-' . $filter['mail'];
  402. }
  403. if (isset($filter['grps'])) {
  404. $result .= 'grps-' . $filter['grps'];
  405. }
  406. return $result;
  407. }
  408. /**
  409. * Create an array of $numberOfAdds users passing a certain $filter, including belonging
  410. * to a certain group and save them to a object-wide array. If the array
  411. * already exists try to add $numberOfAdds further users to it.
  412. *
  413. * @param array $filter
  414. * @param int $numberOfAdds additional number of users requested
  415. * @return int number of Users actually add to Array
  416. */
  417. protected function fillGroupUserArray($filter, $numberOfAdds)
  418. {
  419. if (isset($this->grpsusers[$this->filterToString($filter)])) {
  420. $actualstart = count($this->grpsusers[$this->filterToString($filter)]);
  421. } else {
  422. $this->grpsusers[$this->filterToString($filter)] = [];
  423. $actualstart = 0;
  424. }
  425. $i=0;
  426. $count = 0;
  427. $this->constructPattern($filter);
  428. foreach ($this->users as $user => &$info) {
  429. if ($i++ < $actualstart) {
  430. continue;
  431. }
  432. if ($info === false) {
  433. $info = $this->getUserData($user);
  434. }
  435. if ($this->filter($user, $info)) {
  436. $this->grpsusers[$this->filterToString($filter)][$user] = $info;
  437. if (($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break;
  438. }
  439. }
  440. return $count;
  441. }
  442. /**
  443. * Bulk retrieval of user data
  444. *
  445. * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
  446. *
  447. * @param int $start index of first user to be returned
  448. * @param int $limit max number of users to be returned
  449. * @param array $filter array of field/pattern pairs, null for no filter
  450. * @return array userinfo (refer getUserData for internal userinfo details)
  451. */
  452. public function retrieveUsers($start = 0, $limit = 0, $filter = array())
  453. {
  454. $adldap = $this->initAdLdap(null);
  455. if (!$adldap) return array();
  456. //if (!$this->users) {
  457. //get info for given user
  458. $result = $adldap->user()->all(false, $this->constructSearchString($filter));
  459. if (!$result) return array();
  460. $this->users = array_fill_keys($result, false);
  461. //}
  462. $i = 0;
  463. $count = 0;
  464. $result = array();
  465. if (!isset($filter['grps'])) {
  466. /** @var admin_plugin_usermanager $usermanager */
  467. $usermanager = plugin_load("admin", "usermanager", false);
  468. $usermanager->setLastdisabled(false);
  469. $this->constructPattern($filter);
  470. foreach ($this->users as $user => &$info) {
  471. if ($i++ < $start) {
  472. continue;
  473. }
  474. if ($info === false) {
  475. $info = $this->getUserData($user);
  476. }
  477. $result[$user] = $info;
  478. if (($limit > 0) && (++$count >= $limit)) break;
  479. }
  480. } else {
  481. /** @var admin_plugin_usermanager $usermanager */
  482. $usermanager = plugin_load("admin", "usermanager", false);
  483. $usermanager->setLastdisabled(true);
  484. if (!isset($this->grpsusers[$this->filterToString($filter)]) ||
  485. count($this->grpsusers[$this->filterToString($filter)]) < ($start+$limit)
  486. ) {
  487. if(!isset($this->grpsusers[$this->filterToString($filter)])) {
  488. $this->grpsusers[$this->filterToString($filter)] = [];
  489. }
  490. $this->fillGroupUserArray(
  491. $filter,
  492. $start+$limit - count($this->grpsusers[$this->filterToString($filter)]) +1
  493. );
  494. }
  495. if (!$this->grpsusers[$this->filterToString($filter)]) return array();
  496. foreach ($this->grpsusers[$this->filterToString($filter)] as $user => &$info) {
  497. if ($i++ < $start) {
  498. continue;
  499. }
  500. $result[$user] = $info;
  501. if (($limit > 0) && (++$count >= $limit)) break;
  502. }
  503. }
  504. return $result;
  505. }
  506. /**
  507. * Modify user data
  508. *
  509. * @param string $user nick of the user to be changed
  510. * @param array $changes array of field/value pairs to be changed
  511. * @return bool
  512. */
  513. public function modifyUser($user, $changes)
  514. {
  515. $return = true;
  516. $adldap = $this->initAdLdap($this->getUserDomain($user));
  517. if (!$adldap) {
  518. msg($this->getLang('connectfail'), -1);
  519. return false;
  520. }
  521. // password changing
  522. if (isset($changes['pass'])) {
  523. try {
  524. $return = $adldap->user()->password($this->getUserName($user), $changes['pass']);
  525. } catch (adLDAPException $e) {
  526. if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
  527. $return = false;
  528. }
  529. if (!$return) msg($this->getLang('passchangefail'), -1);
  530. }
  531. // changing user data
  532. $adchanges = array();
  533. if (isset($changes['name'])) {
  534. // get first and last name
  535. $parts = explode(' ', $changes['name']);
  536. $adchanges['surname'] = array_pop($parts);
  537. $adchanges['firstname'] = join(' ', $parts);
  538. $adchanges['display_name'] = $changes['name'];
  539. }
  540. if (isset($changes['mail'])) {
  541. $adchanges['email'] = $changes['mail'];
  542. }
  543. if (count($adchanges)) {
  544. try {
  545. $return = $return & $adldap->user()->modify($this->getUserName($user), $adchanges);
  546. } catch (adLDAPException $e) {
  547. if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
  548. $return = false;
  549. }
  550. if (!$return) msg($this->getLang('userchangefail'), -1);
  551. }
  552. return $return;
  553. }
  554. /**
  555. * Initialize the AdLDAP library and connect to the server
  556. *
  557. * When you pass null as domain, it will reuse any existing domain.
  558. * Eg. the one of the logged in user. It falls back to the default
  559. * domain if no current one is available.
  560. *
  561. * @param string|null $domain The AD domain to use
  562. * @return adLDAP|bool true if a connection was established
  563. */
  564. protected function initAdLdap($domain)
  565. {
  566. if (is_null($domain) && is_array($this->opts)) {
  567. $domain = $this->opts['domain'];
  568. }
  569. $this->opts = $this->loadServerConfig((string) $domain);
  570. if (isset($this->adldap[$domain])) return $this->adldap[$domain];
  571. // connect
  572. try {
  573. $this->adldap[$domain] = new adLDAP($this->opts);
  574. return $this->adldap[$domain];
  575. } catch (Exception $e) {
  576. if ($this->conf['debug']) {
  577. msg('AD Auth: '.$e->getMessage(), -1);
  578. }
  579. $this->success = false;
  580. $this->adldap[$domain] = null;
  581. }
  582. return false;
  583. }
  584. /**
  585. * Get the domain part from a user
  586. *
  587. * @param string $user
  588. * @return string
  589. */
  590. public function getUserDomain($user)
  591. {
  592. list(, $domain) = sexplode('@', $user, 2, '');
  593. return $domain;
  594. }
  595. /**
  596. * Get the user part from a user
  597. *
  598. * When an account suffix is set, we strip the domain part from the user
  599. *
  600. * @param string $user
  601. * @return string
  602. */
  603. public function getUserName($user)
  604. {
  605. if ($this->conf['account_suffix'] !== '') {
  606. list($user) = explode('@', $user, 2);
  607. }
  608. return $user;
  609. }
  610. /**
  611. * Fetch the configuration for the given AD domain
  612. *
  613. * @param string $domain current AD domain
  614. * @return array
  615. */
  616. protected function loadServerConfig($domain)
  617. {
  618. // prepare adLDAP standard configuration
  619. $opts = $this->conf;
  620. $opts['domain'] = $domain;
  621. // add possible domain specific configuration
  622. if ($domain && is_array($this->conf[$domain])) foreach ($this->conf[$domain] as $key => $val) {
  623. $opts[$key] = $val;
  624. }
  625. // handle multiple AD servers
  626. $opts['domain_controllers'] = explode(',', $opts['domain_controllers']);
  627. $opts['domain_controllers'] = array_map('trim', $opts['domain_controllers']);
  628. $opts['domain_controllers'] = array_filter($opts['domain_controllers']);
  629. // compatibility with old option name
  630. if (empty($opts['admin_username']) && !empty($opts['ad_username'])) {
  631. $opts['admin_username'] = $opts['ad_username'];
  632. }
  633. if (empty($opts['admin_password']) && !empty($opts['ad_password'])) {
  634. $opts['admin_password'] = $opts['ad_password'];
  635. }
  636. $opts['admin_password'] = conf_decodeString($opts['admin_password']); // deobfuscate
  637. // we can change the password if SSL is set
  638. if ($opts['update_pass'] && ($opts['use_ssl'] || $opts['use_tls'])) {
  639. $this->cando['modPass'] = true;
  640. } else {
  641. $this->cando['modPass'] = false;
  642. }
  643. // adLDAP expects empty user/pass as NULL, we're less strict FS#2781
  644. if (empty($opts['admin_username'])) $opts['admin_username'] = null;
  645. if (empty($opts['admin_password'])) $opts['admin_password'] = null;
  646. // user listing needs admin priviledges
  647. if (!empty($opts['admin_username']) && !empty($opts['admin_password'])) {
  648. $this->cando['getUsers'] = true;
  649. } else {
  650. $this->cando['getUsers'] = false;
  651. }
  652. return $opts;
  653. }
  654. /**
  655. * Returns a list of configured domains
  656. *
  657. * The default domain has an empty string as key
  658. *
  659. * @return array associative array(key => domain)
  660. */
  661. public function getConfiguredDomains()
  662. {
  663. $domains = array();
  664. if (empty($this->conf['account_suffix'])) return $domains; // not configured yet
  665. // add default domain, using the name from account suffix
  666. $domains[''] = ltrim($this->conf['account_suffix'], '@');
  667. // find additional domains
  668. foreach ($this->conf as $key => $val) {
  669. if (is_array($val) && isset($val['account_suffix'])) {
  670. $domains[$key] = ltrim($val['account_suffix'], '@');
  671. }
  672. }
  673. Sort::ksort($domains);
  674. return $domains;
  675. }
  676. /**
  677. * Check provided user and userinfo for matching patterns
  678. *
  679. * The patterns are set up with $this->_constructPattern()
  680. *
  681. * @author Chris Smith <chris@jalakai.co.uk>
  682. *
  683. * @param string $user
  684. * @param array $info
  685. * @return bool
  686. */
  687. protected function filter($user, $info)
  688. {
  689. foreach ($this->pattern as $item => $pattern) {
  690. if ($item == 'user') {
  691. if (!preg_match($pattern, $user)) return false;
  692. } elseif ($item == 'grps') {
  693. if (!count(preg_grep($pattern, $info['grps']))) return false;
  694. } else {
  695. if (!preg_match($pattern, $info[$item])) return false;
  696. }
  697. }
  698. return true;
  699. }
  700. /**
  701. * Create a pattern for $this->_filter()
  702. *
  703. * @author Chris Smith <chris@jalakai.co.uk>
  704. *
  705. * @param array $filter
  706. */
  707. protected function constructPattern($filter)
  708. {
  709. $this->pattern = array();
  710. foreach ($filter as $item => $pattern) {
  711. $this->pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
  712. }
  713. }
  714. }