You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

805 lines
27 KiB

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