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