Merge branch '2014.11' into 2015.11
[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  int
129      */
130     public function setAccountStatus($_accountId, $_status)
131     {
132         $this->checkRight('MANAGE_ACCOUNTS');
133         
134         $result = $this->_userBackend->setStatus($_accountId, $_status);
135         
136         if ($_status === Tinebase_Model_FullUser::ACCOUNT_STATUS_DISABLED) {
137             // TODO send this for blocked/expired, too? allow to configure this?
138             Tinebase_User::getInstance()->sendDeactivationNotification($_accountId);
139         }
140         
141         return $result;
142     }
143     
144     /**
145      * set the password for a given account
146      *
147      * @param  Tinebase_Model_FullUser  $_account the account
148      * @param  string                   $_password the new password
149      * @param  string                   $_passwordRepeat the new password again
150      * @param  bool                     $_mustChange
151      * @return void
152      * 
153      * @todo add must change pwd info to normal tine user accounts
154      */
155     public function setAccountPassword(Tinebase_Model_FullUser $_account, $_password, $_passwordRepeat, $_mustChange = null)
156     {
157         $this->checkRight('MANAGE_ACCOUNTS');
158         
159         if ($_password != $_passwordRepeat) {
160             throw new Admin_Exception("Passwords don't match.");
161         }
162         
163         $this->_userBackend->setPassword($_account, $_password, true, $_mustChange);
164         
165         Tinebase_Core::getLogger()->info(
166             __METHOD__ . '::' . __LINE__ . 
167             ' Set new password for user ' . $_account->accountLoginName . '. Must change:' . $_mustChange
168         );
169     }
170     
171     /**
172      * update user
173      *
174      * @param  Tinebase_Model_FullUser    $_user            the user
175      * @param  string                     $_password        the new password
176      * @param  string                     $_passwordRepeat  the new password again
177      * 
178      * @return Tinebase_Model_FullUser
179      */
180     public function update(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
181     {
182         $this->checkRight('MANAGE_ACCOUNTS');
183         
184         $oldUser = $this->_userBackend->getUserByProperty('accountId', $_user, 'Tinebase_Model_FullUser');
185         
186         if ($oldUser->accountLoginName !== $_user->accountLoginName) {
187             $this->_checkLoginNameExistance($_user);
188         }
189         $this->_checkLoginNameLength($_user);
190         $this->_checkPrimaryGroupExistance($_user);
191         
192         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Update user ' . $_user->accountLoginName);
193         
194         try {
195             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
196             
197             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
198                 $_user->contact_id = $oldUser->contact_id;
199                 $contact = $this->createOrUpdateContact($_user);
200                 $_user->contact_id = $contact->getId();
201             }
202             
203             Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'update', $oldUser);
204             
205             $user = $this->_userBackend->updateUser($_user);
206             
207             // make sure primary groups is in the list of groupmemberships
208             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
209             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
210             
211             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
212             
213         } catch (Exception $e) {
214             Tinebase_TransactionManager::getInstance()->rollBack();
215             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e);
216             
217             if ($e instanceof Zend_Db_Statement_Exception && preg_match('/Lock wait timeout exceeded/', $e->getMessage())) {
218                 throw new Tinebase_Exception_Backend_Database_LockTimeout($e->getMessage());
219             }
220             
221             throw $e;
222         }
223         
224         // fire needed events
225         $event = new Admin_Event_UpdateAccount;
226         $event->account = $user;
227         $event->oldAccount = $oldUser;
228         Tinebase_Event::fireEvent($event);
229         
230         if (!empty($_password) && !empty($_passwordRepeat)) {
231             $this->setAccountPassword($_user, $_password, $_passwordRepeat, FALSE);
232         }
233
234         return $user;
235     }
236     
237     /**
238      * create user
239      *
240      * @param  Tinebase_Model_FullUser  $_account           the account
241      * @param  string                     $_password           the new password
242      * @param  string                     $_passwordRepeat  the new password again
243      * @return Tinebase_Model_FullUser
244      */
245     public function create(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
246     {
247         $this->checkRight('MANAGE_ACCOUNTS');
248         
249         // avoid forging accountId, gets created in backend
250         unset($_user->accountId);
251         
252         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create new user ' . $_user->accountLoginName);
253         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_user->toArray(), TRUE));
254         
255         $this->_checkLoginNameExistance($_user);
256         $this->_checkLoginNameLength($_user);
257         $this->_checkPrimaryGroupExistance($_user);
258         
259         if ($_password != $_passwordRepeat) {
260             throw new Admin_Exception("Passwords don't match.");
261         } else if (empty($_password)) {
262             $_password = '';
263             $_passwordRepeat = '';
264         }
265         Tinebase_User::getInstance()->checkPasswordPolicy($_password, $_user);
266         
267         try {
268             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
269             
270             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
271                 $contact = $this->createOrUpdateContact($_user);
272                 $_user->contact_id = $contact->getId();
273             }
274             
275             Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'create');
276             
277             $user = $this->_userBackend->addUser($_user);
278             
279             // make sure primary groups is in the list of groupmemberships
280             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
281             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
282             
283             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
284
285         } catch (Exception $e) {
286             Tinebase_TransactionManager::getInstance()->rollBack();
287             Tinebase_Exception::log($e);
288             throw $e;
289         }
290         
291         $event = new Admin_Event_AddAccount(array(
292             'account' => $user
293         ));
294         Tinebase_Event::fireEvent($event);
295         
296         $this->setAccountPassword($user, $_password, $_passwordRepeat);
297
298         return $user;
299     }
300     
301     /**
302      * look for user with the same login name
303      * 
304      * @param Tinebase_Model_FullUser $user
305      * @return boolean
306      * @throws Tinebase_Exception_SystemGeneric
307      */
308     protected function _checkLoginNameExistance(Tinebase_Model_FullUser $user)
309     {
310         try {
311             $existing = Tinebase_User::getInstance()->getUserByLoginName($user->accountLoginName);
312             if ($user->getId() === NULL || $existing->getId() !== $user->getId()) {
313                 throw new Tinebase_Exception_SystemGeneric('Login name already exists. Please choose another one.');
314             }
315         } catch (Tinebase_Exception_NotFound $tenf) {
316         }
317         
318         return TRUE;
319     }
320     
321     /**
322      * Check login name length
323      *
324      * @param Tinebase_Model_FullUser $user
325      * @return boolean
326      * @throws Tinebase_Exception_SystemGeneric
327      */
328     protected function _checkLoginNameLength(Tinebase_Model_FullUser $user)
329     {
330         $maxLoginNameLength = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_USERNAME_LENGTH);
331         if (!empty($maxLoginNameLength) && strlen($user->accountLoginName) > $maxLoginNameLength) {
332             throw new Tinebase_Exception_SystemGeneric('The login name exceeds the maximum length of  ' . $maxLoginNameLength . ' characters. Please choose another one.');
333         }
334         return TRUE;
335     }
336     
337     /**
338      * look for primary group, if it does not exist, fallback to default user group
339      * 
340      * @param Tinebase_Model_FullUser $user
341      * @throws Tinebase_Exception_SystemGeneric
342      */
343     protected function _checkPrimaryGroupExistance(Tinebase_Model_FullUser $user)
344     {
345         try {
346             $group = Tinebase_Group::getInstance()->getGroupById($user->accountPrimaryGroup);
347         } catch (Tinebase_Exception_Record_NotDefined $ternd) {
348             $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
349             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
350                 . ' Group with id ' . $user->accountPrimaryGroup . ' not found. Use default group (' . $defaultUserGroup->name
351                 . ') as primary group for ' . $user->accountLoginName);
352             
353             $user->accountPrimaryGroup = $defaultUserGroup->getId();
354         }
355     }
356     
357     /**
358      * delete accounts
359      *
360      * @param   mixed $_accountIds  array of account ids
361      * @return  array with success flag
362      * @throws  Tinebase_Exception_Record_NotAllowed
363      */
364     public function delete($_accountIds)
365     {
366         $this->checkRight('MANAGE_ACCOUNTS');
367         
368         $groupsController = Admin_Controller_Group::getInstance();
369         
370         foreach ((array)$_accountIds as $accountId) {
371             if ($accountId === Tinebase_Core::getUser()->getId()) {
372                 $message = 'You are not allowed to delete yourself!';
373                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $message);
374                 throw new Tinebase_Exception_AccessDenied($message);
375             }
376             
377             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " about to remove user with id: {$accountId}");
378             
379             $oldUser = $this->get($accountId);
380             
381             $memberships = $groupsController->getGroupMemberships($accountId);
382             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " removing user from groups: " . print_r($memberships, true));
383             
384             foreach ((array)$memberships as $groupId) {
385                 $groupsController->removeGroupMember($groupId, $accountId);
386             }
387             
388             $this->_userBackend->deleteUser($accountId);
389         }
390     }
391     
392     /**
393      * returns all shared addressbooks
394      * 
395      * @return Tinebase_Record_RecordSet of shared addressbooks
396      * 
397      * @todo do we need to fetch ALL shared containers here (even if admin user has NO grants for them)?
398      */
399     public function searchSharedAddressbooks()
400     {
401         $this->checkRight('MANAGE_ACCOUNTS');
402         
403         return Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ, TRUE);
404     }
405     
406     /**
407      * returns default internal addressbook container
408      * 
409      * @return string|int ID
410      */
411     public function getDefaultInternalAddressbook()
412     {
413         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
414         if (empty($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])) {
415             $internalAdb = Addressbook_Setup_Initialize::setDefaultInternalAddressbook();
416             $internalAdbId = $internalAdb->getId();
417         } else {
418             $internalAdbId = $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK];
419         }
420         return $internalAdbId;
421     }
422     
423     /**
424      * create or update contact in addressbook backend
425      * 
426      * @param  Tinebase_Model_FullUser $_user
427      * @return Addressbook_Model_Contact
428      */
429     public function createOrUpdateContact(Tinebase_Model_FullUser $_user)
430     {
431         $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
432         $contactsBackend->setGetDisabledContacts(true);
433         
434         if (empty($_user->container_id)) {
435             $_user->container_id = $this->getDefaultInternalAddressbook();
436         }
437         
438         try {
439             if (empty($_user->contact_id)) { // jump to catch block
440                 throw new Tinebase_Exception_NotFound('contact_id is empty');
441             }
442             
443             $contact = $contactsBackend->get($_user->contact_id);
444             
445             // update exisiting contact
446             $contact->n_family   = $_user->accountLastName;
447             $contact->n_given    = $_user->accountFirstName;
448             $contact->n_fn       = $_user->accountFullName;
449             $contact->n_fileas   = $_user->accountDisplayName;
450             $contact->email      = $_user->accountEmailAddress;
451             $contact->type       = Addressbook_Model_Contact::CONTACTTYPE_USER;
452             $contact->container_id = $_user->container_id;
453             
454             // add modlog info
455             Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
456             
457             $contact = $contactsBackend->update($contact);
458             
459         } catch (Tinebase_Exception_NotFound $tenf) {
460             // add new contact
461             $contact = new Addressbook_Model_Contact(array(
462                 'n_family'      => $_user->accountLastName,
463                 'n_given'       => $_user->accountFirstName,
464                 'n_fn'          => $_user->accountFullName,
465                 'n_fileas'      => $_user->accountDisplayName,
466                 'email'         => $_user->accountEmailAddress,
467                 'type'          => Addressbook_Model_Contact::CONTACTTYPE_USER,
468                 'container_id'  => $_user->container_id
469             ));
470             
471             // add modlog info
472             Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
473     
474             $contact = $contactsBackend->create($contact);
475         }
476         
477         return $contact;
478     }
479 }