UserStorageMacOSX.php 31 KB

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