7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @copyright Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
9 * @author Lars Kneschke <l.kneschke@metaways.de>
25 const ACTIVEDIRECTORY = 'ActiveDirectory';
28 const TYPO3 = 'Typo3';
31 * user status constants
35 * @todo use constants from model
37 const STATUS_BLOCKED = 'blocked';
38 const STATUS_DISABLED = 'disabled';
39 const STATUS_ENABLED = 'enabled';
40 const STATUS_EXPIRED = 'expired';
43 * Key under which the default user group name setting will be stored/retrieved
46 const DEFAULT_USER_GROUP_NAME_KEY = 'defaultUserGroupName';
49 * Key under which the default admin group name setting will be stored/retrieved
52 const DEFAULT_ADMIN_GROUP_NAME_KEY = 'defaultAdminGroupName';
54 protected static $_contact2UserMapping = array(
55 'n_family' => 'accountLastName',
56 'n_given' => 'accountFirstName',
57 'n_fn' => 'accountFullName',
58 'n_fileas' => 'accountDisplayName',
59 'email' => 'accountEmailAddress',
60 'container_id' => 'container_id',
66 * don't use the constructor. use the singleton
68 private function __construct() {}
71 * don't clone. Use the singleton.
73 private function __clone() {}
76 * holds the instance of the singleton
78 * @var Tinebase_User_Interface
80 private static $_instance = NULL;
83 * Holds the accounts backend type (e.g. Ldap or Sql.
84 * Property is lazy loaded on first access via getter {@see getConfiguredBackend()}
86 * @var array | optional
88 private static $_backendType;
91 * Holds the backend configuration options.
92 * Property is lazy loaded from {@see Tinebase_Config} on first access via
93 * getter {@see getBackendConfiguration()}
95 * @var array | optional
97 private static $_backendConfiguration;
100 * Holds the backend configuration options.
101 * Property is lazy loaded from {@see Tinebase_Config} on first access via
102 * getter {@see getBackendConfiguration()}
104 * @var array | optional
106 private static $_backendConfigurationDefaults = array(
108 self::DEFAULT_USER_GROUP_NAME_KEY => Tinebase_Group::DEFAULT_USER_GROUP,
109 self::DEFAULT_ADMIN_GROUP_NAME_KEY => Tinebase_Group::DEFAULT_ADMIN_GROUP,
112 'host' => 'localhost',
115 'bindRequiresDn' => true,
116 'useStartTls' => false,
117 'useRfc2307bis' => false,
119 'userFilter' => 'objectclass=posixaccount',
120 'userSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
122 'groupFilter' => 'objectclass=posixgroup',
123 'groupSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
124 'pwEncType' => 'SSHA',
125 'minUserId' => '10000',
126 'maxUserId' => '29999',
127 'minGroupId' => '11000',
128 'maxGroupId' => '11099',
129 'groupUUIDAttribute' => 'entryUUID',
130 'userUUIDAttribute' => 'entryUUID',
131 self::DEFAULT_USER_GROUP_NAME_KEY => Tinebase_Group::DEFAULT_USER_GROUP,
132 self::DEFAULT_ADMIN_GROUP_NAME_KEY => Tinebase_Group::DEFAULT_ADMIN_GROUP,
135 self::ACTIVEDIRECTORY => array(
136 'host' => 'localhost',
139 'bindRequiresDn' => true,
140 'useRfc2307' => false,
142 'userFilter' => 'objectclass=user',
143 'userSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
145 'groupFilter' => 'objectclass=group',
146 'groupSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
147 'minUserId' => '10000',
148 'maxUserId' => '29999',
149 'minGroupId' => '11000',
150 'maxGroupId' => '11099',
151 'groupUUIDAttribute' => 'objectGUID',
152 'userUUIDAttribute' => 'objectGUID',
153 self::DEFAULT_USER_GROUP_NAME_KEY => 'Domain Users',
154 self::DEFAULT_ADMIN_GROUP_NAME_KEY => 'Domain Admins',
160 * the singleton pattern
162 * @return Tinebase_User_Abstract
164 public static function getInstance()
166 if (self::$_instance === NULL) {
167 $backendType = self::getConfiguredBackend();
168 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' accounts backend: ' . $backendType);
170 self::$_instance = self::factory($backendType);
173 return self::$_instance;
177 * return an instance of the current user backend
179 * @param string $backendType name of the user backend
180 * @return Tinebase_User_Abstract
181 * @throws Tinebase_Exception_InvalidArgument
183 public static function factory($backendType)
185 $options = self::getBackendConfiguration();
187 // this is a dangerous TRACE as there might be passwords in here!
188 //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
189 // . print_r($options, TRUE));
191 $options['plugins'] = array();
193 // manage email user settings
194 if (Tinebase_EmailUser::manages(Tinebase_Config::IMAP)) {
196 $options['plugins'][] = Tinebase_EmailUser::getInstance(Tinebase_Config::IMAP);
197 } catch (Exception $e) {
198 if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
199 . ' Could not add IMAP EmailUser plugin: ' . $e);
202 if (Tinebase_EmailUser::manages(Tinebase_Config::SMTP)) {
204 $options['plugins'][] = Tinebase_EmailUser::getInstance(Tinebase_Config::SMTP);
205 } catch (Exception $e) {
206 if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
207 . ' Could not add SMTP EmailUser plugin: ' . $e);
211 switch ($backendType) {
212 case self::ACTIVEDIRECTORY:
213 $result = new Tinebase_User_ActiveDirectory($options);
219 if (isset(Tinebase_Core::getConfig()->samba) && Tinebase_Core::getConfig()->samba->get('manageSAM', FALSE) == true) {
220 $options['plugins'][] = new Tinebase_User_Plugin_Samba(Tinebase_Core::getConfig()->samba->toArray());
223 $result = new Tinebase_User_Ldap($options);
228 $result = new Tinebase_User_Sql($options);
233 $result = new Tinebase_User_Typo3($options);
238 throw new Tinebase_Exception_InvalidArgument("User backend type $backendType not implemented.");
241 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
242 . ' Created user backend of type ' . $backendType);
248 * returns the configured rs backend
252 public static function getConfiguredBackend()
254 if (!isset(self::$_backendType)) {
255 if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
256 self::setBackendType(Tinebase_Config::getInstance()->get(Tinebase_Config::USERBACKENDTYPE, self::SQL));
258 self::setBackendType(self::SQL);
262 return self::$_backendType;
266 * setter for {@see $_backendType}
268 * @todo persist in db
270 * @param string $backendType
273 public static function setBackendType($backendType)
275 if (empty($backendType)) {
276 throw new Tinebase_Exception_InvalidArgument('Backend type can not be empty!');
279 $newBackendType = ucfirst($backendType);
280 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
281 ' Setting backend type to ' . $newBackendType);
283 self::$_backendType = $newBackendType;
287 * Setter for {@see $_backendConfiguration}
290 * Setting will not be written to Database or Filesystem.
291 * To persist the change call {@see saveBackendConfiguration()}
293 * @param mixed $_value
294 * @param string $_key
295 * @param boolean $_applyDefaults
298 * @todo generalize this (see Tinebase_Auth::setBackendConfiguration)
300 public static function setBackendConfiguration($_value, $_key = null, $_applyDefaults = false)
302 $defaultValues = self::$_backendConfigurationDefaults[self::getConfiguredBackend()];
304 if (is_null($_key) && !is_array($_value)) {
305 throw new Tinebase_Exception_InvalidArgument('To set backend configuration either a key and value '
306 . 'parameter are required or the value parameter should be a hash');
307 } elseif (is_null($_key) && is_array($_value)) {
308 $configToSet = $_applyDefaults ? array_merge($defaultValues, $_value) : $_value;
309 foreach ($configToSet as $key => $value) {
310 self::setBackendConfiguration($value, $key);
313 if ( ! (isset($defaultValues[$_key]) || array_key_exists($_key, $defaultValues))) {
314 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
315 " Cannot set backend configuration option '$_key' for accounts storage " . self::getConfiguredBackend());
318 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
319 ' Setting backend key ' . $_key . ' to ' . (preg_match('/password|pwd|pass|passwd/i', $_key) ? '********' : $_value));
321 self::$_backendConfiguration[$_key] = $_value;
326 * Delete the given config setting or all config settings if {@param $_key} is not specified
328 * @param string | optional $_key
331 public static function deleteBackendConfiguration($_key = null)
333 if (is_null($_key)) {
334 self::$_backendConfiguration = array();
335 } elseif ((isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration))) {
336 unset(self::$_backendConfiguration[$_key]);
338 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' configuration option does not exist: ' . $_key);
343 * Write backend configuration setting {@see $_backendConfigurationSettings} and {@see $_backendType} to
348 public static function saveBackendConfiguration()
350 Tinebase_Config::getInstance()->set(Tinebase_Config::USERBACKEND, self::getBackendConfiguration());
351 Tinebase_Config::getInstance()->set(Tinebase_Config::USERBACKENDTYPE, self::getConfiguredBackend());
355 * Getter for {@see $_backendConfiguration}
357 * @param String | optional $_key
358 * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
360 public static function getBackendConfiguration($_key = null, $_default = null)
362 //lazy loading for $_backendConfiguration
363 if (!isset(self::$_backendConfiguration)) {
364 if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
365 $rawBackendConfiguration = Tinebase_Config::getInstance()->get(Tinebase_Config::USERBACKEND, new Tinebase_Config_Struct())->toArray();
367 $rawBackendConfiguration = array();
369 self::$_backendConfiguration = is_array($rawBackendConfiguration) ? $rawBackendConfiguration : Zend_Json::decode($rawBackendConfiguration);
373 return (isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration)) ? self::$_backendConfiguration[$_key] : $_default;
375 return self::$_backendConfiguration;
380 * Returns default configuration for all supported backends
381 * and overrides the defaults with concrete values stored in this configuration
383 * @param boolean $_getConfiguredBackend
384 * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
386 public static function getBackendConfigurationWithDefaults($_getConfiguredBackend = TRUE)
389 $defaultConfig = self::getBackendConfigurationDefaults();
390 foreach ($defaultConfig as $backendType => $backendConfig) {
391 $config[$backendType] = ($_getConfiguredBackend && $backendType == self::getConfiguredBackend() ? self::getBackendConfiguration() : array());
392 if (is_array($config[$backendType])) {
393 foreach ($backendConfig as $key => $value) {
394 if (! (isset($config[$backendType][$key]) || array_key_exists($key, $config[$backendType]))) {
395 $config[$backendType][$key] = $value;
399 $config[$backendType] = $backendConfig;
406 * Getter for {@see $_backendConfigurationDefaults}
407 * @param String | optional $_backendType
410 public static function getBackendConfigurationDefaults($_backendType = null) {
412 if (!(isset(self::$_backendConfigurationDefaults[$_backendType]) || array_key_exists($_backendType, self::$_backendConfigurationDefaults))) {
413 throw new Tinebase_Exception_InvalidArgument("Unknown backend type '$_backendType'");
415 return self::$_backendConfigurationDefaults[$_backendType];
417 return self::$_backendConfigurationDefaults;
422 * syncronize user from syncbackend to local sql backend
424 * @param mixed $username the login id of the user to synchronize
425 * @param array $options
426 * @return Tinebase_Model_FullUser
427 * @throws Tinebase_Exception
429 * @todo make use of dbmail plugin configurable (should be false by default)
430 * @todo switch to new primary group if it could not be found
431 * @todo write a test and refactor this ... :(
433 public static function syncUser($username, $options = array())
435 if ($username instanceof Tinebase_Model_FullUser) {
436 $username = $username->accountLoginName;
439 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " sync user data for: " . $username);
441 $userBackend = Tinebase_User::getInstance();
442 if (isset($options['ldapplugins']) && is_array($options['ldapplugins'])) {
443 foreach ($options['ldapplugins'] as $plugin) {
444 $userBackend->registerLdapPlugin($plugin);
448 $user = $userBackend->getUserByPropertyFromSyncBackend('accountLoginName', $username, 'Tinebase_Model_FullUser');
449 $user->accountPrimaryGroup = Tinebase_Group::getInstance()->resolveGIdNumberToUUId($user->accountPrimaryGroup);
451 $userProperties = method_exists($userBackend, 'getLastUserProperties') ? $userBackend->getLastUserProperties() : array();
453 $hookResult = self::_syncUserHook($user, $userProperties);
458 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
459 . print_r($user->toArray(), TRUE));
461 self::getPrimaryGroupForUser($user);
464 $currentUser = $userBackend->getUserByProperty('accountId', $user, 'Tinebase_Model_FullUser');
466 $currentUser->accountLoginName = $user->accountLoginName;
467 $currentUser->accountLastPasswordChange = $user->accountLastPasswordChange;
468 $currentUser->accountExpires = $user->accountExpires;
469 $currentUser->accountPrimaryGroup = $user->accountPrimaryGroup;
470 $currentUser->accountDisplayName = $user->accountDisplayName;
471 $currentUser->accountLastName = $user->accountLastName;
472 $currentUser->accountFirstName = $user->accountFirstName;
473 $currentUser->accountFullName = $user->accountFullName;
474 $currentUser->accountEmailAddress = $user->accountEmailAddress;
475 $currentUser->accountHomeDirectory = $user->accountHomeDirectory;
476 $currentUser->accountLoginShell = $user->accountLoginShell;
478 $currentUser->accountStatus = isset($user->accountStatus)
479 ? $user->accountStatus
480 : Tinebase_Model_User::ACCOUNT_STATUS_ENABLED;
482 if (! empty($user->visibility) && $currentUser->visibility !== $user->visibility) {
483 $currentUser->visibility = $user->visibility;
484 if (empty($currentUser->contact_id) && $currentUser->visibility == Tinebase_Model_FullUser::VISIBILITY_DISPLAYED) {
485 self::createContactForSyncedUser($currentUser);
489 $syncedUser = $userBackend->updateUserInSqlBackend($currentUser);
490 if (! empty($user->container_id)) {
491 $syncedUser->container_id = $user->container_id;
493 $userBackend->updatePluginUser($syncedUser, $user);
495 } catch (Tinebase_Exception_NotFound $ten) {
497 $invalidUser = $userBackend->getUserByPropertyFromSqlBackend('accountLoginName', $username, 'Tinebase_Model_FullUser');
498 if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__
499 . " Remove invalid user: " . $username);
500 $userBackend->deleteUserInSqlBackend($invalidUser);
501 } catch (Tinebase_Exception_NotFound $ten) {
505 if ($user->visibility !== Tinebase_Model_FullUser::VISIBILITY_HIDDEN) {
506 self::createContactForSyncedUser($user);
508 $syncedUser = $userBackend->addUserInSqlBackend($user);
509 $userBackend->addPluginUser($syncedUser, $user);
512 self::syncContactData($syncedUser, $options);
514 // sync group memberships
515 Tinebase_Group::syncMemberships($syncedUser);
521 * import contactdata(phone, address, fax, birthday. photo)
523 * @param Tinebase_Model_FullUser $syncedUser
524 * @param array $options
526 public static function syncContactData($syncedUser, $options)
528 if (! Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_CONTACT_DATA, true)
529 || ! isset($options['syncContactData'])
530 || ! $options['syncContactData']
531 || ! Tinebase_Application::getInstance()->isInstalled('Addressbook')
532 || $syncedUser->visibility === Tinebase_Model_FullUser::VISIBILITY_HIDDEN
534 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
535 . ' Contact data sync disabled');
539 $addressbook = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
542 $contact = $addressbook->getByUserId($syncedUser->getId());
543 $originalContact = clone $contact;
545 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
546 . ' user: ' .print_r($syncedUser->toArray(), true));
548 Tinebase_User::getInstance()->updateContactFromSyncBackend($syncedUser, $contact);
549 $contact = self::_user2Contact($syncedUser, $contact);
551 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
552 . ' new contact: ' . print_r($contact->toArray(), true)
553 . ' orig contact:' . print_r($originalContact->toArray(), true));
555 // TODO allow to diff jpegphoto, too / maybe this should only be done when called via CLI/cronjob
556 $diff = $contact->diff($originalContact, array('jpegphoto'));
557 if (! $diff->isEmpty() || ($originalContact->jpegphoto == 0 && ! empty($contact->jpegphoto))) {
559 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
560 Tinebase_Container::getInstance()->increaseContentSequence($contact->container_id);
562 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
563 . ' Updating contact data for user ' . $syncedUser->accountLoginName);
564 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
565 . ' Diff: ' . print_r($diff->toArray(), true));
567 $addressbook->update($contact);
569 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
570 . ' User contact is up to date.');
572 } catch (Addressbook_Exception_NotFound $aenf) {
573 self::createContactForSyncedUser($syncedUser);
574 $syncedUser = Tinebase_User::getInstance()->updateUserInSqlBackend($syncedUser);
575 } catch (Tinebase_Exception_NotFound $tenf) {
576 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
577 . ' Contact information seems to be missing in sync backend');
578 Tinebase_Exception::log($tenf);
583 * get primary group for user and make sure that group exists
585 * @param Tinebase_Model_FullUser $user
586 * @throws Tinebase_Exception
587 * @return Tinebase_Model_Group
589 public static function getPrimaryGroupForUser($user)
591 $groupBackend = Tinebase_Group::getInstance();
594 $group = $groupBackend->getGroupById($user->accountPrimaryGroup);
595 } catch (Tinebase_Exception_Record_NotDefined $tern) {
596 if ($groupBackend->isDisabledBackend()) {
597 // groups are sql only
598 $group = $groupBackend->getDefaultGroup();
599 $user->accountPrimaryGroup = $group->getId();
602 $group = $groupBackend->getGroupByIdFromSyncBackend($user->accountPrimaryGroup);
603 } catch (Tinebase_Exception_Record_NotDefined $ternd) {
604 throw new Tinebase_Exception('Primary group ' . $user->accountPrimaryGroup . ' not found in sync backend.');
607 $groupBackend->getGroupByName($group->name);
608 throw new Tinebase_Exception('Group already exists but it has a different ID: ' . $group->name);
610 } catch (Tinebase_Exception_Record_NotDefined $tern) {
611 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
612 . " Adding group " . $group->name);
613 $group = $groupBackend->addGroupInSqlBackend($group);
622 * call configured hooks for adjusting synced user data
624 * @param Tinebase_Model_FullUser $user
625 * @param array $userProperties
626 * @return boolean if false, user is skipped
628 protected static function _syncUserHook(Tinebase_Model_FullUser $user, $userProperties)
631 $hookClass = Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_HOOK_CLASS);
632 if ($hookClass && class_exists($hookClass)) {
633 $hook = new $hookClass();
634 if (method_exists($hook, 'syncUser')) {
635 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
636 . ' Calling ' . $hookClass . '::syncUser() ...');
639 $result = call_user_func_array(array($hook, 'syncUser'), array($user, $userProperties));
640 } catch (Tinebase_Exception $te) {
641 Tinebase_Exception::log($te);
651 * create contact in addressbook
653 * @param Tinebase_Model_FullUser $user
655 public static function createContactForSyncedUser($user)
657 if (! Tinebase_Application::getInstance()->isInstalled('Addressbook')) {
661 $contact = self::_user2Contact($user);
664 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
666 $addressbook = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
667 $contact = $addressbook->create($contact);
669 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
670 . " Added contact " . $contact->n_given);
672 $user->contact_id = $contact->getId();
676 * sync user data to contact
678 * @param Tinebase_Model_FullUser $user
679 * @param Addressbook_Model_Contact $contact
680 * @return Addressbook_Model_Contact
682 protected static function _user2Contact($user, $contact = null)
684 if ($contact === null) {
685 $contact = new Addressbook_Model_Contact(array(), true);
688 $contact->type = Addressbook_Model_Contact::CONTACTTYPE_USER;
690 foreach (self::$_contact2UserMapping as $contactKey => $userKey) {
691 if (! empty($contact->{$contactKey}) && $contact->{$contactKey} == $user->{$userKey}) {
695 switch ($contactKey) {
697 $contact->container_id = (! empty($user->container_id)) ? $user->container_id : Admin_Controller_User::getInstance()->getDefaultInternalAddressbook();
700 $contact->{$contactKey} = $user->{$userKey};
708 * import users from sync backend
710 * @param array $options
712 public static function syncUsers($options)
714 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
715 .' Start synchronizing users with options ' . print_r($options, true));
717 $users = Tinebase_User::getInstance()->getUsersFromSyncBackend(NULL, NULL, 'ASC', NULL, NULL, 'Tinebase_Model_FullUser');
719 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
720 . ' About to sync ' . count($users) . ' users from sync backend ...');
722 foreach ($users as $user) {
724 self::syncUser($user, $options);
725 } catch (Tinebase_Exception_NotFound $ten) {
726 Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " User {$user->accountLoginName} not synced: "
727 . $ten->getMessage());
731 if (isset($options['deleteUsers']) && $options['deleteUsers']) {
732 self::_syncDeletedUsers($users);
735 // @todo this should be improved: only the cache of synced users + group memberships should be cleaned
736 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
737 . ' Finished synchronizing users. Clearing cache after user sync ...');
738 Tinebase_Core::getCache()->clean();
742 * deletes user in tine20 db that no longer exist in sync backend
744 * @param Tinebase_Record_RecordSet $usersInSyncBackend
746 protected static function _syncDeletedUsers(Tinebase_Record_RecordSet $usersInSyncBackend)
748 $userIdsInSqlBackend = Tinebase_User::getInstance()->getAllUserIdsFromSqlBackend();
749 $deletedInSyncBackend = array_diff($userIdsInSqlBackend, $usersInSyncBackend->getArrayOfIds());
751 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
752 . ' About to delete / expire ' . count($deletedInSyncBackend) . ' users in SQL backend...');
754 foreach ($deletedInSyncBackend as $userToDelete) {
755 $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $userToDelete, 'Tinebase_Model_FullUser');
757 if (in_array($user->accountLoginName, self::getSystemUsernames())) {
761 // at first, we expire+deactivate the user
762 $now = Tinebase_DateTime::now();
763 if (! $user->accountExpires || $user->accountStatus !== Tinebase_Model_User::ACCOUNT_STATUS_DISABLED) {
764 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
765 . ' Disable user and set expiry date of ' . $user->accountLoginName . ' to ' . $now);
766 $user->accountExpires = $now;
767 $user->accountStatus = Tinebase_Model_User::ACCOUNT_STATUS_DISABLED;
768 Tinebase_User::getInstance()->updateUserInSqlBackend($user);
770 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
771 . ' User already expired ' . print_r($user->toArray(), true));
773 // TODO make time span configurable?
774 if ($user->accountExpires->isEarlier($now->subYear(1))) {
775 // if he or she is already expired longer than configured expiry, we remove them!
776 Tinebase_User::getInstance()->deleteUserInSqlBackend($userToDelete);
778 if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true && ! empty($user->contact_id)) {
779 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
780 . ' Deleting user contact of ' . $user->accountLoginName);
782 $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
783 $contactsBackend->delete($user->contact_id);
786 // keep user in expiry state
793 * returns login_names of system users
797 public static function getSystemUsernames()
799 return array('cronuser', 'calendarscheduling');
803 * get all user passwords from ldap
804 * - set pw for user (in sql and sql plugins)
805 * - do not encrypt the pw again as it is encrypted in LDAP
807 * @throws Tinebase_Exception_Backend
809 public static function syncLdapPasswords()
811 $userBackend = Tinebase_User::getInstance();
812 if (! $userBackend instanceof Tinebase_User_Ldap) {
813 throw new Tinebase_Exception_Backend('Needs LDAP accounts backend');
816 $result = $userBackend->getUserAttributes(array('entryUUID', 'userPassword'));
818 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
819 . ' About to sync ' . count($result) . ' user passwords from LDAP to Tine 2.0.');
821 $sqlBackend = Tinebase_User::factory(self::SQL);
822 foreach ($result as $user) {
824 $sqlBackend->setPassword($user['entryUUID'], $user['userPassword'], FALSE);
825 } catch (Tinebase_Exception_NotFound $tenf) {
826 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
827 . ' Could not find user with id ' . $user['entryUUID'] . ' in SQL backend.');
833 * create initial admin account
835 * Method is called during Setup Initialization
837 * $_options may contain the following keys:
840 * 'adminLoginName' => 'admin',
841 * 'adminPassword' => 'lars',
842 * 'adminFirstName' => 'Tine 2.0',
843 * 'adminLastName' => 'Admin Account',
844 * 'adminEmailAddress' => 'admin@tine20domain.org',
845 * 'expires' => Tinebase_DateTime object
849 * @param array $_options [hash that may contain override values for admin user name and password]
851 * @throws Tinebase_Exception_InvalidArgument
853 public static function createInitialAccounts($_options)
855 if (! isset($_options['adminPassword']) || ! isset($_options['adminLoginName'])) {
856 throw new Tinebase_Exception_InvalidArgument('Admin password and login name have to be set when creating initial account.', 503);
859 $adminLoginName = $_options['adminLoginName'];
860 $adminPassword = $_options['adminPassword'];
861 $adminFirstName = isset($_options['adminFirstName']) ? $_options['adminFirstName'] : 'Tine 2.0';
862 $adminLastName = isset($_options['adminLastName']) ? $_options['adminLastName'] : 'Admin Account';
863 $adminEmailAddress = ((isset($_options['adminEmailAddress']) || array_key_exists('adminEmailAddress', $_options))) ? $_options['adminEmailAddress'] : NULL;
865 // get admin & user groups
866 $userBackend = Tinebase_User::getInstance();
867 $groupsBackend = Tinebase_Group::getInstance();
869 $adminGroup = $groupsBackend->getDefaultAdminGroup();
870 $userGroup = $groupsBackend->getDefaultGroup();
872 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating initial admin user (login: ' . $adminLoginName . ' / email: ' . $adminEmailAddress . ')');
874 $user = new Tinebase_Model_FullUser(array(
875 'accountLoginName' => $adminLoginName,
876 'accountStatus' => Tinebase_Model_User::ACCOUNT_STATUS_ENABLED,
877 'accountPrimaryGroup' => $userGroup->getId(),
878 'accountLastName' => $adminLastName,
879 'accountDisplayName' => $adminLastName . ', ' . $adminFirstName,
880 'accountFirstName' => $adminFirstName,
881 'accountExpires' => (isset($_options['expires'])) ? $_options['expires'] : NULL,
882 'accountEmailAddress' => $adminEmailAddress
885 if ($adminEmailAddress !== NULL) {
886 $user->imapUser = new Tinebase_Model_EmailUser(array(
887 'emailPassword' => $adminPassword
889 $user->smtpUser = new Tinebase_Model_EmailUser(array(
890 'emailPassword' => $adminPassword
894 // update or create user in local sql backend
896 $userBackend->getUserByProperty('accountLoginName', $adminLoginName);
897 $user = $userBackend->updateUserInSqlBackend($user);
898 } catch (Tinebase_Exception_NotFound $ten) {
899 // call addUser here to make sure, sql user plugins (email, ...) are triggered
900 $user = $userBackend->addUser($user);
903 // set the password for the account
904 Tinebase_User::getInstance()->setPassword($user, $adminPassword);
906 // add the admin account to all groups
907 Tinebase_Group::getInstance()->addGroupMember($adminGroup, $user);
908 Tinebase_Group::getInstance()->addGroupMember($userGroup, $user);