source: trunk/lib/Auth_File.inc.php

Last change on this file was 690, checked in by anonymous, 5 years ago

Remove App's 'ssl_domain' and 'ssl_enabled' parameters; determine SSL usage by detecting the presence of HTTPS env var (or HTTP_X_FORWARDED_PROTO). Update Session parameters for greater logevity and security. Add 'session_dir' to store site-specific sess_* files with a longer gc_maxlifetime duration.

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