0010622: user contact email is not updated during LDAP sync
[tine20] / tine20 / Admin / Controller / User.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Admin
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo        catch error (and show alert) if postfix email already exists
12  * @todo        extend Tinebase_Controller_Record_Abstract
13  */
14
15 /**
16  * User Controller for Admin application
17  *
18  * @package     Admin
19  * @subpackage  Controller
20  */
21 class Admin_Controller_User extends Tinebase_Controller_Abstract
22 {
23     /**
24      * @var Tinebase_User_Abstract
25      */
26     protected $_userBackend = NULL;
27     
28     /**
29      * @var Tinebase_SambaSAM_Ldap
30      */
31     protected $_samBackend = NULL;
32
33     /**
34      * the constructor
35      *
36      * don't use the constructor. use the singleton 
37      */
38     private function __construct() 
39     {
40         $this->_applicationName = 'Admin';
41         
42         $this->_userBackend = Tinebase_User::getInstance();
43     }
44
45     /**
46      * don't clone. Use the singleton.
47      *
48      */
49     private function __clone() 
50     {
51     }
52
53     /**
54      * holds the instance of the singleton
55      *
56      * @var Admin_Controller_User
57      */
58     private static $_instance = NULL;
59
60     /**
61      * the singleton pattern
62      *
63      * @return Admin_Controller_User
64      */
65     public static function getInstance() 
66     {
67         if (self::$_instance === NULL) {
68             self::$_instance = new Admin_Controller_User;
69         }
70         
71         return self::$_instance;
72     }
73
74     /**
75      * get list of full accounts -> renamed to search full users
76      *
77      * @param string $_filter string to search accounts for
78      * @param string $_sort
79      * @param string $_dir
80      * @param int $_start
81      * @param int $_limit
82      * @return Tinebase_Record_RecordSet with record class Tinebase_Model_FullUser
83      */
84     public function searchFullUsers($_filter, $_sort = NULL, $_dir = 'ASC', $_start = NULL, $_limit = NULL)
85     {
86         $this->checkRight('VIEW_ACCOUNTS');
87         
88         $result = $this->_userBackend->getUsers($_filter, $_sort, $_dir, $_start, $_limit, 'Tinebase_Model_FullUser');
89         
90         return $result;
91     }
92     
93     /**
94      * count users
95      *
96      * @param string $_filter string to search user accounts for
97      * @return int total user count
98      */
99     public function searchCount($_filter)
100     {
101         $this->checkRight('VIEW_ACCOUNTS');
102         
103         $result = $this->_userBackend->getUsersCount($_filter);
104         
105         return $result;
106     }
107     
108     /**
109      * get account
110      *
111      * @param   string  $_accountId  account id to get
112      * @return  Tinebase_Model_FullUser
113      */
114     public function get($_userId)
115     {
116         $this->checkRight('VIEW_ACCOUNTS');
117         
118         $user = $this->_userBackend->getUserById($_userId, 'Tinebase_Model_FullUser');
119         
120         return $user;
121     }
122     
123     /**
124      * set account status
125      *
126      * @param   string $_accountId  account id
127      * @param   string $_status     status to set
128      * @return  array with success flag
129      */
130     public function setAccountStatus($_accountId, $_status)
131     {
132         $this->checkRight('MANAGE_ACCOUNTS');
133         
134         $result = $this->_userBackend->setStatus($_accountId, $_status);
135         
136         return $result;
137     }
138     
139     /**
140      * set the password for a given account
141      *
142      * @param  Tinebase_Model_FullUser  $_account the account
143      * @param  string                   $_password the new password
144      * @param  string                   $_passwordRepeat the new password again
145      * @param  bool                     $_mustChange
146      * @return void
147      * 
148      * @todo add must change pwd info to normal tine user accounts
149      */
150     public function setAccountPassword(Tinebase_Model_FullUser $_account, $_password, $_passwordRepeat, $_mustChange = null)
151     {
152         $this->checkRight('MANAGE_ACCOUNTS');
153         
154         if ($_password != $_passwordRepeat) {
155             throw new Admin_Exception("Passwords don't match.");
156         }
157         
158         $this->_userBackend->setPassword($_account, $_password, true, $_mustChange);
159         
160         Tinebase_Core::getLogger()->info(
161             __METHOD__ . '::' . __LINE__ . 
162             ' Set new password for user ' . $_account->accountLoginName . '. Must change:' . $_mustChange
163         );
164     }
165     
166     /**
167      * update user
168      *
169      * @param  Tinebase_Model_FullUser    $_user            the user
170      * @param  string                     $_password        the new password
171      * @param  string                     $_passwordRepeat  the new password again
172      * 
173      * @return Tinebase_Model_FullUser
174      */
175     public function update(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
176     {
177         $this->checkRight('MANAGE_ACCOUNTS');
178         
179         $oldUser = $this->_userBackend->getUserByProperty('accountId', $_user, 'Tinebase_Model_FullUser');
180         
181         if ($oldUser->accountLoginName !== $_user->accountLoginName) {
182             $this->_checkLoginNameExistance($_user);
183         }
184         $this->_checkLoginNameLength($_user);
185         $this->_checkPrimaryGroupExistance($_user);
186         
187         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Update user ' . $_user->accountLoginName);
188         
189         try {
190             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
191             
192             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
193                 $_user->contact_id = $oldUser->contact_id;
194                 $contact = $this->createOrUpdateContact($_user);
195                 $_user->contact_id = $contact->getId();
196             }
197             
198             $user = $this->_userBackend->updateUser($_user);
199     
200             // make sure primary groups is in the list of groupmemberships
201             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
202             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
203             
204             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
205             
206         } catch (Exception $e) {
207             Tinebase_TransactionManager::getInstance()->rollBack();
208             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e);
209             
210             if ($e instanceof Zend_Db_Statement_Exception && preg_match('/Lock wait timeout exceeded/', $e->getMessage())) {
211                 throw new Tinebase_Exception_Backend_Database_LockTimeout($e->getMessage());
212             }
213             
214             throw $e;
215         }
216         
217         // fire needed events
218         $event = new Admin_Event_UpdateAccount;
219         $event->account = $user;
220         $event->oldAccount = $oldUser;
221         Tinebase_Event::fireEvent($event);
222         
223         if (!empty($_password) && !empty($_passwordRepeat)) {
224             $this->setAccountPassword($_user, $_password, $_passwordRepeat, FALSE);
225         }
226
227         return $user;
228     }
229     
230     /**
231      * create user
232      *
233      * @param  Tinebase_Model_FullUser  $_account           the account
234      * @param  string                     $_password           the new password
235      * @param  string                     $_passwordRepeat  the new password again
236      * @return Tinebase_Model_FullUser
237      */
238     public function create(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
239     {
240         $this->checkRight('MANAGE_ACCOUNTS');
241         
242         // avoid forging accountId, gets created in backend
243         unset($_user->accountId);
244         
245         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create new user ' . $_user->accountLoginName);
246         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_user->toArray(), TRUE));
247         
248         $this->_checkLoginNameExistance($_user);
249         $this->_checkLoginNameLength($_user);
250         $this->_checkPrimaryGroupExistance($_user);
251         
252         if ($_password != $_passwordRepeat) {
253             throw new Admin_Exception("Passwords don't match.");
254         } else if (empty($_password)) {
255             $_password = '';
256             $_passwordRepeat = '';
257         }
258         Tinebase_User::getInstance()->checkPasswordPolicy($_password, $_user);
259         
260         try {
261             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
262             
263             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
264                 $contact = $this->createOrUpdateContact($_user);
265                 $_user->contact_id = $contact->getId();
266             }
267             
268             $user = $this->_userBackend->addUser($_user);
269             
270             // make sure primary groups is in the list of groupmemberships
271             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
272             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
273             
274             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
275
276         } catch (Exception $e) {
277             Tinebase_TransactionManager::getInstance()->rollBack();
278             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
279             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
280             throw $e;
281         }
282         
283         $event = new Admin_Event_AddAccount(array(
284             'account' => $user
285         ));
286         Tinebase_Event::fireEvent($event);
287         
288         $this->setAccountPassword($user, $_password, $_passwordRepeat);
289
290         return $user;
291     }
292     
293     /**
294      * look for user with the same login name
295      * 
296      * @param Tinebase_Model_FullUser $user
297      * @return boolean
298      * @throws Tinebase_Exception_SystemGeneric
299      */
300     protected function _checkLoginNameExistance(Tinebase_Model_FullUser $user)
301     {
302         try {
303             $existing = Tinebase_User::getInstance()->getUserByLoginName($user->accountLoginName);
304             if ($user->getId() === NULL || $existing->getId() !== $user->getId()) {
305                 throw new Tinebase_Exception_SystemGeneric('Login name already exists. Please choose another one.');
306             }
307         } catch (Tinebase_Exception_NotFound $tenf) {
308         }
309         
310         return TRUE;
311     }
312     
313     /**
314      * Check login name length
315      *
316      * @param Tinebase_Model_FullUser $user
317      * @return boolean
318      * @throws Tinebase_Exception_SystemGeneric
319      */
320     protected function _checkLoginNameLength(Tinebase_Model_FullUser $user)
321     {
322         $maxLoginNameLength = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_USERNAME_LENGTH);
323         if (!empty($maxLoginNameLength) && strlen($user->accountLoginName) > $maxLoginNameLength) {
324             throw new Tinebase_Exception_SystemGeneric('The login name exceeds the maximum length of  ' . $maxLoginNameLength . ' characters. Please choose another one.');
325         }
326         return TRUE;
327     }
328     
329     /**
330      * look for primary group, if it does not exist, fallback to default user group
331      * 
332      * @param Tinebase_Model_FullUser $user
333      * @throws Tinebase_Exception_SystemGeneric
334      */
335     protected function _checkPrimaryGroupExistance(Tinebase_Model_FullUser $user)
336     {
337         try {
338             $group = Tinebase_Group::getInstance()->getGroupById($user->accountPrimaryGroup);
339         } catch (Tinebase_Exception_Record_NotDefined $ternd) {
340             $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
341             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
342                 . ' Group with id ' . $user->accountPrimaryGroup . ' not found. Use default group (' . $defaultUserGroup->name
343                 . ') as primary group for ' . $user->accountLoginName);
344             
345             $user->accountPrimaryGroup = $defaultUserGroup->getId();
346         }
347     }
348     
349     /**
350      * delete accounts
351      *
352      * @param   mixed $_accountIds  array of account ids
353      * @return  array with success flag
354      * @throws  Tinebase_Exception_Record_NotAllowed
355      */
356     public function delete($_accountIds)
357     {
358         $this->checkRight('MANAGE_ACCOUNTS');
359         
360         $groupsController = Admin_Controller_Group::getInstance();
361         
362         foreach ((array)$_accountIds as $accountId) {
363             if ($accountId === Tinebase_Core::getUser()->getId()) {
364                 $message = 'You are not allowed to delete yourself!';
365                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $message);
366                 throw new Tinebase_Exception_AccessDenied($message);
367             }
368             
369             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " about to remove user with id: {$accountId}");
370             
371             $oldUser = $this->get($accountId);
372             
373             $memberships = $groupsController->getGroupMemberships($accountId);
374             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " removing user from groups: " . print_r($memberships, true));
375             
376             foreach ((array)$memberships as $groupId) {
377                 $groupsController->removeGroupMember($groupId, $accountId);
378             }
379             
380             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true && !empty($oldUser->contact_id)) {
381                 $this->_deleteContact($oldUser->contact_id);
382             }
383             
384             $this->_userBackend->deleteUser($accountId);
385         }
386     }
387     
388     /**
389      * returns all shared addressbooks
390      * 
391      * @return Tinebase_Record_RecordSet of shared addressbooks
392      * 
393      * @todo do we need to fetch ALL shared containers here (even if admin user has NO grants for them)?
394      */
395     public function searchSharedAddressbooks()
396     {
397         $this->checkRight('MANAGE_ACCOUNTS');
398         
399         return Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ, TRUE);
400     }
401     
402     /**
403      * returns default internal addressbook container
404      * 
405      * @return string|int ID
406      */
407     public function getDefaultInternalAddressbook()
408     {
409         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
410         return $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK];
411     }
412     
413     /**
414      * create or update contact in addressbook backend
415      * 
416      * @param  Tinebase_Model_FullUser $_user
417      * @return Addressbook_Model_Contact
418      */
419     public function createOrUpdateContact(Tinebase_Model_FullUser $_user)
420     {
421         $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
422         $contactsBackend->setGetDisabledContacts(true);
423         
424         if (empty($_user->container_id)) {
425             $_user->container_id = $this->getDefaultInternalAddressbook();
426         }
427         
428         try {
429             if (empty($_user->contact_id)) { // jump to catch block
430                 throw new Tinebase_Exception_NotFound('contact_id is empty');
431             }
432             
433             $contact = $contactsBackend->get($_user->contact_id);
434             
435             // update exisiting contact
436             $contact->n_family   = $_user->accountLastName;
437             $contact->n_given    = $_user->accountFirstName;
438             $contact->n_fn       = $_user->accountFullName;
439             $contact->n_fileas   = $_user->accountDisplayName;
440             $contact->email      = $_user->accountEmailAddress;
441             $contact->type       = Addressbook_Model_Contact::CONTACTTYPE_USER;
442             $contact->container_id = $_user->container_id;
443             
444             // add modlog info
445             Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
446             
447             $contact = $contactsBackend->update($contact);
448             
449         } catch (Tinebase_Exception_NotFound $tenf) {
450             // add new contact
451             $contact = new Addressbook_Model_Contact(array(
452                 'n_family'      => $_user->accountLastName,
453                 'n_given'       => $_user->accountFirstName,
454                 'n_fn'          => $_user->accountFullName,
455                 'n_fileas'      => $_user->accountDisplayName,
456                 'email'         => $_user->accountEmailAddress,
457                 'type'          => Addressbook_Model_Contact::CONTACTTYPE_USER,
458                 'container_id'  => $_user->container_id
459             ));
460             
461             // add modlog info
462             Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
463     
464             $contact = $contactsBackend->create($contact);
465         }
466         
467         return $contact;
468     }
469     
470     /**
471      * delete contact associated with user
472      * 
473      * @param string  $_contactId
474      */
475     protected function _deleteContact($_contactId)
476     {
477         $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
478         
479         $contactsBackend->delete($_contactId);
480     }
481 }