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

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

added public and private keywords to all properties and methods, changed old classname constructor function to construct, removed more ?> closing tags

File size: 14.6 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
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
48class Auth_File {
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 AUTH_ENCRYPT_* types specified above.
61    'encryption_type' => AUTH_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    // Associative array of usernames to hashed passwords.
78    private $_users = array();
79
80    /**
81     * Constructs a new htpasswd authentication object.
82     *
83     * @access public
84     *
85     * @param optional array $params  A hash containing parameters.
86     */
87    public function __construct($namespace = '') {
88        $this -> _ns = $namespace;
89
90        // Initialize default parameters.
91        $this -> setParam($this -> _default_params);
92    }
93
94    /**
95     * Set the params of an auth object.
96     *
97     * @param  array $params   Array of parameter keys and value to set.
98     * @return bool true on success, false on failure
99     */
100    public function setParam($params) {
101        if (isset($params) && is_array($params)) {
102            // Merge new parameters with old overriding only those passed.
103            $this -> _params = array_merge($this -> _params, $params);
104        }
105    }
106
107    /**
108     * Return the value of a parameter, if it exists.
109     *
110     * @access public
111     * @param string $param        Which parameter to return.
112     * @return mixed               Configured parameter value.
113     */
114    public function getParam($param) {
115        $app = &App::getInstance();
116
117        if (isset($this -> _params[$param])) {
118            return $this -> _params[$param];
119        } else {
120            $app -> logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
121            return null;
122        }
123    }
124
125    /**
126     * Clear any authentication tokens in the current session. A.K.A. logout.
127     *
128     * @access public
129     */
130    public function clear() {
131        $_SESSION['_auth_file'][$this -> _ns] = array('authenticated' => false);
132    }
133
134    /**
135     * Sets a variable into a registered auth session.
136     *
137     * @access public
138     * @param mixed $key      Which value to set.
139     * @param mixed $val      Value to set variable to.
140     */
141    public function set($key, $val) {
142        if (!isset($_SESSION['_auth_file'][$this -> _ns]['user_data'])) {
143            $_SESSION['_auth_file'][$this -> _ns]['user_data'] = array();
144        }
145        $_SESSION['_auth_file'][$this -> _ns]['user_data'][$key] = $val;
146    }
147
148    /**
149     * Returns a specified value from a registered auth session.
150     *
151     * @access public
152     * @param mixed $key      Which value to return.
153     * @param mixed $default  Value to return if key not found in user_data.
154     * @return mixed          Value stored in session.
155     */
156    public function get($key, $default = '') {
157        if (isset($_SESSION['_auth_file'][$this -> _ns][$key])) {
158            return $_SESSION['_auth_file'][$this -> _ns][$key];
159        } else if (isset($_SESSION['_auth_file'][$this -> _ns]['user_data'][$key])) {
160            return $_SESSION['_auth_file'][$this -> _ns]['user_data'][$key];
161        } else {
162            return $default;
163        }
164    }
165
166    /**
167     * Find out if a set of login credentials are valid. Only supports
168     * htpasswd files with DES passwords right now.
169     *
170     * @access public
171     *
172     * @param string $username      The username to check.
173     * @param array $password      The password to compare to username.
174     *
175     * @return boolean  Whether or not the credentials are valid.
176     */
177    public function authenticate($username, $password) {
178        $app = &App::getInstance();
179
180        if ('' == trim($password)) {
181            $app -> logMsg(_("No password provided for authentication."), LOG_INFO, __FILE__, __LINE__);
182            return false;
183        }
184
185        // Load users file.
186        $this -> _loadHTPasswdFile();
187
188        if (!isset($this -> _users[$username])) {
189            $app -> logMsg(_("User ID provided does not exist."), LOG_INFO, __FILE__, __LINE__);
190            return false;
191        }
192
193        if ($this -> _encrypt($password, $this -> _users[$username]) != $this -> _users[$username]) {
194            $app -> logMsg(sprintf('Authentication failed for user %s', $username), LOG_INFO, __FILE__, __LINE__);
195            return false;
196        }
197
198        // Authentication successful!
199        return true;
200    }
201
202    /**
203     * If user passes authentication create authenticated session.
204     *
205     * @access public
206     *
207     * @param string $username     The username to check.
208     * @param array $password     The password to compare to username.
209     *
210     * @return boolean  Whether or not the credentials are valid.
211     */
212    public function login($username, $password) {
213        $username = mb_strtolower(trim($username));
214
215        $this -> clear();
216
217        if (!$this -> authenticate($username, $password)) {
218            // No login: failed authentication!
219            return false;
220        }
221
222        $_SESSION['_auth_file'][$this -> _ns] = array('authenticated' => true, 'username' => $username, 'login_datetime' => date('Y-m-d H:i:s'), 'last_access_datetime' => date('Y-m-d H:i:s'), 'remote_ip' => getRemoteAddr());
223
224        // We're logged-in!
225        return true;
226    }
227
228    /**
229     * Test if user has a currently logged-in session.
230     *  - authentication flag set to true
231     *  - username not empty
232     *  - total logged-in time is not greater than login_timeout
233     *  - idle time is not greater than idle_timeout
234     *  - remote address is the same as the login remote address.
235     *
236     * @access public
237     */
238    public function isLoggedIn() {
239        $app = &App::getInstance();
240
241        // 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.
242        if ($trusted_net = ipInRange(getRemoteAddr(), $this -> _params['trusted_networks'])) {
243            $user_in_trusted_network = true;
244            $app -> logMsg(sprintf('User %s accessing from trusted network %s', $_SESSION['_auth_file'][$this -> _ns]['username'], $trusted_net), LOG_DEBUG, __FILE__, __LINE__);
245        } else if (preg_match('/proxy.aol.com$/i', getRemoteAddr(true))) {
246            $user_in_trusted_network = true;
247            $app -> logMsg(sprintf('User %s accessing from trusted network proxy.aol.com', $_SESSION['_auth_file'][$this -> _ns]['username']), LOG_DEBUG, __FILE__, __LINE__);
248        } else {
249            $user_in_trusted_network = false;
250        }
251
252        // Test login with information stored in session. Skip IP matching for users from trusted networks.
253        if (isset($_SESSION['_auth_file'][$this -> _ns]) && true === $_SESSION['_auth_file'][$this -> _ns]['authenticated'] && !empty($_SESSION['_auth_file'][$this -> _ns]['username']) && strtotime($_SESSION['_auth_file'][$this -> _ns]['login_datetime']) > time() - $this -> _params['login_timeout'] && strtotime($_SESSION['_auth_file'][$this -> _ns]['last_access_datetime']) > time() - $this -> _params['idle_timeout'] && ($_SESSION['_auth_file'][$this -> _ns]['remote_ip'] == getRemoteAddr() || $user_in_trusted_network)) {
254            // User is authenticated!
255            $_SESSION['_auth_file'][$this -> _ns]['last_access_datetime'] = date('Y-m-d H:i:s');
256            return true;
257        } else if (isset($_SESSION['_auth_file'][$this -> _ns]) && true === $_SESSION['_auth_file'][$this -> _ns]['authenticated']) {
258            if (strtotime($_SESSION['_auth_file'][$this -> _ns]['last_access_datetime']) > time() - 43200) {
259                // Only raise message if last session is less than 12 hours old.
260                $app -> raiseMsg(_("Your session has closed. You need to log-in again."), MSG_NOTICE, __FILE__, __LINE__);
261            }
262
263            // Log the reason for login expiration.
264            $expire_reasons = array();
265            if (empty($_SESSION['_auth_file'][$this -> _ns]['username'])) {
266                $expire_reasons[] = 'username not found';
267            }
268            if (strtotime($_SESSION['_auth_file'][$this -> _ns]['login_datetime']) <= time() - $this -> _params['login_timeout']) {
269                $expire_reasons[] = 'login_timeout expired';
270            }
271            if (strtotime($_SESSION['_auth_file'][$this -> _ns]['last_access_datetime']) <= time() - $this -> _params['idle_timeout']) {
272                $expire_reasons[] = 'idle_timeout expired';
273            }
274            if ($_SESSION['_auth_file'][$this -> _ns]['remote_ip'] != getRemoteAddr() && !$user_in_trusted_network) {
275                $expire_reasons[] = sprintf('remote_ip not matched (%s != %s)', $_SESSION['_auth_file'][$this -> _ns]['remote_ip'], getRemoteAddr());
276            }
277            $app -> logMsg(sprintf('User %s session expired: %s', $_SESSION['_auth_file'][$this -> _ns]['username'], join(', ', $expire_reasons)), LOG_INFO, __FILE__, __LINE__);
278        }
279
280        return false;
281    }
282
283    /**
284     * Redirect user to login page if they are not logged in.
285     *
286     * @param string $message The text description of a message to raise.
287     * @param int    $type    The type of message: MSG_NOTICE,
288     *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
289     * @param string $file    __FILE__.
290     * @param string $line    __LINE__.
291     * @access public
292     */
293    public function requireLogin($message = '', $type = MSG_NOTICE, $file = null, $line = null) {
294        $app = &App::getInstance();
295
296        if (!$this -> isLoggedIn()) {
297            // Display message for requiring login. (RaiseMsg will ignore empty strings.)
298            $app -> raiseMsg($message, $type, $file, $line);
299
300            // Login scripts must have the same 'login' tag for boomerangURL verification/manipulation.
301            $app -> setBoomerangURL(absoluteMe(), 'login');
302            $app -> dieURL($this -> _params['login_url']);
303        }
304    }
305
306    /**
307     * Wrapper function for compatibility with lib/Lock.inc.php.
308     *
309     * @param  string  $username    Username to return.
310     * @return string               Username, or false if none found.
311     */
312    public function getUsername($username) {
313        if ('' != $username) {
314            return $username;
315        } else {
316            return false;
317        }
318    }
319
320    /*
321     * Reads the configured htpasswd file into the _users array.
322     *
323     * @access   public
324     * @return   false on error, true on success.
325     * @author   Quinn Comendant <quinn@strangecode.com>
326     * @version  1.0
327     * @since    18 Apr 2006 18:17:48
328     */
329    private function _loadHTPasswdFile() {
330        $app = &App::getInstance();
331
332        static $users = null;
333
334        if (!file_exists($this -> _params['htpasswd_file'])) {
335            $app -> logMsg(sprintf('htpasswd file missing or not specified: %s', $this -> _params['htpasswd_file']), LOG_ERR, __FILE__, __LINE__);
336            return false;
337        }
338
339        if (!isset($users)) {
340            if (false === ($users = file($this -> _params['htpasswd_file']))) {
341                $app -> logMsg(sprintf('Could not read htpasswd file: %s', $this -> _params['htpasswd_file']), LOG_ERR, __FILE__, __LINE__);
342                return false;
343            }
344        }
345
346        if (is_array($users)) {
347            foreach ($users as $u) {
348                list($user, $pass) = explode(':', $u, 2);
349                $this -> _users[trim($user)] = trim($pass);
350            }
351            return true;
352        }
353        return false;
354    }
355
356    /**
357     * Hash a given password according to the configured encryption
358     * type.
359     *
360     * @param string $password              The password to encrypt.
361     * @param string $encrypted_password    The currently encrypted password to use as salt, if needed.
362     *
363     * @return string  The hashed password.
364     */
365    private function _encrypt($password, $encrypted_password = null) {
366        switch ($this->_params['encryption_type']) {
367            case AUTH_ENCRYPT_PLAINTEXT :
368                return $password;
369                break;
370
371            case AUTH_ENCRYPT_SHA1 :
372                return sha1($password);
373                break;
374
375            case AUTH_ENCRYPT_MD5 :
376                return md5($password);
377                break;
378
379            case AUTH_ENCRYPT_CRYPT :
380            default :
381                return crypt($password, $encrypted_password);
382                break;
383        }
384    }
385
386} // end class
Note: See TracBrowser for help on using the repository browser.