adLDAPGroups.php 22 KB

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