Merge branch '2013.10' into 2014.11
[tine20] / tine20 / Tinebase / Auth.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Auth
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * main authentication class
14  * 
15  * @todo 2010-05-20 cweiss: the default option handling looks like a big mess -> someone needs to tidy up here!
16  * 
17  * @package     Tinebase
18  * @subpackage  Auth 
19  */
20
21 class Tinebase_Auth
22 {
23     /**
24      * constant for Sql auth
25      *
26      */
27     const SQL = 'Sql';
28     
29     /**
30      * constant for LDAP auth
31      *
32      */
33     const LDAP = 'Ldap';
34
35     /**
36      * constant for IMAP auth
37      *
38      */
39     const IMAP = 'Imap';
40
41     /**
42      * constant for DigitalCertificate auth / SSL
43      *
44      */
45     const MODSSL = 'ModSsl';
46
47     /**
48      * General Failure
49      */
50     const FAILURE                       =  Zend_Auth_Result::FAILURE;
51
52     /**
53      * Failure due to identity not being found.
54      */
55     const FAILURE_IDENTITY_NOT_FOUND    = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND;
56
57     /**
58      * Failure due to identity being ambiguous.
59      */
60     const FAILURE_IDENTITY_AMBIGUOUS    = Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS;
61
62     /**
63      * Failure due to invalid credential being supplied.
64      */
65     const FAILURE_CREDENTIAL_INVALID    = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID;
66
67     /**
68      * Failure due to uncategorized reasons.
69      */
70     const FAILURE_UNCATEGORIZED         = Zend_Auth_Result::FAILURE_UNCATEGORIZED;
71     
72     /**
73      * Failure due the account is disabled
74      */
75     const FAILURE_DISABLED              = -100;
76
77     /**
78      * Failure due the account is expired
79      */
80     const FAILURE_PASSWORD_EXPIRED      = -101;
81     
82     /**
83      * Failure due the account is temporarily blocked
84      */
85     const FAILURE_BLOCKED               = -102;
86         
87     /**
88      * database connection failure
89      */
90     const FAILURE_DATABASE_CONNECTION   = -103;
91         
92     /**
93      * Authentication success.
94      */
95     const SUCCESS                        =  Zend_Auth_Result::SUCCESS;
96
97     /**
98      * the name of the authenticationbackend
99      *
100      * @var string
101      */
102     protected static $_backendType;
103     
104     /**
105      * Holds the backend configuration options.
106      * Property is lazy loaded from {@see Tinebase_Config} on first access via
107      * getter {@see getBackendConfiguration()}
108      * 
109      * @var array | optional
110      */
111     private static $_backendConfiguration;
112     
113     /**
114      * Holds the backend configuration options.
115      * Property is lazy loaded from {@see Tinebase_Config} on first access via
116      * getter {@see getBackendConfiguration()}
117      * 
118      * @var array | optional
119      */
120     private static $_backendConfigurationDefaults = array(
121         self::SQL => array(
122             'tryUsernameSplit' => '1',
123             'accountCanonicalForm' => '2',
124             'accountDomainName' => '',
125             'accountDomainNameShort' => '',
126         ),
127         self::LDAP => array(
128             'host' => '',
129             'username' => '',
130             'password' => '',
131             'bindRequiresDn' => true,
132             'useStartTls' => false,
133             'baseDn' => '',
134             'accountFilterFormat' => NULL,
135             'accountCanonicalForm' => '2',
136             'accountDomainName' => '',
137             'accountDomainNameShort' => '',
138             'tryUsernameSplit' => '0'
139          ),
140          self::IMAP => array(
141             'host'      => '',
142             'port'      => 143,
143             'ssl'       => 'tls',
144             'domain'    => '',
145          ),
146          self::MODSSL => array(
147              'casfile'           => null,
148              'crlspath'          => null,
149              'validation'        => null,
150              'tryUsernameSplit'  => '1'
151          )
152      );
153     
154     /**
155      * the instance of the authenticationbackend
156      *
157      * @var Tinebase_Auth_Interface
158      */
159     protected $_backend;
160     
161     /**
162      * the constructor
163      *
164      * don't use the constructor. use the singleton 
165      */
166     private function __construct() 
167     {
168         $this->setBackend();
169     }
170     
171     /**
172      * don't clone. Use the singleton.
173      *
174      */
175     private function __clone() {}
176
177     /**
178      * holds the instance of the singleton
179      *
180      * @var Tinebase_Auth
181      */
182     private static $_instance = NULL;
183     
184     /**
185      * the singleton pattern
186      *
187      * @return Tinebase_Auth
188      */
189     public static function getInstance() 
190     {
191         if (self::$_instance === NULL) {
192             self::$_instance = new Tinebase_Auth;
193         }
194         
195         return self::$_instance;
196     }
197     
198     /**
199      * authenticate user
200      *
201      * @param string $_username
202      * @param string $_password
203      * @return Zend_Auth_Result
204      */
205     public function authenticate($_username, $_password)
206     {
207         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
208             __METHOD__ . '::' . __LINE__ . ' Trying to authenticate '. $_username);
209         
210         try {
211             $this->_backend->setIdentity($_username);
212         } catch (Zend_Auth_Adapter_Exception $zaae) {
213             return new Zend_Auth_Result(
214                 Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,
215                 $_username,
216                 array($zaae->getMessage())
217             );
218         }
219         
220         $this->_backend->setCredential($_password);
221         
222         if (Tinebase_Session::isStarted()) {
223             Zend_Auth::getInstance()->setStorage(new Zend_Auth_Storage_Session());
224         } else {
225             Zend_Auth::getInstance()->setStorage(new Zend_Auth_Storage_NonPersistent());
226         }
227         $result = Zend_Auth::getInstance()->authenticate($this->_backend);
228         
229         return $result;
230     }
231     
232     /**
233      * check if password is valid
234      *
235      * @param string $_username
236      * @param string $_password
237      * @return boolean
238      */
239     public function isValidPassword($_username, $_password)
240     {
241         $this->_backend->setIdentity($_username);
242         $this->_backend->setCredential($_password);
243         
244         $result = $this->_backend->authenticate();
245
246         if ($result->isValid()) {
247             return true;
248         }
249         
250         return false;
251     }
252     
253     /**
254      * returns the configured rs backend
255      * 
256      * @return string
257      */
258     public static function getConfiguredBackend()
259     {
260         if (!isset(self::$_backendType)) {
261             if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
262                 self::setBackendType(Tinebase_Config::getInstance()->get(Tinebase_Config::AUTHENTICATIONBACKENDTYPE, self::SQL));
263             } else {
264                 self::setBackendType(self::SQL);
265             }
266         }
267         
268         return self::$_backendType;
269     }
270     
271     /**
272      * set the auth backend
273      */
274     public function setBackend()
275     {
276         $backendType = self::getConfiguredBackend();
277         
278         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
279             __METHOD__ . '::' . __LINE__ .' authentication backend: ' . $backendType);
280         
281         $this->_backend = Tinebase_Auth_Factory::factory($backendType);
282     }
283     
284     /**
285      * setter for {@see $_backendType}
286      * 
287      * @todo persist in db
288      * 
289      * @param string $_backendType
290      * @return void
291      */
292     public static function setBackendType($_backendType)
293     {
294         self::$_backendType = ucfirst($_backendType);
295     }
296     
297     /**
298      * Setter for {@see $_backendConfiguration}
299      * 
300      * NOTE:
301      * Setting will not be written to Database or Filesystem.
302      * To persist the change call {@see saveBackendConfiguration()}
303      * 
304      * @param mixed $_value
305      * @param string $_key
306      * @param boolean $_applyDefaults
307      * @return void
308      */
309     public static function setBackendConfiguration($_value, $_key = null, $_applyDefaults = false)
310     {
311         $defaultValues = self::$_backendConfigurationDefaults[self::getConfiguredBackend()];
312         
313         if (is_null($_key) && !is_array($_value)) {
314             throw new Tinebase_Exception_InvalidArgument('To set backend configuration either a key and value parameter are required or the value parameter should be a hash');
315         } elseif (is_null($_key) && is_array($_value)) {
316             $configToSet = $_applyDefaults ? array_merge($defaultValues, $_value) : $_value;
317             foreach ($configToSet as $key => $value) {
318                 self::setBackendConfiguration($value, $key);
319             }
320         } else {
321             if ( ! (isset($defaultValues[$_key]) || array_key_exists($_key, $defaultValues))) {
322                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
323                     "Cannot set backend configuration option '$_key' for accounts storage " . self::getConfiguredBackend());
324                 return;
325             }
326             self::$_backendConfiguration[$_key] = $_value;
327         }
328     }
329     
330     /**
331      * Delete the given config setting or all config settings if {@param $_key} is not specified
332      * 
333      * @param string | optional $_key
334      * @return void
335      */
336     public static function deleteBackendConfiguration($_key = null)
337     {
338         if (is_null($_key)) {
339             self::$_backendConfiguration = array();
340         } elseif ((isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration))) {
341             unset(self::$_backendConfiguration[$_key]);
342         } else {
343             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' configuration option does not exist: ' . $_key);
344         }
345     }
346     
347     /**
348      * Write backend configuration setting {@see $_backendConfigurationSettings} and {@see $_backendType} to
349      * db config table.
350      * 
351      * @return void
352      */
353     public static function saveBackendConfiguration()
354     {
355         Tinebase_Config::getInstance()->set(Tinebase_Config::AUTHENTICATIONBACKEND, self::getBackendConfiguration());
356         Tinebase_Config::getInstance()->set(Tinebase_Config::AUTHENTICATIONBACKENDTYPE, self::getConfiguredBackend());
357     }
358     
359     /**
360      * Getter for {@see $_backendConfiguration}
361      * 
362      * @param boolean $_getConfiguredBackend
363      * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
364      */
365     public static function getBackendConfiguration($_key = null, $_default = null)
366     {
367         //lazy loading for $_backendConfiguration
368         if (!isset(self::$_backendConfiguration)) {
369             if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
370                 $rawBackendConfiguration = Tinebase_Config::getInstance()->get(Tinebase_Config::AUTHENTICATIONBACKEND, new Tinebase_Config_Struct())->toArray();
371             } else {
372                 $rawBackendConfiguration = array();
373             }
374             self::$_backendConfiguration = is_array($rawBackendConfiguration) ? $rawBackendConfiguration : Zend_Json::decode($rawBackendConfiguration);
375             
376             if (!empty(self::$_backendConfiguration['password'])) {
377                 Tinebase_Core::getLogger()->getFormatter()->addReplacement(self::$_backendConfiguration['password']);
378             }
379         }
380         
381         if (isset($_key)) {
382             return (isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration)) ? self::$_backendConfiguration[$_key] : $_default;
383         } else {
384             return self::$_backendConfiguration;
385         }
386     }
387     
388     /**
389      * Returns default configuration for all supported backends 
390      * and overrides the defaults with concrete values stored in this configuration 
391      * 
392      * @param String | optional $_key
393      * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
394      */
395     public static function getBackendConfigurationWithDefaults($_getConfiguredBackend = TRUE)
396     {
397         $config = array();
398         $defaultConfig = self::getBackendConfigurationDefaults();
399         foreach ($defaultConfig as $backendType => $backendConfig) {
400             $config[$backendType] = ($_getConfiguredBackend && $backendType == self::getConfiguredBackend() ? self::getBackendConfiguration() : array());
401             if (is_array($config[$backendType])) {
402                 foreach ($backendConfig as $key => $value) {
403                     // 2010-05-20 cweiss Zend_Ldap changed and does not longer throw exceptions
404                     // on unsupported values, we might skip this cleanup here.
405                     if (! (isset($config[$backendType][$key]) || array_key_exists($key, $config[$backendType]))) {
406                         $config[$backendType][$key] = $value;
407                     }
408                 }
409             } else {
410                 $config[$backendType] = $backendConfig;
411             }
412         }
413         return $config;
414     }
415     
416     /**
417      * Getter for {@see $_backendConfigurationDefaults}
418      * @param String | optional $_backendType
419      * @return array
420      */
421     public static function getBackendConfigurationDefaults($_backendType = null) {
422         if ($_backendType) {
423             if (!(isset(self::$_backendConfigurationDefaults[$_backendType]) || array_key_exists($_backendType, self::$_backendConfigurationDefaults))) {
424                 throw new Tinebase_Exception_InvalidArgument("Unknown backend type '$_backendType'");
425             }
426             return self::$_backendConfigurationDefaults[$_backendType];
427         } else {
428             return self::$_backendConfigurationDefaults;
429         }
430     }
431     
432 }