1<?php
2
3namespace dokuwiki\plugin\pureldap\classes;
4
5use FreeDSx\Ldap\Entry\Entry;
6use FreeDSx\Ldap\Exception\ProtocolException;
7use FreeDSx\Ldap\LdapClient;
8use FreeDSx\Ldap\Operations;
9use FreeDSx\Ldap\Search\Filters;
10
11/**
12 * Keeps a copy of all AD groups and provides recursive operations
13 *
14 * All groups are cached as full DN here
15 */
16class GroupHierarchyCache
17{
18    /** @var LdapClient */
19    protected $ldap;
20
21    /** @var array List of group DNs and their parent and children */
22    protected $groupHierarchy;
23
24    /**
25     * GroupHierarchyCache constructor.
26     *
27     * @param LdapClient $ldap
28     * @param bool $usefs Use filesystem caching?
29     */
30    public function __construct(LdapClient $ldap, $usefs)
31    {
32        $this->ldap = $ldap;
33
34        if ($usefs) {
35            $this->groupHierarchy = $this->getCachedGroupList();
36        } else {
37            $this->groupHierarchy = $this->getGroupList();
38        }
39    }
40
41    /**
42     * Use a file system cached version of the group hierarchy
43     *
44     * The cache expires after $conf['auth_security_timeout']
45     *
46     * @return array
47     */
48    protected function getCachedGroupList()
49    {
50        global $conf;
51
52        $cachename = getcachename('grouphierarchy', '.pureldap-gch');
53        $cachetime = @filemtime($cachename);
54
55        // valid file system cache? use it
56        if ($cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) {
57            return json_decode(file_get_contents($cachename), true, 512, JSON_THROW_ON_ERROR);
58        }
59
60        // get fresh data and store in cache
61        $groups = $this->getGroupList();
62        file_put_contents($cachename, json_encode($groups, JSON_THROW_ON_ERROR));
63        return $groups;
64    }
65
66    /**
67     * Load all group information from AD
68     *
69     * @return array
70     */
71    protected function getGroupList()
72    {
73        $filter = Filters::equal('objectCategory', 'group');
74        $search = Operations::search($filter, 'memberOf', 'cn');
75        $paging = $this->ldap->paging($search);
76
77        $groups = [];
78
79        while ($paging->hasEntries()) {
80            try {
81                $entries = $paging->getEntries();
82            } catch (ProtocolException $e) {
83                return $groups; // return what we have
84            }
85            /** @var Entry $entry */
86            foreach ($entries as $entry) {
87                $dn = (string)$entry->getDn();
88                $groups[$dn] = [];
89                if ($entry->has('memberOf')) {
90                    $parents = $entry->get('memberOf')->getValues();
91                    $groups[$dn]['parents'] = $parents;
92                    foreach ($parents as $parent) {
93                        $groups[$parent]['children'][] = $dn;
94                    }
95                }
96            }
97        }
98        return $groups;
99    }
100
101    /**
102     * Recursive method to get all children or parents
103     *
104     * @param string $group
105     * @param string $type
106     * @param array $data list to fill
107     */
108    protected function getHierarchy($group, $type, &$data)
109    {
110        if (empty($this->groupHierarchy[$group][$type])) return;
111
112        $parents = $this->groupHierarchy[$group][$type];
113        foreach ($parents as $parent) {
114            if (in_array($parent, $data)) continue; // we did this one already
115            $data[] = $parent;
116            $this->getHierarchy($parent, $type, $data);
117        }
118    }
119
120    /**
121     * Get all parents of a group
122     *
123     * @param string $group
124     * @return string[]
125     */
126    public function getParents($group)
127    {
128        $parents = [];
129        $this->getHierarchy($group, 'parents', $parents);
130        return $parents;
131    }
132
133    /**
134     * Get all children of a group
135     *
136     * @param string $group
137     * @return string[]
138     */
139    public function getChildren($group)
140    {
141        $children = [];
142        $this->getHierarchy($group, 'children', $children);
143        return $children;
144    }
145}
146