introduces new param --onlyusers for ldap sync
[tine20] / tine20 / Setup / Frontend / Cli.php
1 <?php
2 /**
3  * Tine 2.0
4  * @package     Tinebase
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @author      Philipp Schüle <p.schuele@metaways.de>
7  * @copyright   Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
8  * 
9  * @todo        add ext check again
10  */
11
12 /**
13  * cli server
14  *
15  * This class handles all requests from cli scripts
16  *
17  * @package     Tinebase
18  */
19 class Setup_Frontend_Cli
20 {
21     /**
22      * the internal name of the application
23      *
24      * @var string
25      */
26     protected $_appname = 'Setup';
27
28     /**
29      * authentication
30      *
31      * @param string $_username
32      * @param string $_password
33      * 
34      * @return boolean
35      */
36     public function authenticate($_username, $_password)
37     {
38         return false;
39     }
40     
41     /**
42      * handle request (call -ApplicationName-_Cli.-MethodName- or -ApplicationName-_Cli.getHelp)
43      *
44      * @param Zend_Console_Getopt $_opts
45      * @param boolean $exitAfterHandle
46      * @return void
47      */
48     public function handle(Zend_Console_Getopt $_opts, $exitAfterHandle = true)
49     {
50         Setup_Core::set(Setup_Core::USER, 'setupuser');
51         
52         $result = 0;
53         if (isset($_opts->install)) {
54             $this->_install($_opts);
55         } elseif(isset($_opts->update)) {
56             $result = $this->_update($_opts);
57         } elseif(isset($_opts->uninstall)) {
58             $this->_uninstall($_opts);
59         } elseif(isset($_opts->list)) {
60             $this->_listInstalled();
61         } elseif(isset($_opts->sync_accounts_from_ldap)) {
62             $this->_importAccounts($_opts);
63         } elseif(isset($_opts->sync_passwords_from_ldap)) {
64             $this->_syncPasswords($_opts);
65         } elseif(isset($_opts->egw14import)) {
66             $this->_egw14Import($_opts);
67         } elseif(isset($_opts->check_requirements)) {
68             $this->_checkRequirements($_opts);
69         } elseif(isset($_opts->setconfig)) {
70             $this->_setConfig($_opts);
71         } elseif(isset($_opts->create_admin)) {
72             $this->_createAdminUser($_opts);
73         } elseif(isset($_opts->getconfig)) {
74             $this->_getConfig($_opts);
75         }
76         
77         if ($exitAfterHandle) {
78             exit($result);
79         }
80     }
81     
82     /**
83      * install new applications
84      *
85      * @param Zend_Console_Getopt $_opts
86      */
87     protected function _install(Zend_Console_Getopt $_opts)
88     {
89         $controller = Setup_Controller::getInstance();
90         
91         if($_opts->install === true) {
92             $applications = $controller->getInstallableApplications();
93             $applications = array_keys($applications);
94         } else {
95             $applications = array();
96             $applicationNames = explode(',', $_opts->install);
97             foreach($applicationNames as $applicationName) {
98                 $applicationName = ucfirst(trim($applicationName));
99                 try {
100                     $controller->getSetupXml($applicationName);
101                     $applications[] = $applicationName;
102                 } catch (Setup_Exception_NotFound $e) {
103                     echo "Application $applicationName not found! Skipped...\n";
104                 }
105             }
106         }
107         
108         $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
109         $this->_promptRemainingOptions($applications, $options);
110         
111         $controller->installApplications($applications, $options);
112         
113         if ((isset($options['acceptedTermsVersion']) || array_key_exists('acceptedTermsVersion', $options))) {
114             Setup_Controller::getInstance()->saveAcceptedTerms($options['acceptedTermsVersion']);
115         }
116         
117         echo "Successfully installed " . count($applications) . " applications.\n";
118     }
119
120     /**
121      * prompt remaining options
122      * 
123      * @param array $_applications
124      * @param array $_options
125      * @return void
126      * 
127      * @todo add required version server side
128      */
129     protected function _promptRemainingOptions($_applications, &$_options) {
130         if (in_array('Tinebase', $_applications)) {
131             
132             if (! isset($_options['acceptedTermsVersion'])) {
133                 fwrite(STDOUT, PHP_EOL . file_get_contents(dirname(dirname(dirname(__FILE__))) . '/LICENSE' ));
134                 $licenseAnswer = Tinebase_Server_Cli::promptInput('I have read the license agreement and accept it (type "yes" to accept)');
135                 
136                 
137                 fwrite(STDOUT, PHP_EOL . file_get_contents(dirname(dirname(dirname(__FILE__))) . '/PRIVACY' ));
138                 $privacyAnswer = Tinebase_Server_Cli::promptInput('I have read the privacy agreement and accept it (type "yes" to accept)');
139             
140                 if (! (strtoupper($licenseAnswer) == 'YES' && strtoupper($privacyAnswer) == 'YES')) {
141                     echo "error: you need to accept the terms! exiting \n";
142                     exit (1);
143                 }
144                 
145                 $_options['acceptedTermsVersion'] = 1;
146             }
147             
148             
149             // initial username
150             if (! isset($_options['adminLoginName'])) {
151                 $_options['adminLoginName'] = Tinebase_Server_Cli::promptInput('Inital Admin Users Username');
152                 if (! $_options['adminLoginName']) {
153                     echo "error: username must be given! exiting \n";
154                     exit (1);
155                 }
156             }
157             
158             // initial password
159             if (! isset($_options['adminPassword'])) {
160                 $_options['adminPassword'] = $this->_promptPassword();
161             }
162         }
163     }
164     
165     /**
166      * prompt password
167      * 
168      * @return string
169      */
170     protected function _promptPassword()
171     {
172         $password1 = Tinebase_Server_Cli::promptInput('Admin user password', TRUE);
173         if (! $password1) {
174             echo "Error: Password must not be empty! Exiting ... \n";
175             exit (1);
176         }
177         $password2 = Tinebase_Server_Cli::promptInput('Confirm password', TRUE);
178         if ($password1 !== $password2) {
179             echo "Error: Passwords do not match! Exiting ... \n";
180             exit (1);
181         }
182         
183         return $password1;
184     }
185     
186     /**
187      * update existing applications
188      *
189      * @param Zend_Console_Getopt $_opts
190      * @return integer
191      */
192     protected function _update(Zend_Console_Getopt $_opts)
193     {
194         $maxLoops = 50;
195         do {
196             $result = $this->_updateApplications();
197             if ($_opts->v && ! empty($result['messages'])) {
198                 echo "Messages:\n";
199                 foreach ($result['messages'] as $message) {
200                     echo "  " . $message . "\n";
201                 }
202             }
203             $maxLoops--;
204         } while ($result['updated'] > 0 && $maxLoops > 0);
205         
206         return ($maxLoops > 0) ? 0 : 1;
207     }
208     
209     /**
210      * update all applications
211      * 
212      * @return array
213      */
214     protected function _updateApplications()
215     {
216         $controller = Setup_Controller::getInstance();
217         $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
218         
219         foreach ($applications as $key => &$application) {
220             try {
221                 if (! $controller->updateNeeded($application)) {
222                     unset($applications[$key]);
223                 }
224             } catch (Setup_Exception_NotFound $e) {
225                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
226                     . ' Failed to check if an application needs an update:' . $e->getMessage());
227                 unset($applications[$key]);
228             }
229         }
230
231         $result = array();
232         if (count($applications) > 0) {
233             $result = $controller->updateApplications($applications);
234             echo "Updated " . $result['updated'] . " application(s).\n";
235         } else {
236             $result['updated'] = 0;
237         }
238         
239         return $result;
240     }
241
242     /**
243      * uninstall applications
244      *
245      * @param Zend_Console_Getopt $_opts
246      */
247     protected function _uninstall(Zend_Console_Getopt $_opts)
248     {
249         $controller = Setup_Controller::getInstance();
250         
251         if($_opts->uninstall === true) {
252             $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
253         } else {
254             $applications = new Tinebase_Record_RecordSet('Tinebase_Model_Application');
255             $applicationNames = explode(',', $_opts->uninstall);
256             foreach($applicationNames as $applicationName) {
257                 $applicationName = ucfirst(trim($applicationName));
258                 try {
259                     $application = Tinebase_Application::getInstance()->getApplicationByName($applicationName);
260                     $applications->addRecord($application);
261                 } catch (Tinebase_Exception_NotFound $e) {
262                 }
263             }
264         }
265         
266         $controller->uninstallApplications($applications->name);
267
268         echo "Successfully uninstalled " . count($applications) . " applications.\n";
269     }
270
271     /**
272      * list installed apps
273      */
274     protected function _listInstalled()
275     {
276         try {
277             $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
278         } catch (Zend_Db_Statement_Exception $e) {
279             echo "No applications installed\n";
280             return;
281         }
282         
283         echo "Currently installed applications:\n";
284         foreach($applications as $application) {
285             echo "* $application\n";
286         }
287     }
288     
289     /**
290      * import accounts from ldap
291      *
292      * @param Zend_Console_Getopt $_opts
293      */
294     protected function _importAccounts(Zend_Console_Getopt $_opts)
295     {
296         // disable timelimit during import of user accounts
297         Setup_Core::setExecutionLifeTime(0);
298         
299         // import groups
300         if (! $_opts->onlyusers) {
301             Tinebase_Group::syncGroups();
302         }
303         
304         // import users
305         $options = array('syncContactData' => TRUE);
306         if ($_opts->dbmailldap) {
307             $options['ldapplugins'] = array(
308                 new Tinebase_EmailUser_Imap_LdapDbmailSchema(),
309                 new Tinebase_EmailUser_Smtp_LdapDbmailSchema()
310             );
311         }
312         Tinebase_User::syncUsers($options);
313     }
314     
315     /**
316      * sync ldap passwords
317      * 
318      * @param Zend_Console_Getopt $_opts
319      */
320     protected function _syncPasswords(Zend_Console_Getopt $_opts)
321     {
322         Tinebase_User::syncLdapPasswords();
323     }
324     
325     /**
326      * import from egw14
327      * 
328      * @param Zend_Console_Getopt $_opts
329      */
330     protected function _egw14Import(Zend_Console_Getopt $_opts)
331     {
332         $args = $_opts->getRemainingArgs();
333         
334         if (count($args) < 1 || ! is_readable($args[0])) {
335             echo "can not open config file \n";
336             echo "see tine20.org/wiki/EGW_Migration_Howto for details \n\n";
337             echo "usage: ./setup.php --egw14import /path/to/config.ini (see Tinebase/Setup/Import/Egw14/config.ini)\n\n";
338             exit(1);
339         }
340         
341         try {
342             $config = new Zend_Config(array(), TRUE);
343             $config->merge(new Zend_Config_Ini($args[0]));
344             $config = $config->merge($config->all);
345         } catch (Zend_Config_Exception $e) {
346             fwrite(STDERR, "Error while parsing config file($args[0]) " .  $e->getMessage() . PHP_EOL);
347             exit(1);
348         }
349         
350         $writer = new Zend_Log_Writer_Stream('php://output');
351         $logger = new Zend_Log($writer);
352         
353         $filter = new Zend_Log_Filter_Priority((int) $config->loglevel);
354         $logger->addFilter($filter);
355         
356         $importer = new Tinebase_Setup_Import_Egw14($config, $logger);
357         $importer->import();
358     }
359     
360     /**
361      * do the environment check
362      *
363      * @return array
364      */
365     protected function _checkRequirements(Zend_Console_Getopt $_opts)
366     {
367         $results = Setup_Controller::getInstance()->checkRequirements();
368         if ($results['success']) {
369           echo "OK - All requirements are met\n";
370         } else {
371           echo "ERRORS - The following requirements are not met: \n";
372           foreach ($results['results'] as $result) {
373             if (!empty($result['message'])) {
374               echo "- " . strip_tags($result['message']) . "\n";
375             }
376           }
377         }
378     }
379     
380     /**
381      * set config
382      *
383      * @return array
384      */
385     protected function _setConfig(Zend_Console_Getopt $_opts)
386     {
387         $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
388         $errors = array();
389         if (empty($options['configkey'])) {
390             $errors[] = 'Missing argument: configkey';
391         }
392         if (! isset($options['configvalue'])) {
393             $errors[] = 'Missing argument: configvalue';
394         }
395         $configKey = (string)$options['configkey'];
396         $configValue = (is_json($options['configvalue'])) ? Zend_Json::decode($options['configvalue']) : self::parseConfigValue($options['configvalue']);
397         $applicationName = (isset($options['app'])) ? $options['app'] : 'Tinebase';
398         
399         if (empty($errors)) {
400            Setup_Controller::getInstance()->setConfigOption($configKey, $configValue, $applicationName);
401            echo "OK - Updated configuration option $configKey for application $applicationName\n";
402         } else {
403             echo "ERRORS - The following errors occured: \n";
404             foreach ($errors as $error) {
405                 echo "- " . $error . "\n";
406             }
407         }
408     }
409     
410     /**
411      * get config
412      *
413      */
414     protected function _getConfig(Zend_Console_Getopt $_opts)
415     {
416         $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
417         $applicationName = (isset($options['app'])) ? $options['app'] : 'Tinebase';
418         $config = Tinebase_Config_Abstract::factory($applicationName);
419         
420         $errors = array();
421         if (empty($options['configkey'])) {
422             $errors[] = 'Missing argument: configkey';
423             $errors[] = 'Available config settings:';
424             $errors[] = print_r($config::getProperties(), true);
425         }
426         $configKey = (string)$options['configkey'];
427         
428         if (empty($errors)) {
429             $value = $config->get($configKey);
430             $value = is_string($value) ? $value : Zend_Json::encode($value);
431             echo $value . " \n";
432         } else {
433             echo "ERRORS - The following errors occured: \n";
434             foreach ($errors as $error) {
435                 echo "- " . $error . "\n";
436             }
437         }
438     }
439     
440     /**
441      * create admin user / activate existing user / allow to reset password
442      * 
443      * @param Zend_Console_Getopt $_opts
444      * 
445      * @todo check role by rights and not by name
446      * @todo replace echos with stdout logger
447      */
448     protected function _createAdminUser(Zend_Console_Getopt $_opts)
449     {
450         if (! Setup_Controller::getInstance()->isInstalled('Tinebase')) {
451             die('Install Tinebase first.');
452         }
453         
454         echo "Please enter a username. If the user already exists, he is reactivated and you can reset the password.\n";
455         $username = Tinebase_Server_Cli::promptInput('Username');
456         $tomorrow = Tinebase_DateTime::now()->addDay(1);
457         
458         try {
459             $user = Tinebase_User::getInstance()->getFullUserByLoginName($username);
460             echo "User $username already exists.\n";
461             Tinebase_User::getInstance()->setStatus($user->getId(), Tinebase_Model_User::ACCOUNT_STATUS_ENABLED);
462             echo "Activated admin user '$username'.\n";
463             
464             $expire = Tinebase_Server_Cli::promptInput('Should the admin user expire tomorrow (default: "no", "y" or "yes" for expiry)?');
465             if ($expire === 'y' or $expire === 'yes') {
466                 Tinebase_User::getInstance()->setExpiryDate($user->getId(), $tomorrow);
467                 echo "User expires tomorrow at $tomorrow.\n";
468             }
469             
470             $resetPw = Tinebase_Server_Cli::promptInput('Do you want to reset the password (default: "no", "y" or "yes" for reset)?');
471             if ($resetPw === 'y' or $resetPw === 'yes') {
472                 $password = $this->_promptPassword();
473                 Tinebase_User::getInstance()->setPassword($user, $password);
474                 echo "User password has been reset.\n";
475             }
476             
477             $this->_checkAdminGroupMembership($user);
478             $this->_checkAdminRole($user);
479             
480         } catch (Tinebase_Exception_NotFound $tenf) {
481             // create new admin user that expires tomorrow
482             $password = $this->_promptPassword();
483             Tinebase_User::createInitialAccounts(array(
484                 'adminLoginName' => $username,
485                 'adminPassword'  => $password,
486                 'expires'        => $tomorrow,
487             ));
488             echo "Created new admin user '$username' that expires tomorrow.\n";
489         }
490     }
491
492     /**
493      * check admin group membership
494      * 
495      * @param Tinebase_Model_FullUser $user
496      */
497     protected function _checkAdminGroupMembership($user)
498     {
499         $adminGroup = Tinebase_Group::getInstance()->getDefaultAdminGroup();
500         $memberships = Tinebase_Group::getInstance()->getGroupMemberships($user);
501         if (! in_array($adminGroup->getId(), $memberships)) {
502             try {
503                 Tinebase_Group::getInstance()->addGroupMember($adminGroup, $user);
504                 echo "Added user to default admin group\n";
505                 // @todo clear roles/groups cache
506             } catch (Exception $e) {
507                 Tinebase_Exception::log($e);
508                 echo "Could not add user to default admin group: " . $e->getMessage();
509             }
510         }
511     }
512     
513     /**
514      * check admin role membership
515      * 
516      * @param Tinebase_Model_FullUser $user
517      */
518     protected function _checkAdminRole($user)
519     {
520         $roleMemberships = Tinebase_Acl_Roles::getInstance()->getRoleMemberships($user->getId());
521         $adminRoleFound = FALSE;
522         foreach ($roleMemberships as $roleId) {
523             $role = Tinebase_Acl_Roles::getInstance()->getRoleById($roleId);
524             if ($role->name === 'admin role') {
525                 $adminRoleFound = TRUE;
526                 //print_r(Tinebase_Acl_Roles::getInstance()->getRoleRights($role->getId()));
527                 break;
528             }
529         }
530         
531         if (! $adminRoleFound || ! Tinebase_Acl_Roles::getInstance()->hasRight('Admin', $user->getId(), Tinebase_Acl_Rights::ADMIN)) {
532             echo "Admin role not found for user " . $user->accountLoginName . ".\n";
533             $adminRole = new Tinebase_Model_Role(array(
534                 'name'                  => 'admin role',
535                 'description'           => 'admin role for tine. this role has all rights per default.',
536             ));
537             $adminRole = Tinebase_Acl_Roles::getInstance()->createRole($adminRole);
538             Tinebase_Acl_Roles::getInstance()->setRoleMembers($adminRole->getId(), array(
539                 array(
540                     'id'    => $user->getId(),
541                     'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, 
542                 )
543             ));
544             
545             // add all rights for all apps
546             $enabledApps = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED);
547             $roleRights = array();
548             foreach ($enabledApps as $application) {
549                 $allRights = Tinebase_Application::getInstance()->getAllRights($application->getId());
550                 foreach ($allRights as $right) {
551                     $roleRights[] = array(
552                         'application_id' => $application->getId(),
553                         'right'          => $right,
554                     );
555                 }
556             }
557             Tinebase_Acl_Roles::getInstance()->setRoleRights($adminRole->getId(), $roleRights);
558             
559             echo "Created admin role for user " . $user->accountLoginName . ".\n";
560             // @todo clear roles/groups cache
561         }
562     }
563     
564     /**
565      * parse options
566      * 
567      * @param string $_value
568      * @return array|string
569      */
570     public static function parseConfigValue($_value)
571     {
572         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_value, TRUE));
573         
574         $result = array(
575             'active' => 1
576         );
577
578         // keep spaces, \: and \,
579         $_value = preg_replace(array('/ /', '/\\\:/', '/\\\,/', '/\s*/'), array('§', '@', ';', ''), $_value);
580         
581         $parts = explode(',', $_value);
582         
583         foreach ($parts as $part) {
584             $part = str_replace(';', ',', $part);
585             $part = str_replace('§', ' ', $part);
586             $part = str_replace('@', ':', $part);
587             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $part);
588             if (strpos($part, '_') !== FALSE) {
589                 list($key, $sub) = preg_split('/_/', $part, 2);
590                 if (preg_match('/:/', $sub)) {
591                     list($subKey, $value) = explode(':', $sub);
592                     $result[$key][$subKey] = $value;
593                 } else {
594                     // might be a '_' in the value
595                     if (preg_match('/:/', $part)) {
596                         $exploded = explode(':', $part);
597                         $key = array_shift($exploded);
598                         $result[$key] = implode(':', $exploded);
599                     } else {
600                         throw new Timetracker_Exception_UnexpectedValue('You have an error in the config syntax (":" expected): ' . $part);
601                     }
602                 }
603             } else {
604                 if (strpos($part, ':') !== FALSE) {
605                     list($key, $value) = preg_split('/:/', $part, 2);
606                     $result[$key] = $value;
607                 } else {
608                     $result = $part;
609                 }
610             }
611         }
612
613         return $result;
614     }
615     
616     /**
617      * parse remaining args
618      * 
619      * @param string $_args
620      * @return array
621      */
622     protected function _parseRemainingArgs($_args)
623     {
624         $options = array();
625         foreach ($_args as $arg) {
626             if (strpos($arg, '=') !== FALSE) {
627                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $arg);
628                 list($key, $value) = preg_split('/=/', $arg, 2);
629                 $options[$key] = $value;
630             }
631         }
632         
633         return $options;
634     }
635 }