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

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

detabbed all files ;P

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