3 * FusionForge session management
5 * Copyright 1999-2001, VA Linux Systems, Inc.
6 * Copyright 2001-2002, 2009, Roland Mas
7 * Copyright 2004-2005, GForge, LLC
9 * Thorsten “mirabilos” Glaser <t.glaser@tarent.de>
11 * This file is part of FusionForge. FusionForge is free software;
12 * you can redistribute it and/or modify it under the terms of the
13 * GNU General Public License as published by the Free Software
14 * Foundation; either version 2 of the Licence, or (at your option)
17 * FusionForge is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License along
23 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 require_once $gfcommon.'include/account.php';
28 require_once $gfcommon.'include/utils.php';
29 require_once $gfcommon.'include/escapingUtils.php';
32 * A User object if user is logged in
34 * @var constant $G_SESSION
41 $session_ser = getStringFromCookie('session_ser');
44 * session_build_session_cookie() - Construct session cookie for the user
46 * @param int User_id of the logged in user
47 * @return cookie value
49 function session_build_session_cookie($user_id) {
50 $session_cookie_data = array(
52 getStringFromServer('REMOTE_ADDR'),
53 getStringFromServer('HTTP_USER_AGENT'),
55 $session_cookie = "" . time();
56 foreach ($session_cookie_data as $s) {
57 /* for escaping; this is not really HTML */
58 $session_cookie .= '<' . util_html_encode($s);
60 $session_cookie_hmac = hash_hmac("sha256", $session_cookie,
61 forge_get_config('session_key'));
62 $session_serial_cookie = base64_encode($session_cookie) . '!' .
63 base64_encode($session_cookie_hmac);
64 return $session_serial_cookie;
68 * session_get_session_cookie_hash() - Get hash of session cookie
70 * This hash can be used as a key to identify session, e.g. in DB.
72 * @param string Value of the session cookie
75 function session_get_session_cookie_hash($session_cookie) {
77 * we cannot just use the HMAC as that may be longer than
78 * the database fields, and this code used to return a
79 * string of the size of an md5(), so just md5 it
81 return md5($session_cookie);
85 * session_check_session_cookie() - Check that session cookie passed from user is ok
87 * @param string Value of the session cookie
88 * @return user_id if cookie is ok, false otherwise
90 function session_check_session_cookie($session_cookie) {
91 if (!preg_match('#^[A-Za-z0-9+/=]*![A-Za-z0-9+/=]*$#',
94 * does not match basic format, off; recommended by
95 * http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html
96 * to protect the below code from malformed strings
101 list($session_cookie, $session_cookie_hmac) = explode('!',
103 $session_cookie = base64_decode($session_cookie);
104 $session_cookie_hmac = base64_decode($session_cookie_hmac);
105 if (hash_hmac("sha256", $session_cookie,
106 forge_get_config('session_key')) !== $session_cookie_hmac) {
111 list($time, $user_id, $ip, $user_agent) = explode('<', $session_cookie);
112 $user_id = util_unconvert_htmlspecialchars($user_id);
113 $ip = util_unconvert_htmlspecialchars($ip);
114 $user_agent = util_unconvert_htmlspecialchars($user_agent);
116 if (!session_check_ip($ip, getStringFromServer('REMOTE_ADDR'))) {
119 if (trim($user_agent) != getStringFromServer('HTTP_USER_AGENT')) {
122 if ((forge_get_config('session_expire') > 0) &&
123 ($time - time() >= forge_get_config('session_expire'))) {
131 * session_logout() - Log the user off the system.
133 * This function destroys object associated with the current session,
134 * making user "logged out". Deletes both user and session cookies.
139 function session_logout() {
140 // delete both session and username cookies
141 // NB: cookies must be deleted with the same scope parameters they were set with
143 session_cookie('session_ser', '');
145 RBACEngine::getInstance()->invalidateRoleCaches();
150 * session_login_valid() - Log the user to the system.
152 * High-level function for user login. Check credentials, and if they
153 * are valid, open new session.
155 * @param string User name
156 * @param string User password (in clear text)
157 * @param bool Allow login to non-confirmed user account (only for confirmation of the very account)
158 * @return true/false, if false reason is in global $feedback
162 function session_login_valid($loginname, $passwd, $allowpending=0) {
163 global $feedback, $error_msg, $warning_msg;
165 if (!$loginname || !$passwd) {
166 $warning_msg = _('Missing Password Or Users Name');
170 $hook_params = array();
171 $hook_params['loginname'] = $loginname;
172 $hook_params['passwd'] = $passwd;
173 $result = plugin_hook("session_before_login", $hook_params);
175 // Refuse login if not all the plugins are ok.
177 if (!util_ifsetor($feedback)) {
178 $warning_msg = _('Invalid Password Or User Name');
183 return session_login_valid_dbonly($loginname, $passwd, $allowpending);
186 function session_login_valid_dbonly($loginname, $passwd, $allowpending) {
187 global $feedback, $userstatus;
189 // Try to get the users from the database using user_id and (MD5) user_pw
190 if (forge_get_config('require_unique_email')) {
191 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE (user_name=$1 OR email=$1) AND user_pw=$2',
195 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE user_name=$1 AND user_pw=$2',
199 if (!$res || db_numrows($res) < 1) {
200 // No user whose MD5 passwd matches the MD5 of the provided passwd
201 // Selecting by user_name/email only
202 if (forge_get_config('require_unique_email')) {
203 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE user_name=$1 OR email=$1',
204 array ($loginname)) ;
206 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE user_name=$1',
207 array ($loginname)) ;
209 if (!$res || db_numrows($res) < 1) {
210 // No user by that name
211 $feedback=_('Invalid Password Or User Name');
214 // There is a user with the provided user_name/email, but the MD5 passwds do not match
215 // We'll have to try checking the (crypt) unix_pw
216 $usr = db_fetch_array($res);
217 $userstatus = $usr['status'] ;
219 if (crypt ($passwd, $usr['unix_pw']) != $usr['unix_pw']) {
220 // Even the (crypt) unix_pw does not patch
221 // This one has clearly typed a bad passwd
222 $feedback=_('Invalid Password Or User Name');
225 // User exists, (crypt) unix_pw matches
226 // Update the (MD5) user_pw and retry authentication
227 // It should work, except for status errors
228 $res = db_query_params ('UPDATE users SET user_pw=$1 WHERE user_id=$2',
231 return session_login_valid_dbonly($loginname, $passwd, $allowpending) ;
234 // If we're here, then the user has typed a password matching the (MD5) user_pw
235 // Let's check whether it also matches the (crypt) unix_pw
236 $usr = db_fetch_array($res);
238 if (crypt ($passwd, $usr['unix_pw']) != $usr['unix_pw']) {
239 // The (crypt) unix_pw does not match
240 if ($usr['unix_pw'] == '') {
241 // Empty unix_pw, we'll take the MD5 as authoritative
242 // Update the (crypt) unix_pw and retry authentication
243 // It should work, except for status errors
244 $res = db_query_params ('UPDATE users SET unix_pw=$1 WHERE user_id=$2',
245 array (account_genunixpw($passwd),
247 return session_login_valid_dbonly($loginname, $passwd, $allowpending) ;
249 // Invalidate (MD5) user_pw, refuse authentication
250 $res = db_query_params ('UPDATE users SET user_pw=$1 WHERE user_id=$2',
251 array ('OUT OF DATE',
253 $feedback=_('Invalid Password Or User Name');
258 // Yay. The provided password matches both fields in the database.
259 // Let's check the status of this user
261 // if allowpending (for verify.php) then allow
262 $userstatus=$usr['status'];
263 if ($allowpending && ($usr['status'] == 'P')) {
266 if ($usr['status'] == 'S') {
268 $feedback = _('Account Suspended');
271 if ($usr['status'] == 'P') {
273 $feedback = _('Account Pending');
276 if ($usr['status'] == 'D') {
278 $feedback = _('Account Deleted');
281 if ($usr['status'] != 'A') {
282 //unacceptable account flag
283 $feedback = _('Account Not Active');
287 // create a new session
288 session_set_new(db_result($res,0,'user_id'));
295 * session_check_ip() - Check 2 IP addresses for match
297 * This function checks that IP addresses match
299 * IPv4 addresses are allowed to match with some
300 * fuzz factor (within 255.255.0.0 subnet).
302 * For IPv6 addresses, no fuzz is needed since there's
303 * usually no NAT in IPv6.
305 * @param string The old IP address
306 * @param string The new IP address
310 function session_check_ip($oldip, $newip) {
311 if (strstr($oldip, ':')) {
313 if (strstr($newip, ':')) {
314 // New IP is IPv6 too
315 return ($oldip == $newip);
320 if (strstr($newip, ':')) {
324 $eoldip = explode(".", $oldip);
325 $enewip = explode(".", $newip);
327 // require same Class B subnet
328 return (($eoldip[0] == $enewip[0]) && ($eoldip[1] == $enewip[1]));
332 * session_issecure() - Check if current session is secure
337 function session_issecure() {
338 return (strtoupper(getStringFromServer('HTTPS')) == "ON");
342 * session_cookie() - Set a session cookie
344 * Set a cookie with default temporal scope of the current browser session
345 * and URL space of the current webserver
347 * @param string Name of cookie
348 * @param string Value of cookie
349 * @param string Domain scope (default '')
350 * @param string Expiration time in UNIX seconds (default 0)
352 function session_cookie($name, $value, $domain='', $expiration=0) {
353 if (php_sapi_name() == 'cli') {
357 $expiration = time() + $expiration;
359 /* evolvis: force secure (SSL-only) session cookies */
360 //$force_secure = true;
361 /* not (yet?) in FusionForge */
362 $force_secure = false;
363 if ($force_secure && !session_issecure()) {
366 setcookie($name, $value, $expiration, '/', $domain, $force_secure, true);
370 * session_redirect_uri() - Redirect browser
372 * @param string Absolute URI
373 * @return never returns
375 function session_redirect_uri($loc) {
376 sysdebug_off("Status: 301 Moved Permanently", true, 301);
377 header("Location: ${loc}", true);
378 echo "\nPlease go to ${loc} instead!\n";
383 * session_redirect() - Redirect browser within the site
385 * @param string Absolute path within the site
386 * @return never returns
388 function session_redirect($loc) {
389 session_redirect_uri(util_make_url($loc));
393 * session_require() - DEPRECATED Convenience function to easily enforce permissions
395 * Calling page will terminate with error message if current user
398 * @param array Associative array specifying criteria
399 * @return does not return if check is failed
402 function session_require($req, $reason='') {
403 if (!session_loggedin()) {
404 exit_not_logged_in();
407 $user =& user_get_object(user_getid());
408 if (!$user->isActive()) {
410 exit_error(_('Your account is no longer active; you have been disconnected'), '');
413 if (!array_key_exists('group', $req)) {
414 exit_permission_denied($reason, '');
417 $group = group_get_object($req['group']);
418 if (!$group || !is_object($group)) {
420 } elseif ($group->isError()) {
421 exit_error($reason ? $reason : $group->getErrorMessage(), '');
424 $perm =& $group->getPermission();
425 if (!$perm || !is_object($perm) || $perm->isError()) {
426 exit_permission_denied($reason, '');
429 if (isset($req['admin_flags']) && $req['admin_flags']) {
430 if (!$perm->isAdmin()) {
431 exit_permission_denied($reason, '');
434 if (!$perm->isMember()) {
435 exit_permission_denied($reason, '');
441 * session_require_perm() - Convenience function to easily enforce permissions
443 * Calling page will terminate with error message if current user
447 function session_require_perm($section, $reference, $action=NULL, $reason='') {
448 if (!forge_check_perm($section, $reference, $action)) {
449 exit_permission_denied($reason, '');
454 * session_require_global_perm() - Convenience function to easily enforce permissions
456 * Calling page will terminate with error message if current user
460 function session_require_global_perm($section, $action=NULL, $reason='') {
461 if (!forge_check_global_perm($section, $action)) {
463 $reason = sprintf(_('Permission denied. The %s administrators will have to grant you permission to view this page.'),
464 forge_get_config('forge_name'));
466 exit_permission_denied($reason, '');
471 * session_require_login() - Convenience function to easily enforce permissions
473 * Calling page will terminate with error message if current user
477 function session_require_login() {
478 if (!session_loggedin()) {
479 exit_not_logged_in();
484 * session_set_new() - Setup session for the given user
486 * This function sets up SourceForge session for the given user,
487 * making one be "logged in".
489 * @param int The user ID
492 function session_set_new($user_id) {
495 // set session cookie
497 $cookie = session_build_session_cookie($user_id);
498 session_cookie("session_ser", $cookie, "", forge_get_config('session_expire'));
499 $session_ser = $cookie;
501 $res = db_query_params('SELECT count(*) as c FROM user_session
502 WHERE session_hash=$1',
503 array(($shash = session_get_session_cookie_hash($cookie))));
504 if (!$res || db_result($res, 0, 'c') < 1) {
505 db_query_params('INSERT INTO user_session
506 (session_hash,ip_addr,time,user_id)
507 VALUES ($1,$2,$3,$4)',
510 getStringFromServer('REMOTE_ADDR'),
516 // check uniqueness of the session_hash in the database
517 $res = session_getdata($user_id);
520 exit_error(db_error(), '');
521 } elseif (db_numrows($res) < 1) {
522 exit_error(_('Could not fetch user session data'), '');
524 session_set_internal($user_id, $res);
528 function session_set_internal($user_id, $res=false) {
531 $G_SESSION = user_get_object($user_id, $res);
533 $G_SESSION->setLoggedIn(true);
536 RBACEngine::getInstance()->invalidateRoleCaches();
540 * session_set_admin() - Setup session for the admin user
542 * This function sets up a session for the administrator
546 function session_set_admin() {
547 $admins = RBACEngine::getInstance()->getUsersByAllowedAction('forge_admin', -1);
548 if (count($admins) == 0) {
549 exit_error(_('No admin users ?'), '');
552 * Use the user with the lowest numerical user ID.
553 * This is to prevent complaints from real humans
554 * if the system is doing something in their stead
555 * (for example by populate_template_project.php).
556 * Usually, “admin” has the ID 101.
558 $admin_ids = array();
559 foreach ($admins as $admin) {
560 $admin_ids[] = $admin->getID();
563 session_set_new($admin_ids[0]);
567 * Private optimization function for logins - fetches user data, language, and session
570 * @param int The user ID
573 function session_getdata($user_id) {
574 return db_query_params('SELECT u.*, sl.language_id, sl.name,
575 sl.filename, sl.classname, sl.language_code,
576 t.dirname, t.fullname
577 FROM users u, supported_languages sl, themes t
578 WHERE u.language=sl.language_id
579 AND u.theme_id=t.theme_id
585 * session_set() - Re-initialize session for the logged in user
587 * This function checks that the user is logged in and if so, initialize
588 * internal session environment.
592 function session_set() {
593 plugin_hook('session_set_entry');
597 // assume bad session_hash and session. If all checks work, then allow
598 // otherwise make new session
601 // If user says he's logged in (by presenting cookie), check that
603 $user_id = session_check_session_cookie($session_ser);
605 $result = session_getdata($user_id);
606 if (db_numrows($result) > 0) {
610 } // else (hash does not exist) or (session hash is bad)
613 $G_SESSION = user_get_object($user_id, $result);
615 $G_SESSION->setLoggedIn(true);
620 // if there was bad session cookie, kill it and the user cookie
625 plugin_hook('session_set_return');
627 RBACEngine::getInstance()->invalidateRoleCaches();
630 //TODO - this should be generalized and used for pre.php,
631 //SOAP, forum_gateway.php, tracker_gateway.php, etc to
633 function session_continue($sessionKey) {
635 $session_ser = $sessionKey;
637 setup_gettext_from_context();
638 setup_tz_from_context();
639 $LUSER =& session_get_user();
640 if (!is_object($LUSER) || $LUSER->isError()) {
646 function setup_tz_from_context() {
647 $LUSER =& session_get_user();
648 if (!is_object($LUSER) || $LUSER->isError()) {
649 $tz = forge_get_config('default_timezone');
651 $tz = $LUSER->getTimeZone();
654 date_default_timezone_set($tz);
658 * session_get_user() - Wrapper function to return the User object for the logged in user.
663 function &session_get_user() {
670 * Get user_id of logged in user
672 function user_getid() {
675 return $G_SESSION->getID();
682 * See if user is logged in
684 function session_loggedin() {
688 return $G_SESSION->isLoggedIn();
695 // c-file-style: "bsd"