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