b4b2782ef735a0bcac44a2cb09bb179680f59216
[tine20] / tine20 / Tinebase / EmailUser / Sql.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  EmailUser
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2012-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schüle
10  * */
11
12 /**
13  * plugin to handle sql email accounts
14  * 
15  * @package    Tinebase
16  * @subpackage EmailUser
17  */
18 abstract class Tinebase_EmailUser_Sql extends Tinebase_User_Plugin_Abstract
19 {
20     /**
21      * user table name with prefix
22      *
23      * @var string
24      */
25     protected $_userTable = NULL;
26
27     /**
28      * schema of the table
29      *
30      * @var array
31      */
32     protected $_schema = NULL;
33
34     /**
35      * email user config
36      * 
37      * @var array 
38      */
39      protected $_config = array();
40     
41     /**
42      * user properties mapping
43      *
44      * @var array
45      */
46     protected $_propertyMapping = array();
47
48     /**
49      * config key (IMAP/SMTP)
50      * 
51      * @var string
52      */
53     protected $_configKey = NULL;
54     
55     /**
56      * subconfig for user email backend (for example: dovecot)
57      * 
58      * @var string
59      */
60     protected $_subconfigKey =  NULL;
61     
62     /**
63     * client id
64     *
65     * @var string
66     */
67     protected $_clientId = NULL;
68     
69     /**
70      * the constructor
71      * 
72      * @param array $_options
73      */
74     public function __construct(array $_options = array())
75     {
76         if ($this instanceof Tinebase_EmailUser_Smtp_Interface) {
77             $this->_configKey = Tinebase_Config::SMTP;
78         } else if ($this instanceof Tinebase_EmailUser_Imap_Interface) {
79             $this->_configKey = Tinebase_Config::IMAP;
80         } else {
81             throw new Tinebase_Exception_UnexpectedValue('Plugin must be instance of Tinebase_EmailUser_Smtp_Interface or Tinebase_EmailUser_Imap_Interface');
82         }
83         
84         // get email user backend config options (host, dbname, username, password, port)
85         $emailConfig = Tinebase_Config::getInstance()->get($this->_configKey, new Tinebase_Config_Struct())->toArray();
86         
87         // merge _config and email backend config
88         if ($this->_subconfigKey) {
89             // flatten array
90             $emailConfig = array_merge($emailConfig[$this->_subconfigKey], $emailConfig);
91         }
92         // merge _config and email backend config
93         $this->_config = array_merge($this->_config, $emailConfig);
94         
95         // _tablename (for example "dovecot_users")
96         $this->_userTable = $this->_config['prefix'] . $this->_config['userTable'];
97         
98         // connect to DB
99         $this->_getDB();
100         $this->_dbCommand = Tinebase_Backend_Sql_Command::factory($this->_db);
101         
102         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($this->_config, TRUE));
103     }
104     
105     /**
106      * get new email user
107      * 
108      * @param  Tinebase_Model_FullUser   $_user
109      * @return Tinebase_Model_EmailUser
110      */
111     public function getNewUser(Tinebase_Model_FullUser $_user)
112     {
113         $result = new Tinebase_Model_EmailUser(array(
114             'emailUserId'     => $_user->getId(),
115             'emailUsername' => $this->_appendDomain($_user->accountLoginName)
116         ));
117         
118         return $result;
119     }
120     
121     /**
122     * delete user by id
123     *
124     * @param  Tinebase_Model_FullUser  $_user
125     */
126     public function inspectDeleteUser(Tinebase_Model_FullUser $_user)
127     {
128         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
129             . ' Delete ' . $this->_configKey . ' email settings for user ' . $_user->accountLoginName);
130         
131         $this->_deleteUserById($_user->getId());
132     }
133     
134     /**
135      * delete user by id
136      * 
137      * @param string $id
138      */
139     protected function _deleteUserById($id)
140     {
141         $where = array(
142             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $id)
143         );
144         $this->_appendClientIdOrDomain($where);
145         
146         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
147             . ' ' . print_r($where, TRUE));
148         
149         $this->_db->delete($this->_userTable, $where);
150     }
151     
152     /**
153      * append domain if set or domain IS NULL
154      * 
155      * @param array $where
156      * @return string
157      * 
158      * @todo check if user table has domain or client_idnr field and use mapping for the field identifier
159      */
160     protected function _appendClientIdOrDomain(&$where = NULL)
161     {
162         if ($this->_clientId !== NULL) {
163             $cond = $this->_db->quoteInto($this->_db->quoteIdentifier($this->_userTable . '.' . 'client_idnr') . ' = ?', $this->_clientId);
164         } else {
165             if ((isset($this->_config['domain']) || array_key_exists('domain', $this->_config)) && ! empty($this->_config['domain'])) {
166                 $cond = $this->_db->quoteInto($this->_db->quoteIdentifier($this->_userTable . '.' . 'domain') . ' = ?',   $this->_config['domain']);
167             } else {
168                 $cond = $this->_db->quoteIdentifier($this->_userTable . '.' . 'domain') . " =''";
169             }
170         }
171         
172         if ($where !== NULL) {
173             $where[] = $cond;
174         }
175         
176         return $cond;
177     }
178
179     /**
180      * @param Tinebase_Model_User $_user
181      * @return mixed
182      */
183     public function getRawUserById(Tinebase_Model_User $_user)
184     {
185         $userId = $_user->getId();
186
187         $select = $this->_getSelect()
188             ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']) . ' = ?',   $userId);
189
190         // Perform query - retrieve user from database
191         $stmt = $this->_db->query($select);
192         $queryResult = $stmt->fetch();
193         $stmt->closeCursor();
194
195         if (!$queryResult) {
196             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
197                 . ' ' . $this->_subconfigKey . ' config for user ' . $userId . ' not found!');
198         }
199
200         return $queryResult;
201     }
202
203     /**
204      * inspect get user by property
205      * 
206      * @param Tinebase_Model_User  $_user  the user object
207      */
208     public function inspectGetUserByProperty(Tinebase_Model_User $_user)
209     {
210         if (! $_user instanceof Tinebase_Model_FullUser) {
211             return;
212         }
213         
214         $rawUser = $this->getRawUserById($_user);
215         
216         // convert data to Tinebase_Model_EmailUser
217         $emailUser = $this->_rawDataToRecord((array)$rawUser);
218         
219         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
220             . ' ' . print_r($emailUser->toArray(), TRUE));
221         
222         // modify/correct user name
223         // set emailUsername to Tine 2.0 account login name and append domain for login purposes if set
224         if (empty($emailUser->emailUsername)) {
225             $emailUser->emailUsername = $this->_getEmailUserName($_user);
226         }
227         
228         if ($this instanceof Tinebase_EmailUser_Smtp_Interface) {
229             $_user->smtpUser  = $emailUser;
230             $_user->emailUser = Tinebase_EmailUser::merge($_user->emailUser, clone $_user->smtpUser);
231         } else {
232             $_user->imapUser  = $emailUser;
233             $_user->emailUser = Tinebase_EmailUser::merge(clone $_user->imapUser, $_user->emailUser);
234         }
235     }
236     
237     protected function _getConfiguredSystemDefaults()
238     {
239         $systemDefaults = array();
240         
241         $hostAttribute = ($this instanceof Tinebase_EmailUser_Imap_Interface) ? 'host' : 'hostname';
242         if (!empty($this->_config[$hostAttribute])) {
243             $systemDefaults['emailHost'] = $this->_config[$hostAttribute];
244         }
245         
246         if (!empty($this->_config['port'])) {
247             $systemDefaults['emailPort'] = $this->_config['port'];
248         }
249         
250         if (!empty($this->_config['ssl'])) {
251             $systemDefaults['emailSecure'] = $this->_config['ssl'];
252         }
253         
254         if (!empty($this->_config['auth'])) {
255             $systemDefaults['emailAuth'] = $this->_config['auth'];
256         }
257         
258         return $systemDefaults;
259     }
260     
261     /**
262      * update/set email user password
263      * 
264      * @param  string  $_userId
265      * @param  string  $_password
266      * @param  bool    $_encrypt encrypt password
267      */
268     public function inspectSetPassword($_userId, $_password, $_encrypt = TRUE)
269     {
270         if (!isset($this->_propertyMapping['emailPassword'])) {
271             return;
272         }
273         
274         $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP, new Tinebase_Config_Struct())->toArray();
275         if ((isset($imapConfig['pwsuffix']) || array_key_exists('pwsuffix', $imapConfig))) {
276             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
277                 ' Appending configured pwsuffix to new email account password.');
278             $password = $_password . $imapConfig['pwsuffix'];
279         } else {
280             $password = $_password;
281         }
282         
283         $values = array(
284             $this->_propertyMapping['emailPassword'] => ($_encrypt) ? Hash_Password::generate($this->_config['emailScheme'], $password) : $password
285         );
286         
287         $where = array(
288             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $_userId)
289         );
290         $this->_appendClientIdOrDomain($where);
291         
292         $this->_db->update($this->_userTable, $values, $where);
293     }
294     
295     /*********  protected functions  *********/
296     
297     /**
298      * get the basic select object to fetch records from the database
299      *  
300      * @param  array|string|Zend_Db_Expr  $_cols        columns to get, * per default
301      * @param  boolean                    $_getDeleted  get deleted records (if modlog is active)
302      * @return Zend_Db_Select
303      */
304     abstract protected function _getSelect($_cols = '*', $_getDeleted = FALSE);
305     
306     /**
307      * adds email properties for a new user
308      * 
309      * @param  Tinebase_Model_FullUser  $_addedUser
310      * @param  Tinebase_Model_FullUser  $_newUserProperties
311      */
312     protected function _addUser(Tinebase_Model_FullUser $_addedUser, Tinebase_Model_FullUser $_newUserProperties)
313     {
314         if (! $_addedUser->accountEmailAddress) {
315             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
316             . ' User ' . $_addedUser->accountDisplayName . ' has no email address defined. Skipping email user creation.');
317             return;
318         }
319         
320         $emailUserData = $this->_recordToRawData($_addedUser, $_newUserProperties);
321
322         $emailUsername = $emailUserData[$this->_propertyMapping['emailUsername']];
323         
324         $this->_checkEmailExistance($emailUsername);
325         
326         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
327             . ' Adding new ' . $this->_configKey . ' email user ' . $emailUsername);
328         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' 
329             . print_r($emailUserData, TRUE));
330         
331         try {
332             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
333             
334             // generate random password if not set
335             if (isset($this->_propertyMapping['emailPassword']) && empty($emailUserData[$this->_propertyMapping['emailPassword']])) {
336                 $emailUserData[$this->_propertyMapping['emailPassword']] = Hash_Password::generate($this->_config['emailScheme'], Tinebase_Record_Abstract::generateUID());
337             }
338             
339             $insertData = $emailUserData;
340             $this->_beforeAddOrUpdate($insertData);
341
342             $insertData = array_intersect_key($insertData, $this->getSchema());
343             $this->_db->insert($this->_userTable, $insertData);
344             
345             $this->_afterAddOrUpdate($emailUserData);
346             
347             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
348             
349             $this->inspectGetUserByProperty($_addedUser);
350             
351         } catch (Zend_Db_Statement_Exception $zdse) {
352             Tinebase_TransactionManager::getInstance()->rollBack();
353             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Error while creating email user: ' . $zdse);
354         }
355     }
356     
357     /**
358      * interceptor before add
359      * 
360      * @param array $emailUserData
361      */
362     protected function _beforeAddOrUpdate(&$emailUserData)
363     {
364         
365     }
366     
367     /**
368      * interceptor after add
369      * 
370      * @param array $emailUserData
371      */
372     protected function _afterAddOrUpdate(&$emailUserData)
373     {
374         
375     }
376     
377     /**
378      * check if user email already exists in table
379      * 
380      * @param  string  $email
381      */
382     protected function _checkEmailExistance($email)
383     {
384         $select = $this->_getSelect()
385             ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUsername']) . ' = ?',   $email)
386             ->where($this->_appendClientIdOrDomain());
387         
388         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
389         
390         $stmt = $this->_db->query($select);
391         $queryResult = $stmt->fetch();
392         $stmt->closeCursor();
393         
394         if (! $queryResult) {
395             return;
396         }
397         
398         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($queryResult, TRUE));
399         
400         $userId = $queryResult[$this->_propertyMapping['emailUserId']];
401         
402         try {
403             Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $userId);
404             throw new Tinebase_Exception_SystemGeneric('Could not overwrite existing email user.');
405         } catch (Tinebase_Exception_NotFound $tenf) {
406             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Delete obsolete email user ' .$userId);
407             $this->_deleteUserById($userId);
408         }
409     }
410     
411     /**
412      * updates email properties for an existing user
413      * 
414      * @param  Tinebase_Model_FullUser  $_updatedUser
415      * @param  Tinebase_Model_FullUser  $_newUserProperties
416      */
417     protected function _updateUser(Tinebase_Model_FullUser $_updatedUser, Tinebase_Model_FullUser $_newUserProperties)
418     {
419         $emailUserData = $this->_recordToRawData($_updatedUser, $_newUserProperties);
420
421         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))  Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating Dovecot user ' . $emailUserData[$this->_propertyMapping['emailUsername']]);
422         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($emailUserData, TRUE));
423         
424         $where = array(
425             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $emailUserData[$this->_propertyMapping['emailUserId']])
426         );
427         $this->_appendClientIdOrDomain($where);
428         
429         try {
430             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
431             
432             $updateData = $emailUserData;
433             
434             $this->_beforeAddOrUpdate($updateData);
435
436             $updateData = array_intersect_key($updateData, $this->getSchema());
437             $this->_db->update($this->_userTable, $updateData, $where);
438             
439             $this->_afterAddOrUpdate($emailUserData);
440             
441             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
442             
443             $this->inspectGetUserByProperty($_updatedUser);
444             
445         } catch (Zend_Db_Statement_Exception $zdse) {
446             Tinebase_TransactionManager::getInstance()->rollBack();
447             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Error while updating email user: ' . $zdse->getMessage());
448         }
449     }
450     
451     /**
452      * check if user exists already in email backjend user table
453      * 
454      * @param  Tinebase_Model_FullUser  $_user
455      * @throws Tinebase_Exception_Backend_Database
456      */
457     protected function _userExists(Tinebase_Model_FullUser $_user)
458     {
459         $select = $this->_getSelect();
460         
461         $select
462           ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']) . ' = ?',   $_user->getId());
463         
464         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
465         
466         // Perform query - retrieve user from database
467         try {
468             $stmt = $this->_db->query($select);
469         } catch (Zend_Db_Statement_Exception $zdse) {
470             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $zdse);
471             throw new Tinebase_Exception_Backend_Database($zdse->getMessage());
472         }
473         $queryResult = $stmt->fetch();
474         $stmt->closeCursor();
475         
476         if (!$queryResult) {
477             return false;
478         }
479         
480         return true;
481     }
482
483     /**
484      * returns the db schema
485      * @return array
486      * @throws Tinebase_Exception_Backend_Database
487      *
488      * @refactor use trait (see \Tinebase_Backend_Sql_Abstract::getSchema)
489      */
490     public function getSchema()
491     {
492         if (!$this->_schema) {
493             try {
494                 $this->_schema = Tinebase_Db_Table::getTableDescriptionFromCache($this->_userTable, $this->_db);
495             } catch (Zend_Db_Adapter_Exception $zdae) {
496                 throw new Tinebase_Exception_Backend_Database('Connection failed: ' . $zdae->getMessage());
497             }
498         }
499
500         return $this->_schema;
501     }
502
503     /**
504      * converts raw data from adapter into a single record / do mapping
505      *
506      * @param  array                    $_data
507      * @return Tinebase_Model_EmailUser
508      */
509     abstract protected function _rawDataToRecord(array $_rawdata);
510      
511     /**
512      * returns array of raw user data
513      *
514      * @param  Tinebase_Model_FullUser  $_user
515      * @param  Tinebase_Model_FullUser  $_newUserProperties
516      * @return array
517      */
518     abstract protected function _recordToRawData(Tinebase_Model_FullUser $_user, Tinebase_Model_FullUser $_newUserProperties);
519 }