UserStorageMacOSX.php 31 KB

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