0011190: can't activate TLS for LDAP connections
[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 temporarly 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          ),
139          self::IMAP => array(
140             'host'      => '',
141             'port'      => 143,
142             'ssl'       => 'tls',
143             'domain'    => '',
144          ),
145          self::MODSSL => array(
146              'casfile'           => null,
147              'crlspath'          => null,
148              'validation'        => null,
149              'tryUsernameSplit'  => '1'
150          )
151      );
152     
153     /**
154      * the instance of the authenticationbackend
155      *
156      * @var Tinebase_Auth_Interface
157      */
158     protected $_backend;
159     
160     /**
161      * the constructor
162      *
163      * don't use the constructor. use the singleton 
164      */
165     private function __construct() 
166     {
167         $this->setBackend();
168     }
169     
170     /**
171      * don't clone. Use the singleton.
172      *
173      */
174     private function __clone() {}
175
176     /**
177      * holds the instance of the singleton
178      *
179      * @var Tinebase_Auth
180      */
181     private static $_instance = NULL;
182     
183     /**
184      * the singleton pattern
185      *
186      * @return Tinebase_Auth
187      */
188     public static function getInstance() 
189     {
190         if (self::$_instance === NULL) {
191             self::$_instance = new Tinebase_Auth;
192         }
193         
194         return self::$_instance;
195     }
196     
197     /**
198      * authenticate user
199      *
200      * @param string $_username
201      * @param string $_password
202      * @return Zend_Auth_Result
203      */
204     public function authenticate($_username, $_password)
205     {
206         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
207             __METHOD__ . '::' . __LINE__ . ' Trying to authenticate '. $_username);
208         
209         $this->_backend->setIdentity($_username);
210         $this->_backend->setCredential($_password);
211         
212         if (Zend_Session::isStarted()) {
213             Zend_Auth::getInstance()->setStorage(new Zend_Auth_Storage_Session());
214         } else {
215             Zend_Auth::getInstance()->setStorage(new Zend_Auth_Storage_NonPersistent());
216         }
217         $result = Zend_Auth::getInstance()->authenticate($this->_backend);
218         
219         return $result;
220     }
221     
222     /**
223      * check if password is valid
224      *
225      * @param string $_username
226      * @param string $_password
227      * @return boolean
228      */
229     public function isValidPassword($_username, $_password)
230     {
231         $this->_backend->setIdentity($_username);
232         $this->_backend->setCredential($_password);
233         
234         $result = $this->_backend->authenticate();
235
236         if ($result->isValid()) {
237             return true;
238         }
239         
240         return false;
241     }
242     
243     /**
244      * returns the configured rs backend
245      * 
246      * @return string
247      */
248     public static function getConfiguredBackend()
249     {
250         if (!isset(self::$_backendType)) {
251             if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
252                 self::setBackendType(Tinebase_Config::getInstance()->get(Tinebase_Config::AUTHENTICATIONBACKENDTYPE, self::SQL));
253             } else {
254                 self::setBackendType(self::SQL);
255             }
256         }
257         
258         return self::$_backendType;
259     }
260     
261     /**
262      * set the auth backend
263      */
264     public function setBackend()
265     {
266         $backendType = self::getConfiguredBackend();
267         
268         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
269             __METHOD__ . '::' . __LINE__ .' authentication backend: ' . $backendType);
270         
271         $this->_backend = Tinebase_Auth_Factory::factory($backendType);
272     }
273     
274     /**
275      * setter for {@see $_backendType}
276      * 
277      * @todo persist in db
278      * 
279      * @param string $_backendType
280      * @return void
281      */
282     public static function setBackendType($_backendType)
283     {
284         self::$_backendType = ucfirst($_backendType);
285     }
286     
287     /**
288      * Setter for {@see $_backendConfiguration}
289      * 
290      * NOTE:
291      * Setting will not be written to Database or Filesystem.
292      * To persist the change call {@see saveBackendConfiguration()}
293      * 
294      * @param mixed $_value
295      * @param string $_key
296      * @param boolean $_applyDefaults
297      * @return void
298      */
299     public static function setBackendConfiguration($_value, $_key = null, $_applyDefaults = false)
300     {
301         $defaultValues = self::$_backendConfigurationDefaults[self::getConfiguredBackend()];
302         
303         if (is_null($_key) && !is_array($_value)) {
304             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');
305         } elseif (is_null($_key) && is_array($_value)) {
306             $configToSet = $_applyDefaults ? array_merge($defaultValues, $_value) : $_value;
307             foreach ($configToSet as $key => $value) {
308                 self::setBackendConfiguration($value, $key);
309             }
310         } else {
311             if ( ! (isset($defaultValues[$_key]) || array_key_exists($_key, $defaultValues))) {
312                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
313                     "Cannot set backend configuration option '$_key' for accounts storage " . self::getConfiguredBackend());
314                 return;
315             }
316             self::$_backendConfiguration[$_key] = $_value;
317         }
318     }
319     
320     /**
321      * Delete the given config setting or all config settings if {@param $_key} is not specified
322      * 
323      * @param string | optional $_key
324      * @return void
325      */
326     public static function deleteBackendConfiguration($_key = null)
327     {
328         if (is_null($_key)) {
329             self::$_backendConfiguration = array();
330         } elseif ((isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration))) {
331             unset(self::$_backendConfiguration[$_key]);
332         } else {
333             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' configuration option does not exist: ' . $_key);
334         }
335     }
336     
337     /**
338      * Write backend configuration setting {@see $_backendConfigurationSettings} and {@see $_backendType} to
339      * db config table.
340      * 
341      * @return void
342      */
343     public static function saveBackendConfiguration()
344     {
345         Tinebase_Config::getInstance()->set(Tinebase_Config::AUTHENTICATIONBACKEND, self::getBackendConfiguration());
346         Tinebase_Config::getInstance()->set(Tinebase_Config::AUTHENTICATIONBACKENDTYPE, self::getConfiguredBackend());
347     }
348     
349     /**
350      * Getter for {@see $_backendConfiguration}
351      * 
352      * @param boolean $_getConfiguredBackend
353      * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
354      */
355     public static function getBackendConfiguration($_key = null, $_default = null)
356     {
357         //lazy loading for $_backendConfiguration
358         if (!isset(self::$_backendConfiguration)) {
359             if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
360                 $rawBackendConfiguration = Tinebase_Config::getInstance()->get(Tinebase_Config::AUTHENTICATIONBACKEND, new Tinebase_Config_Struct())->toArray();
361             } else {
362                 $rawBackendConfiguration = array();
363             }
364             self::$_backendConfiguration = is_array($rawBackendConfiguration) ? $rawBackendConfiguration : Zend_Json::decode($rawBackendConfiguration);
365         }
366
367         if (isset($_key)) {
368             return (isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration)) ? self::$_backendConfiguration[$_key] : $_default;
369         } else {
370             return self::$_backendConfiguration;
371         }
372     }
373     
374     /**
375      * Returns default configuration for all supported backends 
376      * and overrides the defaults with concrete values stored in this configuration 
377      * 
378      * @param String | optional $_key
379      * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
380      */
381     public static function getBackendConfigurationWithDefaults($_getConfiguredBackend = TRUE)
382     {
383         $config = array();
384         $defaultConfig = self::getBackendConfigurationDefaults();
385         foreach ($defaultConfig as $backendType => $backendConfig) {
386             $config[$backendType] = ($_getConfiguredBackend && $backendType == self::getConfiguredBackend() ? self::getBackendConfiguration() : array());
387             if (is_array($config[$backendType])) {
388                 foreach ($backendConfig as $key => $value) {
389                     // 2010-05-20 cweiss Zend_Ldap changed and does not longer throw exceptions
390                     // on unsupported values, we might skip this cleanup here.
391                     if (! (isset($config[$backendType][$key]) || array_key_exists($key, $config[$backendType]))) {
392                         $config[$backendType][$key] = $value;
393                     }
394                 }
395             } else {
396                 $config[$backendType] = $backendConfig;
397             }
398         }
399         return $config;
400     }
401     
402     /**
403      * Getter for {@see $_backendConfigurationDefaults}
404      * @param String | optional $_backendType
405      * @return array
406      */
407     public static function getBackendConfigurationDefaults($_backendType = null) {
408         if ($_backendType) {
409             if (!(isset(self::$_backendConfigurationDefaults[$_backendType]) || array_key_exists($_backendType, self::$_backendConfigurationDefaults))) {
410                 throw new Tinebase_Exception_InvalidArgument("Unknown backend type '$_backendType'");
411             }
412             return self::$_backendConfigurationDefaults[$_backendType];
413         } else {
414             return self::$_backendConfigurationDefaults;
415         }
416     }
417     
418 }