fix image text
[pear] / Auth.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
3
4 /**
5  * The main include file for Auth package
6  *
7  * PHP versions 4 and 5
8  *
9  * LICENSE: This source file is subject to version 3.01 of the PHP license
10  * that is available through the world-wide-web at the following URI:
11  * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
12  * the PHP License and are unable to obtain it through the web, please
13  * send a note to license@php.net so we can mail you a copy immediately.
14  *
15  * @category   Authentication
16  * @package    Auth
17  * @author     Martin Jansen <mj@php.net>
18  * @author     Adam Ashley <aashley@php.net>
19  * @copyright  2001-2006 The PHP Group
20  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
21  * @version    CVS: $Id: Auth.php 289651 2009-10-15 04:39:07Z aashley $
22  * @link       http://pear.php.net/package/Auth
23  */
24
25 /**
26  * Returned if session exceeds idle time
27  */
28 define('AUTH_IDLED',                    -1);
29 /**
30  * Returned if session has expired
31  */
32 define('AUTH_EXPIRED',                  -2);
33 /**
34  * Returned if container is unable to authenticate user/password pair
35  */
36 define('AUTH_WRONG_LOGIN',              -3);
37 /**
38  * Returned if a container method is not supported.
39  */
40 define('AUTH_METHOD_NOT_SUPPORTED',     -4);
41 /**
42  * Returned if new Advanced security system detects a breach
43  */
44 define('AUTH_SECURITY_BREACH',          -5);
45 /**
46  * Returned if checkAuthCallback says session should not continue.
47  */
48 define('AUTH_CALLBACK_ABORT',           -6);
49
50 /**
51  * Auth Log level - INFO
52  */
53 define('AUTH_LOG_INFO',     6);
54 /**
55  * Auth Log level - DEBUG
56  */
57 define('AUTH_LOG_DEBUG',    7);
58
59 /**
60  * Auth Advanced Security - IP Checks
61  */
62 define('AUTH_ADV_IPCHECK', 1);
63 /**
64  * Auth Advanced Security - User Agent Checks
65  */
66 define('AUTH_ADV_USERAGENT', 2);
67 /**
68  * Auth Advanced Security - Challenge Response
69  */
70 define('AUTH_ADV_CHALLENGE', 3);
71
72
73 /**
74  * PEAR::Auth
75  *
76  * The PEAR::Auth class provides methods for creating an
77  * authentication system using PHP.
78  *
79  * @category   Authentication
80  * @package    Auth
81  * @author     Martin Jansen <mj@php.net>
82  * @author     Adam Ashley <aashley@php.net>
83  * @copyright  2001-2006 The PHP Group
84  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
85  * @version    Release: @package_version@  File: $Revision: 289651 $
86  * @link       http://pear.php.net/package/Auth
87  */
88 class Auth {
89
90     // {{{ properties
91
92     /**
93      * Auth lifetime in seconds
94      *
95      * If this variable is set to 0, auth never expires
96      *
97      * @var  integer
98      * @see  setExpire(), checkAuth()
99      */
100     var $expire = 0;
101
102     /**
103      * Has the auth session expired?
104      *
105      * @var   bool
106      * @see   checkAuth()
107      */
108     var $expired = false;
109
110     /**
111      * Maximum idletime in seconds
112      *
113      * The difference to $expire is, that the idletime gets
114      * refreshed each time checkAuth() is called. If this
115      * variable is set to 0, idletime is never checked.
116      *
117      * @var integer
118      * @see setIdle(), checkAuth()
119      */
120     var $idle = 0;
121
122     /**
123      * Is the maximum idletime over?
124      *
125      * @var boolean
126      * @see checkAuth()
127      */
128     var $idled = false;
129
130     /**
131      * Storage object
132      *
133      * @var object
134      * @see Auth(), validateLogin()
135      */
136     var $storage = '';
137
138     /**
139      * User-defined function that creates the login screen
140      *
141      * @var string
142      */
143     var $loginFunction = '';
144
145     /**
146      * Should the login form be displayed
147      *
148      * @var   bool
149      * @see   setShowlogin()
150      */
151     var $showLogin = true;
152
153     /**
154       * Is Login Allowed from this page
155       *
156       * @var  bool
157       * @see setAllowLogin
158       */
159     var $allowLogin = true;
160
161     /**
162      * Current authentication status
163      *
164      * @var string
165      */
166     var $status = '';
167
168     /**
169      * Username
170      *
171      * @var string
172      */
173     var $username = '';
174
175     /**
176      * Password
177      *
178      * @var string
179      */
180     var $password = '';
181
182     /**
183      * checkAuth callback function name
184      *
185      * @var string
186      * @see setCheckAuthCallback()
187      */
188     var $checkAuthCallback = '';
189
190     /**
191      * Login callback function name
192      *
193      * @var string
194      * @see setLoginCallback()
195      */
196     var $loginCallback = '';
197
198     /**
199      * Failed Login callback function name
200      *
201      * @var string
202      * @see setFailedLoginCallback()
203      */
204     var $loginFailedCallback = '';
205
206     /**
207      * Logout callback function name
208      *
209      * @var string
210      * @see setLogoutCallback()
211      */
212     var $logoutCallback = '';
213
214     /**
215      * Auth session-array name
216      *
217      * @var string
218      */
219     var $_sessionName = '_authsession';
220
221     /**
222      * Package Version
223      *
224      * @var string
225      */
226     var $version = "@version@";
227
228     /**
229      * Flag to use advanced security
230      * When set extra checks will be made to see if the
231      * user's IP or useragent have changed across requests.
232      * Turned off by default to preserve BC.
233      *
234      * @var mixed Boolean to turn all advanced security options on or off
235      *            Array containing named values turning specific advanced
236      *            security features on or off individually
237      *              array(
238      *                  AUTH_ADV_IPCHECK    => true,
239      *                  AUTH_ADV_USERAGENT  => true,
240      *                  AUTH_ADV_CHALLENGE  => true,
241      *              );
242      */
243     var $advancedsecurity = false;
244
245     /**
246      * Username key in POST array
247      *
248      * @var string
249      */
250     var $_postUsername = 'username';
251
252     /**
253      * Password key in POST array
254      *
255      * @var string
256      */
257     var $_postPassword = 'password';
258
259     /**
260      * Holds a reference to the session auth variable
261      * @var array
262      */
263     var $session;
264
265     /**
266      * Holds a reference to the global server variable
267      * @var array
268      */
269     var $server;
270
271     /**
272      * Holds a reference to the global post variable
273      * @var array
274      */
275     var $post;
276
277     /**
278      * Holds a reference to the global cookie variable
279      * @var array
280      */
281     var $cookie;
282
283     /**
284      * A hash to hold various superglobals as reference
285      * @var array
286      */
287     var $authdata;
288
289     /**
290       * How many times has checkAuth been called
291       * @var int
292       */
293     var $authChecks = 0;
294
295     /**
296      * PEAR::Log object
297      *
298      * @var object Log
299      */
300     var $logger = null;
301
302     /**
303      * Whether to enable logging of behaviour
304      *
305      * @var boolean
306      */
307     var $enableLogging = false;
308
309     /**
310      * Whether to regenerate session id everytime start is called
311      *
312      * @var boolean
313      */
314     var $regenerateSessionId = false;
315
316     // }}}
317     // {{{ Auth() [constructor]
318
319     /**
320      * Constructor
321      *
322      * Set up the storage driver.
323      *
324      * @param string    Type of the storage driver
325      * @param mixed     Additional options for the storage driver
326      *                  (example: if you are using DB as the storage
327      *                   driver, you have to pass the dsn string here)
328      *
329      * @param string    Name of the function that creates the login form
330      * @param boolean   Should the login form be displayed if necessary?
331      * @return void
332      */
333     function Auth($storageDriver, $options = '', $loginFunction = '', $showLogin = true)
334     {
335         $this->applyAuthOptions($options);
336
337         // Start the session suppress error if already started
338         if(!session_id()){
339             @session_start();
340             if(!session_id()) {
341                 // Throw error
342                 include_once 'PEAR.php';
343                 PEAR::throwError('Session could not be started by Auth, '
344                         .'possibly headers are already sent, try putting '
345                         .'ob_start in the beginning of your script');
346             }
347         }
348
349         // Make Sure Auth session variable is there
350         if(!isset($_SESSION[$this->_sessionName])) {
351             $_SESSION[$this->_sessionName] = array();
352         }
353
354         // Assign Some globals to internal references, this will replace _importGlobalVariable
355         $this->session =& $_SESSION[$this->_sessionName];
356         $this->server =& $_SERVER;
357         $this->post =& $_POST;
358         $this->cookie =& $_COOKIE;
359
360         if ($loginFunction != '' && is_callable($loginFunction)) {
361             $this->loginFunction = $loginFunction;
362         }
363
364         if (is_bool($showLogin)) {
365             $this->showLogin = $showLogin;
366         }
367
368         if (is_object($storageDriver)) {
369             $this->storage =& $storageDriver;
370             // Pass a reference to auth to the container, ugly but works
371             // this is used by the DB container to use method setAuthData not staticaly.
372             $this->storage->_auth_obj =& $this;
373         } else {
374             // $this->storage = $this->_factory($storageDriver, $options);
375             //
376             $this->storage_driver = $storageDriver;
377             $this->storage_options =& $options;
378         }
379     }
380
381     // }}}
382     // {{{ applyAuthOptions()
383
384     /**
385       * Set the Auth options
386       *
387       * Some options which are Auth specific will be applied
388       * the rest will be left for usage by the container
389       *
390       * @param array    An array of Auth options
391       * @return array   The options which were not applied
392       * @access private
393       */
394     function &applyAuthOptions(&$options)
395     {
396         if(is_array($options)){
397             if (!empty($options['sessionName'])) {
398                 $this->_sessionName = $options['sessionName'];
399                 unset($options['sessionName']);
400             }
401             if (isset($options['allowLogin'])) {
402                 $this->allowLogin = $options['allowLogin'];
403                 unset($options['allowLogin']);
404             }
405             if (!empty($options['postUsername'])) {
406                 $this->_postUsername = $options['postUsername'];
407                 unset($options['postUsername']);
408             }
409             if (!empty($options['postPassword'])) {
410                 $this->_postPassword = $options['postPassword'];
411                 unset($options['postPassword']);
412             }
413             if (isset($options['advancedsecurity'])) {
414                 $this->advancedsecurity = $options['advancedsecurity'];
415                 unset($options['advancedsecurity']);
416             }
417             if (isset($options['enableLogging'])) {
418                 $this->enableLogging = $options['enableLogging'];
419                 unset($options['enableLogging']);
420             }
421             if (isset($options['regenerateSessionId']) && is_bool($options['regenerateSessionId'])) {
422                 $this->regenerateSessionId = $options['regenerateSessionId'];
423             }
424         }
425         return($options);
426     }
427
428     // }}}
429     // {{{ _loadStorage()
430
431     /**
432       * Load Storage Driver if not already loaded
433       *
434       * Suspend storage instantiation to make Auth lighter to use
435       * for calls which do not require login
436       *
437       * @return bool    True if the conainer is loaded, false if the container
438       *                 is already loaded
439       * @access private
440       */
441     function _loadStorage()
442     {
443         if(!is_object($this->storage)) {
444             $this->storage =& $this->_factory($this->storage_driver,
445                     $this->storage_options);
446             $this->storage->_auth_obj =& $this;
447             $this->log('Loaded storage container ('.$this->storage_driver.')', AUTH_LOG_DEBUG);
448             return(true);
449         }
450         return(false);
451     }
452
453     // }}}
454     // {{{ _factory()
455
456     /**
457      * Return a storage driver based on $driver and $options
458      *
459      * @static
460      * @param  string $driver  Type of storage class to return
461      * @param  string $options Optional parameters for the storage class
462      * @return object Object   Storage object
463      * @access private
464      */
465     function &_factory($driver, $options = '')
466     {
467         $storage_class = 'Auth_Container_' . $driver;
468         include_once 'Auth/Container/' . $driver . '.php';
469         $obj =& new $storage_class($options);
470         return $obj;
471     }
472
473     // }}}
474     // {{{ assignData()
475
476     /**
477      * Assign data from login form to internal values
478      *
479      * This function takes the values for username and password
480      * from $HTTP_POST_VARS/$_POST and assigns them to internal variables.
481      * If you wish to use another source apart from $HTTP_POST_VARS/$_POST,
482      * you have to derive this function.
483      *
484      * @global $HTTP_POST_VARS, $_POST
485      * @see    Auth
486      * @return void
487      * @access private
488      */
489     function assignData()
490     {
491         $this->log('Auth::assignData() called.', AUTH_LOG_DEBUG);
492
493         if (   isset($this->post[$this->_postUsername])
494             && $this->post[$this->_postUsername] != '') {
495             $this->username = (get_magic_quotes_gpc() == 1
496                     ? stripslashes($this->post[$this->_postUsername])
497                     : $this->post[$this->_postUsername]);
498         }
499         if (   isset($this->post[$this->_postPassword])
500             && $this->post[$this->_postPassword] != '') {
501             $this->password = (get_magic_quotes_gpc() == 1
502                     ? stripslashes($this->post[$this->_postPassword])
503                     : $this->post[$this->_postPassword] );
504         }
505     }
506
507     // }}}
508     // {{{ start()
509
510     /**
511      * Start new auth session
512      *
513      * @return void
514      * @access public
515      */
516     function start()
517     {
518         $this->log('Auth::start() called.', AUTH_LOG_DEBUG);
519
520         // #10729 - Regenerate session id here if we are generating it on every
521         //          page load.
522         if ($this->regenerateSessionId) {
523             session_regenerate_id(true);
524         }
525
526         $this->assignData();
527         if (!$this->checkAuth() && $this->allowLogin) {
528             $this->login();
529         }
530     }
531
532     // }}}
533     // {{{ login()
534
535     /**
536      * Login function
537      *
538      * @return void
539      * @access private
540      */
541     function login()
542     {
543         $this->log('Auth::login() called.', AUTH_LOG_DEBUG);
544
545         $login_ok = false;
546         $this->_loadStorage();
547
548         // Check if using challenge response
549         (isset($this->post['authsecret']) && $this->post['authsecret'] == 1)
550             ? $usingChap = true
551             : $usingChap = false;
552
553
554         // When the user has already entered a username, we have to validate it.
555         if (!empty($this->username)) {
556             if (true === $this->storage->fetchData($this->username, $this->password, $usingChap)) {
557                 $this->session['challengekey'] = md5($this->username.$this->password);
558                 $login_ok = true;
559                 $this->log('Successful login.', AUTH_LOG_INFO);
560             }
561         }
562
563         if (!empty($this->username) && $login_ok) {
564             $this->setAuth($this->username);
565             if (is_callable($this->loginCallback)) {
566                 $this->log('Calling loginCallback ('.$this->loginCallback.').', AUTH_LOG_DEBUG);
567                 call_user_func_array($this->loginCallback, array($this->username, &$this));
568             }
569         }
570
571         // If the login failed or the user entered no username,
572         // output the login screen again.
573         if (!empty($this->username) && !$login_ok) {
574             $this->log('Incorrect login.', AUTH_LOG_INFO);
575             $this->status = AUTH_WRONG_LOGIN;
576             if (is_callable($this->loginFailedCallback)) {
577                 $this->log('Calling loginFailedCallback ('.$this->loginFailedCallback.').', AUTH_LOG_DEBUG);
578                 call_user_func_array($this->loginFailedCallback, array($this->username, &$this));
579             }
580         }
581
582         if ((empty($this->username) || !$login_ok) && $this->showLogin) {
583             $this->log('Rendering Login Form.', AUTH_LOG_INFO);
584             if (is_callable($this->loginFunction)) {
585                 $this->log('Calling loginFunction ('.$this->loginFunction.').', AUTH_LOG_DEBUG);
586                 call_user_func_array($this->loginFunction, array($this->username, $this->status, &$this));
587             } else {
588                 // BC fix Auth used to use drawLogin for this
589                 // call is sub classes implement this
590                 if (is_callable(array($this, 'drawLogin'))) {
591                     $this->log('Calling Auth::drawLogin()', AUTH_LOG_DEBUG);
592                     return $this->drawLogin($this->username, $this);
593                 }
594
595                 $this->log('Using default Auth_Frontend_Html', AUTH_LOG_DEBUG);
596
597                 // New Login form
598                 include_once 'Auth/Frontend/Html.php';
599                 return Auth_Frontend_Html::render($this, $this->username);
600             }
601         } else {
602             return;
603         }
604     }
605
606     // }}}
607     // {{{ setExpire()
608
609     /**
610      * Set the maximum expire time
611      *
612      * @param  integer time in seconds
613      * @param  bool    add time to current expire time or not
614      * @return void
615      * @access public
616      */
617     function setExpire($time, $add = false)
618     {
619         $add ? $this->expire += $time : $this->expire = $time;
620     }
621
622     // }}}
623     // {{{ setIdle()
624
625     /**
626      * Set the maximum idle time
627      *
628      * @param  integer time in seconds
629      * @param  bool    add time to current maximum idle time or not
630      * @return void
631      * @access public
632      */
633     function setIdle($time, $add = false)
634     {
635         $add ? $this->idle += $time : $this->idle = $time;
636     }
637
638     // }}}
639     // {{{ setSessionName()
640
641     /**
642      * Set name of the session to a customized value.
643      *
644      * If you are using multiple instances of PEAR::Auth
645      * on the same domain, you can change the name of
646      * session per application via this function.
647      * This will chnage the name of the session variable
648      * auth uses to store it's data in the session
649      *
650      * @param  string New name for the session
651      * @return void
652      * @access public
653      */
654     function setSessionName($name = 'session')
655     {
656         $this->_sessionName = '_auth_'.$name;
657         // Make Sure Auth session variable is there
658         if(!isset($_SESSION[$this->_sessionName])) {
659             $_SESSION[$this->_sessionName] = array();
660         }
661         $this->session =& $_SESSION[$this->_sessionName];
662     }
663
664     // }}}
665     // {{{ setShowLogin()
666
667     /**
668      * Should the login form be displayed if necessary?
669      *
670      * @param  bool    show login form or not
671      * @return void
672      * @access public
673      */
674     function setShowLogin($showLogin = true)
675     {
676         $this->showLogin = $showLogin;
677     }
678
679     // }}}
680     // {{{ setAllowLogin()
681
682     /**
683      * Is Login Allowed from this page?
684      *
685      * @param  bool    allow login from this page or not
686      * @return void
687      * @access public
688      */
689     function setAllowLogin($allowLogin = true)
690     {
691         $this->allowLogin = $allowLogin;
692     }
693
694     // }}}
695     // {{{ setCheckAuthCallback()
696
697     /**
698      * Register a callback function to be called whenever the validity of the login is checked
699      * The function will receive two parameters, the username and a reference to the auth object.
700      *
701      * @param  string  callback function name
702      * @return void
703      * @access public
704      * @since Method available since Release 1.4.3
705      */
706     function setCheckAuthCallback($checkAuthCallback)
707     {
708         $this->checkAuthCallback = $checkAuthCallback;
709     }
710
711     // }}}
712     // {{{ setLoginCallback()
713
714     /**
715      * Register a callback function to be called on user login.
716      * The function will receive two parameters, the username and a reference to the auth object.
717      *
718      * @param  string  callback function name
719      * @return void
720      * @see    setLogoutCallback()
721      * @access public
722      */
723     function setLoginCallback($loginCallback)
724     {
725         $this->loginCallback = $loginCallback;
726     }
727
728     // }}}
729     // {{{ setFailedLoginCallback()
730
731     /**
732      * Register a callback function to be called on failed user login.
733      * The function will receive two parameters, the username and a reference to the auth object.
734      *
735      * @param  string  callback function name
736      * @return void
737      * @access public
738      */
739     function setFailedLoginCallback($loginFailedCallback)
740     {
741         $this->loginFailedCallback = $loginFailedCallback;
742     }
743
744     // }}}
745     // {{{ setLogoutCallback()
746
747     /**
748      * Register a callback function to be called on user logout.
749      * The function will receive three parameters, the username and a reference to the auth object.
750      *
751      * @param  string  callback function name
752      * @return void
753      * @see    setLoginCallback()
754      * @access public
755      */
756     function setLogoutCallback($logoutCallback)
757     {
758         $this->logoutCallback = $logoutCallback;
759     }
760
761     // }}}
762     // {{{ setAuthData()
763
764     /**
765      * Register additional information that is to be stored
766      * in the session.
767      *
768      * @param  string  Name of the data field
769      * @param  mixed   Value of the data field
770      * @param  boolean Should existing data be overwritten? (default
771      *                 is true)
772      * @return void
773      * @access public
774      */
775     function setAuthData($name, $value, $overwrite = true)
776     {
777         if (!empty($this->session['data'][$name]) && $overwrite == false) {
778             return;
779         }
780         $this->session['data'][$name] = $value;
781     }
782
783     // }}}
784     // {{{ getAuthData()
785
786     /**
787      * Get additional information that is stored in the session.
788      *
789      * If no value for the first parameter is passed, the method will
790      * return all data that is currently stored.
791      *
792      * @param  string Name of the data field
793      * @return mixed  Value of the data field.
794      * @access public
795      */
796     function getAuthData($name = null)
797     {
798         if (!isset($this->session['data'])) {
799             return null;
800         }
801         if(!isset($name)) {
802             return $this->session['data'];
803         }
804         if (isset($name) && isset($this->session['data'][$name])) {
805             return $this->session['data'][$name];
806         }
807         return null;
808     }
809
810     // }}}
811     // {{{ setAuth()
812
813     /**
814      * Register variable in a session telling that the user
815      * has logged in successfully
816      *
817      * @param  string Username
818      * @return void
819      * @access public
820      */
821     function setAuth($username)
822     {
823         $this->log('Auth::setAuth() called.', AUTH_LOG_DEBUG);
824
825         // #10729 - Regenerate session id here only if generating at login only
826         //          Don't do it if we are regenerating on every request so we don't
827         //          regenerate it twice in one request.
828         if (!$this->regenerateSessionId) {
829             // #2021 - Change the session id to avoid session fixation attacks php 4.3.3 >
830             session_regenerate_id(true);
831         }
832
833         if (!isset($this->session) || !is_array($this->session)) {
834             $this->session = array();
835         }
836
837         if (!isset($this->session['data'])) {
838             $this->session['data'] = array();
839         }
840
841         $this->session['sessionip'] = isset($this->server['REMOTE_ADDR'])
842             ? $this->server['REMOTE_ADDR']
843             : '';
844         $this->session['sessionuseragent'] = isset($this->server['HTTP_USER_AGENT'])
845             ? $this->server['HTTP_USER_AGENT']
846             : '';
847         $this->session['sessionforwardedfor'] = isset($this->server['HTTP_X_FORWARDED_FOR'])
848             ? $this->server['HTTP_X_FORWARDED_FOR']
849             : '';
850
851         // This should be set by the container to something more safe
852         // Like md5(passwd.microtime)
853         if(empty($this->session['challengekey'])) {
854             $this->session['challengekey'] = md5($username.microtime());
855         }
856
857         $this->session['challengecookie'] = md5($this->session['challengekey'].microtime());
858         setcookie('authchallenge', $this->session['challengecookie'], 0, '/');
859
860         $this->session['registered'] = true;
861         $this->session['username']   = $username;
862         $this->session['timestamp']  = time();
863         $this->session['idle']       = time();
864     }
865
866     // }}}
867     // {{{ setAdvancedSecurity()
868
869     /**
870       * Enables advanced security checks
871       *
872       * Currently only ip change and useragent change
873       * are detected
874       * @todo Add challenge cookies - Create a cookie which changes every time
875       *       and contains some challenge key which the server can verify with
876       *       a session var cookie might need to be crypted (user pass)
877       * @param bool Enable or disable
878       * @return void
879       * @access public
880       */
881     function setAdvancedSecurity($flag=true)
882     {
883         $this->advancedsecurity = $flag;
884     }
885
886     // }}}
887     // {{{ checkAuth()
888
889     /**
890      * Checks if there is a session with valid auth information.
891      *
892      * @access public
893      * @return boolean  Whether or not the user is authenticated.
894      */
895     function checkAuth()
896     {
897         $this->log('Auth::checkAuth() called.', AUTH_LOG_DEBUG);
898         $this->authChecks++;
899         if (isset($this->session)) {
900             // Check if authentication session is expired
901             if (   $this->expire > 0
902                 && isset($this->session['timestamp'])
903                 && ($this->session['timestamp'] + $this->expire) < time()) {
904                 $this->log('Session Expired', AUTH_LOG_INFO);
905                 $this->expired = true;
906                 $this->status = AUTH_EXPIRED;
907                 $this->logout();
908                 return false;
909             }
910
911             // Check if maximum idle time is reached
912             if (   $this->idle > 0
913                 && isset($this->session['idle'])
914                 && ($this->session['idle'] + $this->idle) < time()) {
915                 $this->log('Session Idle Time Reached', AUTH_LOG_INFO);
916                 $this->idled = true;
917                 $this->status = AUTH_IDLED;
918                 $this->logout();
919                 return false;
920             }
921
922             if (   isset($this->session['registered'])
923                 && isset($this->session['username'])
924                 && $this->session['registered'] == true
925                 && $this->session['username'] != '') {
926                 Auth::updateIdle();
927
928                 if ($this->_isAdvancedSecurityEnabled()) {
929                     $this->log('Advanced Security Mode Enabled.', AUTH_LOG_DEBUG);
930
931                     // Only Generate the challenge once
932                     if (   $this->authChecks == 1
933                         && $this->_isAdvancedSecurityEnabled(AUTH_ADV_CHALLENGE)) {
934                         $this->log('Generating new Challenge Cookie.', AUTH_LOG_DEBUG);
935                         $this->session['challengecookieold'] = $this->session['challengecookie'];
936                         $this->session['challengecookie'] = md5($this->session['challengekey'].microtime());
937                         setcookie('authchallenge', $this->session['challengecookie'], 0, '/');
938                     }
939
940                     // Check for ip change
941                     if (   $this->_isAdvancedSecurityEnabled(AUTH_ADV_IPCHECK)
942                         && isset($this->server['REMOTE_ADDR'])
943                         && $this->session['sessionip'] != $this->server['REMOTE_ADDR']) {
944                         $this->log('Security Breach. Remote IP Address changed.', AUTH_LOG_INFO);
945                         // Check if the IP of the user has changed, if so we
946                         // assume a man in the middle attack and log him out
947                         $this->expired = true;
948                         $this->status = AUTH_SECURITY_BREACH;
949                         $this->logout();
950                         return false;
951                     }
952
953                     // Check for ip change (if connected via proxy)
954                     if (   $this->_isAdvancedSecurityEnabled(AUTH_ADV_IPCHECK)
955                         && isset($this->server['HTTP_X_FORWARDED_FOR'])
956                         && $this->session['sessionforwardedfor'] != $this->server['HTTP_X_FORWARDED_FOR']) {
957                         $this->log('Security Breach. Forwarded For IP Address changed.', AUTH_LOG_INFO);
958                         // Check if the IP of the user connecting via proxy has
959                         // changed, if so we assume a man in the middle attack
960                         // and log him out.
961                         $this->expired = true;
962                         $this->status = AUTH_SECURITY_BREACH;
963                         $this->logout();
964                         return false;
965                     }
966
967                     // Check for useragent change
968                     if (   $this->_isAdvancedSecurityEnabled(AUTH_ADV_USERAGENT)
969                         && isset($this->server['HTTP_USER_AGENT'])
970                         && $this->session['sessionuseragent'] != $this->server['HTTP_USER_AGENT']) {
971                         $this->log('Security Breach. User Agent changed.', AUTH_LOG_INFO);
972                         // Check if the User-Agent of the user has changed, if
973                         // so we assume a man in the middle attack and log him out
974                         $this->expired = true;
975                         $this->status = AUTH_SECURITY_BREACH;
976                         $this->logout();
977                         return false;
978                     }
979
980                     // Check challenge cookie here, if challengecookieold is not set
981                     // this is the first time and check is skipped
982                     // TODO when user open two pages similtaneuly (open in new window,open
983                     // in tab) auth breach is caused find out a way around that if possible
984                     if (   $this->_isAdvancedSecurityEnabled(AUTH_ADV_CHALLENGE)
985                         && isset($this->session['challengecookieold'])
986                         && $this->session['challengecookieold'] != $this->cookie['authchallenge']) {
987                         $this->log('Security Breach. Challenge Cookie mismatch.', AUTH_LOG_INFO);
988                         $this->expired = true;
989                         $this->status = AUTH_SECURITY_BREACH;
990                         $this->logout();
991                         $this->login();
992                         return false;
993                     }
994                 }
995
996                 if (is_callable($this->checkAuthCallback)) {
997                     $this->log('Calling checkAuthCallback ('.$this->checkAuthCallback.').', AUTH_LOG_DEBUG);
998                     $checkCallback = call_user_func_array($this->checkAuthCallback, array($this->username, &$this));
999                     if ($checkCallback == false) {
1000                         $this->log('checkAuthCallback failed.', AUTH_LOG_INFO);
1001                         $this->expired = true;
1002                         $this->status = AUTH_CALLBACK_ABORT;
1003                         $this->logout();
1004                         return false;
1005                     }
1006                 }
1007
1008                 $this->log('Session OK.', AUTH_LOG_INFO);
1009                 return true;
1010             }
1011         } else {
1012             $this->log('Unable to locate session storage.', AUTH_LOG_DEBUG);
1013             return false;
1014         }
1015         $this->log('No login session.', AUTH_LOG_DEBUG);
1016         return false;
1017     }
1018
1019     // }}}
1020     // {{{ staticCheckAuth() [static]
1021
1022     /**
1023      * Statically checks if there is a session with valid auth information.
1024      *
1025      * @access public
1026      * @see checkAuth
1027      * @return boolean  Whether or not the user is authenticated.
1028      * @static
1029      */
1030     function staticCheckAuth($options = null)
1031     {
1032         static $staticAuth;
1033         if(!isset($staticAuth)) {
1034             $staticAuth = new Auth('null', $options);
1035         }
1036         $staticAuth->log('Auth::staticCheckAuth() called', AUTH_LOG_DEBUG);
1037         return $staticAuth->checkAuth();
1038     }
1039
1040     // }}}
1041     // {{{ getAuth()
1042
1043     /**
1044      * Has the user been authenticated?
1045      *
1046      * Is there a valid login session. Previously this was different from
1047      * checkAuth() but now it is just an alias.
1048      *
1049      * @access public
1050      * @return bool  True if the user is logged in, otherwise false.
1051      */
1052     function getAuth()
1053     {
1054         $this->log('Auth::getAuth() called.', AUTH_LOG_DEBUG);
1055         return $this->checkAuth();
1056     }
1057
1058     // }}}
1059     // {{{ logout()
1060
1061     /**
1062      * Logout function
1063      *
1064      * This function clears any auth tokens in the currently
1065      * active session and executes the logout callback function,
1066      * if any
1067      *
1068      * @access public
1069      * @return void
1070      */
1071     function logout()
1072     {
1073         $this->log('Auth::logout() called.', AUTH_LOG_DEBUG);
1074
1075         if (is_callable($this->logoutCallback) && isset($this->session['username'])) {
1076             $this->log('Calling logoutCallback ('.$this->logoutCallback.').', AUTH_LOG_DEBUG);
1077             call_user_func_array($this->logoutCallback, array($this->session['username'], &$this));
1078         }
1079
1080         $this->username = '';
1081         $this->password = '';
1082
1083         $this->session = null;
1084     }
1085
1086     // }}}
1087     // {{{ updateIdle()
1088
1089     /**
1090      * Update the idletime
1091      *
1092      * @access private
1093      * @return void
1094      */
1095     function updateIdle()
1096     {
1097         $this->session['idle'] = time();
1098     }
1099
1100     // }}}
1101     // {{{ getUsername()
1102
1103     /**
1104      * Get the username
1105      *
1106      * @return string
1107      * @access public
1108      */
1109     function getUsername()
1110     {
1111         if (isset($this->session['username'])) {
1112             return($this->session['username']);
1113         }
1114         return('');
1115     }
1116
1117     // }}}
1118     // {{{ getStatus()
1119
1120     /**
1121      * Get the current status
1122      *
1123      * @return string
1124      * @access public
1125      */
1126     function getStatus()
1127     {
1128         return $this->status;
1129     }
1130
1131     // }}}
1132     // {{{ getPostUsernameField()
1133
1134     /**
1135      * Gets the post varible used for the username
1136      *
1137      * @return string
1138      * @access public
1139      */
1140     function getPostUsernameField()
1141     {
1142         return($this->_postUsername);
1143     }
1144
1145     // }}}
1146     // {{{ getPostPasswordField()
1147
1148     /**
1149      * Gets the post varible used for the username
1150      *
1151      * @return string
1152      * @access public
1153      */
1154     function getPostPasswordField()
1155     {
1156         return($this->_postPassword);
1157     }
1158
1159     // }}}
1160     // {{{ sessionValidThru()
1161
1162     /**
1163      * Returns the time up to the session is valid
1164      *
1165      * @access public
1166      * @return integer
1167      */
1168     function sessionValidThru()
1169     {
1170         if (!isset($this->session['idle'])) {
1171             return 0;
1172         }
1173         if ($this->idle == 0) {
1174             return 0;
1175         }
1176         return ($this->session['idle'] + $this->idle);
1177     }
1178
1179     // }}}
1180     // {{{ listUsers()
1181
1182     /**
1183      * List all users that are currently available in the storage
1184      * container
1185      *
1186      * @access public
1187      * @return array
1188      */
1189     function listUsers()
1190     {
1191         $this->log('Auth::listUsers() called.', AUTH_LOG_DEBUG);
1192         $this->_loadStorage();
1193         return $this->storage->listUsers();
1194     }
1195
1196     // }}}
1197     // {{{ addUser()
1198
1199     /**
1200      * Add user to the storage container
1201      *
1202      * @access public
1203      * @param  string Username
1204      * @param  string Password
1205      * @param  mixed  Additional parameters
1206      * @return mixed  True on success, PEAR error object on error
1207      *                and AUTH_METHOD_NOT_SUPPORTED otherwise.
1208      */
1209     function addUser($username, $password, $additional = '')
1210     {
1211         $this->log('Auth::addUser() called.', AUTH_LOG_DEBUG);
1212         $this->_loadStorage();
1213         return $this->storage->addUser($username, $password, $additional);
1214     }
1215
1216     // }}}
1217     // {{{ removeUser()
1218
1219     /**
1220      * Remove user from the storage container
1221      *
1222      * @access public
1223      * @param  string Username
1224      * @return mixed  True on success, PEAR error object on error
1225      *                and AUTH_METHOD_NOT_SUPPORTED otherwise.
1226      */
1227     function removeUser($username)
1228     {
1229         $this->log('Auth::removeUser() called.', AUTH_LOG_DEBUG);
1230         $this->_loadStorage();
1231         return $this->storage->removeUser($username);
1232     }
1233
1234     // }}}
1235     // {{{ changePassword()
1236
1237     /**
1238      * Change password for user in the storage container
1239      *
1240      * @access public
1241      * @param string Username
1242      * @param string The new password
1243      * @return mixed True on success, PEAR error object on error
1244      *               and AUTH_METHOD_NOT_SUPPORTED otherwise.
1245      */
1246     function changePassword($username, $password)
1247     {
1248         $this->log('Auth::changePassword() called', AUTH_LOG_DEBUG);
1249         $this->_loadStorage();
1250         return $this->storage->changePassword($username, $password);
1251     }
1252
1253     // }}}
1254     // {{{ log()
1255
1256     /**
1257      * Log a message from the Auth system
1258      *
1259      * @access public
1260      * @param string The message to log
1261      * @param string The log level to log the message under. See the Log documentation for more info.
1262      * @return boolean
1263      */
1264     function log($message, $level = AUTH_LOG_DEBUG)
1265     {
1266         if (!$this->enableLogging) return false;
1267
1268         $this->_loadLogger();
1269
1270         $this->logger->log('AUTH: '.$message, $level);
1271     }
1272
1273     // }}}
1274     // {{{ _loadLogger()
1275
1276     /**
1277       * Load Log object if not already loaded
1278       *
1279       * Suspend logger instantiation to make Auth lighter to use
1280       * for calls which do not require logging
1281       *
1282       * @return bool    True if the logger is loaded, false if the logger
1283       *                 is already loaded
1284       * @access private
1285       */
1286     function _loadLogger()
1287     {
1288         if(is_null($this->logger)) {
1289             if (!class_exists('Log')) {
1290                 include_once 'Log.php';
1291             }
1292             $this->logger =& Log::singleton('null',
1293                     null,
1294                     'auth['.getmypid().']',
1295                     array(),
1296                     AUTH_LOG_DEBUG);
1297             return(true);
1298         }
1299         return(false);
1300     }
1301
1302     // }}}
1303     // {{{ attachLogObserver()
1304
1305     /**
1306      * Attach an Observer to the Auth Log Source
1307      *
1308      * @param object Log_Observer A Log Observer instance
1309      * @return boolean
1310      */
1311     function attachLogObserver(&$observer) {
1312
1313         $this->_loadLogger();
1314
1315         return $this->logger->attach($observer);
1316
1317     }
1318
1319     // }}}
1320     // {{{ _isAdvancedSecurityEnabled()
1321
1322     /**
1323      * Is advanced security enabled?
1324      *
1325      * Pass one of the Advanced Security constants as the first parameter
1326      * to check if that advanced security check is enabled.
1327      *
1328      * @param integer
1329      * @return boolean
1330      */
1331     function _isAdvancedSecurityEnabled($feature = null) {
1332
1333         if (is_null($feature)) {
1334
1335             if ($this->advancedsecurity === true)
1336                 return true;
1337
1338             if (   is_array($this->advancedsecurity)
1339                 && in_array(true, $this->advancedsecurity, true))
1340                 return true;
1341
1342             return false;
1343
1344         } else {
1345
1346             if (is_array($this->advancedsecurity)) {
1347
1348                 if (   isset($this->advancedsecurity[$feature])
1349                     && $this->advancedsecurity[$feature] == true)
1350                     return true;
1351
1352                 return false;
1353
1354             }
1355
1356             return (bool)$this->advancedsecurity;
1357
1358         }
1359
1360     }
1361
1362     // }}}
1363
1364 }
1365 ?>