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

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

Changed some global constants to class constants (in cases where backwards compatability wouldn't break).

File size: 14.5 KB
Line 
1<?php
2/**
3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
5 * Copyright 2001-2012 Strangecode, LLC
6 *
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.
13 *
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.
18 *
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
23/*
24 * Auth_File.inc.php
25 *
26 * The Auth_File class provides a htpasswd file implementation for
27 * authentication.
28 *
29 * @author  Quinn Comendant <quinn@strangecode.com>
30 * @version 1.2
31 */
32
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// ));
41
42class Auth_File {
43
44    // Available encryption types for class Auth_File.
45    const ENCRYPT_MD5 = 'md5';
46    const ENCRYPT_CRYPT = 'crypt';
47    const ENCRYPT_SHA1 = 'sha1';
48    const ENCRYPT_PLAINTEXT = 'plaintext';
49
50    // Namespace of this auth object.
51    private $_ns;
52
53    // Parameters to be specified by setParam().
54    private $_params = array();
55    private $_default_params = array(
56
57        // Full path to htpasswd file.
58        'htpasswd_file' => null,
59
60        // The type of encryption to use for passwords stored in the db_table. Use one of the self::ENCRYPT_* types specified above.
61        'encryption_type' => self::ENCRYPT_CRYPT,
62
63        // The URL to the login script.
64        'login_url' => '/',
65
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,
69
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,
73
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    );
77
78    // Associative array of usernames to hashed passwords.
79    private $_users = array();
80
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;
91
92        // Initialize default parameters.
93        $this->setParam($this->_default_params);
94    }
95
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    {
104        if (isset($params) && is_array($params)) {
105            // Merge new parameters with old overriding only those passed.
106            $this->_params = array_merge($this->_params, $params);
107        }
108    }
109
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    {
119        $app = &App::getInstance();
120
121        if (isset($this->_params[$param])) {
122            return $this->_params[$param];
123        } else {
124            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
125            return null;
126        }
127    }
128
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);
137    }
138
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();
150        }
151        $_SESSION['_auth_file'][$this->_ns]['user_data'][$key] = $val;
152    }
153
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];
168        } else {
169            return $default;
170        }
171    }
172
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    {
186        $app = &App::getInstance();
187
188        if ('' == trim($password)) {
189            $app->logMsg(_("No password provided for authentication."), LOG_INFO, __FILE__, __LINE__);
190            return false;
191        }
192
193        // Load users file.
194        $this->_loadHTPasswdFile();
195
196        if (!isset($this->_users[$username])) {
197            $app->logMsg(_("User ID provided does not exist."), LOG_INFO, __FILE__, __LINE__);
198            return false;
199        }
200
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__);
203            return false;
204        }
205
206        // Authentication successful!
207        return true;
208    }
209
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    {
222        $username = mb_strtolower(trim($username));
223
224        $this->clear();
225
226        if (!$this->authenticate($username, $password)) {
227            // No login: failed authentication!
228            return false;
229        }
230
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        );
238
239        // We're logged-in!
240        return true;
241    }
242
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    {
255        $app = &App::getInstance();
256
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.
258        if ($trusted_net = ipInRange(getRemoteAddr(), $this->_params['trusted_networks'])) {
259            $user_in_trusted_network = true;
260            $app->logMsg(sprintf('User %s accessing from trusted network %s', $_SESSION['_auth_file'][$this->_ns]['username'], $trusted_net), LOG_DEBUG, __FILE__, __LINE__);
261        } else if (preg_match('/proxy.aol.com$/i', getRemoteAddr(true))) {
262            $user_in_trusted_network = true;
263            $app->logMsg(sprintf('User %s accessing from trusted network proxy.aol.com', $_SESSION['_auth_file'][$this->_ns]['username']), LOG_DEBUG, __FILE__, __LINE__);
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.
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        ) {
276            // User is authenticated!
277            $_SESSION['_auth_file'][$this->_ns]['last_access_datetime'] = date('Y-m-d H:i:s');
278            return true;
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) {
281                // Only raise message if last session is less than 12 hours old.
282                $app->raiseMsg(_("Your session has closed. You need to log-in again."), MSG_NOTICE, __FILE__, __LINE__);
283            }
284
285            // Log the reason for login expiration.
286            $expire_reasons = array();
287            if (empty($_SESSION['_auth_file'][$this->_ns]['username'])) {
288                $expire_reasons[] = 'username not found';
289            }
290            if (strtotime($_SESSION['_auth_file'][$this->_ns]['login_datetime']) <= time() - $this->_params['login_timeout']) {
291                $expire_reasons[] = 'login_timeout expired';
292            }
293            if (strtotime($_SESSION['_auth_file'][$this->_ns]['last_access_datetime']) <= time() - $this->_params['idle_timeout']) {
294                $expire_reasons[] = 'idle_timeout expired';
295            }
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());
298            }
299            $app->logMsg(sprintf('User %s session expired: %s', $_SESSION['_auth_file'][$this->_ns]['username'], join(', ', $expire_reasons)), LOG_INFO, __FILE__, __LINE__);
300        }
301
302        return false;
303    }
304
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    {
317        $app = &App::getInstance();
318
319        if (!$this->isLoggedIn()) {
320            // Display message for requiring login. (RaiseMsg will ignore empty strings.)
321            $app->raiseMsg($message, $type, $file, $line);
322
323            // Login scripts must have the same 'login' tag for boomerangURL verification/manipulation.
324            $app->setBoomerangURL(absoluteMe(), 'login');
325            $app->dieURL($this->_params['login_url']);
326        }
327    }
328
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    */
335    public function getUsername($username) {
336        if ('' != $username) {
337            return $username;
338        } else {
339            return false;
340        }
341    }
342
343    /*
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    {
354        $app = &App::getInstance();
355
356        static $users = null;
357
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__);
360            return false;
361        }
362
363        if (!isset($users)) {
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__);
366                return false;
367            }
368        }
369
370        if (is_array($users)) {
371            foreach ($users as $u) {
372                list($user, $pass) = explode(':', $u, 2);
373                $this->_users[trim($user)] = trim($pass);
374            }
375            return true;
376        }
377        return false;
378    }
379
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    {
391        switch ($this->_params['encryption_type']) {
392        case self::ENCRYPT_PLAINTEXT :
393            return $password;
394            break;
395
396        case self::ENCRYPT_SHA1 :
397            return sha1($password);
398            break;
399
400        case self::ENCRYPT_MD5 :
401            return md5($password);
402            break;
403
404        case self::ENCRYPT_CRYPT :
405        default :
406            return crypt($password, $encrypted_password);
407            break;
408        }
409    }
410
411} // end class
Note: See TracBrowser for help on using the repository browser.