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