source: branches/2.0singleton/lib/Auth_SQL.inc.php

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

Q - Finished integrating singleton methods into existing code. Renamed SessionCache? to Cache, and renamed methods in Cache and Prefs

File size: 39.9 KB
Line 
1<?php
2/**
3 * The Auth_SQL:: class provides a SQL implementation for authentication.
4 *
5 * @author  Quinn Comendant <quinn@strangecode.com>
6 * @version 2.0
7 */
8
9// Available encryption types for class Auth_SQL.
10define('AUTH_ENCRYPT_MD5', 'md5');
11define('AUTH_ENCRYPT_CRYPT', 'crypt');
12define('AUTH_ENCRYPT_SHA1', 'sha1');
13define('AUTH_ENCRYPT_PLAINTEXT', 'plaintext');
14
15require_once dirname(__FILE__) . '/Email.inc.php';
16
17class Auth_SQL {
18
19    var $_auth = '';
20    var $_sess = '_auth_';
21    var $_authentication_tested;
22    var $_params = array();
23
24    // Default param values.
25    var $_default_params = array(
26
27        // Automatically create table and verify columns. Better set to false after site launch.
28        'create_table' => true,
29
30        // The database table containing users to authenticate.
31        'db_table' => 'user_tbl',
32
33        // The name of the primary key for the db_table.
34        'db_primary_key' => 'user_id',
35
36        // The name of the username key for the db_table.
37        'db_username_column' => 'username',
38
39        // If using the db_login_table feature, specify the db_login_table. The primary key must match the primary key for the db_table.
40        'db_login_table' => 'user_login_tbl',
41
42        // The type of encryption to use for passwords stored in the db_table. Use one of the AUTH_ENCRYPT_* types specified above.
43        'encryption_type' => AUTH_ENCRYPT_MD5,
44
45        // The URL to the login script.
46        'login_url' => '/',
47
48        // The maximum amount of time a user is allowed to be logged in. They will be forced to login again if they expire.
49        // This applies to admins and users. In seconds. 21600 seconds = 6 hours.
50        'login_timeout' => 21600,
51
52        // 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.
53        // This applies to admins and users. In seconds. 3600 seconds = 1 hour.
54        'idle_timeout' => 3600,
55
56        // The period of time to compare login abuse attempts. If a threshold of logins is reached in this amount of time the account is blocked.
57        // Days and hours, like this: 'DD:HH'
58        'login_abuse_timeframe' => '04:00',
59
60        // The number of warnings a user will receive (and their password reset each time) before their account is completely blocked.
61        'login_abuse_warnings' => 3,
62
63        // The maximum number of IP addresses a user can login with over the timeout period before their account is blocked.
64        'login_abuse_max_ips' => 5,
65
66        // The IP address subnet size threshold. Uses a CIDR notation network mask (see CIDR cheatsheet at bottom).
67        // Any integar between 0 and 32 is permitted. Setting this to '24' permits any address in a
68        // class C network (255.255.255.0) to be considered the same. Setting to '32' compares each IP absolutely.
69        // Setting to '0' ignores all IPs, thus disabling login_abuse checking.
70        'login_abuse_ip_bitmask' => 32,
71
72        // Specify usernames to exclude from the account abuse detection system. This is specified as a hardcoded array provided at
73        // class instantiation time, or can be saved in the db_table under the login_abuse_exempt field.
74        'login_abuse_exempt_usernames' => array(),
75
76        // An array of IP blocks that are bypass the remote_ip comparison check. Useful for dynamic IPs or those behind proxy servers.
77        'trusted_networks' => array(),
78
79        // Allow user accounts to be blocked? Requires the user table to have the columns 'blocked' and 'blocked_reason'
80        'blocking' => false,
81
82        // Use a db_login_table to detect excessive logins. This requires blocking to be enabled.
83        'abuse_detection' => false,
84    );
85
86    /**
87     * Constructs a new authentication object.
88     *
89     * @access public
90     * @param optional array $params  A hash containing parameters.
91     */
92    function Auth_SQL($auth_name=null)
93    {
94        $app =& App::getInstance();
95   
96        if (isset($auth_name)) {
97            $this->_auth = $auth_name;
98            $this->_sess .= $auth_name;
99        }
100
101        // Initialize default parameters.
102        $this->setParam($this->_default_params);
103
104        // Get create tables config from global context.
105        if (!is_null($app->getParam('db_create_tables'))) {
106            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
107        }
108    }
109
110    /**
111     * Setup the database tables for this class.
112     *
113     * @access  public
114     * @author  Quinn Comendant <quinn@strangecode.com>
115     * @since   26 Aug 2005 17:09:36
116     */
117    function initDB($recreate_db=false)
118    {
119        $app =& App::getInstance();
120        $db =& DB::getInstance();
121   
122   
123        static $_db_tested = false;
124
125        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
126
127            // User table.
128            if ($recreate_db) {
129                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
130                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_DEBUG, __FILE__, __LINE__);
131            }
132
133            // The minimal columns for a table compatable with the Auth_SQL class.
134            $db->query("CREATE TABLE IF NOT EXISTS " . $this->getParam('db_table') . " (
135                " . $this->getParam('db_primary_key') . " smallint(11) NOT NULL auto_increment,
136                " . $this->getParam('db_username_column') . " varchar(255) NOT NULL default '',
137                userpass varchar(255) NOT NULL default '',
138                first_name varchar(255) NOT NULL default '',
139                last_name varchar(255) NOT NULL default '',
140                email varchar(255) NOT NULL default '',
141                user_type enum('public', 'editor', 'admin', 'root') default NULL,
142                login_abuse_exempt enum('true') default NULL,
143                blocked enum('true') default NULL,
144                blocked_reason varchar(255) NOT NULL default '',
145                abuse_warning_level tinyint(4) NOT NULL default '0',
146                seconds_online int(11) NOT NULL default '0',
147                last_login_datetime datetime NOT NULL default '0000-00-00 00:00:00',
148                last_access_datetime datetime NOT NULL default '0000-00-00 00:00:00',
149                last_login_ip varchar(255) NOT NULL default '0.0.0.0',
150                added_by_user_id smallint(11) default NULL,
151                modified_by_user_id smallint(11) default NULL,
152                added_datetime datetime NOT NULL default '0000-00-00 00:00:00',
153                modified_datetime datetime NOT NULL default '0000-00-00 00:00:00',
154                PRIMARY KEY (" . $this->getParam('db_primary_key') . "),
155                KEY " . $this->getParam('db_username_column') . " (" . $this->getParam('db_username_column') . "),
156                KEY userpass (userpass),
157                KEY email (email)
158            )");
159
160            if (!$db->columnExists($this->getParam('db_table'), array(
161                $this->getParam('db_primary_key'),
162                $this->getParam('db_username_column'),
163                'userpass',
164                'first_name',
165                'last_name',
166                'email',
167                'user_type',
168                'login_abuse_exempt',
169                'blocked',
170                'blocked_reason',
171                'abuse_warning_level',
172                'seconds_online',
173                'last_login_datetime',
174                'last_access_datetime',
175                'last_login_ip',
176                'added_by_user_id',
177                'modified_by_user_id',
178                'added_datetime',
179                'modified_datetime',
180            ), false, false)) {
181                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
182                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
183            }
184
185            // Login table is used for abuse_detection features.
186            if ($this->getParam('abuse_detection')) {
187                if ($recreate_db) {
188                    $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_login_table'));
189                    $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_login_table')), LOG_DEBUG, __FILE__, __LINE__);
190                }
191                $db->query("CREATE TABLE IF NOT EXISTS " . $this->getParam('db_login_table') . " (
192                    " . $this->getParam('db_primary_key') . " smallint(11) NOT NULL default '0',
193                    login_datetime datetime NOT NULL default '0000-00-00 00:00:00',
194                    remote_ip_binary char(32) NOT NULL default '',
195                    KEY " . $this->getParam('db_primary_key') . " (" . $this->getParam('db_primary_key') . "),
196                    KEY login_datetime (login_datetime),
197                    KEY remote_ip_binary (remote_ip_binary)
198                )");
199
200                if (!$db->columnExists($this->getParam('db_login_table'), array(
201                    $this->getParam('db_primary_key'),
202                    'login_datetime',
203                    'remote_ip_binary',
204                ), false, false)) {
205                    $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_login_table')), LOG_ALERT, __FILE__, __LINE__);
206                    trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_login_table')), E_USER_ERROR);
207                }
208            }
209        }
210        $_db_tested = true;
211    }
212
213    /**
214     * Set the params of an auth object.
215     *
216     * @param  array $params   Array of parameter keys and value to set.
217     * @return bool true on success, false on failure
218     */
219    function setParam($params)
220    {
221        if (isset($params) && is_array($params)) {
222            // Merge new parameters with old overriding only those passed.
223            $this->_params = array_merge($this->_params, $params);
224        }
225    }
226
227    /**
228     * Return the value of a parameter, if it exists.
229     *
230     * @access public
231     * @param string $param        Which parameter to return.
232     * @return mixed               Configured parameter value.
233     */
234    function getParam($param)
235    {
236        $app =& App::getInstance();
237   
238        if (isset($this->_params[$param])) {
239            return $this->_params[$param];
240        } else {
241            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
242            return null;
243        }
244    }
245
246    /**
247     * Clear any authentication tokens in the current session. A.K.A. logout.
248     *
249     * @access public
250     */
251    function clearAuth()
252    {
253        $db =& DB::getInstance();
254   
255        $this->initDB();
256
257        $db->query("
258            UPDATE " . $this->_params['db_table'] . " SET
259            seconds_online = seconds_online + (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_access_datetime)),
260            last_login_datetime = '0000-00-00 00:00:00'
261            WHERE " . $this->_params['db_primary_key'] . " = '" . $this->getVal('user_id') . "'
262        ");
263        $_SESSION[$this->_sess] = array('authenticated' => false);
264    }
265
266    /**
267     * Sets a variable into a registered auth session.
268     *
269     * @access public
270     * @param mixed $key      Which value to set.
271     * @param mixed $val      Value to set variable to.
272     */
273    function setVal($key, $val)
274    {
275        if (!isset($_SESSION[$this->_sess]['user_data'])) {
276            $_SESSION[$this->_sess]['user_data'] = array();
277        }
278        $_SESSION[$this->_sess]['user_data'][$key] = $val;
279    }
280
281    /**
282     * Returns a specified value from a registered auth session.
283     *
284     * @access public
285     * @param mixed $key      Which value to return.
286     * @param mixed $default  Value to return if key not found in user_data.
287     * @return mixed          Value stored in session.
288     */
289    function getVal($key, $default='')
290    {
291        if (isset($_SESSION[$this->_sess][$key])) {
292            return $_SESSION[$this->_sess][$key];
293        } else if (isset($_SESSION[$this->_sess]['user_data'][$key])) {
294            return $_SESSION[$this->_sess]['user_data'][$key];
295        } else {
296            return $default;
297        }
298    }
299
300    /**
301     * Find out if a set of login credentials are valid.
302     *
303     * @access private
304     * @param string $username      The username to check.
305     * @param string $password      The password to compare to username.
306     * @return mixed  False if credentials not found in DB, or returns DB row matching credentials.
307     */
308    function authenticate($username, $password)
309    {
310        $app =& App::getInstance();
311        $db =& DB::getInstance();
312
313        $this->initDB();
314
315        // Query DB for user matching credentials.
316        // FIXME: Cannot compare crypt style passwords this way.
317        $qid = $db->query("
318            SELECT *, " . $this->_params['db_primary_key'] . " AS user_id
319            FROM " . $this->_params['db_table'] . "
320            WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "'
321            AND BINARY userpass = '" . $db->escapeString($this->encryptPassword($password)) . "'
322        ");
323
324        // Return user data if found.
325        if ($user_data = mysql_fetch_assoc($qid)) {
326            $app->logMsg(sprintf('Authentication successful for %s %s (%s)', $this->_auth, $user_data['user_id'], $username), LOG_INFO, __FILE__, __LINE__);
327            return $user_data;
328        } else {
329            $app->logMsg(sprintf('Authentication failed for %s %s (encrypted attempted password: %s)', $this->_auth, $username, $this->encryptPassword($password)), LOG_NOTICE, __FILE__, __LINE__);
330            return false;
331        }
332    }
333
334    /**
335     * If user authenticated, register login into session.
336     *
337     * @access private
338     * @param string $username     The username to check.
339     * @param string $password     The password to compare to username.
340     * @return boolean  Whether or not the credentials are valid.
341     */
342    function login($username, $password)
343    {
344        $app =& App::getInstance();
345        $db =& DB::getInstance();
346   
347        $this->initDB();
348
349        $this->clearAuth();
350
351        if (!$user_data = $this->authenticate($username, $password)) {
352            // No login: failed authentication!
353            return false;
354        }
355
356        // Register authenticated session.
357        $_SESSION[$this->_sess] = array(
358            'authenticated'         => true,
359            'user_id'               => $user_data['user_id'],
360            'auth_name'             => $this->_auth,
361            'username'              => $username,
362            'login_datetime'        => date('Y-m-d H:i:s'),
363            'last_access_datetime'  => date('Y-m-d H:i:s'),
364            'remote_ip'             => getRemoteAddr(),
365            'login_abuse_exempt'    => isset($user_data['login_abuse_exempt']) ? !empty($user_data['login_abuse_exempt']) : in_array($username, $this->_params['login_abuse_exempt_usernames']),
366            'user_data'             => $user_data
367        );
368
369        /**
370         * Check if the account is blocked, respond in context to reason. Cancel the login if blocked.
371         */
372        if ($this->getParam('blocking')) {
373            if (!empty($user_data['blocked'])) {
374
375                $app->logMsg(sprintf('%s %s (%s) login failed due to blocked account: %s', ucfirst($this->_auth), $this->getVal('user_id'), $this->getVal('username'), $this->getVal('blocked_reason')), LOG_NOTICE, __FILE__, __LINE__);
376
377                switch ($user_data['blocked_reason']) {
378                    case 'account abuse' :
379                        $app->raiseMsg(sprintf(_("This account has been blocked due to possible account abuse. Please contact us to reactivate."), null), MSG_WARNING, __FILE__, __LINE__);
380                        break;
381                    default :
382                        $app->raiseMsg(sprintf(_("This account is currently not active. %s"), $user_data['blocked_reason']), MSG_WARNING, __FILE__, __LINE__);
383                        break;
384                }
385
386                // No login: user is blocked!
387                $this->clearAuth();
388                return false;
389            }
390        }
391
392        /**
393         * Check the db_login_table for too many logins under this account.
394         * (1) Count the number of unique IP addresses that logged in under this user within the login_abuse_timeframe
395         * (2) If this number exceeds the login_abuse_max_ips, assume multiple people are logging in under the same account.
396        **/
397        if ($this->getParam('abuse_detection') && !$this->getVal('login_abuse_exempt')) {
398            $qid = $db->query("
399                SELECT COUNT(DISTINCT LEFT(remote_ip_binary, " . $this->_params['login_abuse_ip_bitmask'] . "))
400                FROM " . $this->_params['db_login_table'] . "
401                WHERE " . $this->_params['db_primary_key'] . " = '" . $this->getVal('user_id') . "'
402                AND DATE_ADD(login_datetime, INTERVAL '" . $this->_params['login_abuse_timeframe'] . "' DAY_HOUR) > NOW()
403            ");
404            list($distinct_ips) = mysql_fetch_row($qid);
405            if ($distinct_ips > $this->_params['login_abuse_max_ips']) {
406                if ($this->getVal('abuse_warning_level') < $this->_params['login_abuse_warnings']) {
407                    // Warn the user with a password reset.
408                    $this->resetPassword(null, _("This is a security precaution. We have detected this account has been accessed from multiple computers simultaneously. It is against policy to share login information with others. If further account abuse is detected this account will be blocked."));
409                    $app->raiseMsg(_("Your password has been reset as a security precaution. Please check your email for more information."), MSG_NOTICE, __FILE__, __LINE__);
410                    $app->logMsg(sprintf('Account abuse detected for %s %s (%s) from IP %s', $this->_auth, $this->getVal('user_id'), $this->getVal('username'), $this->getVal('remote_ip')), LOG_WARNING, __FILE__, __LINE__);
411                } else {
412                    // Block the account with the reason of account abuse.
413                    $this->blockAccount(null, 'account abuse');
414                    $app->raiseMsg(_("Your account has been blocked as a security precaution. Please contact us for more information."), MSG_NOTICE, __FILE__, __LINE__);
415                    $app->logMsg(sprintf('Account blocked for %s %s (%s) from IP %s', $this->_auth, $this->getVal('user_id'), $this->getVal('username'), $this->getVal('remote_ip')), LOG_ALERT, __FILE__, __LINE__);
416                }
417                // Increment user's warning level.
418                $db->query("UPDATE " . $this->_params['db_table'] . " SET abuse_warning_level = abuse_warning_level + 1 WHERE " . $this->_params['db_primary_key'] . " = '" . $this->getVal('user_id') . "'");
419                // Reset the login counter for this user.
420                $db->query("DELETE FROM " . $this->_params['db_login_table'] . " WHERE " . $this->_params['db_primary_key'] . " = '" . $this->getVal('user_id') . "'");
421                // No login: reset password because of account abuse!
422                $this->clearAuth();
423                return false;
424            }
425
426            // Update the login counter table with this login access. Convert IP to binary.
427            $db->query("
428                INSERT INTO " . $this->_params['db_login_table'] . " (
429                    " . $this->_params['db_primary_key'] . ",
430                    login_datetime,
431                    remote_ip_binary
432                ) VALUES (
433                    '" . $this->getVal('user_id') . "',
434                    '" . $this->getVal('login_datetime') . "',
435                    '" . sprintf('%032b', ip2long($this->getVal('remote_ip'))) . "'
436                )
437            ");
438        }
439
440        // Update user table with this login.
441        $db->query("
442            UPDATE " . $this->_params['db_table'] . " SET
443                last_login_datetime = '" . $this->getVal('login_datetime') . "',
444                last_access_datetime = '" . $this->getVal('login_datetime') . "',
445                last_login_ip = '" . $this->getVal('remote_ip') . "'
446            WHERE " . $this->_params['db_primary_key'] . " = '" . $this->getVal('user_id') . "'
447        ");
448
449        // We're logged-in!
450        return true;
451    }
452
453    /**
454     * Test if user has a currently logged-in session.
455     *  - authentication flag set to true
456     *  - username not empty
457     *  - total logged-in time is not greater than login_timeout
458     *  - idle time is not greater than idle_timeout
459     *  - remote address is the same as the login remote address (aol users excluded).
460     *
461     * @access public
462     */
463    function isLoggedIn($user_id=null)
464    {
465        $app =& App::getInstance();
466        $db =& DB::getInstance();
467   
468        $this->initDB();
469
470        if (isset($user_id)) {
471            // Check the login status of a specific user.
472            $qid = $db->query("
473                SELECT 1 FROM " . $this->_params['db_table'] . "
474                WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
475                AND DATE_ADD(last_login_datetime, INTERVAL '" . $this->_params['login_timeout'] . "' SECOND) > NOW()
476                AND DATE_ADD(last_access_datetime, INTERVAL '" . $this->_params['idle_timeout'] . "' SECOND) > NOW()
477            ");
478            return (mysql_num_rows($qid) > 0);
479        }
480
481        // User login test need only be run once per script execution. We cache the result in the session.
482        if ($this->_authentication_tested && isset($_SESSION[$this->_sess]['authenticated'])) {
483            return $_SESSION[$this->_sess]['authenticated'];
484        }
485
486        // Tesing login should occur once. This is the first time. Set flag.
487        $this->_authentication_tested = true;
488
489        // 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.
490        if ($trusted_net = ipInRange(getRemoteAddr(), $this->_params['trusted_networks'])) {
491            $user_in_trusted_network = true;
492            $app->logMsg(sprintf('%s%s accessing from trusted network %s',
493                ucfirst($this->_auth),
494                ($this->getVal('user_id') ? ' ' . $this->getVal('user_id') . ' (' .  $this->getVal('username') . ')' : ''),
495                $trusted_net
496            ), LOG_DEBUG, __FILE__, __LINE__);
497        } else if (preg_match('/proxy.aol.com$/i', getRemoteAddr(true))) {
498            $user_in_trusted_network = true;
499            $app->logMsg(sprintf('%s%s accessing from trusted network proxy.aol.com',
500                ucfirst($this->_auth),
501                ($this->getVal('user_id') ? ' ' . $this->getVal('user_id') . ' (' .  $this->getVal('username') . ')' : '')
502            ), LOG_DEBUG, __FILE__, __LINE__);
503        } else {
504            $user_in_trusted_network = false;
505        }
506
507        // Test login with information stored in session. Skip IP matching for users from trusted networks.
508        if (isset($_SESSION[$this->_sess])
509            && true === $_SESSION[$this->_sess]['authenticated']
510            && !empty($_SESSION[$this->_sess]['username'])
511            && strtotime($_SESSION[$this->_sess]['login_datetime']) > time() - $this->_params['login_timeout']
512            && strtotime($_SESSION[$this->_sess]['last_access_datetime']) > time() - $this->_params['idle_timeout']
513            && ($_SESSION[$this->_sess]['remote_ip'] == getRemoteAddr() || $user_in_trusted_network)
514        ) {
515            // User is authenticated!
516            $_SESSION[$this->_sess]['last_access_datetime'] = date('Y-m-d H:i:s');
517
518            // Update the DB with the last_access_datetime and increment the seconds_online.
519            $db->query("
520                UPDATE " . $this->_params['db_table'] . " SET
521                seconds_online = seconds_online + (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_access_datetime)) + 1,
522                last_access_datetime = '" . $this->getVal('last_access_datetime') . "'
523                WHERE " . $this->_params['db_primary_key'] . " = '" . $this->getVal('user_id') . "'
524            ");
525            if (mysql_affected_rows($db->getDBH()) > 0) {
526                // User record still exists in DB. Do this to ensure user was not delete from DB between accesses. Notice "+ 1" in SQL above to ensure record is modified.
527                return true;
528            } else {
529                $app->logMsg(sprintf('User update failed. Record not found for %s %s (%s).', $this->_auth, $this->getVal('user_id'), $this->getVal('username')), LOG_NOTICE, __FILE__, __LINE__);
530            }
531        } else if (isset($_SESSION[$this->_sess]) && true === $_SESSION[$this->_sess]['authenticated']) {
532            // User is authenticated, but login has expired.
533            if (strtotime($_SESSION[$this->_sess]['last_access_datetime']) > time() - 43200) {
534                // Only raise message if last session is less than 12 hours old.
535                $app->raiseMsg(sprintf(_("Your %s session has closed. You need to log-in again."), strtolower($this->_auth)), MSG_NOTICE, __FILE__, __LINE__);
536            }
537
538            // Log the reason for login expiration.
539            $expire_reasons = array();
540            if (empty($_SESSION[$this->_sess]['username'])) {
541                $expire_reasons[] = 'username not found';
542            }
543            if (strtotime($_SESSION[$this->_sess]['login_datetime']) <= time() - $this->_params['login_timeout']) {
544                $expire_reasons[] = 'login_timeout expired';
545            }
546            if (strtotime($_SESSION[$this->_sess]['last_access_datetime']) <= time() - $this->_params['idle_timeout']) {
547                $expire_reasons[] = 'idle_timeout expired';
548            }
549            if ($_SESSION[$this->_sess]['remote_ip'] != getRemoteAddr() && !$user_in_trusted_network) {
550                $expire_reasons[] = sprintf('remote_ip not matched (%s != %s)', $_SESSION[$this->_sess]['remote_ip'], getRemoteAddr());
551            }
552            $app->logMsg(sprintf('%s %s (%s) session expired: %s', ucfirst($this->_auth), $this->getVal('user_id'), $this->getVal('username'), join(', ', $expire_reasons)), LOG_INFO, __FILE__, __LINE__);
553        }
554
555        // User is not authenticated.
556        $this->clearAuth();
557        return false;
558    }
559
560    /**
561     * Redirect user to login page if they are not logged in.
562     *
563     * @param string $message The text description of a message to raise.
564     * @param int    $type    The type of message: MSG_NOTICE,
565     *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
566     * @param string $file    __FILE__.
567     * @param string $line    __LINE__.
568     * @access public
569     */
570    function requireLogin($message='', $type=MSG_NOTICE, $file=null, $line=null)
571    {
572        $app =& App::getInstance();
573   
574        if (!$this->isLoggedIn()) {
575            // Display message for requiring login. (RaiseMsg will ignore empty strings.)
576            $app->raiseMsg($message, $type, $file, $line);
577
578            // Login scripts must have the same 'login' tag for boomerangURL verification/manipulation.
579            $app->setBoomerangURL(absoluteMe(), 'login');
580            $app->dieURL($this->_params['login_url']);
581        }
582    }
583
584    /**
585     * This sets the 'blocked' field for a user in the db_table, and also
586     * adds an optional reason
587     *
588     * @param  string   $reason      The reason for blocking the account.
589     */
590    function blockAccount($user_id=null, $reason='')
591    {
592        $app =& App::getInstance();
593        $db =& DB::getInstance();
594   
595        $this->initDB();
596
597        if ($this->getParam('blocking')) {
598            if (strlen($db->escapeString($reason)) > 255) {
599                // blocked_reason field is varchar(255).
600                $app->logMsg(sprintf('Blocked reason provided is greater than 255 characters: %s', $reason), LOG_WARNING, __FILE__, __LINE__);
601            }
602
603            // Get user_id if specified.
604            $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
605            $db->query("
606                UPDATE " . $this->_params['db_table'] . " SET
607                blocked = 'true',
608                blocked_reason = '" . $db->escapeString($reason) . "'
609                WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
610            ");
611        }
612    }
613
614    /**
615     * Unblocks a user in the db_table, and clears any blocked_reason.
616     */
617    function unblockAccount($user_id=null)
618    {
619        $db =& DB::getInstance();
620   
621        $this->initDB();
622   
623        if ($this->getParam('blocking')) {
624            // Get user_id if specified.
625            $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
626            $db->query("
627                UPDATE " . $this->_params['db_table'] . " SET
628                blocked = '',
629                blocked_reason = ''
630                WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
631            ");
632        }
633    }
634
635    /**
636     * Returns true if username already exists in database.
637     *
638     * @param  string  $username    Username to look for.
639     * @return bool                 True if username exists.
640     */
641    function usernameExists($username)
642    {
643        $db =& DB::getInstance();
644   
645        $this->initDB();
646
647        $qid = $db->query("
648            SELECT 1
649            FROM " . $this->_params['db_table'] . "
650            WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "'
651        ");
652        return (mysql_num_rows($qid) > 0);
653    }
654
655    /**
656     * Returns a username for a specified user id.
657     *
658     * @param  string  $user_id     User id to look for.
659     * @return string               Username, or false if none found.
660     */
661    function getUsername($user_id)
662    {
663        $db =& DB::getInstance();
664   
665        $this->initDB();
666
667        $qid = $db->query("
668            SELECT " . $this->_params['db_username_column'] . "
669            FROM " . $this->_params['db_table'] . "
670            WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
671        ");
672        if (list($username) = mysql_fetch_row($qid)) {
673            return $username;
674        } else {
675            return false;
676        }
677    }
678
679    /**
680     * Returns a randomly generated password based on $pattern. The pattern is any
681     * sequence of 'x', 'V', 'C', 'v', 'c', or 'd' and if it is something like 'cvccv' this
682     * function will generate a pronouncable password. Recommend using more complex
683     * patterns, at minimum the US State Department standard: cvcddcvc.
684     *
685     * - x    a random upper or lower alpha character or digit
686     * - C    a random upper or lower consanant
687     * - V    a random upper or lower vowel
688     * - c    a random lowercase consanant
689     * - v    a random lowercase vowel
690     * - d    a random digit
691     *
692     * @param  string $pattern  a sequence of character types, above.
693     * @return string           a password
694     */
695    function generatePassword($pattern='CvccvCdd')
696    {
697        mt_srand((double) microtime() * 10000000);
698        $str = '';
699        for ($i=0; $i<strlen($pattern); $i++) {
700            $x = substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZaeiouyAEIOUY0123456789', (mt_rand() % 60), 1);
701            $c = substr('bcdfghjklmnprstvwxz', (mt_rand() % 19), 1);
702            $C = substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZ', (mt_rand() % 38), 1);
703            $v = substr('aeiouy', (mt_rand() % 6), 1);
704            $V = substr('aeiouyAEIOUY', (mt_rand() % 12), 1);
705            $d = substr('0123456789', (mt_rand() % 10), 1);
706            $str .= $$pattern{$i};
707        }
708        return $str;
709    }
710
711    /**
712     *
713     */
714    function encryptPassword($password)
715    {
716        switch ($this->_params['encryption_type']) {
717        case AUTH_ENCRYPT_PLAINTEXT :
718            return $password;
719            break;
720
721        case AUTH_ENCRYPT_CRYPT :
722            return crypt($password);
723            break;
724
725        case AUTH_ENCRYPT_SHA1 :
726            return sha1($password);
727            break;
728
729        case AUTH_ENCRYPT_MD5 :
730        default :
731            return md5($password);
732            break;
733        }
734    }
735
736    /**
737     *
738     */
739    function setPassword($user_id=null, $password)
740    {
741        $app =& App::getInstance();
742        $db =& DB::getInstance();
743   
744        $this->initDB();
745
746        // Get user_id if specified.
747        $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
748
749        // Issue the password change query.
750        $db->query("
751            UPDATE " . $this->_params['db_table'] . "
752            SET userpass = '" . $db->escapeString($this->encryptPassword($password)) . "'
753            WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
754        ");
755       
756        if (mysql_affected_rows($db->getDBH()) != 1) {
757            $app->logMsg(sprintf('setPassword failed to update password for user %s', $user_id), LOG_NOTICE, __FILE__, __LINE__);
758        }
759    }
760
761    /**
762     * Resets the password for the user with the specified id.
763     *
764     * @param  string $user_id   The id of the user to reset.
765     * @param  string $reason    Additional message to add to the reset email.
766     * @return string            The user's new password.
767     */
768    function resetPassword($user_id=null, $reason='')
769    {
770        $app =& App::getInstance();
771        $db =& DB::getInstance();
772   
773        $this->initDB();
774
775        // Get user_id if specified.
776        $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
777
778        // Reset password of a specific user.
779        $qid = $db->query("
780            SELECT * FROM " . $this->_params['db_table'] . "
781            WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
782        ");
783        if (!$user_data = mysql_fetch_assoc($qid)) {
784            $app->logMsg(sprintf('Reset password failed. %s %s not found.', ucfirst($this->_auth), $user_id), LOG_NOTICE, __FILE__, __LINE__);
785            return false;
786        }
787
788        // Get new password.
789        $password = $this->generatePassword();
790
791        // Update password query.
792        $this->setPassword($user_id, $password);
793
794        // Make sure user has an email on record before continuing.
795        if (!isset($user_data['email']) || '' == trim($user_data['email'])) {
796            $app->logMsg(sprintf('Password reset but notification failed, no email address for %s %s (%s).', $this->_auth, $user_data[$this->_params['db_primary_key']], $user_data[$this->_params['db_username_column']]), LOG_NOTICE, __FILE__, __LINE__);
797        } else {
798            // Body for email.
799            $email_body = <<<EOF
800Hello {NAME},
801
802Your password at {SITE_NAME} has been reset. {REASON}
803Your new login information is:
804
805USERNAME: {USERNAME}
806PASSWORD: {PASSWORD}
807
808If you have any questions or concerns please reply to this email or visit the website below.
809
810Thank you,
811{SITE_NAME}
812{SITE_URL}/
813
814EOF;
815            $email = new Email(array(
816                'to' => $user_data['email'],
817                'from' => sprintf('%s <%s>', $app->getParam('site_name'), $app->getParam('site_email')),
818                'subject' => sprintf('%s password change', $app->getParam('site_name'))
819            ));
820            $email->setString($email_body);
821            $email->replace(array(
822                'site_name' => $app->getParam('site_name'),
823                'site_url' => $app->getParam('site_url'),
824                'name' => ('' != $user_data['first_name'] . $user_data['last_name'] ? $user_data['first_name'] . ' ' . $user_data['last_name'] : $user_data[$this->_params['db_username_column']]),
825                'username' => $user_data[$this->_params['db_username_column']],
826                'password' => $password,
827                'reason' => $reason,
828            ));
829            $email->send();
830        }
831
832        return array(
833            'username' => $user_data[$this->_params['db_username_column']],
834            'userpass' => $password
835        );
836    }
837
838    /**
839     * If the current user has access to the specified $security_zone, return true.
840     * If the optional $priv is supplied, test that against the zone.
841     *
842     * @param  constant $security_zone   string of comma delimited priviliges for the zone
843     * @param  string   $priv            a privilege that might be found in a zone
844     * @return bool     true if user is a member of security zone, false otherwise
845     */
846    function inClearanceZone($security_zone, $priv='')
847    {
848        return true;
849        $zone_members = preg_split('/,\s*/', $security_zone);
850        $priv = empty($priv) ? $this->getVal('priv') : $priv;
851
852        // If the current user's privilege level is NOT in that array or if the
853        // user has no privilege, return false. Otherwise the user is clear.
854        if (!in_array($priv, $zone_members) || empty($priv)) {
855            return false;
856        } else {
857            return true;
858        }
859    }
860
861    /**
862     * This function tests a list of arguments $security_zone against the priv that the current user has.
863     * If the user doesn't have one of the supplied privs, die.
864     *
865     * @param  constant $security_zone   string of comma delimited priviliges for the zone
866     */
867    function requireAccessClearance($security_zone, $message='')
868    {
869        $app =& App::getInstance();
870   
871        return true;
872        $zone_members = preg_split('/,\s*/', $security_zone);
873
874        /* If the current user's privilege level is NOT in that array or if the
875         * user has no privilege, DIE with a message. */
876        if (!in_array($this->getVal('priv'), $zone_members) || !$this->getVal('priv')) {
877            $message = empty($message) ? _("You have insufficient privileges to view that page.") : $message;
878            $app->raiseMsg($message, MSG_NOTICE, __FILE__, __LINE__);
879            $app->dieBoomerangURL();
880        }
881    }
882
883} // end class
884
885// CIDR cheat-sheet
886//
887// Netmask              Netmask (binary)                 CIDR     Notes
888// _____________________________________________________________________________
889// 255.255.255.255  11111111.11111111.11111111.11111111  /32  Host (single addr)
890// 255.255.255.254  11111111.11111111.11111111.11111110  /31  Unusable
891// 255.255.255.252  11111111.11111111.11111111.11111100  /30    2  useable
892// 255.255.255.248  11111111.11111111.11111111.11111000  /29    6  useable
893// 255.255.255.240  11111111.11111111.11111111.11110000  /28   14  useable
894// 255.255.255.224  11111111.11111111.11111111.11100000  /27   30  useable
895// 255.255.255.192  11111111.11111111.11111111.11000000  /26   62  useable
896// 255.255.255.128  11111111.11111111.11111111.10000000  /25  126  useable
897// 255.255.255.0    11111111.11111111.11111111.00000000  /24 "Class C" 254 useable
898//
899// 255.255.254.0    11111111.11111111.11111110.00000000  /23    2  Class C's
900// 255.255.252.0    11111111.11111111.11111100.00000000  /22    4  Class C's
901// 255.255.248.0    11111111.11111111.11111000.00000000  /21    8  Class C's
902// 255.255.240.0    11111111.11111111.11110000.00000000  /20   16  Class C's
903// 255.255.224.0    11111111.11111111.11100000.00000000  /19   32  Class C's
904// 255.255.192.0    11111111.11111111.11000000.00000000  /18   64  Class C's
905// 255.255.128.0    11111111.11111111.10000000.00000000  /17  128  Class C's
906// 255.255.0.0      11111111.11111111.00000000.00000000  /16  "Class B"
907//
908// 255.254.0.0      11111111.11111110.00000000.00000000  /15    2  Class B's
909// 255.252.0.0      11111111.11111100.00000000.00000000  /14    4  Class B's
910// 255.248.0.0      11111111.11111000.00000000.00000000  /13    8  Class B's
911// 255.240.0.0      11111111.11110000.00000000.00000000  /12   16  Class B's
912// 255.224.0.0      11111111.11100000.00000000.00000000  /11   32  Class B's
913// 255.192.0.0      11111111.11000000.00000000.00000000  /10   64  Class B's
914// 255.128.0.0      11111111.10000000.00000000.00000000  /9   128  Class B's
915// 255.0.0.0        11111111.00000000.00000000.00000000  /8   "Class A"
916//
917// 254.0.0.0        11111110.00000000.00000000.00000000  /7
918// 252.0.0.0        11111100.00000000.00000000.00000000  /6
919// 248.0.0.0        11111000.00000000.00000000.00000000  /5
920// 240.0.0.0        11110000.00000000.00000000.00000000  /4
921// 224.0.0.0        11100000.00000000.00000000.00000000  /3
922// 192.0.0.0        11000000.00000000.00000000.00000000  /2
923// 128.0.0.0        10000000.00000000.00000000.00000000  /1
924// 0.0.0.0          00000000.00000000.00000000.00000000  /0   IP space
925?>
Note: See TracBrowser for help on using the repository browser.