1<?php 2 3namespace dokuwiki\plugin\pureldap\classes; 4 5use dokuwiki\Utf8\PhpString; 6use FreeDSx\Ldap\Entry\Entries; 7use FreeDSx\Ldap\Entry\Entry; 8use FreeDSx\Ldap\Exception\OperationException; 9use FreeDSx\Ldap\Exception\ProtocolException; 10use FreeDSx\Ldap\Operations; 11use FreeDSx\Ldap\Search\Filters; 12 13class ADClient extends Client 14{ 15 16 /** @inheritDoc */ 17 public function getUser($username, $fetchgroups = true) 18 { 19 if (!$this->autoAuth()) return null; 20 21 $filter = Filters::and( 22 Filters::equal('objectClass', 'user'), 23 Filters::equal('userPrincipalName', $this->qualifiedUser($username)) 24 ); 25 $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 26 27 try { 28 /** @var Entries $entries */ 29 $entries = $this->ldap->search(Operations::search($filter)); 30 } catch (OperationException $e) { 31 $this->fatal($e); 32 return null; 33 } 34 if ($entries->count() !== 1) return null; 35 $entry = $entries->first(); 36 return $this->entry2User($entry); 37 } 38 39 /** @inheritDoc */ 40 public function getGroups($match = null, $filtermethod = 'equal') 41 { 42 if (!$this->autoAuth()) return []; 43 44 $filter = Filters::and( 45 Filters::equal('objectClass', 'group') 46 ); 47 if ($match !== null) { 48 // FIXME this is a workaround that removes regex anchors as passed by the groupuser plugin 49 // a proper fix requires splitbrain/dokuwiki#3028 to be properly fixed 50 $match = ltrim($match, '^'); 51 $match = rtrim($match, '$'); 52 53 $filter->add(Filters::$filtermethod('cn', $match)); 54 } 55 56 $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 57 $search = Operations::search($filter, 'cn'); 58 $paging = $this->ldap->paging($search); 59 60 $groups = []; 61 while ($paging->hasEntries()) { 62 try { 63 $entries = $paging->getEntries(); 64 } catch (ProtocolException $e) { 65 $this->fatal($e); 66 return $groups; // we return what we got so far 67 } 68 69 foreach ($entries as $entry) { 70 /** @var Entry $entry */ 71 $groups[$entry->getDn()->toString()] = $this->attr2str($entry->get('cn')); 72 } 73 } 74 75 asort($groups); 76 return $groups; 77 } 78 79 /** 80 * Fetch users matching the given filters 81 * 82 * @param array $match 83 * @param string $filtermethod The method to use for filtering 84 * @return array 85 */ 86 public function getFilteredUsers($match, $filtermethod = 'equal') 87 { 88 if (!$this->autoAuth()) return []; 89 90 $filter = Filters::and(Filters::equal('objectClass', 'user')); 91 if (isset($match['user'])) { 92 $filter->add(Filters::$filtermethod('userPrincipalName', $match['user'])); 93 } 94 if (isset($match['name'])) { 95 $filter->add(Filters::$filtermethod('displayName', $match['name'])); 96 } 97 if (isset($match['mail'])) { 98 $filter->add(Filters::$filtermethod('mail', $match['mail'])); 99 } 100 if (isset($match['grps'])) { 101 // memberOf can not be checked with a substring match, so we need to get the right groups first 102 $groups = $this->getGroups($match['grps'], $filtermethod); 103 $or = Filters::or(); 104 foreach ($groups as $dn => $group) { 105 $or->add(Filters::equal('memberOf', $dn)); 106 } 107 $filter->add($or); 108 } 109 $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 110 $search = Operations::search($filter); 111 $paging = $this->ldap->paging($search); 112 113 $users = []; 114 while ($paging->hasEntries()) { 115 try { 116 $entries = $paging->getEntries(); 117 } catch (ProtocolException $e) { 118 $this->fatal($e); 119 break; // we abort and return what we have so far 120 } 121 122 foreach ($entries as $entry) { 123 $userinfo = $this->entry2User($entry); 124 $users[$userinfo['user']] = $this->entry2User($entry); 125 } 126 } 127 128 ksort($users); 129 return $users; 130 } 131 132 /** 133 * @inheritDoc 134 * userPrincipalName in the form <user>@<domain> 135 */ 136 public function qualifiedUser($user) 137 { 138 $user = PhpString::strtolower($user); 139 if (!$this->config['domain']) return $user; 140 141 list($user, $domain) = explode('@', $user, 2); 142 if (!$domain) { 143 $domain = $this->config['domain']; 144 } 145 146 return $user . '@' . $domain; 147 } 148 149 /** 150 * @inheritDoc 151 * Removes the account suffix from the given user 152 */ 153 public function simpleUser($user) 154 { 155 $user = PhpString::strtolower($user); 156 if (!$this->config['domain']) return $user; 157 158 // strip account suffix 159 list($luser, $suffix) = explode('@', $user, 2); 160 if ($suffix === $this->config['domain']) return $luser; 161 162 return $user; 163 } 164 165 /** 166 * Transform an LDAP entry to a user info array 167 * 168 * @param Entry $entry 169 * @return array 170 */ 171 protected function entry2User(Entry $entry) 172 { 173 return [ 174 'user' => $this->simpleUser($this->attr2str($entry->get('UserPrincipalName'))), 175 'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')), 176 'mail' => $this->attr2str($entry->get('mail')), 177 'dn' => $entry->getDn()->toString(), 178 'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive 179 ]; 180 } 181 182 /** 183 * Get the list of groups the given user is member of 184 * 185 * This method currently does no LDAP queries and thus is inexpensive. 186 * 187 * @param Entry $userentry 188 * @return array 189 * @todo implement nested group memberships FIXME already correct? 190 */ 191 protected function getUserGroups(Entry $userentry) 192 { 193 $groups = [$this->config['defaultgroup']]; // always add default 194 195 // we simply take the first CN= part of the group DN and return it as the group name 196 // this should be correct for ActiveDirectory and saves us additional LDAP queries 197 if ($userentry->has('memberOf')) { 198 foreach ($userentry->get('memberOf')->getValues() as $dn) { 199 list($cn) = explode(',', $dn, 2); 200 $groups[] = PhpString::strtolower(substr($cn, 3)); 201 } 202 } 203 204 // resolving the primary group in AD is complicated but basically never needed 205 // http://support.microsoft.com/?kbid=321360 206 $gid = $userentry->get('primaryGroupID')->firstValue(); 207 if ($gid == 513) { 208 $groups[] = 'domain users'; 209 } 210 211 sort($groups); 212 return $groups; 213 } 214} 215