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 * This file is part of FusionForge. FusionForge is free software;
10 * you can redistribute it and/or modify it under the terms of the
11 * GNU General Public License as published by the Free Software
12 * Foundation; either version 2 of the Licence, or (at your option)
15 * FusionForge is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 require_once $gfcommon.'include/account.php';
26 require_once $gfcommon.'include/utils.php';
27 require_once $gfcommon.'include/escapingUtils.php';
30 * A User object if user is logged in
32 * @var constant $G_SESSION
39 $session_ser = getStringFromCookie('session_ser');
42 * session_build_session_token() - Construct session token for the user
44 * @param int User_id of the logged in user
45 * @return string token value
47 function session_build_session_token($user_id) {
52 $session_serial = $user_id.'-*-'.time().'-*-'.getStringFromServer('REMOTE_ADDR').'-*-'.getStringFromServer('HTTP_USER_AGENT');
53 $session_serial_hash = md5($session_serial.forge_get_config('session_key'));
54 $session_serial_token = base64_encode($session_serial).'-*-'.$session_serial_hash;
55 return $session_serial_token;
59 * session_get_hash_from_token() - Get hash of session token
61 * This hash can be used as a key to identify session, e.g. in DB.
63 * @param string Value of the session token
66 function session_get_hash_from_token($session_token) {
67 list ($junk, $hash) = explode('-*-', $session_token);
72 * session_check_session_token() - Check that session token passed from user is ok
74 * @param string Value of the session token
75 * @return user_id if token is ok, false otherwise
77 function session_check_session_token($session_token) {
78 if ($session_token == '') {
82 list ($session_serial, $hash) = explode('-*-', $session_token);
83 $session_serial = base64_decode($session_serial);
84 $new_hash = md5($session_serial.forge_get_config('session_key'));
86 if ($hash != $new_hash) {
90 list($user_id, $time, $ip, $user_agent) = explode('-*-', $session_serial, 4);
92 if (!session_check_ip($ip, getStringFromServer('REMOTE_ADDR'))) {
95 if (trim($user_agent) != getStringFromServer('HTTP_USER_AGENT')) {
98 if ((forge_get_config('session_expire') > 0) &&
99 ($time - time() >= forge_get_config('session_expire'))) {
107 * session_logout() - Log the user off the system.
109 * This function destroys object associated with the current session,
110 * making user "logged out". Deletes both user and session cookies.
115 function session_logout() {
116 plugin_hook('close_auth_session');
117 RBACEngine::getInstance()->invalidateRoleCaches() ;
122 * session_login_valid() - Log the user to the system.
124 * High-level function for user login. Check credentials, and if they
125 * are valid, open new session.
127 * @param string User name
128 * @param string User password (in clear text)
129 * @param bool Allow login to non-confirmed user account (only for confirmation of the very account)
130 * @return true/false, if false reason is in global $feedback
134 function session_login_valid($loginname, $passwd, $allowpending=0) {
135 global $feedback,$error_msg,$warning_msg;
137 if (!$loginname || !$passwd) {
138 $warning_msg = _('Missing Password Or Users Name');
142 $hook_params = array () ;
143 $hook_params['loginname'] = $loginname ;
144 $hook_params['passwd'] = $passwd ;
145 $result = plugin_hook ("session_before_login", $hook_params) ;
147 // Refuse login if not all the plugins are ok.
150 $warning_msg = _('Invalid Password Or User Name');
155 return session_login_valid_dbonly ($loginname, $passwd, $allowpending) ;
158 function session_login_valid_dbonly($loginname, $passwd, $allowpending=false) {
159 return session_check_credentials_in_database($loginname, $passwd, $allowpending);
162 function session_check_credentials_in_database($loginname, $passwd, $allowpending=false) {
163 global $warning_msg ,$userstatus;
165 // Try to get the users from the database using user_id and (MD5) user_pw
166 if (forge_get_config('require_unique_email')) {
167 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE (user_name=$1 OR email=$1) AND user_pw=$2',
171 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE user_name=$1 AND user_pw=$2',
175 if (!$res || db_numrows($res) < 1) {
176 // No user whose MD5 passwd matches the MD5 of the provided passwd
177 // Selecting by user_name/email only
178 if (forge_get_config('require_unique_email')) {
179 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE user_name=$1 OR email=$1',
180 array ($loginname)) ;
182 $res = db_query_params ('SELECT user_id,status,unix_pw FROM users WHERE user_name=$1',
183 array ($loginname)) ;
185 if (!$res || db_numrows($res) < 1) {
186 // No user by that name
187 $warning_msg = _('Invalid Password Or User Name');
190 // There is a user with the provided user_name/email, but the MD5 passwds do not match
191 // We'll have to try checking the (crypt) unix_pw
192 $usr = db_fetch_array($res);
193 $userstatus = $usr['status'] ;
195 if (crypt ($passwd, $usr['unix_pw']) != $usr['unix_pw']) {
196 // Even the (crypt) unix_pw does not patch
197 // This one has clearly typed a bad passwd
198 $warning_msg = _('Invalid Password Or User Name');
201 // User exists, (crypt) unix_pw matches
202 // Update the (MD5) user_pw and retry authentication
203 // It should work, except for status errors
204 $res = db_query_params ('UPDATE users SET user_pw=$1 WHERE user_id=$2',
207 return session_check_credentials_in_database($loginname, $passwd, $allowpending) ;
210 // If we're here, then the user has typed a password matching the (MD5) user_pw
211 // Let's check whether it also matches the (crypt) unix_pw
212 $usr = db_fetch_array($res);
214 if (crypt ($passwd, $usr['unix_pw']) != $usr['unix_pw']) {
215 // The (crypt) unix_pw does not match
216 if ($usr['unix_pw'] == '') {
217 // Empty unix_pw, we'll take the MD5 as authoritative
218 // Update the (crypt) unix_pw and retry authentication
219 // It should work, except for status errors
220 $res = db_query_params ('UPDATE users SET unix_pw=$1 WHERE user_id=$2',
221 array (account_genunixpw($passwd),
223 return session_check_credentials_in_database($loginname, $passwd, $allowpending) ;
225 // Invalidate (MD5) user_pw, refuse authentication
226 $res = db_query_params ('UPDATE users SET user_pw=$1 WHERE user_id=$2',
227 array ('OUT OF DATE',
229 $warning_msg =_('Invalid Password Or User Name');
234 // Yay. The provided password matches both fields in the database.
235 // Let's check the status of this user
237 // if allowpending (for verify.php) then allow
238 $userstatus=$usr['status'];
239 if ($allowpending && ($usr['status'] == 'P')) {
242 if ($usr['status'] == 'S') {
244 $warning_msg = _('Account Suspended');
247 if ($usr['status'] == 'P') {
249 $warning_msg = _('Account Pending');
252 if ($usr['status'] == 'D') {
254 $warning_msg = _('Account Deleted');
257 if ($usr['status'] != 'A') {
258 //unacceptable account flag
259 $warning_msg = _('Account Not Active');
263 //create a new session
264 session_set_new(db_result($res, 0, 'user_id'));
271 * session_check_ip() - Check 2 IP addresses for match
273 * This function checks that IP addresses match
275 * IPv4 addresses are allowed to match with some
276 * fuzz factor (within 255.255.0.0 subnet).
278 * For IPv6 addresses, no fuzz is needed since there's
279 * usually no NAT in IPv6.
281 * @param string The old IP address
282 * @param string The new IP address
286 function session_check_ip($oldip,$newip) {
287 if (strstr ($oldip, ':')) {
289 if (strstr ($newip, ':')) {
290 // New IP is IPv6 too
291 return ($oldip == $newip) ;
297 if (strstr ($newip, ':')) {
301 $eoldip = explode(".",$oldip);
302 $enewip = explode(".",$newip);
304 // require same class b subnet
305 return ( ($eoldip[0] == $enewip[0])
306 && ($eoldip[1] == $enewip[1]) ) ;
312 * session_issecure() - Check if current session is secure
317 function session_issecure() {
318 return (strtoupper(getStringFromServer('HTTPS')) == "ON");
322 * session_set_cookie() - Set a session cookie
324 * Set a cookie with default temporal scope of the current browser session
325 * and URL space of the current webserver
327 * @param string Name of cookie
328 * @param string Value of cookie
329 * @param string Domain scope (default '')
330 * @param string Expiration time in UNIX seconds (default 0)
333 function session_set_cookie($name ,$value, $domain = '', $expiration = 0) {
334 if (php_sapi_name() != 'cli') {
335 if ( $expiration != 0){
336 setcookie($name, $value, time() + $expiration, '/', $domain, 0);
338 setcookie($name, $value, $expiration, '/', $domain, 0);
344 * session_redirect() - Redirect browser within the site and exit.
346 * @param string $loc Absolute path within the site
348 function session_redirect($loc) {
349 util_save_messages();
350 session_redirect_external(util_make_url ($loc));
355 * session_redirect_external() - Redirect browser to a (potentially external) URL
357 * @param string Absolute URL, not necessarily within the site
358 * @return never returns
360 function session_redirect_external($url) {
361 header('Location: '.$url);
367 * session_redirect404() - Redirect browser to 404 error page
369 * @return never returns
371 function session_redirect404() {
372 global $HTML, $gfwww, $gfcommon;
374 header("HTTP/1.0 404 Not Found");
375 require_once $gfwww.'404.php';
381 * session_require() - DEPRECATED Convenience function to easily enforce permissions
383 * Calling page will terminate with error message if current user
386 * @param array Associative array specifying criteria
387 * @return does not return if check is failed
390 function session_require($req, $reason='') {
391 if (!session_loggedin()) {
392 exit_not_logged_in();
395 $user =& user_get_object(user_getid());
396 if (! $user->isActive()) {
398 exit_error(_('Your account is no longer active ; you have been disconnected'),'');
401 if (array_key_exists('group', $req)) {
402 $group = group_get_object($req['group']);
403 if (!$group || !is_object($group)) {
405 } elseif ($group->isError()) {
406 exit_error($reason == '' ? $group->getErrorMessage() : $reason, '');
409 $perm =& $group->getPermission ();
410 if (!$perm || !is_object($perm) || $perm->isError()) {
411 exit_permission_denied($reason,'');
414 if (isset($req['admin_flags']) && $req['admin_flags']) {
415 if (!$perm->isAdmin()) {
416 exit_permission_denied($reason,'');
419 if (!$perm->isMember()) {
420 exit_permission_denied($reason,'');
424 exit_permission_denied($reason,'');
429 * session_require_perm() - Convenience function to easily enforce permissions
431 * Calling page will terminate with error message if current user
435 function session_require_perm ($section, $reference, $action = NULL, $reason='') {
436 if (!forge_check_perm ($section, $reference, $action)) {
437 exit_permission_denied ($reason,'');
442 * session_require_global_perm() - Convenience function to easily enforce permissions
444 * Calling page will terminate with error message if current user
448 function session_require_global_perm ($section, $action = NULL, $reason='') {
449 if (!forge_check_global_perm ($section, $action)) {
451 $reason = sprintf (_('Permission denied. The %s administrators will have to grant you permission to view this page.'),
452 forge_get_config ('forge_name')) ;
454 exit_permission_denied ($reason,'');
459 * session_require_login() - Convenience function to easily enforce permissions
461 * Calling page will terminate with error message if current user
465 function session_require_login () {
466 if (!session_loggedin()) {
467 exit_not_logged_in () ;
472 * session_set_new() - Setup session for the given user
474 * This function sets up SourceForge session for the given user,
475 * making one be "logged in".
477 * @param int The user ID
480 function session_set_new($user_id) {
481 $token = session_build_session_token($user_id);
483 $res = db_query_params ('SELECT count(*) as c FROM user_session WHERE session_hash = $1',
484 array (session_get_hash_from_token($token))) ;
485 if (!$res || db_result($res,0,'c') < 1) {
486 db_query_params ('INSERT INTO user_session (session_hash,ip_addr,time,user_id) VALUES ($1,$2,$3,$4)',
487 array (session_get_hash_from_token($token),
488 getStringFromServer('REMOTE_ADDR'),
493 // check uniqueness of the session_hash in the database
494 $res = session_getdata($user_id);
497 exit_error(db_error(),'');
499 else if (db_numrows($res) < 1) {
500 exit_error(_('Could not fetch user session data'),'');
502 session_set_internal ($user_id, $res) ;
506 function session_set_internal ($user_id, $res=false) {
509 $G_SESSION = user_get_object($user_id,$res);
511 $G_SESSION->setLoggedIn(true);
514 RBACEngine::getInstance()->invalidateRoleCaches() ;
519 * session_set_admin() - Setup session for the admin user
521 * This function sets up a session for the administrator
525 function session_set_admin() {
526 $admins = RBACEngine::getInstance()->getUsersByAllowedAction ('forge_admin', -1) ;
527 if (count ($admins) == 0) {
528 exit_error(_('No admin users ?'),'');
530 session_set_new ($admins[0]->getID());
534 * Private optimization function for logins - fetches user data, language, and session
537 * @param int The user ID
540 function session_getdata($user_id) {
541 return db_query_params ('SELECT u.*,sl.language_id, sl.name, sl.filename, sl.classname, sl.language_code, t.dirname, t.fullname
542 FROM users u, supported_languages sl, themes t
543 WHERE u.language=sl.language_id
544 AND u.theme_id=t.theme_id
550 * session_set() - Re-initialize session for the logged in user
552 * This function checks that the user is logged in and if so, initialize
553 * internal session environment.
557 function session_set() {
561 // assume bad session_hash and session. If all checks work, then allow
562 // otherwise make new session
566 // pass the session_ser from cookie to the auth plugins
567 // (see AuthBuiltinPlugin::checkAuthSession() or likes)
568 // expect FORGE_AUTH_AUTHORITATIVE_ACCEPT, FORGE_AUTH_AUTHORITATIVE_REJECT or FORGE_AUTH_NOT_AUTHORITATIVE
570 $params['auth_token'] = $session_ser;
571 $params['results'] = array();
572 plugin_hook_by_reference('check_auth_session', $params);
576 foreach ($params['results'] as $p => $r) {
577 if ($r == FORGE_AUTH_AUTHORITATIVE_ACCEPT) {
579 } elseif ($r == FORGE_AUTH_AUTHORITATIVE_REJECT) {
583 if ($seen_yes && !$seen_no) {
584 // see AuthBuiltinPlugin::fetchAuthUser() or likes
585 // expect user object in results
587 $params['results'] = NULL;
588 plugin_hook_by_reference('fetch_authenticated_user', $params);
589 $user = $params['results'];
593 $params['username'] = $user->getUnixName();
594 $params['event'] = 'every-page';
595 plugin_hook('sync_account_info', $params);
597 $user->setLoggedIn(true);
603 // TODO: else... what ?
605 $re = RBACEngine::getInstance();
606 $re->invalidateRoleCaches() ;
610 * Re initializes a session, trusting a non-sufficient plugin only temporarily
612 * The checkAuthSession of the Auth plugin will have to acknowledge the 'sufficient_forced' param in 'check_auth_session' hook
613 * @param string $authpluginname
615 function session_set_for_authplugin($authpluginname) {
619 // assume bad session_hash and session. If all checks work, then allow
620 // otherwise make new session
624 // pass the session_ser from cookie to the auth plugins
625 // (see AuthBuiltinPlugin::checkAuthSession() or likes)
626 // expect FORGE_AUTH_AUTHORITATIVE_ACCEPT, FORGE_AUTH_AUTHORITATIVE_REJECT or FORGE_AUTH_NOT_AUTHORITATIVE
628 $params['sufficient_forced'] = $authpluginname;
630 $params['auth_token'] = $session_ser;
631 $params['results'] = array();
633 plugin_hook_by_reference('check_auth_session', $params);
636 foreach ($params['results'] as $p => $r) {
637 if ($r == FORGE_AUTH_AUTHORITATIVE_ACCEPT) {
644 // see AuthBuiltinPlugin::fetchAuthUser() or likes
645 // expect user object in results
647 $params['results'] = NULL;
649 plugin_hook_by_reference('fetch_authenticated_user', $params);
651 $user = $params['results'];
655 $params['username'] = $user->getUnixName();
656 $params['event'] = 'every-page';
657 plugin_hook('sync_account_info', $params);
659 $user->setLoggedIn(true);echo "user:".$user->getUnixName();
666 // TODO: else... what ?
668 $re = RBACEngine::getInstance();
669 //print_r($re->getGlobalRoles());
670 //print_r($re->getPublicRoles());
671 $re->invalidateRoleCaches() ;
672 //print_r($re->getAvailableRoles());
677 //TODO - this should be generalized and used for pre.php,
678 //SOAP, forum_gateway.php, tracker_gateway.php, etc to
680 function session_continue($sessionKey) {
682 $session_ser = $sessionKey;
684 setup_gettext_from_context();
685 setup_tz_from_context();
686 $LUSER =& session_get_user();
687 if (!is_object($LUSER) || $LUSER->isError()) {
694 function setup_tz_from_context() {
695 $LUSER =& session_get_user();
696 if (!is_object($LUSER) || $LUSER->isError()) {
697 $tz = forge_get_config('default_timezone');
699 $tz = $LUSER->getTimeZone();
702 date_default_timezone_set($tz);
706 * session_get_user() - Wrapper function to return the User object for the logged in user.
711 function &session_get_user() {
718 * Get user_id of logged in user
721 function user_getid() {
724 return $G_SESSION->getID();
732 * See if user is logged in
734 function session_loggedin() {
738 return $G_SESSION->isLoggedIn();
746 // c-file-style: "bsd"