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