11078ec26SAndreas Gohr<?php 21078ec26SAndreas Gohr 31078ec26SAndreas Gohrnamespace dokuwiki\plugin\pureldap\classes; 41078ec26SAndreas Gohr 51078ec26SAndreas Gohruse FreeDSx\Ldap\Entry\Entries; 61078ec26SAndreas Gohruse FreeDSx\Ldap\Entry\Entry; 71078ec26SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException; 85a3b9122SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException; 91078ec26SAndreas Gohruse FreeDSx\Ldap\Operations; 101078ec26SAndreas Gohruse FreeDSx\Ldap\Search\Filters; 111078ec26SAndreas Gohr 121078ec26SAndreas Gohrclass ADClient extends Client 131078ec26SAndreas Gohr{ 141078ec26SAndreas Gohr 151078ec26SAndreas Gohr /** @inheritDoc */ 161078ec26SAndreas Gohr public function getUser($username, $fetchgroups = true) 171078ec26SAndreas Gohr { 181078ec26SAndreas Gohr if (!$this->autoAuth()) return null; 191078ec26SAndreas Gohr 201078ec26SAndreas Gohr $filter = Filters::and( 211078ec26SAndreas Gohr Filters::equal('objectClass', 'user'), 221078ec26SAndreas Gohr Filters::equal('userPrincipalName', $username) 231078ec26SAndreas Gohr ); 24*b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 251078ec26SAndreas Gohr 261078ec26SAndreas Gohr try { 271078ec26SAndreas Gohr /** @var Entries $entries */ 281078ec26SAndreas Gohr $entries = $this->ldap->search(Operations::search($filter)); 291078ec26SAndreas Gohr } catch (OperationException $e) { 30*b21740b4SAndreas Gohr $this->fatal($e); 311078ec26SAndreas Gohr return null; 321078ec26SAndreas Gohr } 331078ec26SAndreas Gohr if ($entries->count() !== 1) return null; 341078ec26SAndreas Gohr $entry = $entries->first(); 35*b21740b4SAndreas Gohr return $this->entry2User($entry); 36*b21740b4SAndreas Gohr } 371078ec26SAndreas Gohr 38*b21740b4SAndreas Gohr /** @inheritDoc */ 39*b21740b4SAndreas Gohr public function getGroups($match = null, $filtermethod = 'equal') 40*b21740b4SAndreas Gohr { 41*b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 42*b21740b4SAndreas Gohr 43*b21740b4SAndreas Gohr $filter = Filters::and( 44*b21740b4SAndreas Gohr Filters::equal('objectClass', 'group') 45*b21740b4SAndreas Gohr ); 46*b21740b4SAndreas Gohr if ($match !== null) { 47*b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('cn', $match)); 48*b21740b4SAndreas Gohr } 49*b21740b4SAndreas Gohr 50*b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 51*b21740b4SAndreas Gohr $search = Operations::search($filter, 'cn'); 52*b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 53*b21740b4SAndreas Gohr 54*b21740b4SAndreas Gohr $groups = []; 55*b21740b4SAndreas Gohr while ($paging->hasEntries()) { 56*b21740b4SAndreas Gohr try { 57*b21740b4SAndreas Gohr $entries = $paging->getEntries(); 58*b21740b4SAndreas Gohr } catch (ProtocolException $e) { 59*b21740b4SAndreas Gohr $this->fatal($e); 60*b21740b4SAndreas Gohr return $groups; // we return what we got so far 61*b21740b4SAndreas Gohr } 62*b21740b4SAndreas Gohr 63*b21740b4SAndreas Gohr foreach ($entries as $entry) { 64*b21740b4SAndreas Gohr /** @var Entry $entry */ 65*b21740b4SAndreas Gohr $groups[$entry->getDn()->toString()] = $this->attr2str($entry->get('cn')); 66*b21740b4SAndreas Gohr } 67*b21740b4SAndreas Gohr } 68*b21740b4SAndreas Gohr 69*b21740b4SAndreas Gohr return $groups; 70*b21740b4SAndreas Gohr } 71*b21740b4SAndreas Gohr 72*b21740b4SAndreas Gohr /** 73*b21740b4SAndreas Gohr * Fetch users matching the given filters 74*b21740b4SAndreas Gohr * 75*b21740b4SAndreas Gohr * @param array $match 76*b21740b4SAndreas Gohr * @param string $filtermethod The method to use for filtering 77*b21740b4SAndreas Gohr * @return array 78*b21740b4SAndreas Gohr */ 79*b21740b4SAndreas Gohr public function getFilteredUsers($match, $filtermethod = 'equal') 80*b21740b4SAndreas Gohr { 81*b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 82*b21740b4SAndreas Gohr 83*b21740b4SAndreas Gohr $filter = Filters::and(Filters::equal('objectClass', 'user')); 84*b21740b4SAndreas Gohr if (isset($match['user'])) { 85*b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('userPrincipalName', $match['user'])); 86*b21740b4SAndreas Gohr } 87*b21740b4SAndreas Gohr if (isset($match['name'])) { 88*b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('displayName', $match['name'])); 89*b21740b4SAndreas Gohr } 90*b21740b4SAndreas Gohr if (isset($match['mail'])) { 91*b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('mail', $match['mail'])); 92*b21740b4SAndreas Gohr } 93*b21740b4SAndreas Gohr if (isset($match['grps'])) { 94*b21740b4SAndreas Gohr // memberOf can not be checked with a substring match, so we need to get the right groups first 95*b21740b4SAndreas Gohr $groups = $this->getGroups($match['grps'], $filtermethod); 96*b21740b4SAndreas Gohr $or = Filters::or(); 97*b21740b4SAndreas Gohr foreach ($groups as $dn => $group) { 98*b21740b4SAndreas Gohr $or->add(Filters::equal('memberOf', $dn)); 99*b21740b4SAndreas Gohr } 100*b21740b4SAndreas Gohr $filter->add($or); 101*b21740b4SAndreas Gohr } 102*b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 103*b21740b4SAndreas Gohr $search = Operations::search($filter); 104*b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 105*b21740b4SAndreas Gohr 106*b21740b4SAndreas Gohr $users = []; 107*b21740b4SAndreas Gohr while ($paging->hasEntries()) { 108*b21740b4SAndreas Gohr try { 109*b21740b4SAndreas Gohr $entries = $paging->getEntries(); 110*b21740b4SAndreas Gohr } catch (ProtocolException $e) { 111*b21740b4SAndreas Gohr $this->fatal($e); 112*b21740b4SAndreas Gohr return $users; // we return what we got so far 113*b21740b4SAndreas Gohr } 114*b21740b4SAndreas Gohr 115*b21740b4SAndreas Gohr foreach ($entries as $entry) { 116*b21740b4SAndreas Gohr $users[] = $this->entry2User($entry); 117*b21740b4SAndreas Gohr } 118*b21740b4SAndreas Gohr } 119*b21740b4SAndreas Gohr 120*b21740b4SAndreas Gohr return $users; 121*b21740b4SAndreas Gohr } 122*b21740b4SAndreas Gohr 123*b21740b4SAndreas Gohr /** 124*b21740b4SAndreas Gohr * Transform an LDAP entry to a user info array 125*b21740b4SAndreas Gohr * 126*b21740b4SAndreas Gohr * @param Entry $entry 127*b21740b4SAndreas Gohr * @return array 128*b21740b4SAndreas Gohr */ 129*b21740b4SAndreas Gohr protected function entry2User(Entry $entry) 130*b21740b4SAndreas Gohr { 1311078ec26SAndreas Gohr return [ 132*b21740b4SAndreas Gohr 'user' => $this->attr2str($entry->get('UserPrincipalName')), 1331078ec26SAndreas Gohr 'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')), 1341078ec26SAndreas Gohr 'mail' => $this->attr2str($entry->get('mail')), 1351078ec26SAndreas Gohr 'dn' => $entry->getDn()->toString(), 1361078ec26SAndreas Gohr 'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive 1371078ec26SAndreas Gohr ]; 1381078ec26SAndreas Gohr } 1391078ec26SAndreas Gohr 1401078ec26SAndreas Gohr /** 1411078ec26SAndreas Gohr * Get the list of groups the given user is member of 1421078ec26SAndreas Gohr * 1431078ec26SAndreas Gohr * This method currently does no LDAP queries and thus is inexpensive. 1441078ec26SAndreas Gohr * 1451078ec26SAndreas Gohr * @param Entry $userentry 1461078ec26SAndreas Gohr * @return array 1471078ec26SAndreas Gohr * @todo implement nested group memberships 1481078ec26SAndreas Gohr */ 1491078ec26SAndreas Gohr protected function getUserGroups(Entry $userentry) 1501078ec26SAndreas Gohr { 1511078ec26SAndreas Gohr $groups = [$this->config['defaultgroup']]; // always add default 1521078ec26SAndreas Gohr 1531078ec26SAndreas Gohr // we simply take the first CN= part of the group DN and return it as the group name 1541078ec26SAndreas Gohr // this should be correct for ActiveDirectory and saves us additional LDAP queries 1551078ec26SAndreas Gohr if ($userentry->has('memberOf')) { 156*b21740b4SAndreas Gohr foreach ($userentry->get('memberOf')->getValues() as $dn) { 157*b21740b4SAndreas Gohr list($cn) = explode(',', $dn, 2); 1581078ec26SAndreas Gohr $groups[] = substr($cn, 3); 1591078ec26SAndreas Gohr } 1601078ec26SAndreas Gohr } 1611078ec26SAndreas Gohr 1621078ec26SAndreas Gohr // resolving the primary group in AD is complicated but basically never needed 1631078ec26SAndreas Gohr // http://support.microsoft.com/?kbid=321360 1641078ec26SAndreas Gohr $gid = $userentry->get('primaryGroupID')->firstValue(); 1651078ec26SAndreas Gohr if ($gid == 513) { 1661078ec26SAndreas Gohr $groups[] = 'Domain Users'; 1671078ec26SAndreas Gohr } 1681078ec26SAndreas Gohr 1691078ec26SAndreas Gohr return $groups; 1701078ec26SAndreas Gohr } 1711078ec26SAndreas Gohr} 172