<?php $INC = isset($INC) ? $INC : dirname(__FILE__); require_once($INC . "/util.inc.php"); require_once($INC . "/log.inc.php"); require_once($INC . "/sql.inc.php"); require_once($INC . "/sess.inc.php"); require_once($INC . "/rights.inc.php"); require_once($INC . "/events.inc.php"); require_once($INC . "/cache.inc.php"); require_once($INC . "/mail.inc.php"); require_once($INC . "/plan.inc.php"); require_once($INC . "/totp.inc.php"); $rg_user_rights = array( "C" => "Create repository", "A" => "Create user", "E" => "Edit user", "R" => "Remove user", "S" => "Suspend user", "G" => "Grant rights", "M" => "Give admin rights" ); /* We do not have defaults rights and no compare function for misc */ rg_rights_register("user", $rg_user_rights, "", FALSE, FALSE); $rg_user_error = ""; function rg_user_set_error($str) { global $rg_user_error; $rg_user_error = $str; rg_log($str); } function rg_user_error() { global $rg_user_error; return $rg_user_error; } /* * Event functions */ $rg_user_functions = array( 2000 => "rg_user_event_new", 2001 => "rg_user_event_login", 2002 => "rg_user_event_notify_user", 2005 => "rg_user_event_rename", 2006 => "rg_user_link_by_name" ); rg_event_register_functions($rg_user_functions); /* * Event for adding a new user */ function rg_user_event_new($db, $event) { $ret = array(); $event['op'] = "new"; // create link by name $ret[] = array_merge($event, array("category" => 2006, "prio" => 500)); // notify user $ret[] = array_merge($event, array("category" => 2002, "prio" => 200)); return $ret; } /* * Event for login */ function rg_user_event_login($db, $event) { $ret = FALSE; while (1) { $r = rg_user_set_last_seen($db, $event['ui']['uid'], $event['itime'], $event['ip']); if ($r !== TRUE) break; $ret = array(); break; } return $ret; } /* * Notify user: welcome */ function rg_user_event_notify_user($db, $event) { rg_log("user_event_notify_user: event=" . rg_array2string($event)); if (strcmp($event['op'], "rename") == 0) { $r = rg_mail_template("mail/user/rename", $event); } else { $r = rg_mail_template("mail/user/welcome", $event); } // TODO: we may want to return here an error? return array(); } /* * Event for renaming an user */ function rg_user_event_rename($db, $event) { $ret = array(); $event['op'] = "rename"; // notify user $ret[] = array_merge($event, array("category" => 2002, "prio" => 100)); // TODO: notify watchers return $ret; } /* * Cosmetic function for 'user' info */ function rg_user_cosmetic(&$row) { if (isset($row['itime'])) $row['itime_nice'] = gmdate('Y-m-d', $row['itime']); $row['HTML:gravatar'] = '<img class="gravatar" src="https://www.gravatar.com/avatar/' . $row['email_md5'] . '?s=64&r=g" alt="avatar" width="64" height="64" />'; } /* * Event for creating a link by name to the uid */ function rg_user_link_by_name($db, $event) { global $php_errormsg; rg_log("user_link_by_name: event=" . rg_array2string($event)); $by_id = rg_user_path_by_id($event['ui']['uid']); if (!is_dir($by_id) && (mkdir($by_id, 0755, TRUE) === FALSE)) { rg_user_set_error("cannot mkdir by_id=$by_id ($php_errormsg)"); return FALSE; } $by_name = rg_user_path_by_name($event['ui']['username']); $by_name_parent = dirname($by_name); if (!is_dir($by_name_parent) && (mkdir($by_name_parent, 0755, TRUE) === FALSE)) { rg_user_set_error("cannot mkdir by_name_parent=$by_name_parent ($php_errmsg)"); return FALSE; } $by_id_rel = rg_user_path_by_id_rel($event['ui']['uid']); if (is_link($by_name)) unlink($by_name); if (symlink($by_id_rel, $by_name) === FALSE) { rg_user_set_error("cannot symlink $by_id_rel <- $by_name ($php_errormsg)!"); return FALSE; } return array(); } /* * Returns the relative path to user home based on uid */ function rg_user_path_by_id_rel($uid) { $xuid = sprintf("%08X", $uid); $uid_path = array(); for ($i = 0; $i < 8; $i += 2) $uid_path[] = $xuid[$i] . $xuid[$i + 1]; $uid_path = implode("/", $uid_path); return "../../../../by_id/" . $uid_path . "/" . $xuid; } /* * Returns the path to user home based on uid */ function rg_user_path_by_id($uid) { global $rg_repos; $xuid = sprintf("%08X", $uid); $uid_path = array(); for ($i = 0; $i < 8; $i += 2) $uid_path[] = $xuid[$i] . $xuid[$i + 1]; $uid_path = implode("/", $uid_path); return $rg_repos . "/by_id/" . $uid_path . "/" . $xuid; } /* * Returns the path to user home based on name */ function rg_user_path_by_name($name) { global $rg_repos; $x = $name . "__"; return $rg_repos . "/by_name/" . $x[0] . "/" . $x[1] . "/" . $x[2] . "/" . $name; } /* * Returns the URL to a user home based on $ui */ function rg_user_url($ui) { if ($ui['organization'] == 0) $prefix = "/user"; else $prefix = ""; return $prefix . "/" . $ui['username']; } /* * Computes password hash */ function rg_user_pass($salt, $pass) { return sha512($salt . "===" . $pass); } /* * Validates a password */ function rg_user_pass_ok($pass) { if (strlen($pass) < 5) { rg_user_set_error("password is too short (less than 5 chars)"); return FALSE; } return TRUE; } /* * Returns true if the user is ok */ function rg_user_ok($user) { global $rg_user_allow; global $rg_user_min_len; global $rg_user_max_len; if (rg_chars_allow($user, $rg_user_allow, $invalid) !== TRUE) { rg_user_set_error("invalid user name (invalid chars: '$invalid')"); return FALSE; } if (strlen($user) < $rg_user_min_len) { rg_user_set_error("user name too short (shorter than $rg_user_min_len)"); return FALSE; } if (strlen($user) > $rg_user_max_len) { rg_user_set_error("user name too long (longer than $rg_user_max_len)"); return FALSE; } return TRUE; } /* * Lookup in db the old names, so we can redirect the user to the new name */ function rg_user_lookup_by_old_name($db, $old_name) { rg_prof_start("user_lookup_by_old_name"); rg_log_enter("user_lookup_by_old_name: old_name=$old_name"); $ret = FALSE; while (1) { $x = rg_cache_get("old_name::" . $old_name); if ($x !== FALSE) { $ret = $x; break; } $params = array("old_name" => $old_name); $sql = "SELECT uid FROM users_renames" . " WHERE old_name = @@old_name@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot lookup old name (" . rg_sql_error() . ")"); break; } $rows = rg_sql_num_rows($res); if ($rows > 0) $row = rg_sql_fetch_array($res); rg_sql_free_result($res); if ($rows == 0) $ret = 0; else $ret = $row['uid']; rg_cache_set("old_name::" . $old_name, $ret, RG_SOCKET_NO_WAIT); break; } rg_log_exit(); rg_prof_end("user_lookup_by_old_name"); return $ret; } /* * Add a rename to the database */ function rg_user_insert_rename($db, $uid, $old_name) { rg_prof_start("user_insert_rename"); rg_log_enter("user_insert_rename: uid=$uid old_name=$old_name"); $ret = FALSE; while(1) { // Check if already exists in the database $r = rg_user_lookup_by_old_name($db, $old_name); if ($r === FALSE) break; $params = array("uid" => $uid, "old_name" => $old_name, "now" => time()); if ($r > 0) { $sql = "UPDATE users_renames" . " SET uid = @@uid@@" . " WHERE old_name = @@old_name@@"; } else { $sql = "INSERT INTO users_renames (uid, old_name" . ", itime)" . " VALUES (@@uid@@, @@old_name@@, @@now@@)"; } $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot link with old_name in db" . " (" . rg_sql_error() . ")"); break; } rg_cache_set("old_name::" . $old_name, $uid, RG_SOCKET_NO_WAIT); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_insert_rename"); return $ret; } /* * Rename a user * We keep the old name around, so the clients do not break. */ function rg_user_rename($db, $ui, $new_name) { global $php_errormsg; rg_prof_start("user_rename"); rg_log_enter("user_rename: from=[" . $ui['username'] . "]" . " to=[" . $new_name . "]..."); $user_path = rg_user_path_by_id($ui['uid']); $old_path = rg_user_path_by_id_rel($ui['uid']); $new_path = rg_user_path_by_name($new_name); rg_log("old_path=$old_path new_path=$new_path"); $ret = FALSE; while (1) { $do_link = TRUE; // Check if we already did the rename if (file_exists($new_path)) { if (!is_link($new_path)) { rg_internal_error("$new_path is not a link!"); break; } $v = readlink($new_path); if ($v === FALSE) { rg_internal_error("Cannot read link $new_path!"); break; } rg_log("new_path points to [$v]"); if (strcmp($old_path, $v) == 0) { // Link already done $do_link = FALSE; } else { // Seems that new_path points to other place $r = rename($new_path, $new_path . ".BOGUS." . time()); if ($r !== TRUE) { rg_internal_error("Cannot rename bogus!"); break; } } } if ($do_link === TRUE) { // Now, the new name is free, do the link $r = symlink($old_path, $new_path); if ($r !== TRUE) { rg_internal_error("Cannot symlink $old_path -> $new_path ($php_errormsg)!"); break; } } // TOOD: transaction? $r = rg_user_insert_rename($db, $ui['uid'], $new_name); if ($r !== TRUE) break; // TODO: Check if all parameters are used. $event = array("category" => 2005, "prio" => 50, 'ui' => array( 'rename_from' => $ui['username'], 'rename_to' => $new_name, 'uid' => $ui['uid'] ) ); $r = rg_event_add($db, $event); if ($r !== TRUE) { rg_repo_set_error("cannot add event" . " (" . rg_event_error() . ")"); break; } rg_event_signal_daemon("", 0); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_rename"); return $ret; } /* * Add/edit a user * If uid > 0 - edit, else, add */ function rg_user_edit($db, $d) { global $rg_account_email_confirm; rg_prof_start("user_edit"); rg_log_enter("user_edit: d: " . rg_array2string($d)); $ret = FALSE; while (1) { if (rg_user_ok($d['username']) !== TRUE) break; // TODO: check rights // TODO - check if user is allowed to give passed rights if (($d['confirmed'] == 0) && ($rg_account_email_confirm == 0)) { // no need to confirm account $d['confirmed'] = 1; } $update_pass = !empty($d['pass']); if ($update_pass) { if (strcmp($d['pass'], $d['pass2']) != 0) { rg_user_set_error("passwords are not the same"); break; } $d['salt'] = rg_id(40); $d['pass_crypted'] = rg_user_pass($d['salt'], $d['pass']); } $d['itime'] = time(); if ($d['uid'] == 0) { // add if (rg_user_pass_ok($d['pass']) !== TRUE) break; $sql = "INSERT INTO users (username, realname, salt, pass" . ", email, itime" . ", is_admin, rights, session_time" . ", confirmed, confirm_token, plan_id)" . " VALUES (@@username@@, @@realname@@, @@salt@@" . ", @@pass_crypted@@, @@email@@, @@itime@@" . ", @@is_admin@@, @@rights@@, @@session_time@@" . ", @@confirmed@@, @@confirm_token@@, @@plan_id@@)" . " RETURNING uid"; } else { // edit $salt_pass_add = ""; if ($update_pass) $salt_pass_add = ", pass = @@pass_crypted@@" . ", salt = @@salt@@"; $sql = "UPDATE users" . " SET username = @@username@@" . ", realname = @@realname@@" . ", email = @@email@@" . ", is_admin = @@is_admin@@" . ", rights = @@rights@@" . ", session_time = @@session_time@@" . ", plan_id = @@plan_id@@" . $salt_pass_add . " WHERE uid = @@uid@@" . " RETURNING uid"; } $res = rg_sql_query_params($db, $sql, $d); if ($res === FALSE) { rg_user_set_error("cannot insert/update user (" . rg_sql_error() . ")"); break; } $row = rg_sql_fetch_array($res); rg_sql_free_result($res); // More stuff to be cached $d['email_md5'] = md5(strtolower(trim($d['email']))); if ($d['uid'] == 0) { // add rg_cache_set('user' . '::' . $d['uid'] . '::' . ':info', $d, RG_SOCKET_NO_WAIT); $event = array('category' => 2000, 'prio' => 50, 'ui' => array( 'uid' => $row['uid'], 'realname' => $d['realname'], 'username' => $d['username'], 'email' => $d['confirmed'] > 0 ? $d['email'] : "", 'confirm_token' => $d['confirm_token'] ), 'rg_account_email_confirm' => $rg_account_email_confirm, 'url' => rg_base_url() ); $r = rg_event_add($db, $event); if ($r === FALSE) { rg_user_set_error("Cannot add event!"); break; } rg_event_signal_daemon("", 0); } else { // edit // else, we will overwrite the pass in cache if (!$update_pass) unset($d['pass']); unset($d['pass2']); // not needed in cache rg_cache_merge('user' . '::' . $d['uid'] . '::' . 'info', $d, RG_SOCKET_NO_WAIT); } // TODO: should we cache here the user_by_uid and user_by_name $ret = $row['uid']; break; } rg_log_exit(); rg_prof_end("user_edit"); return $ret; } /* * Delete a user */ function rg_user_remove($db, $rg, $uid) { rg_prof_start("user_remove"); rg_log_enter("user_remove: uid=$uid"); $ret = FALSE; while (1) { $x = array(); $x['obj_id'] = $uid; $x['type'] = 'user'; $x['owner'] = $uid; $x['uid'] = $rg['login_ui']['uid']; $x['username'] = $rg['login_ui']['username']; $x['needed_rights'] = 'R'; $x['ip'] = $rg['ip']; $x['misc'] = ""; if (rg_rights_allow($db, $x) !== TRUE) break; $params = array("uid" => $uid); $sql = "DELETE FROM users WHERE uid = @@uid@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot remove user $uid (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); // invalidate cache rg_cache_unset('user' . '::' . $uid . 'info', RG_SOCKET_NO_WAIT); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_remove"); return $ret; } /* * Returns info about a user (by uid, user or e-mail) */ function rg_user_info($db, $uid, $user, $email) { rg_prof_start("user_info"); rg_log_enter("user_info: uid=$uid user=$user email=$email"); $ret = array(); $ret['ok'] = 0; $ret['exists'] = 0; $ret['uid'] = 0; $ret['is_admin'] = 0; $ret['rights'] = ''; $ret['homepage'] = ''; while (1) { $params = array("uid" => $uid, "user" => $user, "email" => $email); if ($uid > 0) { // We will get all info about fthe user not only 'info' // to populate the cache with all info. $c = rg_cache_get('user' . '::' . $uid); if ($c !== FALSE) { if (isset($c['info'])) { $ret = $c['info']; $ret['exists'] = 1; rg_user_cosmetic($ret); break; } } $sql = "SELECT * FROM users WHERE uid = @@uid@@"; } else if (!empty($user)) { if (rg_user_ok($user) !== TRUE) break; $c = rg_cache_get("username_to_uid::" . $user); if ($c !== FALSE) { $uid = $c; continue; } $sql = "SELECT * FROM users WHERE username = @@user@@"; } else if (!empty($email)) { $c = rg_cache_get("email_to_uid::" . $email); if ($c !== FALSE) { $uid = $c; continue; } $sql = "SELECT * FROM users WHERE email = @@email@@"; } else { rg_user_set_error("invalid user"); break; } $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot get info (" . rg_sql_error() . ")"); break; } $ret['ok'] = 1; $rows = rg_sql_num_rows($res); if ($rows > 0) $row = rg_sql_fetch_array($res); rg_sql_free_result($res); if ($rows == 0) { rg_log("user_info: user [$uid/$user/$email] not found"); rg_user_set_error("user not found"); break; } $ret = array_merge($ret, $row); $ret['homepage'] = rg_re_userpage($ret); $ret['email_md5'] = md5(strtolower(trim($ret['email']))); rg_cache_set('user' . '::' . $ret['uid'] . '::' . 'info', $ret, RG_SOCKET_NO_WAIT); rg_cache_set('username_to_uid::' . $ret['username'], $ret['uid'], RG_SOCKET_NO_WAIT); rg_cache_set('email_to_uid::' . $ret['email'], $ret['uid'], RG_SOCKET_NO_WAIT); rg_user_cosmetic($ret); $ret['exists'] = 1; break; } rg_log_exit(); rg_prof_end("user_info"); return $ret; } /* * Update last_seen field */ function rg_user_set_last_seen($db, $uid, $ts, $ip) { rg_log_enter("user_set_last_seen: uid=$uid ts=$ts"); $ret = FALSE; while (1) { $params = array("last_seen" => $ts, "last_ip" => $ip, "uid" => $uid); $sql = 'UPDATE users SET last_seen = @@last_seen@@' . ', last_ip = @@last_ip@@' . ' WHERE uid = @@uid@@' . ' AND last_seen != @@last_seen@@'; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot update last seen (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); rg_cache_merge('user' . '::' . $uid . '::' . 'info', $params, RG_SOCKET_NO_WAIT); $ret = TRUE; break; } rg_log_exit(); return $ret; } /* * Loads ui based on sid, if possible */ function rg_user_login_by_sid($db, &$rg) { rg_prof_start("user_login_by_sid"); rg_log_enter("user_login_by_sid: sid=" . $rg['sid']); // Make sure it is not passed by client $rg['login_ui'] = array(); $rg['login_ui']['uid'] = 0; $rg['login_ui']['is_admin'] = 0; $rg['login_ui']['rights'] = ""; $rg['login_ui']['username'] = ""; $rg['login_ui']['organization'] = 0; $ret = FALSE; while (1) { if (empty($rg['sid'])) break; // Is a pre login sesison? if (strncmp($rg['sid'], "X", 1) == 0) break; $sess = rg_sess_valid($db, $rg['sid']); if ($sess == FALSE) break; $uid = $sess['uid']; $rg['login_ui'] = rg_user_info($db, $uid, "", ""); if ($rg['login_ui']['exists'] != 1) { rg_log("Uid $uid does not exists (" . rg_user_error() . ")!"); rg_user_set_error("invalid uid"); break; } rg_sess_update($db, $sess); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_login_by_sid"); return $ret; } /* * Test if a password is valid */ function rg_user_pass_valid($db, $uid, $pass) { rg_log_enter("user_pass_valid: uid=$uid"); $ret = FALSE; while (1) { if (empty($pass)) { rg_user_set_error("password is empty"); break; } $ui = rg_user_info($db, $uid, "", ""); if ($ui['exists'] != 1) { rg_user_set_error("user does not exists"); break; } $pass_hash = rg_user_pass($ui['salt'], $pass); if (strcmp($pass_hash, $ui['pass']) != 0) { rg_user_set_error("password is not ok"); break; } rg_log("Pass is valid."); $ret = TRUE; break; } rg_log_exit(); return TRUE; } /* * Set session cookie */ function rg_user_set_session_cookie($db, $uid, $sess_time, $lock_ip) { rg_log_enter("user_set_session_cookie: uid=$uid"); $secure = FALSE; if (isset($_SERVER['HTTPS'])) $secure = TRUE; $sid = rg_id(40); if ($uid > 0) rg_sess_add($db, $uid, $sid, $sess_time, $lock_ip); else $sid = "X" . $sid; setcookie("sid", $sid, 0, "/", $_SERVER['SERVER_NAME'], $secure, TRUE /* httponly */); rg_log_exit(); return $sid; } /* * Auto login the user */ function rg_user_auto_login($db, $uid, $lock_ip, &$ui) { rg_prof_start("user_auto_login"); rg_log_enter("user_auto_login: uid=$uid lock_ip=$lock_ip"); $ret = FALSE; while (1) { $ui = rg_user_info($db, $uid, "", ""); if ($ui['ok'] != 1) break; if ($ui['exists'] != 1) { rg_log("user does not exists"); break; } rg_user_set_session_cookie($db, $uid, $ui['session_time'], $lock_ip); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_auto_login"); return $ret; } /* * Test if login is OK */ function rg_user_login_by_user_pass($db, $user, $pass, $login_token, $lock_ip, &$ui) { rg_prof_start("user_login_by_user_pass"); rg_log_enter("user_login_by_user_pass: user=$user" . " login_token=$login_token lock_ip=$lock_ip"); $ui = array(); $ui['uid'] = 0; $ui['is_admin'] = 0; $ui['rights'] = ""; $ret = FALSE; while (1) { if (empty($user) || empty($pass)) { rg_user_set_error("invalid user, pass or login token"); rg_log("user or pass are empty"); break; } $ui0 = rg_user_info($db, 0, $user, ""); if ($ui0['ok'] != 1) break; if ($ui0['exists'] != 1) { rg_user_set_error("invalid user, pass or login token"); rg_log("user doesn't exists"); break; } if ($ui0['suspended'] > 0) { rg_user_set_error("invalid user, pass or login token"); rg_log("account is suspended"); break; } if ($ui0['confirmed'] == 0) { rg_user_set_error("invalid user, pass or login token"); rg_log("account is not confirmed"); break; } $pass_hash = rg_user_pass($ui0['salt'], $pass); if (strcmp($pass_hash, $ui0['pass']) != 0) { rg_user_set_error("invalid user, pass or login token"); rg_log("pass mismatch db:" . $ui0['pass'] . " computed=$pass_hash"); break; } $vi = rg_totp_verify_any($db, $ui0['uid'], $login_token); if ($vi['ok'] != 1) { rg_user_set_error('login token error: ' . rg_totp_error()); break; } //rg_log_ml('DEBUG: vi: ' . print_r($vi, TRUE)); if (($vi['enrolled'] == 1) && ($vi['token_valid'] != 1)) { rg_user_set_error('invalid user, pass or login token'); rg_log('invalid token'); break; } $event = array( 'ui' => array('uid' => $ui0['uid']), 'category' => 2001, 'prio' => 100, 'itime' => time()); $r = rg_event_add($db, $event); if ($r !== TRUE) { rg_user_set_error('internal error; try again later'); break; } rg_event_signal_daemon('', 0); $ui = $ui0; rg_user_auto_login($db, $ui['uid'], $lock_ip, $ui); $ret = TRUE; break; } rg_log_exit(); rg_prof_start("user_login_by_user_pass"); return $ret; } /* * Suspend an account * 1=suspend, 0=unsuspend */ function rg_user_suspend($db, $rg, $uid, $op) { rg_log_enter("user_suspend: uid=$uid, op=$op"); $ret = FALSE; while (1) { $x = array(); $x['obj_id'] = $uid; $x['type'] = 'user'; $x['owner'] = $uid; $x['uid'] = $rg['login_ui']['uid']; $x['username'] = $rg['login_ui']['username']; $x['needed_rights'] = 'S'; $x['ip'] = $rg['ip']; $x['misc'] = ""; if (rg_rights_allow($db, $x) !== TRUE) break; $now = time(); if ($op == 1) $v = $now; else $v = 0; $params = array("suspeneded" => $v, "uid" => $uid); $sql = "UPDATE users SET suspended = @@suspended@@" . " WHERE uid = @@uid@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot suspend (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); // update cache // TODO: what if we cannot update? rg_cache_set('user' . '::' . $uid . '::' . 'info' . '::' . 'suspended', $v, RG_SOCKET_NO_WAIT); break; } rg_log_exit(); return $ret; } /* * Make/remove admin * @op: 1=make, 0=remove */ function rg_user_make_admin($db, $rg, $uid, $op) { rg_prof_start("user_make_admin"); rg_log_enter("user_make_admin: uid=$uid, op=$op"); $ret = FALSE; while (1) { $x = array(); $x['obj_id'] = $uid; $x['type'] = 'user'; $x['owner'] = $uid; $x['uid'] = $rg['login_ui']['uid']; $x['username'] = $rg['login_ui']['username']; $x['needed_rights'] = 'M'; $x['ip'] = $rg['ip']; $x['misc'] = ""; if (rg_rights_allow($db, $x) !== TRUE) break; $params = array("op" => $op, "uid" => $uid); $sql = "UPDATE users SET is_admin = @@op@@" . " WHERE uid = @@uid@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot make admin (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); rg_cache_set('user' . '::' . $uid . '::' . 'info' . '::' . 'is_admin', 1, RG_SOCKET_NO_WAIT); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_make_admin"); return $ret; } /* * List users * TODO: switch to templates. */ function rg_user_list($db) { rg_prof_start("user_list"); rg_log_enter("user_list"); $ret = FALSE; while (1) { $sql = "SELECT * FROM users ORDER BY username"; $res = rg_sql_query($db, $sql); if ($res === FALSE) { rg_user_set_error("cannot get info (" . rg_sql_error() . ")!"); break; } $ret = ""; $ret .= "<table summary=\"users and operations\">\n"; $ret .= "<tr>\n"; $ret .= " <th>User name</th>\n"; $ret .= " <th>Name</th>\n"; $ret .= " <th>E-mail</th>\n"; $ret .= " <th>Admin?</th>\n"; $ret .= " <th>Creation date (UTC)</th>\n"; $ret .= " <th>Plan</th>\n"; $ret .= " <th>Suspended?</th>\n"; $ret .= " <th>Confirmed?</th>\n"; $ret .= " <th>Session time</th>\n"; $ret .= " <th>Last seen (UTC)</th>\n"; $ret .= " <th>Last IP</th>\n"; $ret .= " <th>Rights</th>\n"; $ret .= " <th>Operations</th>\n"; $ret .= "</tr>\n"; while (($row = rg_sql_fetch_array($res))) { $ret .= "<tr>\n"; $ret .= " <td>" . $row['username'] . "</td>\n"; $ret .= " <td>" . $row['realname'] . "</td>\n"; $ret .= " <td>" . $row['email'] . "</td>\n"; $ret .= " <td>" . ($row['is_admin'] == 1 ? "Yes" : "No") . "</td>\n"; $ret .= " <td>" . gmdate("Y-m-d", $row['itime']) . "</td>\n"; $pi = rg_plan_info($db, $row['plan_id']); $plan = "Unlimited"; if ($pi['exists'] == 1) $plan = $pi['name']; $ret .= " <td>" . $plan . "</td>\n"; $ret .= " <td>" . ($row['suspended'] == 0 ? "No" : "Yes") . "</th>\n"; $ret .= " <td>" . ($row['confirmed'] == 0 ? "No" : gmdate("Y-m-d", $row['confirmed'])) . "</th>\n"; $ret .= " <td>" . $row['session_time'] . "s</td>\n"; $v = $row['last_seen'] == 0 ? "-" : gmdate("Y-m-d", $row['last_seen']); $ret .= " <td>" . $v . "</td>\n"; $ret .= " <td>" . $row['last_ip'] . "</td>\n"; $v = implode(", ", rg_rights_text("user", $row['rights'])); $ret .= " <td>" . $v . "</td>\n"; // operations $url = "/op/admin/users"; $url2 = $row['username']; $ret .= " <td>"; // edit $ret .= "[<a href=\"$url/edit/$url2\">Edit</a>]"; // suspend $v = "suspend"; $t = "Suspend"; if ($row['suspended'] > 0) { $t = "Unsuspend"; $v = "unsuspend"; } $ret .= "[<a href=\"$url/$v/$url2\">$t</a>]"; // admin $v = "make_admin"; $t = "Make admin"; if ($row['is_admin'] == 1) { $t = "Remove admin"; $v = "remove_admin"; } $ret .= "[<a href=\"$url/$v/$url2\">$t</a>]"; // remove if ($row['suspended'] > 0) $ret .= "[<a href=\"$url/remove/$url2\">Remove!</a>]"; $ret .= " </td>"; $ret .= "</tr>\n"; } $ret .= "</table>\n"; rg_sql_free_result($res); break; } rg_log_exit(); rg_prof_end("user_list"); return $ret; } /* * Returns uid by token, if not expired */ function rg_user_forgot_pass_uid($db, $token) { rg_prof_start("user_forgot_pass_uid"); rg_log_enter("user_forgot_pass_uid: token=$token"); $ret = array(); $ret['ok'] = 0; $ret['uid'] = 0; while (1) { $now = time(); $params = array("token" => $token, "now" => $now); $sql = "SELECT uid FROM forgot_pass" . " WHERE token = @@token@@" . " AND expire > @@now@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot lookup token (" . rg_sql_error() . ")"); break; } $ret['ok'] = 1; $rows = rg_sql_num_rows($res); if ($rows > 0) $row = rg_sql_fetch_array($res); rg_sql_free_result($res); if ($rows == 0) break; $ret['uid'] = $row['uid']; break; } rg_log_exit(); rg_prof_end("user_forgot_pass_uid"); return $ret; } /* * Reset password function (send mail) - helper */ function rg_user_forgot_pass_mail_prepare($db, $email) { rg_prof_start('user_forgot_pass_mail_prepare'); rg_log_enter("user_forgot_pass_mail_prepare: email=$email"); $ret = array(); $ret['ok'] = 0; $ret['exists'] = 0; $expire = time() + 24 * 3600; $token = rg_id(20); while (1) { $r = rg_user_info($db, 0, "", $email); if ($r['ok'] == 0) { rg_log("Internal error."); break; } if ($r['exists'] == 0) { rg_log("User does not exists."); $ret['ok'] = 1; break; } $uid = $r['uid']; // store token in database $params = array("token" => $token, "uid" => $uid, "expire" => $expire); $sql = "INSERT INTO forgot_pass (token, uid, expire)" . " VALUES (@@token@@, @@uid@@, @@expire@@)"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot query (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); $ret['ok'] = 1; $ret['exists'] = 1; $ret['token'] = $token; break; } rg_log("DEBUG: user_forgot_pass_mail_prepare: ret=" . rg_array2string($ret)); rg_log_exit(); rg_prof_end('user_forgot_pass_mail_prepare'); return $ret; } /* * Reset password function (send mail) * TODO: Convert to rg_mail_template! Maybe also other functions! */ function rg_user_forgot_pass_mail($db, $email) { global $php_errormsg; global $rg_admin_name, $rg_admin_email; rg_prof_start('user_forgot_pass_mail'); rg_log_enter("user_forgot_pass_mail: email=$email"); $ret = array(); $ret['ok'] = 0; $ret['exists'] = 0; while (1) { $r = rg_user_forgot_pass_mail_prepare($db, $email); if ($r['exists'] != 1) { rg_log("User does not exists."); $ret = $r; break; } $ret['exists'] = 1; $headers = "From: $rg_admin_name <$rg_admin_email>"; $base_url = rg_base_url(); if (!mail($email, "Forgot password", "Hello!\n\n" . "If you want to reset the password, follow:\n" . $base_url . rg_re_url("/op/forgot_link") . "/" . $r['token'] . "\n\nRocketGit team", $headers, "-f $rg_admin_email")) { rg_user_set_error("cannot send mail ($php_errormsg)!"); break; } $ret['ok'] = 1; break; } rg_log("DEBUG: user_forgot_pass_mail: ret=" . rg_array2string($ret) . "."); rg_log_exit(); rg_prof_end('user_forgot_pass_mail'); return $ret; } /* * After reseting the pass, we have to destroy all 'reset pass' requests */ function rg_user_forgot_pass_destroy($db, $uid) { rg_prof_start('user_forgot_pass_destroy'); rg_log_enter("user_forgot_pass_destroy: uid=$uid"); $ret = FALSE; while (1) { $params = array("uid" => $uid); $sql = "DELETE FROM forgot_pass WHERE uid = @@uid@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot query (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); $ret = TRUE; break; } rg_log_exit(); rg_prof_end('user_forgot_pass_destroy'); return $ret; } /* * Change the password of a user */ function rg_user_set_pass($db, $uid, $pass) { rg_prof_start("user_set_pass"); rg_log_enter("user_set_pass: uid=$uid"); $ret = FALSE; while (1) { $salt = rg_id(40); $pass = rg_user_pass($salt, $pass); $params = array("salt" => $salt, "pass" => $pass, "uid" => $uid); $sql = "UPDATE users SET" . " salt = @@salt@@" . ", pass = @@pass@@" . " WHERE uid = @@uid@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot update pass (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); rg_cache_merge('user' . '::' . $uid . '::' . 'info', $params, RG_SOCKET_NO_WAIT); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_set_pass"); return $ret; } /* * Confirm account creation */ function rg_user_confirm($db, $token) { rg_prof_start("user_confirm"); rg_log_enter("user_confirm: token=$token"); $now = time(); $token = preg_replace("/[^A-Za-z0-9]/", '', $token); $ret = FALSE; while (1) { if (empty($token)) { rg_user_set_error("invalid token"); break; } $params = array("token" => $token); $sql = "SELECT uid FROM users WHERE confirm_token = @@token@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot search for token (" . rg_sql_error() . ")"); break; } $rows = rg_sql_num_rows($res); if ($rows == 1) $row = rg_sql_fetch_array($res); rg_sql_free_result($res); if ($rows != 1) { rg_user_set_error("cannot find token"); break; } $uid = $row['uid']; // "< 2" because we mark with "1" if "no need to confirm" $params = array("confirmed" => $now, "uid" => $uid); $sql = "UPDATE users SET confirmed = @@confirmed@@" . " WHERE uid = @@uid@@" . " AND confirmed < 2"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot update confirmed (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); rg_cache_merge('user' . '::' . $uid . '::' . 'info', $params, RG_SOCKET_NO_WAIT); $ret = $uid; break; } rg_log_exit(); rg_prof_end("user_confirm"); return $ret; } /* * Add a suggestion to database */ function rg_user_suggestion($db, $uid, $suggestion) { rg_prof_start("user_suggestion"); rg_log_enter("user_suggestion: uid=$uid suggestion=$suggestion"); $ret = FALSE; while (1) { $params = array('uid' => $uid, 'itime' => time(), 'sug' => $suggestion); $sql = "INSERT INTO suggestions (uid, itime, suggestion)" . " VALUES (@@uid@@, @@itime@@, @@sug@@)"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_user_set_error("cannot add suggestion (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("user_suggestion"); return $ret; } /* * Checks if a user is over limit. * Returns maximum allowed in @max. */ function rg_user_over_limit($db, $ui, &$max) { $pi = rg_plan_info($db, $ui['plan_id']); if ($pi['exists'] != 1) { rg_internal_error("Invalid plan."); return FALSE; } $max = $pi['disk_mb']; if ($max == 0) return FALSE; if ($ui['disk_used_mb'] >= $max) return TRUE; return FALSE; } /* * High level functions */ /* * High-level function for editing a user */ function rg_user_edit_high_level($db, &$rg) { global $rg_session_time; rg_log("user_edit_high_level"); rg_log_ml("user_edit_high_level:rg:" . print_r($rg, TRUE)); $ret = ""; if (($rg['target_ui']['uid'] == 0) && ($rg['rg_account_allow_creation'] != 1)) { $ret .= rg_template("user/create_na.html", $rg, TRUE /* xss */); return $ret; } $owner = $rg['target_ui']['uid']; if ($owner > 0) { $x = array(); $x['obj_id'] = $rg['target_ui']['uid']; $x['type'] = 'user'; $x['owner'] = $owner; $x['uid'] = $rg['login_ui']['uid']; $x['username'] = $rg['login_ui']['username']; $x['needed_rights'] = 'E'; $x['ip'] = $rg['ip']; $x['misc'] = ""; if (rg_rights_allow($db, $x) !== TRUE) { $ret .= rg_template("access_denied.html", $rg, TRUE /* xss */); return $ret; } } if ($rg['target_ui']['uid'] > 0) $rg['create_mode'] = 0; else $rg['create_mode'] = 1; if ($rg['doit'] == 0) { if ($rg['target_ui']['uid'] > 0) { // TODO: check also access rights? $ui = $rg['target_ui']; } else { // Defaults $ui = array(); $ui['uid'] = 0; $ui['username'] = ""; $ui['realname'] = ""; $ui['email'] = ""; $ui['pass'] = ""; $ui['pass2'] = ""; $ui['is_admin'] = "0"; $ui['rights'] = rg_rights_checkboxes("user", "rights", "C"); // TODO $ui['plan_id'] = 0; $ui['session_time'] = $rg_session_time; $ui['tos'] = 1; } } $errmsg = array(); $load_form = TRUE; while (1) { if ($rg['doit'] != 1) break; $ui = array(); $ui['uid'] = $rg['target_ui']['uid']; $ui['username'] = rg_var_str("username"); $ui['realname'] = rg_var_str("realname"); $ui['email'] = rg_var_str("email"); $ui['pass'] = rg_var_str("pass"); $ui['pass2'] = rg_var_str("pass2"); $ui['is_admin'] = rg_var_bool("is_admin"); $ui['rights'] = "C"; // TODO $ui['plan_id'] = rg_var_uint("plan_id"); $ui['session_time'] = rg_var_uint("session_time"); $ui['confirm_token'] = rg_id(20); $ui['confirmed'] = 0; if ($rg['no_tos'] == 1) $ui['tos'] = 1; else $ui['tos'] = rg_var_uint('tos'); if ($ui['tos'] != 1) { $errmsg[] = rg_template('user/tos_deny.html', $rg, TRUE /*xss*/); break; } if (!rg_valid_referer()) { $errmsg[] = "invalid referer; try again"; break; } if (!rg_token_valid($db, $rg, 'user_edit_hl', FALSE)) { $errmsg[] = "invalid token; try again"; break; } if (($rg['login_ui']['is_admin'] != 1) && ($ui['is_admin'] != 0)) { $errmsg[] = "you are not admin, you cannot give admin rights"; break; } if ($ui['uid'] == 0) { $_ui = rg_user_info($db, 0, $ui['username'], ''); if ($_ui['ok'] != 1) { $errmsg[] = rg_user_error(); break; } if ($_ui['exists'] == 1) { $errmsg[] = "user already exists"; break; } } // Security warning - a user can pass "pass" var to me avoiding // being asked for old pass if ($ui['uid'] > 0) { if (!empty($ui['pass'])) rg_security_violation_no_exit("User tried to" . " change pass using 'edit info' page."); $ui['pass'] = ''; } $r = rg_user_edit($db, $ui); if ($r === FALSE) { $errmsg[] = rg_user_error(); break; } // TODO: should we just redirect to login page? // TODO: or to user page if there is no need to confirm the account? if ($ui['uid'] == 0) $ret = rg_template("user/create_ok.html", $rg, TRUE /* xss */); else $ret = rg_template("user/edit_ok.html", $rg, TRUE /* xss */); $load_form = FALSE; break; } if ($load_form) { $rg = array_merge($rg, $ui); $rg['HTML:select_plan'] = rg_plan_select($db, $ui['plan_id']); $rg['HTML:checkbox_rights'] = rg_rights_checkboxes("user", "rights", $ui['rights']); $rg['HTML:errmsg'] = rg_template_errmsg($errmsg); $rg['rg_form_token'] = rg_token_get($db, $rg, 'user_edit_hl'); $ret .= rg_template("user/add_edit.html", $rg, TRUE /* xss */); } return $ret; } ?>