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;
477 if (! empty($user->visibility) && $currentUser->visibility !== $user->visibility) {
478 $currentUser->visibility = $user->visibility;
479 if (empty($currentUser->contact_id) && $currentUser->visibility == Tinebase_Model_FullUser::VISIBILITY_DISPLAYED) {
480 self::createContactForSyncedUser($currentUser);
484 Tinebase_Timemachine_ModificationLog::setRecordMetaData($currentUser, 'update');
485 $syncedUser = $userBackend->updateUserInSqlBackend($currentUser);
486 if (! empty($user->container_id)) {
487 $syncedUser->container_id = $user->container_id;
489 $userBackend->updatePluginUser($syncedUser, $user);
491 } catch (Tinebase_Exception_NotFound $ten) {
493 $invalidUser = $userBackend->getUserByPropertyFromSqlBackend('accountLoginName', $username, 'Tinebase_Model_FullUser');
494 if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__
495 . " Remove invalid user: " . $username);
496 $userBackend->deleteUserInSqlBackend($invalidUser);
497 } catch (Tinebase_Exception_NotFound $ten) {
501 if ($user->visibility !== Tinebase_Model_FullUser::VISIBILITY_HIDDEN) {
502 self::createContactForSyncedUser($user);
504 Tinebase_Timemachine_ModificationLog::setRecordMetaData($user, 'create');
505 $syncedUser = $userBackend->addUserInSqlBackend($user);
506 $userBackend->addPluginUser($syncedUser, $user);
509 self::syncContactData($syncedUser, $options);
511 // sync group memberships
512 Tinebase_Group::syncMemberships($syncedUser);
518 * import contactdata(phone, address, fax, birthday. photo)
520 * @param Tinebase_Model_FullUser $syncedUser
521 * @param array $options
523 public static function syncContactData($syncedUser, $options)
525 if (! Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_CONTACT_DATA, true)
526 || ! isset($options['syncContactData'])
527 || ! $options['syncContactData']
528 || ! Tinebase_Application::getInstance()->isInstalled('Addressbook')
529 || $syncedUser->visibility === Tinebase_Model_FullUser::VISIBILITY_HIDDEN
531 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
532 . ' Contact data sync disabled');
536 $addressbook = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
539 $contact = $addressbook->getByUserId($syncedUser->getId());
540 $originalContact = clone $contact;
542 Tinebase_User::getInstance()->updateContactFromSyncBackend($syncedUser, $contact);
543 $contact = self::_user2Contact($syncedUser, $contact);
545 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
546 . print_r($syncedUser->toArray(), true)
547 . ' new: ' . print_r($contact->toArray(), true)
548 . ' orig:' . print_r($originalContact->toArray(), true));
550 // TODO allow to diff jpegphoto, too / maybe this should only be done when called via CLI/cronjob
551 $diff = $contact->diff($originalContact, array('jpegphoto'));
552 if (! $diff->isEmpty() || ($originalContact->jpegphoto == 0 && ! empty($contact->jpegphoto))) {
554 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
555 if ($contact->container_id !== null) {
556 Tinebase_Container::getInstance()->increaseContentSequence($contact->container_id);
559 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
560 . ' Updating contact data for user ' . $syncedUser->accountLoginName);
561 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
562 . ' Diff: ' . print_r($diff->toArray(), true));
564 $addressbook->update($contact);
566 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
567 . ' User contact is up to date.');
569 } catch (Addressbook_Exception_NotFound $aenf) {
570 self::createContactForSyncedUser($syncedUser);
571 $syncedUser = Tinebase_User::getInstance()->updateUserInSqlBackend($syncedUser);
576 * get primary group for user and make sure that group exists
578 * @param Tinebase_Model_FullUser $user
579 * @throws Tinebase_Exception
580 * @return Tinebase_Model_Group
582 public static function getPrimaryGroupForUser($user)
584 $groupBackend = Tinebase_Group::getInstance();
587 $group = $groupBackend->getGroupById($user->accountPrimaryGroup);
588 } catch (Tinebase_Exception_Record_NotDefined $tern) {
589 if ($groupBackend->isDisabledBackend()) {
590 // groups are sql only
591 $group = $groupBackend->getDefaultGroup();
592 $user->accountPrimaryGroup = $group->getId();
595 $group = $groupBackend->getGroupByIdFromSyncBackend($user->accountPrimaryGroup);
596 } catch (Tinebase_Exception_Record_NotDefined $ternd) {
597 throw new Tinebase_Exception('Primary group ' . $user->accountPrimaryGroup . ' not found in sync backend.');
600 $groupBackend->getGroupByName($group->name);
601 throw new Tinebase_Exception('Group already exists but it has a different ID: ' . $group->name);
603 } catch (Tinebase_Exception_Record_NotDefined $tern) {
604 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
605 . " Adding group " . $group->name);
606 $group = $groupBackend->addGroupInSqlBackend($group);
615 * call configured hooks for adjusting synced user data
617 * @param Tinebase_Model_FullUser $user
618 * @param array $userProperties
619 * @return boolean if false, user is skipped
621 protected static function _syncUserHook(Tinebase_Model_FullUser $user, $userProperties)
624 $hookClass = Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_HOOK_CLASS);
625 if ($hookClass && class_exists($hookClass)) {
626 $hook = new $hookClass();
627 if (method_exists($hook, 'syncUser')) {
628 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
629 . ' Calling ' . $hookClass . '::syncUser() ...');
632 $result = call_user_func_array(array($hook, 'syncUser'), array($user, $userProperties));
633 } catch (Tinebase_Exception $te) {
634 Tinebase_Exception::log($te);
644 * create contact in addressbook
646 * @param Tinebase_Model_FullUser $user
648 public static function createContactForSyncedUser($user)
650 if (! Tinebase_Application::getInstance()->isInstalled('Addressbook')) {
654 $contact = self::_user2Contact($user);
657 Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
659 $addressbook = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
660 $contact = $addressbook->create($contact);
662 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
663 . " Added contact " . $contact->n_given);
665 $user->contact_id = $contact->getId();
669 * sync user data to contact
671 * @param Tinebase_Model_FullUser $user
672 * @param Addressbook_Model_Contact $contact
673 * @return Addressbook_Model_Contact
675 protected static function _user2Contact($user, $contact = null)
677 if ($contact === null) {
678 $contact = new Addressbook_Model_Contact(array(), true);
681 $contact->type = Addressbook_Model_Contact::CONTACTTYPE_USER;
683 foreach (self::$_contact2UserMapping as $contactKey => $userKey) {
684 if (! empty($contact->{$contactKey}) && $contact->{$contactKey} == $user->{$userKey}) {
688 switch ($contactKey) {
690 $contact->container_id = (! empty($user->container_id)) ? $user->container_id : Admin_Controller_User::getInstance()->getDefaultInternalAddressbook();
693 $contact->{$contactKey} = $user->{$userKey};
701 * import users from sync backend
703 * @param array $options
705 public static function syncUsers($options)
707 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
708 .' Start synchronizing users with options ' . print_r($options, true));
710 $users = Tinebase_User::getInstance()->getUsersFromSyncBackend(NULL, NULL, 'ASC', NULL, NULL, 'Tinebase_Model_FullUser');
712 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
713 . ' About to sync ' . count($users) . ' users from sync backend ...');
715 foreach ($users as $user) {
717 self::syncUser($user, $options);
718 } catch (Tinebase_Exception_NotFound $ten) {
719 Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " User {$user->accountLoginName} not synced: "
720 . $ten->getMessage());
724 if (isset($options['deleteUsers']) && $options['deleteUsers']) {
725 self::_syncDeletedUsers($users);
728 // @todo this should be improved: only the cache of synced users + group memberships should be cleaned
729 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
730 . ' Finished synchronizing users. Clearing cache after user sync ...');
731 Tinebase_Core::getCache()->clean();
735 * deletes user in tine20 db that no longer exist in sync backend
737 * @param Tinebase_Record_RecordSet $usersInSyncBackend
739 protected static function _syncDeletedUsers(Tinebase_Record_RecordSet $usersInSyncBackend)
741 $userIdsInSqlBackend = Tinebase_User::getInstance()->getAllUserIdsFromSqlBackend();
742 $deletedInSyncBackend = array_diff($userIdsInSqlBackend, $usersInSyncBackend->getArrayOfIds());
744 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
745 . ' About to delete ' . count($deletedInSyncBackend) . ' users in SQL backend...');
747 foreach ($deletedInSyncBackend as $userToDelete) {
748 Tinebase_User::getInstance()->deleteUserInSqlBackend($userToDelete);
753 * get all user passwords from ldap
754 * - set pw for user (in sql and sql plugins)
755 * - do not encrypt the pw again as it is encrypted in LDAP
757 * @throws Tinebase_Exception_Backend
759 public static function syncLdapPasswords()
761 $userBackend = Tinebase_User::getInstance();
762 if (! $userBackend instanceof Tinebase_User_Ldap) {
763 throw new Tinebase_Exception_Backend('Needs LDAP accounts backend');
766 $result = $userBackend->getUserAttributes(array('entryUUID', 'userPassword'));
768 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
769 . ' About to sync ' . count($result) . ' user passwords from LDAP to Tine 2.0.');
771 $sqlBackend = Tinebase_User::factory(self::SQL);
772 foreach ($result as $user) {
774 $sqlBackend->setPassword($user['entryUUID'], $user['userPassword'], FALSE);
775 } catch (Tinebase_Exception_NotFound $tenf) {
776 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
777 . ' Could not find user with id ' . $user['entryUUID'] . ' in SQL backend.');
783 * create initial admin account
785 * Method is called during Setup Initialization
787 * $_options may contain the following keys:
790 * 'adminLoginName' => 'admin',
791 * 'adminPassword' => 'lars',
792 * 'adminFirstName' => 'Tine 2.0',
793 * 'adminLastName' => 'Admin Account',
794 * 'adminEmailAddress' => 'admin@tine20domain.org',
795 * 'expires' => Tinebase_DateTime object
799 * @param array $_options [hash that may contain override values for admin user name and password]
801 * @throws Tinebase_Exception_InvalidArgument
803 public static function createInitialAccounts($_options)
805 if (! isset($_options['adminPassword']) || ! isset($_options['adminLoginName'])) {
806 throw new Tinebase_Exception_InvalidArgument('Admin password and login name have to be set when creating initial account.', 503);
809 $adminLoginName = $_options['adminLoginName'];
810 $adminPassword = $_options['adminPassword'];
811 $adminFirstName = isset($_options['adminFirstName']) ? $_options['adminFirstName'] : 'Tine 2.0';
812 $adminLastName = isset($_options['adminLastName']) ? $_options['adminLastName'] : 'Admin Account';
813 $adminEmailAddress = ((isset($_options['adminEmailAddress']) || array_key_exists('adminEmailAddress', $_options))) ? $_options['adminEmailAddress'] : NULL;
815 // get admin & user groups
816 $userBackend = Tinebase_User::getInstance();
817 $groupsBackend = Tinebase_Group::getInstance();
819 $adminGroup = $groupsBackend->getDefaultAdminGroup();
820 $userGroup = $groupsBackend->getDefaultGroup();
822 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating initial admin user (login: ' . $adminLoginName . ' / email: ' . $adminEmailAddress . ')');
824 $user = new Tinebase_Model_FullUser(array(
825 'accountLoginName' => $adminLoginName,
826 'accountStatus' => 'enabled',
827 'accountPrimaryGroup' => $userGroup->getId(),
828 'accountLastName' => $adminLastName,
829 'accountDisplayName' => $adminLastName . ', ' . $adminFirstName,
830 'accountFirstName' => $adminFirstName,
831 'accountExpires' => (isset($_options['expires'])) ? $_options['expires'] : NULL,
832 'accountEmailAddress' => $adminEmailAddress
835 if ($adminEmailAddress !== NULL) {
836 $user->imapUser = new Tinebase_Model_EmailUser(array(
837 'emailPassword' => $adminPassword
839 $user->smtpUser = new Tinebase_Model_EmailUser(array(
840 'emailPassword' => $adminPassword
844 // update or create user in local sql backend
846 $userBackend->getUserByProperty('accountLoginName', $adminLoginName);
847 Tinebase_Timemachine_ModificationLog::setRecordMetaData($user, 'update');
848 $user = $userBackend->updateUserInSqlBackend($user);
849 } catch (Tinebase_Exception_NotFound $ten) {
850 // call addUser here to make sure, sql user plugins (email, ...) are triggered
851 Tinebase_Timemachine_ModificationLog::setRecordMetaData($user, 'create');
852 $user = $userBackend->addUser($user);
855 // set the password for the account
856 // empty password triggers password change dialogue during first login
857 if (!empty($adminPassword)) {
858 Tinebase_User::getInstance()->setPassword($user, $adminPassword);
861 // add the admin account to all groups
862 Tinebase_Group::getInstance()->addGroupMember($adminGroup, $user);
863 Tinebase_Group::getInstance()->addGroupMember($userGroup, $user);