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