source: trunk/lib/Auth_SQL.inc.php @ 1

Last change on this file since 1 was 1, checked in by scdev, 19 years ago

Initial import.

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