source: trunk/lib/Auth_File.inc.php @ 136

Last change on this file since 136 was 136, checked in by scdev, 18 years ago

Q - Merged branches/2.0singleton into trunk. Completed updating classes to use singleton methods. Implemented tests. Fixed some bugs. Changed some interfaces.

File size: 13.0 KB
RevLine 
[1]1<?php
2/**
[136]3 * Auth_File.inc.php
4 * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
5 *
6 * The Auth_File class provides a htpasswd file implementation for
[1]7 * authentication.
8 *
9 * @author  Quinn Comendant <quinn@strangecode.com>
[103]10 * @version 1.2
[1]11 */
[103]12 
13// Usage example:
14// $auth = new Auth_File();
15// $auth->setParam(array(
16//     'htpasswd_file' => COMMON_BASE . '/global/site_users.htpasswd',
17//     'login_timeout' => 21600,
18//     'idle_timeout' => 3600,
19//     'login_url' => '/login.php'
20// ));
[65]21
22// Available encryption types for class Auth_SQL.
23define('AUTH_ENCRYPT_MD5', 'md5');
24define('AUTH_ENCRYPT_CRYPT', 'crypt');
25define('AUTH_ENCRYPT_SHA1', 'sha1');
26define('AUTH_ENCRYPT_PLAINTEXT', 'plaintext');
27
[1]28class Auth_File {
[136]29   
30    // Namespace of this auth object.
31    var $_ns;
32   
33    // Parameters to be specified by setParam().
[103]34    var $_params = array();
35    var $_default_params = array(
36       
37        // Full path to htpasswd file.
38        'htpasswd_file' => null,
39
40        // The type of encryption to use for passwords stored in the db_table. Use one of the AUTH_ENCRYPT_* types specified above.
[65]41        'encryption_type' => AUTH_ENCRYPT_CRYPT,
[103]42
43        // The URL to the login script.
44        'login_url' => '/',
45
46        // The maximum amount of time a user is allowed to be logged in. They will be forced to login again if they expire.
47        // This applies to admins and users. In seconds. 21600 seconds = 6 hours.
48        'login_timeout' => 21600,
49
50        // The maximum amount of time a user is allowed to be idle before their session expires. They will be forced to login again if they expire.
51        // This applies to admins and users. In seconds. 3600 seconds = 1 hour.
52        'idle_timeout' => 3600,
53
54        // An array of IP blocks that are bypass the remote_ip comparison check. Useful for dynamic IPs or those behind proxy servers.
55        'trusted_networks' => array(),
[1]56    );
[103]57
58    // Associative array of usernames to hashed passwords.
[65]59    var $_users = array();
[1]60
61    /**
62     * Constructs a new htpasswd authentication object.
63     *
64     * @access public
65     *
66     * @param optional array $params  A hash containing parameters.
67     */
[136]68    function Auth_File($namespace='null')
[1]69    {
[136]70        $this->_ns = '_auth_' . $namespace;
[1]71
[103]72        // Initialize default parameters.
73        $this->setParam($this->_default_params);
[1]74    }
75
76    /**
[65]77     * Set the params of an auth object.
78     *
79     * @param  array $params   Array of parameter keys and value to set.
80     * @return bool true on success, false on failure
81     */
82    function setParam($params)
83    {
84        if (isset($params) && is_array($params)) {
85            // Merge new parameters with old overriding only those passed.
86            $this->_params = array_merge($this->_params, $params);
87        }
88    }
89
90    /**
91     * Return the value of a parameter, if it exists.
92     *
93     * @access public
94     * @param string $param        Which parameter to return.
95     * @return mixed               Configured parameter value.
96     */
97    function getParam($param)
98    {
[136]99        $app =& App::getInstance();
100   
[65]101        if (isset($this->_params[$param])) {
102            return $this->_params[$param];
103        } else {
[136]104            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
[65]105            return null;
106        }
107    }
108
109    /**
110     * Clear any authentication tokens in the current session. A.K.A. logout.
111     *
112     * @access public
113     */
114    function clearAuth()
115    {
[136]116        $_SESSION[$this->_ns] = array('authenticated' => false);
[65]117    }
118
[103]119
[65]120    /**
[103]121     * Sets a variable into a registered auth session.
122     *
123     * @access public
124     * @param mixed $key      Which value to set.
125     * @param mixed $val      Value to set variable to.
126     */
127    function setVal($key, $val)
128    {
[136]129        if (!isset($_SESSION[$this->_ns]['user_data'])) {
130            $_SESSION[$this->_ns]['user_data'] = array();
[103]131        }
[136]132        $_SESSION[$this->_ns]['user_data'][$key] = $val;
[103]133    }
134
135    /**
136     * Returns a specified value from a registered auth session.
137     *
138     * @access public
139     * @param mixed $key      Which value to return.
140     * @param mixed $default  Value to return if key not found in user_data.
141     * @return mixed          Value stored in session.
142     */
143    function getVal($key, $default='')
144    {
[136]145        if (isset($_SESSION[$this->_ns][$key])) {
146            return $_SESSION[$this->_ns][$key];
147        } else if (isset($_SESSION[$this->_ns]['user_data'][$key])) {
148            return $_SESSION[$this->_ns]['user_data'][$key];
[103]149        } else {
150            return $default;
151        }
152    }
153    /**
[1]154     * Find out if a set of login credentials are valid. Only supports
155     * htpasswd files with DES passwords right now.
156     *
[65]157     * @access public
[1]158     *
[65]159     * @param string $username      The username to check.
160     * @param array $password      The password to compare to username.
[1]161     *
162     * @return boolean  Whether or not the credentials are valid.
163     */
[65]164    function authenticate($username, $password)
[1]165    {
[136]166        $app =& App::getInstance();
167   
[65]168        if ('' == trim($password)) {
[136]169            $app->logMsg(_("No password provided for authentication."), LOG_INFO, __FILE__, __LINE__);
[1]170            return false;
171        }
[103]172       
173        // Load users file.
174        $this->_loadHTPasswdFile();
[1]175
[65]176        if (!isset($this->_users[$username])) {
[136]177            $app->logMsg(_("User ID provided does not exist."), LOG_INFO, __FILE__, __LINE__);
[1]178            return false;
179        }
180
[103]181        if ($this->_encrypt($password, $this->_users[$username]) != $this->_users[$username]) {
[136]182            $app->logMsg(sprintf('Authentication failed for user %s', $username), LOG_INFO, __FILE__, __LINE__);
[1]183            return false;
184        }
[103]185       
186        // Authentication successful!
187        return true;
[1]188    }
189
190    /**
191     * If user passes authentication create authenticated session.
192     *
[65]193     * @access public
[1]194     *
[65]195     * @param string $username     The username to check.
196     * @param array $password     The password to compare to username.
[1]197     *
198     * @return boolean  Whether or not the credentials are valid.
199     */
[65]200    function login($username, $password)
[1]201    {
[65]202        $username = strtolower(trim($username));
[42]203
[1]204        $this->clearAuth();
205
[103]206        if (!$this->authenticate($username, $password)) {
207            // No login: failed authentication!
208            return false;
[1]209        }
[103]210       
[136]211        $_SESSION[$this->_ns] = array(
[103]212            'authenticated' => true,
213            'username' => $username,
214            'login_datetime' => date('Y-m-d H:i:s'),
215            'last_access_datetime' => date('Y-m-d H:i:s'),
216            'remote_ip' => getRemoteAddr()
217        );
218
219        // We're logged-in!
220        return true;
[1]221    }
222
223    /**
224     * Test if user has a currently logged-in session.
225     *  - authentication flag set to true
[65]226     *  - username not empty
[1]227     *  - total logged-in time is not greater than login_timeout
228     *  - idle time is not greater than idle_timeout
229     *  - remote address is the same as the login remote address.
230     *
231     * @access public
232     */
233    function isLoggedIn()
234    {
[136]235        $app =& App::getInstance();
236   
[103]237        // Some users will access from networks with a changing IP number (i.e. behind a proxy server). These users must be allowed entry by adding their IP to the list of trusted_networks.
238        if ($trusted_net = ipInRange(getRemoteAddr(), $this->_params['trusted_networks'])) {
239            $user_in_trusted_network = true;
[136]240            $app->logMsg(sprintf('User %s accessing from trusted network %s', $_SESSION[$this->_ns]['username'], $trusted_net), LOG_DEBUG, __FILE__, __LINE__);
[103]241        } else if (preg_match('/proxy.aol.com$/i', getRemoteAddr(true))) {
242            $user_in_trusted_network = true;
[136]243            $app->logMsg(sprintf('User %s accessing from trusted network proxy.aol.com', $_SESSION[$this->_ns]['username']), LOG_DEBUG, __FILE__, __LINE__);
[103]244        } else {
245            $user_in_trusted_network = false;
246        }
247
248        // Test login with information stored in session. Skip IP matching for users from trusted networks.
[136]249        if (isset($_SESSION[$this->_ns])
250            && true === $_SESSION[$this->_ns]['authenticated']
251            && !empty($_SESSION[$this->_ns]['username'])
252            && strtotime($_SESSION[$this->_ns]['login_datetime']) > time() - $this->_params['login_timeout']
253            && strtotime($_SESSION[$this->_ns]['last_access_datetime']) > time() - $this->_params['idle_timeout']
254            && ($_SESSION[$this->_ns]['remote_ip'] == getRemoteAddr() || $user_in_trusted_network)
[103]255        ) {
256            // User is authenticated!
[136]257            $_SESSION[$this->_ns]['last_access_datetime'] = date('Y-m-d H:i:s');
[103]258            return true;
[136]259        } else if (isset($_SESSION[$this->_ns]) && true === $_SESSION[$this->_ns]['authenticated']) {
260            if (strtotime($_SESSION[$this->_ns]['last_access_datetime']) > time() - 43200) {
[103]261                // Only raise message if last session is less than 12 hours old.
[136]262                $app->raiseMsg(_("Your session has closed. You need to log-in again."), MSG_NOTICE, __FILE__, __LINE__);
[1]263            }
[103]264
265            // Log the reason for login expiration.
266            $expire_reasons = array();
[136]267            if (empty($_SESSION[$this->_ns]['username'])) {
[103]268                $expire_reasons[] = 'username not found';
269            }
[136]270            if (strtotime($_SESSION[$this->_ns]['login_datetime']) <= time() - $this->_params['login_timeout']) {
[103]271                $expire_reasons[] = 'login_timeout expired';
272            }
[136]273            if (strtotime($_SESSION[$this->_ns]['last_access_datetime']) <= time() - $this->_params['idle_timeout']) {
[103]274                $expire_reasons[] = 'idle_timeout expired';
275            }
[136]276            if ($_SESSION[$this->_ns]['remote_ip'] != getRemoteAddr() && !$user_in_trusted_network) {
277                $expire_reasons[] = sprintf('remote_ip not matched (%s != %s)', $_SESSION[$this->_ns]['remote_ip'], getRemoteAddr());
[103]278            }
[136]279            $app->logMsg(sprintf('User %s session expired: %s', $_SESSION[$this->_ns]['username'], join(', ', $expire_reasons)), LOG_INFO, __FILE__, __LINE__);
[1]280        }
[103]281
[1]282        return false;
283    }
284
285    /**
[66]286     * Redirect user to login page if they are not logged in.
287     *
288     * @param string $message The text description of a message to raise.
289     * @param int    $type    The type of message: MSG_NOTICE,
290     *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
291     * @param string $file    __FILE__.
292     * @param string $line    __LINE__.
293     * @access public
294     */
295    function requireLogin($message='', $type=MSG_NOTICE, $file=null, $line=null)
296    {
[136]297        $app =& App::getInstance();
298   
[66]299        if (!$this->isLoggedIn()) {
[103]300            // Display message for requiring login. (RaiseMsg will ignore empty strings.)
[136]301            $app->raiseMsg($message, $type, $file, $line);
[66]302
303            // Login scripts must have the same 'login' tag for boomerangURL verification/manipulation.
[136]304            $app->setBoomerangURL(absoluteMe(), 'login');
305            $app->dieURL($this->_params['login_url']);
[66]306        }
307    }
[103]308   
309    /*
310    * Reads the configured htpasswd file into the _users array.
311    *
312    * @access   public
313    * @return   false on error, true on success.
314    * @author   Quinn Comendant <quinn@strangecode.com>
315    * @version  1.0
316    * @since    18 Apr 2006 18:17:48
317    */
318    function _loadHTPasswdFile()
319    {
[136]320        $app =& App::getInstance();
321   
[103]322        static $users = null;
323       
324        if (!file_exists($this->_params['htpasswd_file'])) {
[136]325            $app->logMsg(sprintf('htpasswd file missing or not specified: %s', $this->_params['htpasswd_file']), LOG_ERR, __FILE__, __LINE__);
[103]326            return false;
327        }
328       
329        if (!isset($users)) {
330            if (false === ($users = file($this->_params['htpasswd_file']))) {
[136]331                $app->logMsg(sprintf(_("Could not read htpasswd file: %s"), $this->_params['htpasswd_file']), LOG_ERR, __FILE__, __LINE__);
[103]332                return false;
333            }
334        }
[66]335
[103]336        if (is_array($users)) {
337            foreach ($users as $u) {
338                list($user, $pass) = explode(':', $u, 2);
339                $this->_users[trim($user)] = trim($pass);
340            }
341            return true;
342        }
343        return false;
344    }
345
[66]346    /**
[1]347     * Hash a given password according to the configured encryption
348     * type.
349     *
[65]350     * @param string $password              The password to encrypt.
351     * @param string $encrypted_password    The currently encrypted password to use as salt, if needed.
[1]352     *
353     * @return string  The hashed password.
354     */
[65]355    function _encrypt($password, $encrypted_password=null)
[1]356    {
357        switch ($this->_params['encryption_type']) {
[65]358        case AUTH_ENCRYPT_PLAINTEXT :
359            return $password;
[1]360            break;
361
[65]362        case AUTH_ENCRYPT_SHA1 :
363            return sha1($password);
364            break;
[1]365
[65]366        case AUTH_ENCRYPT_MD5 :
367            return md5($password);
[1]368            break;
[65]369
370        case AUTH_ENCRYPT_CRYPT :
[42]371        default :
[65]372            return crypt($password, $encrypted_password);
373            break;
[1]374        }
375    }
376
[65]377} // end class
[1]378?>
Note: See TracBrowser for help on using the repository browser.