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 $filter->add(Filters::$filtermethod('cn', $match)); 49 } 50 51 $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 52 $search = Operations::search($filter, 'cn'); 53 $paging = $this->ldap->paging($search); 54 55 $groups = []; 56 while ($paging->hasEntries()) { 57 try { 58 $entries = $paging->getEntries(); 59 } catch (ProtocolException $e) { 60 $this->fatal($e); 61 return $groups; // we return what we got so far 62 } 63 64 foreach ($entries as $entry) { 65 /** @var Entry $entry */ 66 $groups[$entry->getDn()->toString()] = $this->attr2str($entry->get('cn')); 67 } 68 } 69 70 return $groups; 71 } 72 73 /** 74 * Fetch users matching the given filters 75 * 76 * @param array $match 77 * @param string $filtermethod The method to use for filtering 78 * @return array 79 */ 80 public function getFilteredUsers($match, $filtermethod = 'equal') 81 { 82 if (!$this->autoAuth()) return []; 83 84 $filter = Filters::and(Filters::equal('objectClass', 'user')); 85 if (isset($match['user'])) { 86 $filter->add(Filters::$filtermethod('userPrincipalName', $match['user'])); 87 } 88 if (isset($match['name'])) { 89 $filter->add(Filters::$filtermethod('displayName', $match['name'])); 90 } 91 if (isset($match['mail'])) { 92 $filter->add(Filters::$filtermethod('mail', $match['mail'])); 93 } 94 if (isset($match['grps'])) { 95 // memberOf can not be checked with a substring match, so we need to get the right groups first 96 $groups = $this->getGroups($match['grps'], $filtermethod); 97 $or = Filters::or(); 98 foreach ($groups as $dn => $group) { 99 $or->add(Filters::equal('memberOf', $dn)); 100 } 101 $filter->add($or); 102 } 103 $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 104 $search = Operations::search($filter); 105 $paging = $this->ldap->paging($search); 106 107 $users = []; 108 while ($paging->hasEntries()) { 109 try { 110 $entries = $paging->getEntries(); 111 } catch (ProtocolException $e) { 112 $this->fatal($e); 113 break; // we abort and return what we have so far 114 } 115 116 foreach ($entries as $entry) { 117 $userinfo = $this->entry2User($entry); 118 $users[$userinfo['user']] = $this->entry2User($entry); 119 } 120 } 121 122 return $users; 123 } 124 125 /** 126 * @inheritDoc 127 * userPrincipalName in the form <user>@<domain> 128 */ 129 public function qualifiedUser($user) 130 { 131 $user = PhpString::strtolower($user); 132 if (!$this->config['domain']) return $user; 133 134 list($user, $domain) = explode('@', $user, 2); 135 if (!$domain) { 136 $domain = $this->config['domain']; 137 } 138 139 return $user . '@' . $domain; 140 } 141 142 /** 143 * @inheritDoc 144 * Removes the account suffix from the given user 145 */ 146 public function simpleUser($user) 147 { 148 $user = PhpString::strtolower($user); 149 if (!$this->config['domain']) return $user; 150 151 // strip account suffix 152 list($luser, $suffix) = explode('@', $user, 2); 153 if ($suffix === $this->config['domain']) return $luser; 154 155 return $user; 156 } 157 158 /** 159 * Transform an LDAP entry to a user info array 160 * 161 * @param Entry $entry 162 * @return array 163 */ 164 protected function entry2User(Entry $entry) 165 { 166 return [ 167 'user' => $this->simpleUser($this->attr2str($entry->get('UserPrincipalName'))), 168 'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')), 169 'mail' => $this->attr2str($entry->get('mail')), 170 'dn' => $entry->getDn()->toString(), 171 'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive 172 ]; 173 } 174 175 /** 176 * Get the list of groups the given user is member of 177 * 178 * This method currently does no LDAP queries and thus is inexpensive. 179 * 180 * @param Entry $userentry 181 * @return array 182 * @todo implement nested group memberships 183 */ 184 protected function getUserGroups(Entry $userentry) 185 { 186 $groups = [$this->config['defaultgroup']]; // always add default 187 188 // we simply take the first CN= part of the group DN and return it as the group name 189 // this should be correct for ActiveDirectory and saves us additional LDAP queries 190 if ($userentry->has('memberOf')) { 191 foreach ($userentry->get('memberOf')->getValues() as $dn) { 192 list($cn) = explode(',', $dn, 2); 193 $groups[] = PhpString::strtolower(substr($cn, 3)); 194 } 195 } 196 197 // resolving the primary group in AD is complicated but basically never needed 198 // http://support.microsoft.com/?kbid=321360 199 $gid = $userentry->get('primaryGroupID')->firstValue(); 200 if ($gid == 513) { 201 $groups[] = 'domain users'; 202 } 203 204 return $groups; 205 } 206} 207