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