1<?php
2/**
3 * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
4 * Version 4.0.4
5 *
6 * PHP Version 5 with SSL and LDAP support
7 *
8 * Written by Scott Barnett, Richard Hyland
9 *   email: scott@wiggumworld.com, adldap@richardhyland.com
10 *   http://adldap.sourceforge.net/
11 *
12 * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
13 *
14 * We'd appreciate any improvements or additions to be submitted back
15 * to benefit the entire community :)
16 *
17 * This library is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public
19 * License as published by the Free Software Foundation; either
20 * version 2.1 of the License.
21 *
22 * This library is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25 * Lesser General Public License for more details.
26 *
27 * @category ToolsAndUtilities
28 * @package adLDAP
29 * @subpackage Groups
30 * @author Scott Barnett, Richard Hyland
31 * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
32 * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
33 * @revision $Revision: 97 $
34 * @version 4.0.4
35 * @link http://adldap.sourceforge.net/
36 */
37require_once(dirname(__FILE__) . '/../adLDAP.php');
38require_once(dirname(__FILE__) . '/../collections/adLDAPGroupCollection.php');
39
40use dokuwiki\Utf8\Sort;
41
42/**
43* GROUP FUNCTIONS
44*/
45class adLDAPGroups {
46    /**
47    * The current adLDAP connection via dependency injection
48    *
49    * @var adLDAP
50    */
51    protected $adldap;
52
53    public function __construct(adLDAP $adldap) {
54        $this->adldap = $adldap;
55    }
56
57    /**
58    * Add a group to a group
59    *
60    * @param string $parent The parent group name
61    * @param string $child The child group name
62    * @return bool
63    */
64    public function addGroup($parent,$child){
65
66        // Find the parent group's dn
67        $parentGroup = $this->ginfo($parent, array("cn"));
68        if ($parentGroup[0]["dn"] === NULL){
69            return false;
70        }
71        $parentDn = $parentGroup[0]["dn"];
72
73        // Find the child group's dn
74        $childGroup = $this->info($child, array("cn"));
75        if ($childGroup[0]["dn"] === NULL){
76            return false;
77        }
78        $childDn = $childGroup[0]["dn"];
79
80        $add = array();
81        $add["member"] = $childDn;
82
83        $result = @ldap_mod_add($this->adldap->getLdapConnection(), $parentDn, $add);
84        if ($result == false) {
85            return false;
86        }
87        return true;
88    }
89
90    /**
91    * Add a user to a group
92    *
93    * @param string $group The group to add the user to
94    * @param string $user The user to add to the group
95    * @param bool $isGUID Is the username passed a GUID or a samAccountName
96    * @return bool
97    */
98    public function addUser($group, $user, $isGUID = false)
99    {
100        // Adding a user is a bit fiddly, we need to get the full DN of the user
101        // and add it using the full DN of the group
102
103        // Find the user's dn
104        $userDn = $this->adldap->user()->dn($user, $isGUID);
105        if ($userDn === false) {
106            return false;
107        }
108
109        // Find the group's dn
110        $groupInfo = $this->info($group, array("cn"));
111        if ($groupInfo[0]["dn"] === NULL) {
112            return false;
113        }
114        $groupDn = $groupInfo[0]["dn"];
115
116        $add = array();
117        $add["member"] = $userDn;
118
119        $result = @ldap_mod_add($this->adldap->getLdapConnection(), $groupDn, $add);
120        if ($result == false) {
121            return false;
122        }
123        return true;
124    }
125
126    /**
127    * Add a contact to a group
128    *
129    * @param string $group The group to add the contact to
130    * @param string $contactDn The DN of the contact to add
131    * @return bool
132    */
133    public function addContact($group, $contactDn)
134    {
135        // To add a contact we take the contact's DN
136        // and add it using the full DN of the group
137
138        // Find the group's dn
139        $groupInfo = $this->info($group, array("cn"));
140        if ($groupInfo[0]["dn"] === NULL) {
141            return false;
142        }
143        $groupDn = $groupInfo[0]["dn"];
144
145        $add = array();
146        $add["member"] = $contactDn;
147
148        $result = @ldap_mod_add($this->adldap->getLdapConnection(), $groupDn, $add);
149        if ($result == false) {
150            return false;
151        }
152        return true;
153    }
154
155    /**
156    * Create a group
157    *
158    * @param array $attributes Default attributes of the group
159    * @return bool
160    */
161    public function create($attributes)
162    {
163        if (!is_array($attributes)){ return "Attributes must be an array"; }
164        if (!array_key_exists("group_name", $attributes)){ return "Missing compulsory field [group_name]"; }
165        if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; }
166        if (!array_key_exists("description", $attributes)){ return "Missing compulsory field [description]"; }
167        if (!is_array($attributes["container"])){ return "Container attribute must be an array."; }
168        $attributes["container"] = array_reverse($attributes["container"]);
169
170        //$member_array = array();
171        //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com";
172        //$member_array[1] = "cn=administrator,cn=Users,dc=yourdomain,dc=com";
173
174        $add = array();
175        $add["cn"] = $attributes["group_name"];
176        $add["samaccountname"] = $attributes["group_name"];
177        $add["objectClass"] = "Group";
178        $add["description"] = $attributes["description"];
179        //$add["member"] = $member_array; UNTESTED
180
181        $container = "OU=" . implode(",OU=", $attributes["container"]);
182        $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . ", " . $container . "," . $this->adldap->getBaseDn(), $add);
183        if ($result != true) {
184            return false;
185        }
186        return true;
187    }
188
189    /**
190    * Delete a group account
191    *
192    * @param string $group The group to delete (please be careful here!)
193    *
194    * @return array
195    */
196    public function delete($group) {
197        if (!$this->adldap->getLdapBind()){ return false; }
198        if ($group === null){ return "Missing compulsory field [group]"; }
199
200        $groupInfo = $this->info($group, array("*"));
201        $dn = $groupInfo[0]['distinguishedname'][0];
202        $result = $this->adldap->folder()->delete($dn);
203        if ($result !== true) {
204            return false;
205        } return true;
206    }
207
208    /**
209    * Remove a group from a group
210    *
211    * @param string $parent The parent group name
212    * @param string $child The child group name
213    * @return bool
214    */
215    public function removeGroup($parent , $child)
216    {
217
218        // Find the parent dn
219        $parentGroup = $this->info($parent, array("cn"));
220        if ($parentGroup[0]["dn"] === NULL) {
221            return false;
222        }
223        $parentDn = $parentGroup[0]["dn"];
224
225        // Find the child dn
226        $childGroup = $this->info($child, array("cn"));
227        if ($childGroup[0]["dn"] === NULL) {
228            return false;
229        }
230        $childDn = $childGroup[0]["dn"];
231
232        $del = array();
233        $del["member"] = $childDn;
234
235        $result = @ldap_mod_del($this->adldap->getLdapConnection(), $parentDn, $del);
236        if ($result == false) {
237            return false;
238        }
239        return true;
240    }
241
242    /**
243    * Remove a user from a group
244    *
245    * @param string $group The group to remove a user from
246    * @param string $user The AD user to remove from the group
247    * @param bool $isGUID Is the username passed a GUID or a samAccountName
248    * @return bool
249    */
250    public function removeUser($group, $user, $isGUID = false)
251    {
252
253        // Find the parent dn
254        $groupInfo = $this->info($group, array("cn"));
255        if ($groupInfo[0]["dn"] === NULL){
256            return false;
257        }
258        $groupDn = $groupInfo[0]["dn"];
259
260        // Find the users dn
261        $userDn = $this->adldap->user()->dn($user, $isGUID);
262        if ($userDn === false) {
263            return false;
264        }
265
266        $del = array();
267        $del["member"] = $userDn;
268
269        $result = @ldap_mod_del($this->adldap->getLdapConnection(), $groupDn, $del);
270        if ($result == false) {
271            return false;
272        }
273        return true;
274    }
275
276    /**
277    * Remove a contact from a group
278    *
279    * @param string $group The group to remove a user from
280    * @param string $contactDn The DN of a contact to remove from the group
281    * @return bool
282    */
283    public function removeContact($group, $contactDn)
284    {
285
286        // Find the parent dn
287        $groupInfo = $this->info($group, array("cn"));
288        if ($groupInfo[0]["dn"] === NULL) {
289            return false;
290        }
291        $groupDn = $groupInfo[0]["dn"];
292
293        $del = array();
294        $del["member"] = $contactDn;
295
296        $result = @ldap_mod_del($this->adldap->getLdapConnection(), $groupDn, $del);
297        if ($result == false) {
298            return false;
299        }
300        return true;
301    }
302
303    /**
304    * Return a list of groups in a group
305    *
306    * @param string $group The group to query
307    * @param bool $recursive Recursively get groups
308    * @return array
309    */
310    public function inGroup($group, $recursive = NULL)
311    {
312        if (!$this->adldap->getLdapBind()){ return false; }
313        if ($recursive === NULL){ $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it
314
315        // Search the directory for the members of a group
316        $info = $this->info($group, array("member","cn"));
317        $groups = $info[0]["member"];
318        if (!is_array($groups)) {
319            return false;
320        }
321
322        $groupArray = array();
323
324        for ($i=0; $i<$groups["count"]; $i++){
325             $filter = "(&(objectCategory=group)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($groups[$i]) . "))";
326             $fields = array("samaccountname", "distinguishedname", "objectClass");
327             $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
328             $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
329
330             // not a person, look for a group
331             if ($entries['count'] == 0 && $recursive == true) {
332                $filter = "(&(objectCategory=group)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($groups[$i]) . "))";
333                $fields = array("distinguishedname");
334                $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
335                $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
336                if (!isset($entries[0]['distinguishedname'][0])) {
337                    continue;
338                }
339                $subGroups = $this->inGroup($entries[0]['distinguishedname'][0], $recursive);
340                if (is_array($subGroups)) {
341                    $groupArray = array_merge($groupArray, $subGroups);
342                    $groupArray = array_unique($groupArray);
343                }
344                continue;
345             }
346
347             $groupArray[] = $entries[0]['distinguishedname'][0];
348        }
349        return $groupArray;
350    }
351
352    /**
353    * Return a list of members in a group
354    *
355    * @param string $group The group to query
356    * @param bool $recursive Recursively get group members
357    * @return array
358    */
359    public function members($group, $recursive = NULL)
360    {
361        if (!$this->adldap->getLdapBind()){ return false; }
362        if ($recursive === NULL){ $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it
363        // Search the directory for the members of a group
364        $info = $this->info($group, array("member","cn"));
365        $users = $info[0]["member"];
366        if (!is_array($users)) {
367            return false;
368        }
369
370        $userArray = array();
371
372        for ($i=0; $i<$users["count"]; $i++){
373             $filter = "(&(objectCategory=person)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($users[$i]) . "))";
374             $fields = array("samaccountname", "distinguishedname", "objectClass");
375             $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
376             $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
377
378             // not a person, look for a group
379             if ($entries['count'] == 0 && $recursive == true) {
380                $filter = "(&(objectCategory=group)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($users[$i]) . "))";
381                $fields = array("samaccountname");
382                $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
383                $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
384                if (!isset($entries[0]['samaccountname'][0])) {
385                    continue;
386                }
387                $subUsers = $this->members($entries[0]['samaccountname'][0], $recursive);
388                if (is_array($subUsers)) {
389                    $userArray = array_merge($userArray, $subUsers);
390                    $userArray = array_unique($userArray);
391                }
392                continue;
393             }
394             else if ($entries['count'] == 0) {
395                continue;
396             }
397
398             if ((!isset($entries[0]['samaccountname'][0]) || $entries[0]['samaccountname'][0] === NULL) && $entries[0]['distinguishedname'][0] !== NULL) {
399                 $userArray[] = $entries[0]['distinguishedname'][0];
400             }
401             else if ($entries[0]['samaccountname'][0] !== NULL) {
402                $userArray[] = $entries[0]['samaccountname'][0];
403             }
404        }
405        return $userArray;
406    }
407
408    /**
409    * Group Information.  Returns an array of raw information about a group.
410    * The group name is case sensitive
411    *
412    * @param string $groupName The group name to retrieve info about
413    * @param array $fields Fields to retrieve
414    * @return array
415    */
416    public function info($groupName, $fields = NULL)
417    {
418        if ($groupName === NULL) { return false; }
419        if (!$this->adldap->getLdapBind()) { return false; }
420
421        if (stristr($groupName, '+')) {
422            $groupName = stripslashes($groupName);
423        }
424
425        $filter = "(&(objectCategory=group)(name=" . $this->adldap->utilities()->ldapSlashes($groupName) . "))";
426        if ($fields === NULL) {
427            $fields = array("member","memberof","cn","description","distinguishedname","objectcategory","samaccountname");
428        }
429        $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
430        $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
431
432        return $entries;
433    }
434
435    /**
436    * Group Information.  Returns an collection
437    * The group name is case sensitive
438    *
439    * @param string $groupName The group name to retrieve info about
440    * @param array $fields Fields to retrieve
441    * @return adLDAPGroupCollection
442    */
443    public function infoCollection($groupName, $fields = NULL)
444    {
445        if ($groupName === NULL) { return false; }
446        if (!$this->adldap->getLdapBind()) { return false; }
447
448        $info = $this->info($groupName, $fields);
449        if ($info !== false) {
450            $collection = new adLDAPGroupCollection($info, $this->adldap);
451            return $collection;
452        }
453        return false;
454    }
455
456    /**
457    * Return a complete list of "groups in groups"
458    *
459    * @param string $group The group to get the list from
460    * @return array
461    */
462    public function recursiveGroups($group)
463    {
464        if ($group === NULL) { return false; }
465
466        $stack = array();
467        $processed = array();
468        $retGroups = array();
469
470        array_push($stack, $group); // Initial Group to Start with
471        while (count($stack) > 0) {
472            $parent = array_pop($stack);
473            array_push($processed, $parent);
474
475            $info = $this->info($parent, array("memberof"));
476
477            if (isset($info[0]["memberof"]) && is_array($info[0]["memberof"])) {
478                $groups = $info[0]["memberof"];
479                if ($groups) {
480                    $groupNames = $this->adldap->utilities()->niceNames($groups);
481                    $retGroups = array_merge($retGroups, $groupNames); //final groups to return
482                    foreach ($groupNames as $id => $groupName) {
483                        if (!in_array($groupName, $processed)) {
484                            array_push($stack, $groupName);
485                        }
486                    }
487                }
488            }
489        }
490
491        return $retGroups;
492    }
493
494    /**
495    * Returns a complete list of the groups in AD based on a SAM Account Type
496    *
497    * @param string $sAMAaccountType The account type to return
498    * @param bool $includeDescription Whether to return a description
499    * @param string $search Search parameters
500    * @param bool $sorted Whether to sort the results
501    * @return array
502    */
503    public function search($sAMAaccountType = adLDAP::ADLDAP_SECURITY_GLOBAL_GROUP, $includeDescription = false, $search = "*", $sorted = true) {
504        if (!$this->adldap->getLdapBind()) { return false; }
505
506        $filter = '(&(objectCategory=group)';
507        if ($sAMAaccountType !== null) {
508            $filter .= '(samaccounttype='. $sAMAaccountType .')';
509        }
510        $filter .= '(cn=' . $search . '))';
511        // Perform the search and grab all their details
512        $fields = array("samaccountname", "description");
513        $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
514        $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
515
516        $groupsArray = array();
517        for ($i=0; $i<$entries["count"]; $i++){
518            if ($includeDescription && strlen($entries[$i]["description"][0]) > 0 ) {
519                $groupsArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["description"][0];
520            }
521            else if ($includeDescription){
522                $groupsArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0];
523            }
524            else {
525                array_push($groupsArray, $entries[$i]["samaccountname"][0]);
526            }
527        }
528        if ($sorted) {
529            Sort::asort($groupsArray);
530        }
531        return $groupsArray;
532    }
533
534    /**
535    * Returns a complete list of all groups in AD
536    *
537    * @param bool $includeDescription Whether to return a description
538    * @param string $search Search parameters
539    * @param bool $sorted Whether to sort the results
540    * @return array
541    */
542    public function all($includeDescription = false, $search = "*", $sorted = true){
543        $groupsArray = $this->search(null, $includeDescription, $search, $sorted);
544        return $groupsArray;
545    }
546
547    /**
548    * Returns a complete list of security groups in AD
549    *
550    * @param bool $includeDescription Whether to return a description
551    * @param string $search Search parameters
552    * @param bool $sorted Whether to sort the results
553    * @return array
554    */
555    public function allSecurity($includeDescription = false, $search = "*", $sorted = true){
556        $groupsArray = $this->search(adLDAP::ADLDAP_SECURITY_GLOBAL_GROUP, $includeDescription, $search, $sorted);
557        return $groupsArray;
558    }
559
560    /**
561    * Returns a complete list of distribution lists in AD
562    *
563    * @param bool $includeDescription Whether to return a description
564    * @param string $search Search parameters
565    * @param bool $sorted Whether to sort the results
566    * @return array
567    */
568    public function allDistribution($includeDescription = false, $search = "*", $sorted = true){
569        $groupsArray = $this->search(adLDAP::ADLDAP_DISTRIBUTION_GROUP, $includeDescription, $search, $sorted);
570        return $groupsArray;
571    }
572
573    /**
574    * Coping with AD not returning the primary group
575    * http://support.microsoft.com/?kbid=321360
576    *
577    * This is a re-write based on code submitted by Bruce which prevents the
578    * need to search each security group to find the true primary group
579    *
580    * @param string $gid Group ID
581    * @param string $usersid User's Object SID
582    * @return mixed
583    */
584    public function getPrimaryGroup($gid, $usersid)
585    {
586        if ($gid === NULL || $usersid === NULL) { return false; }
587        $sr = false;
588
589        $gsid = substr_replace($usersid, pack('V',$gid), strlen($usersid)-4,4);
590        $filter = '(objectsid=' . $this->adldap->utilities()->getTextSID($gsid).')';
591        $fields = array("samaccountname","distinguishedname");
592        $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
593        $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
594
595        if (isset($entries[0]['distinguishedname'][0])) {
596            return $entries[0]['distinguishedname'][0];
597        }
598        return false;
599     }
600
601     /**
602    * Coping with AD not returning the primary group
603    * http://support.microsoft.com/?kbid=321360
604    *
605    * For some reason it's not possible to search on primarygrouptoken=XXX
606    * If someone can show otherwise, I'd like to know about it :)
607    * this way is resource intensive and generally a pain in the @#%^
608    *
609    * @deprecated deprecated since version 3.1, see get get_primary_group
610    * @param string $gid Group ID
611    * @return string
612    */
613    public function cn($gid){
614        if ($gid === NULL) { return false; }
615        $sr = false;
616        $r = '';
617
618        $filter = "(&(objectCategory=group)(samaccounttype=" . adLDAP::ADLDAP_SECURITY_GLOBAL_GROUP . "))";
619        $fields = array("primarygrouptoken", "samaccountname", "distinguishedname");
620        $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
621        $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
622
623        for ($i=0; $i<$entries["count"]; $i++){
624            if ($entries[$i]["primarygrouptoken"][0] == $gid) {
625                $r = $entries[$i]["distinguishedname"][0];
626                $i = $entries["count"];
627            }
628        }
629
630        return $r;
631    }
632}
633?>
634