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

Last change on this file since 570 was 570, checked in by anonymous, 8 years ago

Added missing default config values. Removed unused auth config.

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