xref: /plugin/pureldap/classes/ADClient.php (revision 80ac552f15be014c3406f65bcaa71e07c7c359c8)
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