UserStorageMacOSX.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. <?php
  2. Lib::loadClass('UserStorageBase');
  3. /**
  4. * Test remove user:
  5. * $ dscl /Local/Default -list /Groups GroupMembership | grep test13
  6. * ... for all groups:
  7. * $ dscl -u diradmin -p /Local/Default -delete /Groups/workgroup GroupMembership test13
  8. * $ dscl /LDAPv3/127.0.0.1 -list /Groups GroupMembership | grep test13
  9. * ... for all groups:
  10. * $ dscl -u diradmin -p /LDAPv3/127.0.0.1 -delete /Groups/workgroup GroupMembership test13
  11. * $ dscl -u diradmin -p /Local/Default -delete /Users/test13
  12. */
  13. class UserStorageMacOSX extends UserStorageBase {
  14. private $_rootUser;
  15. private $_rootPass;
  16. private $_host;
  17. private $_ldapRoot;
  18. public function __construct($rootUser, $rootPass, $host) {
  19. $this->_rootUser = $rootUser;
  20. $this->_rootPass = $rootPass;
  21. $this->_host = $host;
  22. }
  23. /**
  24. * @returns object $usr
  25. * $usr->primaryKey
  26. * $usr->login
  27. * $usr->password optional (required in createUser)
  28. * $usr->name
  29. * $usr->email
  30. * $usr->phone
  31. * $usr->homeEmail
  32. * $usr->homePhone
  33. * $usr->employeeType 'Pracownik','Kandydat','Partner','Anonymous'
  34. * $usr->isDisabled 1, 0 or null if not set
  35. */
  36. public function getUser($userName) {
  37. Lib::loadClass('UsersLdapHelper');
  38. $usrLdap = UsersLdapHelper::getUser($userName, true);
  39. if (empty($usrLdap[0])) return null;
  40. $user = new stdClass();
  41. $user->primaryKey = V::get('uidNumber', '', $usrLdap[0]);
  42. $user->login = V::get('uid', '', $usrLdap[0]);
  43. $user->password = '';
  44. $user->name = V::get('cn', '', $usrLdap[0]);
  45. $user->email = V::get('mail', '', $usrLdap[0]);
  46. $user->phone = V::get('telephoneNumber', '', $usrLdap[0]);
  47. $user->homeEmail = '';
  48. $user->homePhone = V::get('homePhone', '', $usrLdap[0]);
  49. $user->employeeType = V::get('employeeType', '', $usrLdap[0]);
  50. return $user;
  51. }
  52. /**
  53. * Group.
  54. *
  55. * @returns object $group
  56. * $group->primaryKey
  57. * $group->realName realName eg. "[123] Grupa 123"
  58. * $group->nestedGroups
  59. * $group->type 'STANOWISKO','PODMIOT','DZIAL','local','network'
  60. * 'local' - access groups like com.apple.access_mail
  61. * 'network' - network group: workgroup
  62. * (optional) $group->zasobID
  63. * (optional) $group->zasobDESC
  64. */
  65. public function getGroup($groupID) {
  66. return $this->_getGroup($groupID, $fetchNested = true);
  67. }
  68. private function _getGroup($groupID, $fetchNested = false) {
  69. if ($groupID <= 0) return false;
  70. $group = null;
  71. Lib::loadClass('UsersLdapHelper');
  72. $groups = UsersLdapHelper::getGroupsByID($groupID);
  73. if (count($groups) == 1) {
  74. $group = reset($groups);
  75. if(V::get('DBG_SU', 0, $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">groupLdap (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($group);echo'</pre>';}
  76. $group = $this->_buildGroupFromLdap($group, $fetchNested);
  77. if(V::get('DBG_SU', 0, $_GET, 'int') > 2){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">group (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($group);echo'</pre>';}
  78. } else if (count($groups) > 1) {
  79. if(V::get('DBG_SU', 0, $_GET, 'int') > 0){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">'."Too much groups in ldap by ID {$groupID}".' (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($groups);echo'</pre>';}
  80. trigger_error("Too much groups in ldap by ID {$groupID}", E_USER_WARNING);
  81. }
  82. return $group;
  83. }
  84. /**
  85. * @returns bool
  86. */
  87. public function isDisabled($usr) {
  88. if (null == $usr->isDisabled) {
  89. //$cmd = "sudo pwpolicy -u {$usr->login} -getpolicy";
  90. $cmd = "sudo pwpolicy -u {$usr->login} --get-effective-policy";// BUG wersja 10.9.3 opcja -getpolicy pokazuje tylko włączone opcje, nie pokaże "isDisabled=0"
  91. $cmdOut = null; $cmdRet = null;
  92. exec($cmd, $cmdOut, $cmdRet);
  93. if(V::get('DBG_SU', 0, $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmd('.$cmd.') ret('.$cmdRet.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdOut);echo'</pre>';}
  94. foreach ($cmdOut as $vLine) {
  95. if (false !== strpos($vLine, 'isDisabled=')) {
  96. if (false !== strpos($vLine, 'isDisabled=0')) {
  97. $usr->isDisabled = 0;
  98. }
  99. else if (false !== strpos($vLine, 'isDisabled=1')) {
  100. $usr->isDisabled = 1;
  101. }
  102. break;
  103. }
  104. }
  105. }
  106. return $usr->isDisabled;
  107. }
  108. /**
  109. * @returns bool
  110. */
  111. public function setDisabled($usrLogin, $isDisabled) {
  112. if (empty($usrLogin) || null === $isDisabled) {
  113. return false;
  114. }
  115. $cmdDisabled = ($isDisabled)? '1' : '0';
  116. $cmd = "pwpolicy -a {$this->_rootUser} -p {$this->_rootPass} -u {$usrLogin} -setpolicy \"isDisabled={$cmdDisabled}\" 2>&1 ";
  117. $cmdOut = null; $cmdRet = null;
  118. exec($cmd, $cmdOut, $cmdRet);
  119. if(V::get('DBG_SU', 0, $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmd('.str_replace($this->_rootPass, '***', $cmd).') ret('.$cmdRet.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdOut);echo'</pre>';}
  120. if ($cmdRet !== 0) {
  121. return false;
  122. }
  123. return true;
  124. }
  125. /**
  126. * @param $usr - user object @see UserStorageBase::getUser()
  127. * $usr->employeeType: Pracownik, Kandydat, Partner, Anonymous
  128. * Pracownik - all access
  129. * Kandydat - no access
  130. * Partner - access: smb/afp? calendar? addressbook?
  131. * Anonymous - no access
  132. */
  133. public function createUser($usr) {
  134. //public function createUser($login, $type, $name = '', $email = '', $pass = '') {
  135. if (V::get('DBG_SU', 0, $_GET, 'int') > 1) {
  136. echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">usr (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($usr);echo'</pre>';
  137. }
  138. $cmdDsclAuth = "dscl -u {$this->_rootUser} -P {$this->_rootPass} /LDAPv3/127.0.0.1 ";
  139. $login = $this->_cleanUid($usr->login);
  140. $name = $this->_cleanText($usr->name);
  141. $type = $usr->employeeType;
  142. $email = $usr->email;
  143. $pass = $usr->password;
  144. $uniqueID = 0;
  145. // test user login and pass by searching for $uniqueID for new user
  146. $cmd = "{$cmdDsclAuth} -list /Users > /dev/null && {$cmdDsclAuth} -list /Users UniqueID|awk '{print \$2}'|sort -n|tail -1 ";
  147. $cmdOut = null; $cmdRet = null;
  148. exec($cmd, $cmdOut, $cmdRet);
  149. if ($cmdRet == 0 && !empty($cmdOut[0])) {
  150. $uniqueID = intval($cmdOut[0]);
  151. if ($uniqueID > 0) {
  152. $uniqueID += 1;
  153. }
  154. }
  155. if ($uniqueID <= 0) {
  156. $this->setError(1, "Error: dscl auth - check login and password in ldap config", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  157. return false;
  158. }
  159. if (empty($name)) {
  160. $name = $login;
  161. } else {
  162. // TODO: replace bad signs str_replace($_SESSION['CONFIG']['BAD_FILE_SIGNS_LETTERS'],$_SESSION['CONFIG']['OK_FILE_SIGNS_LETTERS'],$ADM_NAME)
  163. }
  164. if (empty($pass)) {
  165. $pass = $login;
  166. }
  167. $cmds = array();
  168. //$cmds[] = "{$cmdDsclAuth} -create /Users/{$login} HomeDirectory \"<home_dir><url>afp://{$this->_host}/Users</url><path>{$login}</path></home_dir>\" ";
  169. //$cmds[] = "{$cmdDsclAuth} -create /Users/{$login} NFSHomeDirectory /Network/Servers/{$this->_host}/Users/{$login} ";
  170. $cmds[] = "{$cmdDsclAuth} -create /Users/{$login} NFSHomeDirectory /Users/{$login} ";
  171. $cmds[] = "{$cmdDsclAuth} -create /Users/{$login} UserShell /bin/bash ";// TODO: bash?
  172. $cmds[] = "{$cmdDsclAuth} -create /Users/{$login} UniqueID {$uniqueID} ";
  173. $cmds[] = "{$cmdDsclAuth} -create /Users/{$login} PrimaryGroupID 20 ";// TODO: 20 maja domyslnie inne konta?
  174. $cmds[] = "{$cmdDsclAuth} -create /Users/{$login} RealName \"{$name}\" ";
  175. if (!empty($email)) $cmds[] = "{$cmdDsclAuth} -create /Users/{$login} EMailAddress {$email} ";
  176. $cmds[] = "{$cmdDsclAuth} -passwd /Users/{$login} \"{$pass}\" ";
  177. foreach ($cmds as $cmd) {
  178. $cmdOut = null; $cmdRet = null;
  179. exec($cmd, $cmdOut, $cmdRet);
  180. if ($cmdRet != 0) {
  181. $this->setError(1, "cmd failed: " . str_replace($cmdDsclAuth, "dscl __auth__ ", $cmd), '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  182. return false;
  183. }
  184. }
  185. // TODO: $cmd = "sudo createhomedir -c -u {$login} "; - zawiesza się (nie kończy działania) ale tworzy katalog domowy
  186. return true;
  187. }
  188. private function _getAdminLdap() {
  189. if (!$this->_ldapRoot) {
  190. Lib::loadClass('LDAP');
  191. $this->_ldapRoot = LDAP::getInstance();
  192. if (!$this->_ldapRoot->bindDiradmin($errorMsg)) {
  193. // $errorMsg?
  194. $this->setError(1, "cant bind as diradmin", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  195. return null;
  196. }
  197. }
  198. return $this->_ldapRoot;
  199. }
  200. public function updateUser($userName, $updateData) {
  201. if (empty($updateData)) return true;
  202. foreach ($updateData as $fldName => $val) {
  203. switch ($fldName) {
  204. case 'email':
  205. $ldap = $this->_getAdminLdap();
  206. if ($ldap) {
  207. $attr = array();
  208. $attr['mail'] = $val;
  209. $ldap->mod_replace($userName, $attr);
  210. }
  211. break;
  212. case 'name':
  213. $ldap = $this->_getAdminLdap();
  214. if ($ldap) {
  215. $attr = array();
  216. $attr['cn'] = $val;
  217. $ldap->mod_replace($userName, $attr);
  218. }
  219. break;
  220. case 'phone':
  221. $ldap = $this->_getAdminLdap();
  222. if ($ldap) {
  223. $attr = array();
  224. $attr['telephoneNumber'] = $val;
  225. $ldap->mod_replace($userName, $attr);
  226. }
  227. break;
  228. case 'employeeType':
  229. $ldap = $this->_getAdminLdap();
  230. if ($ldap) {
  231. $attr = array();
  232. $attr['employeeType'] = $val;
  233. $ldap->mod_replace($userName, $attr);
  234. }
  235. break;
  236. case 'password':
  237. if (!empty($val) && !$this->changePassword($userName, $val)) {
  238. $this->setError(1, "Nie udało się zmienić hasła dla usera '{$userName}'", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  239. }
  240. break;
  241. default:
  242. $this->setError(1, "TODO: update user {$userName} field {$fldName} to value '{$val}'", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  243. }
  244. }
  245. if ($this->hasErrors()) {
  246. $this->setError(1, "Nie udało się zaktualizować danych usera '{$userName}'", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  247. return false;
  248. }
  249. return true;
  250. }
  251. public function updateGroup($group, $updateData) {
  252. if (!$group) return false;
  253. if (empty($updateData)) return true;
  254. foreach ($updateData as $fldName => $val) {
  255. switch ($fldName) {
  256. case 'realName':
  257. $ldap = $this->_getAdminLdap();
  258. if ($ldap) {
  259. $attr = array();
  260. $attr['apple-group-realname'] = $val;
  261. $ldap->groupAttrUpdate($group->primaryKey, $attr);
  262. }
  263. break;
  264. default:
  265. $this->setError(1, "TODO: update group {$group->primaryKey} field {$fldName} to value '{$val}'", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  266. }
  267. }
  268. return true;
  269. }
  270. private function _getGroupIdFromUid($groupUid) {
  271. if (empty($groupUid)) return null;
  272. if (!is_numeric(substr($groupUid, 0, 1))) return null;
  273. $tmp = str_replace(array('-', '_'), '_', $groupUid);
  274. $tmp = explode('_', $tmp);
  275. $tmp = reset($tmp);
  276. if (!empty($tmp) && is_numeric($tmp)) {
  277. return $tmp;
  278. }
  279. return null;
  280. }
  281. /**
  282. * User group list by id.
  283. *
  284. * @param bool $fetchNested - contain all groups below connected groups and group PODMIOT from above.
  285. *
  286. * @returns array with group objects @see getGroup
  287. */
  288. public function getUserGroups($usrLogin, $fetchNested = false) {
  289. $groups = array();
  290. Lib::loadClass('UsersLdapHelper');
  291. $groupsNetwork = $this->_getUserGroupsNetwork($usrLogin);
  292. $groupsLocal = $this->_getUserGroupsLocal($usrLogin);
  293. foreach ($groupsLocal as $kGroupUid => $vGroup) {
  294. $groups[$kGroupUid] = $vGroup;
  295. }
  296. foreach ($groupsNetwork as $kGroupUid => $vGroupNetwork) {
  297. if ($vGroupNetwork->primaryKey == 'workgroup') {
  298. $groups[$vGroupNetwork->primaryKey] = $vGroupNetwork;
  299. }
  300. else if ($vGroupNetwork->zasobID > 0) {
  301. $groups[$vGroupNetwork->zasobID] = $vGroupNetwork;
  302. }
  303. }
  304. if (V::get('DBG_SU', 0, $_GET, 'int') > 2) {
  305. echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">groupsNetwork (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($groupsNetwork);echo'</pre>';
  306. echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">groupsLocal (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($groupsLocal);echo'</pre>';
  307. }
  308. return $groups;
  309. }
  310. /**
  311. * Build network group object.
  312. *
  313. * @param object $groupDB {ID, DESC} @see _getUserGroupsAll
  314. * @returns object $group @see getGroup
  315. *
  316. * Example: _buildGroupFromLdap($groupLdap) => {@see getGroup}
  317. */
  318. private function _buildGroupFromLdap($groupLdap, $fetchNested = false) {
  319. $group = new stdClass();
  320. $group->primaryKey = $groupLdap->cn;
  321. $group->realName = V::get('realName', '', $groupLdap);
  322. $group->zasobID = $this->_getGroupIdFromUid($groupLdap->cn);
  323. $group->type = 'unknown';// TODO: try to fetch from name or from ldap attribute
  324. if ($groupLdap->cn == 'workgroup') $group->type = 'network';
  325. if ($fetchNested && !empty($groupLdap->nestedGroups)) {
  326. $group->nestedGroups = $this->_fetchNestedGroupsByAppleUids($groupLdap->nestedGroups);
  327. }
  328. return $group;
  329. }
  330. private function _fetchNestedGroupsByAppleUids($appleUids) {
  331. $groups = array();
  332. if (!is_array($appleUids)) $appleUids = array($appleUids);
  333. Lib::loadClass('UsersLdapHelper');
  334. $groupsLdap = UsersLdapHelper::getGroupsByAppleUids($appleUids);
  335. foreach ($groupsLdap as $vGroupLdap) {
  336. $group = $this->_buildGroupFromLdap($vGroupLdap, $fetchNested = false);
  337. if ($group && $group->zasobID > 0) {
  338. $groups[$group->zasobID] = $group;
  339. }
  340. }
  341. return $groups;
  342. }
  343. /**
  344. * @param string $usrLogin - user login
  345. * @returns array of group objects @see getGroup
  346. */
  347. private function _getUserGroupsNetwork($usrLogin) {
  348. $groups = array();
  349. Lib::loadClass('UsersLdapHelper');
  350. $groupsNetwork = UsersLdapHelper::getUserGroups($usrLogin, 0);
  351. foreach ($groupsNetwork as $vGroupNetwork) {
  352. $groups[$vGroupNetwork->cn] = $this->_buildGroupFromLdap($vGroupNetwork);
  353. }
  354. return $groups;
  355. }
  356. /**
  357. * @param string $usrLogin - user login
  358. * @returns array of group objects @see getGroup
  359. */
  360. private function _getUserGroupsLocal($usrLogin) {
  361. $groups = array();
  362. $cmd = "groups {$usrLogin}";
  363. $cmdOut = null; $cmdRet = null;
  364. exec($cmd, $cmdOut, $cmdRet);
  365. if ($cmdRet == 0 && !empty($cmdOut[0])) {
  366. $groupsCmd = explode(' ', $cmdOut[0]);
  367. foreach ($groupsCmd as $group) {
  368. if ($this->_isGroupLocal($group)) {
  369. $groups[$group] = $this->_buildGroupLocal($group);
  370. }
  371. }
  372. }
  373. if (V::get('DBG_SU', 0, $_GET, 'int') > 1) {
  374. echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">groups (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($groups);echo'</pre>';
  375. }
  376. return $groups;
  377. }
  378. public function getUserGroupsWithNested($usrLogin) {// TODO: NOT USED
  379. $groups = array();
  380. $groupsAll = array();
  381. $cmd = "groups {$usrLogin}";
  382. $cmdOut = null; $cmdRet = null;
  383. exec($cmd, $cmdOut, $cmdRet);
  384. if ($cmdRet == 0 && !empty($cmdOut[0])) {
  385. $pominGrupy = array('staff','everyone','netaccounts');
  386. $groupsCmd = explode(' ', $cmdOut[0]);
  387. foreach ($groupsCmd as $group) {
  388. $groupsAll[] = $group;
  389. $groupID = $this->_getGroupIdFromUid($group);
  390. if (!empty($groupID)) {
  391. $groups[$groupID] = $group;
  392. }
  393. else if ('workgroup' == $group) {
  394. $groups[$group] = $group;
  395. }
  396. else if (substr($group, 0, strlen('com.apple.access_')) == 'com.apple.access_') {
  397. $groups[$group] = $group;
  398. }
  399. }
  400. }
  401. if (V::get('DBG_SU', 0, $_GET, 'int') > 1) {
  402. echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">groupsAll (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($groupsAll);echo'</pre>';
  403. echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">groups (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($groups);echo'</pre>';
  404. }
  405. return $groups;
  406. }
  407. private function _groupNameRemoveID($groupName) {
  408. if (substr($groupName, 0, 1) == '[' && strpos($groupName, ']')) {
  409. $groupName = substr($groupName, strpos($groupName, ']') + 1);
  410. $groupName = trim($groupName);
  411. }
  412. return $groupName;
  413. }
  414. private function _generateGroupName($id, $groupName) {
  415. $groupNameShort = $groupName;
  416. $groupNameShort = $this->_groupNameRemoveID($groupNameShort);
  417. // TODO: polish chars - replace to ascii?
  418. $groupNameShort = preg_replace('/[^a-zA-Z0-9_-]+/', '_', $groupNameShort);
  419. // TODO: skrócić nazwę bo nie widać w aplikacji Server, np.
  420. // RealName: [5] Typowe_stanowisko_obs_uguj_ce_Obieg_Dokument_w_do_implementacji_po_instalacji_systemu
  421. // w apliakcji Server pokauje tylko "[5] ", tak samo w edycji
  422. return "[{$id}] {$groupNameShort}";
  423. }
  424. private function _generateGroupUid($id, $groupName) {
  425. $groupNameShort = $groupName;
  426. $groupNameShort = $this->_groupNameRemoveID($groupNameShort);
  427. $groupNameShort = str_replace(' ', '_', $groupNameShort);
  428. $groupNameShort = preg_replace('/[^a-zA-Z0-9_-]+/', '_', $groupNameShort);
  429. if (strlen($groupNameShort) > 30) {
  430. $groupNameShort = substr($groupNameShort, 0, 30);
  431. }
  432. return "{$id}_{$groupNameShort}";
  433. }
  434. /**
  435. * Create group.
  436. *
  437. * @param object $group @see getGroup
  438. * @returns bool
  439. *
  440. * @require $group->zasobID - Allowed only network group based on Zasob.
  441. */
  442. public function createGroup($group) {
  443. // TEST: $ dscl /LDAPv3/127.0.0.1 -list /Groups PrimaryGroupID
  444. if ($group->zasobID <= 0) {
  445. $this->setError(1, "Error: create group {$group->primaryKey} {$group->realName} - missing zasobID", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  446. return false;
  447. }
  448. $groupName = $this->_generateGroupName($group->zasobID, $group->realName);
  449. $groupUidGenerated = $this->_generateGroupUid($group->zasobID, $group->realName);
  450. /*
  451. * dseditgroup -o create -n /LDAPv3/ldap.company.com -u {$this->_rootUser} -P {$this->_rootPass} -r "Extra Group" -c "a nice comment" -k "some keyword" extragroup
  452. * The group extragroup is created from the node /LDAPv3/ldap.company.com with the realname, comment,
  453. * timetolive (instead of default of 14400 = 4 hours), and keyword atttribute values given above if the user
  454. * myusername has supplied a correct password and has write access.
  455. *
  456. * -r realname
  457. * This is a simple text string.
  458. *
  459. * -t recordtype
  460. * The type of the record to be added to or deleted from the group specified by groupname. Valid values are user, computer, group, or computergroup.
  461. *
  462. */
  463. $cmd = "dseditgroup -o create -n /LDAPv3/127.0.0.1 -u {$this->_rootUser} -P {$this->_rootPass} -r \"{$groupName}\" {$groupUidGenerated}";
  464. $cmdOut = null; $cmdRet = null;
  465. exec($cmd, $cmdOut, $cmdRet);
  466. if(V::get('DBG_SU', 0, $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">create group cmd('.str_replace($this->_rootPass, '***', $cmd).') ret('.$cmdRet.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdOut);echo'</pre>';}
  467. if ($cmdRet !== 0) {
  468. return false;
  469. }
  470. //$command8 = "dscl -u {$user} -P {$pass} /LDAPv3/127.0.0.1 -append /Groups/{$groupUid} GroupMembership {$ACCOUNT} ";
  471. //$command8 = "dscl -u {$user} -P {$pass} /LDAPv3/127.0.0.1 -delete /Groups/{$groupUid} GroupMembership {$ACCOUNT} ";
  472. //$command1 = "dscl -u {$user} -P {$pass} /LDAPv3/127.0.0.1 -create /Groups/{$groupUid} PrimaryGroupID {$PrimaryGroupID} ";
  473. //$command2 = "dscl -u {$user} -P {$pass} /LDAPv3/127.0.0.1 -create /Groups/{$groupUid} RealName \"{$groupName}\" ";
  474. return true;
  475. }
  476. private function _isGroupLocal($groupUid) {
  477. $localGroups = array();
  478. $localGroups[] = 'com.apple.access_mail';
  479. $localGroups[] = 'com.apple.access_addressbook';
  480. $localGroups[] = 'com.apple.access_calendar';
  481. $localGroups[] = 'com.apple.access_smb';
  482. $localGroups[] = 'com.apple.access_afp';
  483. $localGroups[] = 'com.apple.access_vpn';
  484. $localGroups[] = 'com.apple.access_chat';
  485. //$localGroups[] = 'workgroup'; - Network Group
  486. return in_array($groupUid, $localGroups);
  487. }
  488. /**
  489. * Add local group member.
  490. *
  491. * @param string $usrLogin - user login
  492. * @param object $group - @see getGroup
  493. * @returns bool
  494. *
  495. * @require sudoers dla _www
  496. *
  497. * cat /etc/sudoers |grep "'.$ADMIN_USERNAME.' ALL = NOPASSWD: /usr/bin/su" || echo "'.$ADMIN_USERNAME.' ALL = NOPASSWD: /usr/bin/su " >> /etc/sudoers;
  498. * cat /etc/sudoers |grep "'.$ADMIN_USERNAME.' ALL = NOPASSWD: /usr/bin/su"
  499. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /Applications/Server.app/Contents/ServerRoot/usr/sbin/calendarserver_manage_principals" || echo "_www ALL = NOPASSWD: /Applications/Server.app/Contents/ServerRoot/usr/sbin/calendarserver_manage_principals " >> /etc/sudoers;
  500. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /Applications/Server.app/Contents/ServerRoot/usr/sbin/calendarserver_manage_principals"';
  501. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/bin/dscl" || echo "_www ALL = NOPASSWD: /usr/bin/dscl " >> /etc/sudoers;
  502. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/bin/dscl";
  503. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/bin/pwpolicy" || echo "_www ALL = NOPASSWD: /usr/bin/pwpolicy" >> /etc/sudoers;
  504. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/bin/pwpolicy";
  505. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/sbin/createhomedir" || echo "_www ALL = NOPASSWD: /usr/sbin/createhomedir" >> /etc/sudoers;
  506. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/sbin/createhomedir";
  507. *
  508. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/sbin/dseditgroup" || echo "_www ALL = NOPASSWD: /usr/sbin/dseditgroup" >> /etc/sudoers;
  509. * cat /etc/sudoers |grep "_www ALL = NOPASSWD: /usr/sbin/dseditgroup";
  510. */
  511. private function _addUserGroupLocal($usrLogin, $group) {
  512. if (!$group || empty($group->primaryKey) || empty($usrLogin)) return false;
  513. $groupUid = $group->primaryKey;
  514. $cmd = "sudo dscl /Local/Default -append /Groups/{$groupUid} GroupMembership {$usrLogin} ";
  515. $cmdOut = null; $cmdRet = null;
  516. exec($cmd, $cmdOut, $cmdRet);
  517. if ($cmdRet != 0) {
  518. $this->setError(1, "Error: add user '{$usrLogin}' to network group '{$groupUid}' ", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  519. return false;
  520. }
  521. return true;
  522. }
  523. /**
  524. * Remove local group member.
  525. *
  526. * @param string $usrLogin - user login
  527. * @param object $group - @see getGroup
  528. * @returns bool
  529. */
  530. private function _removeUserGroupLocal($usrLogin, $group) {
  531. if (!$group || empty($group->primaryKey) || empty($usrLogin)) return false;
  532. $groupUid = $group->primaryKey;
  533. //$cmd = "sudo dscl /Local/Default -delete /Groups/{$groupUid} GroupMembership {$usrLogin} 2>&1 ";
  534. //$cmd = "dseditgroup -o edit -n /Local/Default -u diradmin -p ... -d username -t user {$groupUid} ";
  535. $cmd = "sudo dseditgroup -o edit -n /Local/Default -d {$usrLogin} -t user {$groupUid} 2>&1 ";
  536. // The group extragroup from the node /LDAPv3/ldap.company.com will have the username deleted if the correct
  537. // password is presented interactively for the user myusername which also need to have write access.
  538. // -t recordtype type of the record to add or delete
  539. // -d recordname name of the record to delete
  540. $cmdOut = null; $cmdRet = null;
  541. exec($cmd, $cmdOut, $cmdRet);
  542. if(V::get('DBG_SU', 0, $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmd('.$cmd.') ret('.$cmdRet.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdOut);echo'</pre>';}
  543. if ($cmdRet != 0) {
  544. $this->setError(1, "Error: remove user '{$usrLogin}' from local group '{$groupUid}' ", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  545. //return false;
  546. }
  547. return true;
  548. }
  549. public function findGroupUidDscl($groupUid) {// not used @see findGroupUid
  550. $groupRealUid = null;
  551. $cmd = "dscl /LDAPv3/127.0.0.1 -list /Groups | grep '^{$groupUid}' ";
  552. $cmdOut = null; $cmdRet = null;
  553. exec($cmd, $cmdOut, $cmdRet);
  554. if ($cmdRet != 0) {
  555. $this->setError(1, "cmd failed - search for group by uid", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  556. return false;
  557. }
  558. if (!empty($cmdOut)) {
  559. foreach ($cmdOut as $vGroupUid) {
  560. $vGroupID = $this->_getGroupIdFromUid($vGroupUid);
  561. if (!empty($vGroupID) && $vGroupID == $groupUid) {
  562. $groupRealUid = $vGroupUid;
  563. break;
  564. }
  565. }
  566. }
  567. return $groupRealUid;
  568. }
  569. public function findGroupUidLdap($groupUid) {
  570. $groupRealUid = null;
  571. Lib::loadClass('UsersLdapHelper');
  572. $groups = UsersLdapHelper::getGroupsByID($groupUid);
  573. if (count($groups) == 1) {
  574. $groupRealUid = reset($groups)->cn;
  575. }
  576. return $groupRealUid;
  577. }
  578. public function findGroupUid($groupUid) {
  579. return $this->findGroupUidLdap($groupUid);
  580. }
  581. /**
  582. * Add network group member.
  583. *
  584. * @param string $usrLogin - user login
  585. * @param object $group - @see getGroup
  586. * @returns bool
  587. */
  588. private function _addUserGroupNetwork($usrLogin, $group) {
  589. if (!$group || empty($group->primaryKey) || empty($usrLogin)) return false;
  590. $groupUid = $group->primaryKey;
  591. $groupName = $group->realName;
  592. $groupRealUid = '';
  593. if ($group->type == 'network') {
  594. $groupRealUid = $group->primaryKey;// workgroup
  595. }
  596. else if (is_numeric($groupUid)) {
  597. $groupRealUid = $this->findGroupUid($groupUid);
  598. }
  599. if (!$groupRealUid) {
  600. if ($group->type == 'network') {
  601. $this->setError(1, "Error: access denied to create network group {$group->primaryKey}", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  602. return false;
  603. }
  604. else if ($group->type == 'local') {
  605. $this->setError(1, "Error: access denied to create local group {$group->primaryKey}", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  606. return false;
  607. }
  608. $created = $this->createGroup($group);
  609. if (!$created) {
  610. $this->setError(1, "Error: create group {$group->primaryKey} {$group->realName}", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  611. return false;
  612. }
  613. }
  614. $cmdDsclAuth = "dscl -u {$this->_rootUser} -P {$this->_rootPass} /LDAPv3/127.0.0.1 ";
  615. $cmd = "{$cmdDsclAuth} -append /Groups/{$groupRealUid} GroupMembership {$usrLogin} ";
  616. $cmdOut = null; $cmdRet = null;
  617. exec($cmd, $cmdOut, $cmdRet);
  618. if ($cmdRet != 0) {// TODO: may return 62 - user already in this group
  619. $this->setError(1, "Error: add user '{$usrLogin}' to network group '{$groupRealUid}' ", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  620. return false;
  621. }
  622. return true;
  623. }
  624. /**
  625. * Remove network group member.
  626. *
  627. * @param string $usrLogin - user login
  628. * @param object $group - @see getGroup
  629. * @returns bool
  630. */
  631. private function _removeUserGroupNetwork($usrLogin, $group) {
  632. if (!$group || empty($group->primaryKey) || empty($usrLogin)) return false;
  633. $groupUid = $group->primaryKey;
  634. $cmdDsclAuth = "dscl -u {$this->_rootUser} -P {$this->_rootPass} /LDAPv3/127.0.0.1 ";
  635. $cmd = "{$cmdDsclAuth} -delete /Groups/{$groupUid} GroupMembership {$usrLogin} ";
  636. $cmdOut = null; $cmdRet = null;
  637. exec($cmd, $cmdOut, $cmdRet);
  638. if ($cmdRet != 0) {
  639. $this->setError(1, "Error: remove user '{$usrLogin}' from network group '{$groupUid}' ", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  640. //return false;// TODO: test
  641. }
  642. return true;
  643. }
  644. /**
  645. * Add group member.
  646. *
  647. * @param string $usrLogin - user login
  648. * @param object $group - @see getGroup
  649. * @returns bool
  650. */
  651. public function addUserGroup($usrLogin, $group) {
  652. // $groupUid, $groupName
  653. if ($group->type == 'local') {
  654. return $this->_addUserGroupLocal($usrLogin, $group);
  655. }
  656. else {
  657. return $this->_addUserGroupNetwork($usrLogin, $group);
  658. }
  659. return false;
  660. }
  661. /**
  662. * Remove group member.
  663. *
  664. * @param string $usrLogin - user login
  665. * @param object $group - @see getGroup
  666. * @returns bool
  667. */
  668. public function removeUserGroup($usrLogin, $group) {
  669. if ($group->type == 'local') {
  670. return $this->_removeUserGroupLocal($usrLogin, $group);
  671. }
  672. else {
  673. return $this->_removeUserGroupNetwork($usrLogin, $group);
  674. }
  675. return false;
  676. }
  677. public function addNestedGroup($groupID, $nestedGroup) {
  678. if ($groupID <= 0) return false;
  679. if ($nestedGroup->zasobID <= 0) return false;
  680. $group = $this->_getGroup($groupID);
  681. $groupNested = $this->_getGroup($nestedGroup->zasobID);
  682. if (!$group || !$groupNested) {
  683. return false;
  684. }
  685. $groupToAdd = $groupNested->primaryKey;
  686. $groupName = $group->primaryKey;
  687. // put a group called {$groupToAdd} into the {$groupName} group
  688. $cmd = "dseditgroup -o edit -n /LDAPv3/127.0.0.1 -u {$this->_rootUser} -P {$this->_rootPass} -a {$groupToAdd} -t group {$groupName}";
  689. $cmdOut = null; $cmdRet = null;
  690. exec($cmd, $cmdOut, $cmdRet);
  691. if ($cmdRet != 0) {
  692. if(V::get('DBG_SU', 0, $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmd('.str_replace($this->_rootPass, '***', $cmd).') ret('.$cmdRet.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdOut);echo'</pre>';}
  693. $this->setError(1, "Error: add nested group '{$groupToAdd}' to group '{$groupName}' ", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  694. return false;
  695. }
  696. return true;
  697. }
  698. public function removeNestedGroup($groupID, $nestedGroup) {
  699. if ($groupID <= 0) return false;
  700. if ($nestedGroup->zasobID <= 0) return false;
  701. $group = $this->_getGroup($groupID);
  702. $groupNested = $this->_getGroup($nestedGroup->zasobID);
  703. if (!$group || !$groupNested) {
  704. return false;
  705. }
  706. $groupToRemove = $groupNested->primaryKey;
  707. $groupName = $group->primaryKey;
  708. // put a group called {$groupToAdd} into the {$groupName} group
  709. $cmd = "dseditgroup -o edit -n /LDAPv3/127.0.0.1 -u {$this->_rootUser} -P {$this->_rootPass} -d {$groupToRemove} -t group {$groupName}";
  710. $cmdOut = null; $cmdRet = null;
  711. exec($cmd, $cmdOut, $cmdRet);
  712. if ($cmdRet != 0) {
  713. if(V::get('DBG_SU', 0, $_GET, 'int') > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">cmd('.str_replace($this->_rootPass, '***', $cmd).') ret('.$cmdRet.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($cmdOut);echo'</pre>';}
  714. $this->setError(1, "Error: remove nested group '{$groupToRemove}' from group '{$groupName}' ", '(' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . ')');
  715. return false;
  716. }
  717. return true;
  718. }
  719. public function changePassword($usrLogin, $passwd) {
  720. $cmdDsclAuth = "dscl -u {$this->_rootUser} -P {$this->_rootPass} /LDAPv3/127.0.0.1 ";
  721. $cmd = "{$cmdDsclAuth} -passwd /Users/{$usrLogin} \"{$passwd}\" ";
  722. $cmdOut = null; $cmdRet = null;
  723. exec($cmd, $cmdOut, $cmdRet);
  724. if ($cmdRet != 0) {
  725. return false;
  726. }
  727. return true;
  728. }
  729. }