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