0009986: allow to use email address as username for system accounts
[tine20] / tine20 / Felamimail / Model / Account.php
1 <?php
2 /**
3  * class to hold Account data
4  * 
5  * @package     Felamimail
6  * @subpackage    Model
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) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo        update account credentials if user password changed
12  * @todo        use generic (JSON encoded) field for 'other' settings like folder names
13  */
14
15 /**
16  * class to hold Account data
17  * 
18  * @property  string    trash_folder
19  * @property  string    sent_folder
20  * @property  string    drafts_folder
21  * @property  string    templates_folder
22  * @property  string    sieve_vacation_active
23  * @property  string    display_format
24  * @property  string    delimiter
25  * @property  string    type
26  * @property  string    signature_position
27  * @property  string    email
28  * @property  string    user_id
29  * @package   Felamimail
30  * @subpackage    Model
31  */
32 class Felamimail_Model_Account extends Tinebase_Record_Abstract
33 {
34     /**
35      * secure connection setting for no secure connection
36      *
37      */
38     const SECURE_NONE = 'none';
39
40     /**
41      * secure connection setting for tls
42      *
43      */
44     const SECURE_TLS = 'tls';
45
46     /**
47      * secure connection setting for ssl
48      *
49      */
50     const SECURE_SSL = 'ssl';
51     
52     /**
53      * system account
54      *
55      */
56     const TYPE_SYSTEM = 'system';
57     
58     /**
59      * user defined account
60      *
61      */
62     const TYPE_USER = 'user';
63
64     /**
65      * display format: plain
66      *
67      */
68     const DISPLAY_PLAIN = 'plain';
69     
70     /**
71      * display format: html
72      *
73      */
74     const DISPLAY_HTML = 'html';
75     
76     /**
77      * signature position above quote
78      *
79      */
80     const SIGNATURE_ABOVE_QUOTE = 'above';
81     
82     /**
83      * signature position above quote
84      *
85      */
86     const SIGNATURE_BELOW_QUOTE = 'below';
87     
88     /**
89      * display format: content type
90      *
91      * -> depending on content_type => text/plain show as plain text
92      */
93     const DISPLAY_CONTENT_TYPE = 'content_type';
94     
95     /**
96      * key in $_validators/$_properties array for the field which 
97      * represents the identifier
98      * 
99      * @var string
100      */    
101     protected $_identifier = 'id';
102     
103     /**
104      * application the record belongs to
105      *
106      * @var string
107      */
108     protected $_application = 'Felamimail';
109     
110     /**
111      * list of zend validator
112      * 
113      * this validators get used when validating user generated content with Zend_Input_Filter
114      *
115      * @var array
116      */
117     protected $_validators = array(
118         'id'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
119         'user_id'               => array(Zend_Filter_Input::ALLOW_EMPTY => true),
120         'name'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
121     // account type (system/user defined)
122         'type'        => array(
123             Zend_Filter_Input::ALLOW_EMPTY => true, 
124             Zend_Filter_Input::DEFAULT_VALUE => self::TYPE_USER,
125             array('InArray', array(self::TYPE_USER, self::TYPE_SYSTEM)),
126         ),
127     // imap server config
128         'host'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
129         'port'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 143),
130         'ssl'                   => array(
131             Zend_Filter_Input::ALLOW_EMPTY => true, 
132             Zend_Filter_Input::DEFAULT_VALUE => self::SECURE_TLS,
133             array('InArray', array(self::SECURE_NONE, self::SECURE_SSL, self::SECURE_TLS)),
134         ),
135         'credentials_id'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
136         'user'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
137         'password'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
138     // other settings (@todo add single JSON encoded field or keyfield for that?)
139         'sent_folder'           => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Sent'),
140         'trash_folder'          => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Trash'),
141         'drafts_folder'         => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Drafts'),
142         'templates_folder'      => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Templates'),
143         'has_children_support'  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 1),
144         'delimiter'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => '/'),
145         'display_format'        => array(
146             Zend_Filter_Input::ALLOW_EMPTY => true, 
147             Zend_Filter_Input::DEFAULT_VALUE => self::DISPLAY_HTML,
148             array('InArray', array(self::DISPLAY_HTML, self::DISPLAY_PLAIN, self::DISPLAY_CONTENT_TYPE)),
149         ),
150     // namespaces
151         'ns_personal'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
152         'ns_other'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
153         'ns_shared'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
154     // user data
155         'email'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true),
156         'from'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => ''),
157         'organization'          => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => ''),
158         'signature'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
159         'signature_position'    => array(
160             Zend_Filter_Input::ALLOW_EMPTY => true, 
161             Zend_Filter_Input::DEFAULT_VALUE => self::SIGNATURE_BELOW_QUOTE,
162             array('InArray', array(self::SIGNATURE_ABOVE_QUOTE, self::SIGNATURE_BELOW_QUOTE)),
163         ),
164         // smtp config
165         'smtp_port'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 25),
166         'smtp_hostname'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
167         'smtp_auth'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'login'),
168         'smtp_ssl'              => array(
169             Zend_Filter_Input::ALLOW_EMPTY => true, 
170             Zend_Filter_Input::DEFAULT_VALUE => self::SECURE_TLS,
171             array('InArray', array(self::SECURE_NONE, self::SECURE_SSL, self::SECURE_TLS)),
172         ),
173         'smtp_credentials_id'   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
174         'smtp_user'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
175         'smtp_password'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
176     // sieve config
177         'sieve_port'            => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 2000),
178         'sieve_hostname'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
179         'sieve_ssl'=> array(
180             Zend_Filter_Input::ALLOW_EMPTY => true, 
181             Zend_Filter_Input::DEFAULT_VALUE => self::SECURE_TLS,
182             array('InArray', array(self::SECURE_NONE, self::SECURE_TLS)),
183         ),
184         'sieve_vacation_active' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
185         //'sieve_credentials_id'  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
186         //'sieve_user'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
187         //'sieve_password'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
188     // modlog information
189         'created_by'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
190         'creation_time'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
191         'last_modified_by'      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
192         'last_modified_time'    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
193         'is_deleted'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
194         'deleted_time'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
195         'deleted_by'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
196         'seq'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
197     );
198     
199     /**
200      * name of fields containing datetime or an array of datetime information
201      *
202      * @var array list of datetime fields
203      */    
204     protected $_datetimeFields = array(
205         'creation_time',
206         'last_modified_time',
207         'deleted_time'
208     );
209     
210     /**
211      * name of fields that should be omited from modlog
212      *
213      * @var array list of modlog omit fields
214      */
215     protected $_modlogOmitFields = array(
216         'user',
217         'password',
218         'smtp_user',
219         'smtp_password',
220         'credentials_id',
221         'smtp_credentials_id'
222     );
223     
224     /**
225      * overwrite constructor to add more filters
226      *
227      * @param mixed $_data
228      * @param bool $_bypassFilters
229      * @param mixed $_convertDates
230      * @return void
231      */
232     public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = true)
233     {
234         // set some fields to default if not set
235         $this->_filters['ssl']              = array(new Zend_Filter_Empty(self::SECURE_TLS), 'StringTrim', 'StringToLower');
236         $this->_filters['smtp_ssl']         = array(new Zend_Filter_Empty(self::SECURE_TLS), 'StringTrim', 'StringToLower');
237         $this->_filters['sieve_ssl']        = array(new Zend_Filter_Empty(self::SECURE_TLS), 'StringTrim', 'StringToLower');
238         $this->_filters['display_format']   = array(new Zend_Filter_Empty(self::DISPLAY_HTML), 'StringTrim', 'StringToLower');
239         $this->_filters['port']             = new Zend_Filter_Empty(NULL);
240         $this->_filters['smtp_port']        = new Zend_Filter_Empty(NULL);
241         $this->_filters['sieve_port']       = new Zend_Filter_Empty(NULL);
242         
243         return parent::__construct($_data, $_bypassFilters, $_convertDates);
244     }
245     
246     /**
247      * get imap config array
248      * - decrypt pwd/user with user password
249      *
250      * @return array
251      */
252     public function getImapConfig()
253     {
254         $this->resolveCredentials(FALSE);
255         
256         $imapConfigFields = array('host', 'port', 'user', 'password');
257         $result = array();
258         foreach ($imapConfigFields as $field) {
259             if ($field === 'user') {
260                 $result[$field] = $this->getUsername();
261             } else {
262                 $result[$field] = $this->{$field};
263             }
264         }
265         
266         if ($this->ssl && $this->ssl != 'none') {
267             $result['ssl'] = strtoupper($this->ssl);
268         }
269         
270         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($result, true));
271         
272         return $result;
273     }
274     
275     /**
276      * add domain from imap settings to username
277      * 
278      * @param string $username
279      * @param string $type (imap|smtp)
280      * @param array $config
281      * @return string
282      */
283     public function getUsername($username = null, $type = Tinebase_Config::IMAP, $config = null)
284     {
285         $result = ($username !== null) ? $username : $this->user;
286         
287         if ($this->type == self::TYPE_SYSTEM) {
288             if (! $config) {
289                 $config = Tinebase_Config::getInstance()->get($type, new Tinebase_Config_Struct())->toArray();
290             }
291             
292             // @todo add documentation for config option and add it to setup gui
293             $domainConfigKey = ($type === Tinebase_Config::IMAP) ? 'domain' : 'primarydomain';
294             if (isset($config['useEmailAsUsername']) && $config['useEmailAsUsername']) {
295                 $result = $this->email;
296             } else if (isset($config[$domainConfigKey]) && ! empty($config[$domainConfigKey])) {
297                 $result .= '@' . $config[$domainConfigKey];
298             }
299         }
300         
301         return $result;
302     }
303     
304     /**
305      * get smtp config
306      *
307      * @return array
308      */
309     public function getSmtpConfig()
310     {
311         $this->resolveCredentials(FALSE, TRUE, TRUE);
312         
313         $result = array();
314         
315         // get values from account
316         if ($this->smtp_hostname) {
317             $result['hostname'] = $this->smtp_hostname;
318         }
319         if ($this->smtp_user) {
320             $result['username'] = $this->smtp_user;
321         }
322         if ($this->smtp_password) {
323             $result['password'] = $this->smtp_password;
324         }
325         if ($this->smtp_auth) {
326             $result['auth'] = $this->smtp_auth;
327         }
328         if ($this->smtp_ssl) {
329             $result['ssl'] = $this->smtp_ssl;
330         }
331         if ($this->smtp_port) {
332             $result['port'] = $this->smtp_port;
333         }
334         
335         // system account: overwriting with values from config if set
336         if ($this->type == self::TYPE_SYSTEM) {
337             $systemAccountConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::SMTP, new Tinebase_Config_Struct())->toArray();
338             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($systemAccountConfig, true));
339             // we don't need username/pass from system config (those are the notification service credentials)
340             // @todo think about renaming config keys (to something like notification_email/pass)
341             unset($systemAccountConfig['username']);
342             unset($systemAccountConfig['password']);
343             $result = array_merge($result, $systemAccountConfig);
344             
345             $result['username'] = $this->getUsername($result['username'], Tinebase_Config::SMTP, $systemAccountConfig);
346         }
347         
348         if ((isset($result['auth']) || array_key_exists('auth', $result)) && $result['auth'] == 'none') {
349             unset($result['username']);
350             unset($result['password']);
351             unset($result['auth']);
352         }
353         if ((isset($result['ssl']) || array_key_exists('ssl', $result)) && $result['ssl'] == 'none') {
354             unset($result['ssl']);
355         }
356         
357         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($result, true));
358         
359         return $result;
360     }
361
362     /**
363      * get sieve config array
364      *
365      * @return array
366      * 
367      * @todo add sieve credentials? this uses imap credentials atm.
368      */
369     public function getSieveConfig()
370     {
371         $this->resolveCredentials(FALSE);
372         
373         $result = array(
374             'host'      => $this->sieve_hostname,
375             'port'      => $this->sieve_port, 
376             'ssl'       => ($this->sieve_ssl && $this->sieve_ssl !== self::SECURE_NONE) ? $this->sieve_ssl : FALSE,
377             'username'  => $this->getUsername(),
378             'password'  => $this->password,
379         );
380         
381         return $result;
382     }
383     
384     /**
385      * to array
386      *
387      * @param boolean $_recursive
388      */
389     public function toArray($_recursive = TRUE)
390     {
391         $result = parent::toArray($_recursive);
392
393         // don't show password
394         unset($result['password']);
395         unset($result['smtp_password']);
396         
397         return $result;
398     }
399
400     /**
401      * resolve imap or smtp credentials
402      *
403      * @param boolean $_onlyUsername
404      * @param boolean $_throwException
405      * @param boolean $_smtp
406      * @return boolean
407      */
408     public function resolveCredentials($_onlyUsername = TRUE, $_throwException = FALSE, $_smtp = FALSE)
409     {
410         if ($_smtp) {
411             $passwordField      = 'smtp_password';
412             $userField          = 'smtp_user';
413             $credentialsField   = 'smtp_credentials_id';
414         } else {
415             $passwordField      = 'password';
416             $userField          = 'user';
417             $credentialsField   = 'credentials_id';
418         }
419         
420         if (! $this->{$userField} || ! ($this->{$passwordField} && ! $_onlyUsername)) {
421             
422             $credentialsBackend = Tinebase_Auth_CredentialCache::getInstance();
423             $userCredentialCache = Tinebase_Core::get(Tinebase_Core::USERCREDENTIALCACHE);
424             
425             if ($userCredentialCache !== NULL) {
426                 try {
427                     $credentialsBackend->getCachedCredentials($userCredentialCache);
428                 } catch (Exception $e) {
429                     return FALSE;
430                 }
431             } else {
432                 Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ 
433                     . ' Something went wrong with the CredentialsCache! Forcing logout ...');
434                 Zend_Session::destroy();
435                 return FALSE;
436             }
437             
438             if ($this->type == self::TYPE_USER) {
439                 if (! $this->{$credentialsField}) {
440                     if ($_throwException) {
441                         throw new Felamimail_Exception('Could not get credentials, no ' . $credentialsField . ' given.');
442                     } else {
443                         return FALSE;
444                     }
445                 }
446                 
447                 try {
448                     // NOTE: cache cleanup process might have removed the cache
449                     $credentials = $credentialsBackend->get($this->{$credentialsField});
450                     $credentials->key = substr($userCredentialCache->password, 0, 24);
451                     $credentialsBackend->getCachedCredentials($credentials);
452                 } catch (Exception $e) {
453                     if ($_throwException) {
454                         throw $e;
455                     } else {
456                         return FALSE;
457                     }
458                 }
459             } else {
460                 // just use tine user credentials to connect to mailserver / or use credentials from config if set
461                 $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP, new Tinebase_Config_Struct())->toArray();
462                 
463                 $credentials = $userCredentialCache;
464                 
465                 // allow to set credentials in config
466                 if ((isset($imapConfig['user']) || array_key_exists('user', $imapConfig)) && (isset($imapConfig['password']) || array_key_exists('password', $imapConfig)) && ! empty($imapConfig['user'])) {
467                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
468                         ' Using credentials from config for system account.');
469                     $credentials->username = $imapConfig['user'];
470                     $credentials->password = $imapConfig['password'];
471                 }
472                 
473                 // allow to set pw suffix in config
474                 if ((isset($imapConfig['pwsuffix']) || array_key_exists('pwsuffix', $imapConfig)) && ! preg_match('/' . preg_quote($imapConfig['pwsuffix']) . '$/', $credentials->password)) {
475                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
476                         ' Appending configured pwsuffix to system account password.');
477                     $credentials->password .= $imapConfig['pwsuffix'];
478                 }
479             }
480             
481             $this->{$userField} = $credentials->username;
482             $this->{$passwordField} = $credentials->password;
483         }
484         
485         return TRUE;
486     }
487
488     /**
489      * returns TRUE if account has capability (i.e. QUOTA, CONDSTORE, ...)
490      * 
491      * @param $_capability
492      * @return boolean
493      */
494     public function hasCapability($_capability)
495     {
496         $capabilities = Felamimail_Controller_Account::getInstance()->updateCapabilities($this);
497         
498         return (in_array($_capability, $capabilities['capabilities']));
499     }
500 }