@@ -58,6 +58,11 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD
5858 */
5959 protected $ cachedGroupsByMember ;
6060
61+ /**
62+ * @var string[] $cachedNestedGroups array of groups with gid (DN) as key
63+ */
64+ protected $ cachedNestedGroups ;
65+
6166 /** @var GroupPluginManager */
6267 protected $ groupPluginManager ;
6368
@@ -71,6 +76,7 @@ public function __construct(Access $access, GroupPluginManager $groupPluginManag
7176
7277 $ this ->cachedGroupMembers = new CappedMemoryCache ();
7378 $ this ->cachedGroupsByMember = new CappedMemoryCache ();
79+ $ this ->cachedNestedGroups = new CappedMemoryCache ();
7480 $ this ->groupPluginManager = $ groupPluginManager ;
7581 }
7682
@@ -212,12 +218,12 @@ public function getDynamicGroupMembers($dnGroup) {
212218 */
213219 private function _groupMembers ($ dnGroup , &$ seen = null ) {
214220 if ($ seen === null ) {
215- $ seen = array () ;
221+ $ seen = [] ;
216222 }
217- $ allMembers = array () ;
223+ $ allMembers = [] ;
218224 if (array_key_exists ($ dnGroup , $ seen )) {
219225 // avoid loops
220- return array () ;
226+ return [] ;
221227 }
222228 // used extensively in cron job, caching makes sense for nested groups
223229 $ cacheKey = '_groupMembers ' .$ dnGroup ;
@@ -226,19 +232,12 @@ private function _groupMembers($dnGroup, &$seen = null) {
226232 return $ groupMembers ;
227233 }
228234 $ seen [$ dnGroup ] = 1 ;
229- $ members = $ this ->access ->readAttribute ($ dnGroup , $ this ->access ->connection ->ldapGroupMemberAssocAttr ,
230- $ this ->access ->connection ->ldapGroupFilter );
235+ $ members = $ this ->access ->readAttribute ($ dnGroup , $ this ->access ->connection ->ldapGroupMemberAssocAttr );
231236 if (is_array ($ members )) {
232- foreach ($ members as $ member ) {
233- $ allMembers [$ member ] = 1 ;
234- $ nestedGroups = $ this ->access ->connection ->ldapNestedGroups ;
235- if (!empty ($ nestedGroups )) {
236- $ subMembers = $ this ->_groupMembers ($ member , $ seen );
237- if ($ subMembers ) {
238- $ allMembers += $ subMembers ;
239- }
240- }
241- }
237+ $ fetcher = function ($ memberDN , &$ seen ) {
238+ return $ this ->_groupMembers ($ memberDN , $ seen );
239+ };
240+ $ allMembers = $ this ->walkNestedGroups ($ dnGroup , $ fetcher , $ members );
242241 }
243242
244243 $ allMembers += $ this ->getDynamicGroupMembers ($ dnGroup );
@@ -251,30 +250,69 @@ private function _groupMembers($dnGroup, &$seen = null) {
251250 * @param string $DN
252251 * @param array|null &$seen
253252 * @return array
253+ * @throws \OC\ServerNotAvailableException
254254 */
255- private function _getGroupDNsFromMemberOf ($ DN , &$ seen = null ) {
256- if ($ seen === null ) {
257- $ seen = array ();
258- }
259- if (array_key_exists ($ DN , $ seen )) {
260- // avoid loops
261- return array ();
262- }
263- $ seen [$ DN ] = 1 ;
255+ private function _getGroupDNsFromMemberOf ($ DN ) {
264256 $ groups = $ this ->access ->readAttribute ($ DN , 'memberOf ' );
265257 if (!is_array ($ groups )) {
266- return array ();
258+ return [];
259+ }
260+
261+ $ fetcher = function ($ groupDN ) {
262+ if (isset ($ this ->cachedNestedGroups [$ groupDN ])) {
263+ $ nestedGroups = $ this ->cachedNestedGroups [$ groupDN ];
264+ } else {
265+ $ nestedGroups = $ this ->access ->readAttribute ($ groupDN , 'memberOf ' );
266+ if (!is_array ($ nestedGroups )) {
267+ $ nestedGroups = [];
268+ }
269+ $ this ->cachedNestedGroups [$ groupDN ] = $ nestedGroups ;
270+ }
271+ return $ nestedGroups ;
272+ };
273+
274+ $ groups = $ this ->walkNestedGroups ($ DN , $ fetcher , $ groups );
275+ return $ this ->access ->groupsMatchFilter ($ groups );
276+ }
277+
278+ /**
279+ * @param string $dn
280+ * @param \Closure $fetcher args: string $dn, array $seen, returns: string[] of dns
281+ * @param array $list
282+ * @return array
283+ */
284+ private function walkNestedGroups (string $ dn , \Closure $ fetcher , array $ list ): array {
285+ $ nesting = (int ) $ this ->access ->connection ->ldapNestedGroups ;
286+ // depending on the input, we either have a list of DNs or a list of LDAP records
287+ // also, the output expects either DNs or records. Testing the first element should suffice.
288+ $ recordMode = is_array ($ list ) && isset ($ list [0 ]) && is_array ($ list [0 ]) && isset ($ list [0 ]['dn ' ][0 ]);
289+
290+ if ($ nesting !== 1 ) {
291+ if ($ recordMode ) {
292+ // the keys are numeric, but should hold the DN
293+ return array_reduce ($ list , function ($ transformed , $ record ) use ($ dn ) {
294+ if ($ record ['dn ' ][0 ] != $ dn ) {
295+ $ transformed [$ record ['dn ' ][0 ]] = $ record ;
296+ }
297+ return $ transformed ;
298+ }, []);
299+ }
300+ return $ list ;
267301 }
268- $ groups = $ this -> access -> groupsMatchFilter ( $ groups );
269- $ allGroups = $ groups ;
270- $ nestedGroups = $ this -> access -> connection -> ldapNestedGroups ;
271- if (( int ) $ nestedGroups === 1 ) {
272- foreach ($ groups as $ group ) {
273- $ subGroups = $ this -> _getGroupDNsFromMemberOf ( $ group , $ seen );
274- $ allGroups = array_merge ( $ allGroups , $ subGroups ) ;
302+
303+ $ seen = [] ;
304+ while ( $ record = array_pop ( $ list )) {
305+ $ recordDN = $ recordMode ? $ record [ ' dn ' ][ 0 ] : $ record ;
306+ if ($ recordDN === $ dn || array_key_exists ( $ recordDN , $ seen ) ) {
307+ // Prevent loops
308+ continue ;
275309 }
310+ $ fetched = $ fetcher ($ record , $ seen );
311+ $ list = array_merge ($ list , $ fetched );
312+ $ seen [$ recordDN ] = $ record ;
276313 }
277- return $ allGroups ;
314+
315+ return $ recordMode ? $ seen : array_keys ($ seen );
278316 }
279317
280318 /**
@@ -737,34 +775,28 @@ public function getUserGroups($uid) {
737775 */
738776 private function getGroupsByMember ($ dn , &$ seen = null ) {
739777 if ($ seen === null ) {
740- $ seen = array () ;
778+ $ seen = [] ;
741779 }
742- $ allGroups = array ();
743780 if (array_key_exists ($ dn , $ seen )) {
744781 // avoid loops
745- return array () ;
782+ return [] ;
746783 }
784+ $ allGroups = [];
747785 $ seen [$ dn ] = true ;
748- $ filter = $ this ->access ->combineFilterWithAnd (array (
749- $ this ->access ->connection ->ldapGroupFilter ,
750- $ this ->access ->connection ->ldapGroupMemberAssocAttr .'= ' .$ dn
751- ));
786+ $ filter = $ this ->access ->connection ->ldapGroupMemberAssocAttr .'= ' .$ dn ;
752787 $ groups = $ this ->access ->fetchListOfGroups ($ filter ,
753- array ( $ this ->access ->connection ->ldapGroupDisplayName , 'dn ' ) );
788+ [ $ this ->access ->connection ->ldapGroupDisplayName , 'dn ' ] );
754789 if (is_array ($ groups )) {
755- foreach ($ groups as $ groupobj ) {
756- $ groupDN = $ groupobj ['dn ' ][0 ];
757- $ allGroups [$ groupDN ] = $ groupobj ;
758- $ nestedGroups = $ this ->access ->connection ->ldapNestedGroups ;
759- if (!empty ($ nestedGroups )) {
760- $ supergroups = $ this ->getGroupsByMember ($ groupDN , $ seen );
761- if (is_array ($ supergroups ) && (count ($ supergroups )>0 )) {
762- $ allGroups = array_merge ($ allGroups , $ supergroups );
763- }
790+ $ fetcher = function ($ dn , &$ seen ) {
791+ if (is_array ($ dn ) && isset ($ dn ['dn ' ][0 ])) {
792+ $ dn = $ dn ['dn ' ][0 ];
764793 }
765- }
794+ return $ this ->getGroupsByMember ($ dn , $ seen );
795+ };
796+ $ allGroups = $ this ->walkNestedGroups ($ dn , $ fetcher , $ groups );
766797 }
767- return $ allGroups ;
798+ $ visibleGroups = $ this ->access ->groupsMatchFilter (array_keys ($ allGroups ));
799+ return array_intersect_key ($ allGroups , array_flip ($ visibleGroups ));
768800 }
769801
770802 /**
@@ -811,7 +843,7 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
811843
812844 $ primaryUsers = $ this ->getUsersInPrimaryGroup ($ groupDN , $ search , $ limit , $ offset );
813845 $ posixGroupUsers = $ this ->getUsersInGidNumber ($ groupDN , $ search , $ limit , $ offset );
814- $ members = array_keys ( $ this ->_groupMembers ($ groupDN) );
846+ $ members = $ this ->_groupMembers ($ groupDN );
815847 if (!$ members && empty ($ posixGroupUsers ) && empty ($ primaryUsers )) {
816848 //in case users could not be retrieved, return empty result set
817849 $ this ->access ->connection ->writeToCache ($ cacheKey , []);
@@ -886,7 +918,7 @@ public function countUsersInGroup($gid, $search = '') {
886918 return false ;
887919 }
888920
889- $ members = array_keys ( $ this ->_groupMembers ($ groupDN) );
921+ $ members = $ this ->_groupMembers ($ groupDN );
890922 $ primaryUserCount = $ this ->countUsersInPrimaryGroup ($ groupDN , '' );
891923 if (!$ members && $ primaryUserCount === 0 ) {
892924 //in case users could not be retrieved, return empty result set
0 commit comments