source: branches/eli_branch/lib/Auth_File.inc.php @ 445

Last change on this file since 445 was 445, checked in by anonymous, 11 years ago

Fixed some issues introduced in changeset:439

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