source: branches/1.1dev/lib/AuthSQL.inc.php @ 310

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

Q - added debugging for match_remote_ip

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