How to allow only one user edit the form in php? - php

I have a php code as shown below in which usernames/passwords are verified through db and only one user are allowed to login (At Line X in the php code below).
In the php code, the table trace_users (user_name, open, write are the columns) keeps the track of logged in users. When any user is logged in, the column open is set to true with value 1 on write column. If the user logs out, the column open is set to false with value 0 on write column.
logged in user => open = true, write = 1
when user logs out => open = false, write = 0
php code:
if ($user_from_db && password_verify($password, $user_from_db->user_pass)) {
$sql = "SELECT * FROM trace_users where open='true'";
if($result1 = mysqli_query($connect, $sql)){
if(mysqli_num_rows($result1) > 0){ // Line X
while($row = mysqli_fetch_array($result1)){
if($row['open'] == "true") {
if(!isset($_SESSION['admin'])) {
$message = "user " . $row['user_name'] . " is currently editing the form. Want to take over ?";
echo "<script type='text/javascript'>if(confirm('$message')) { } else { };</script>"; // Line A
}
break;
}
}
} else{
$_SESSION['admin'] = true;
$_SESSION['user_name'] = $username;
$open="true";
$write="1";
$stmt= $connect->prepare("UPDATE trace_users SET open=?, write=? WHERE user_name=?");
$stmt->bind_param('sss', $open, $write, $_SESSION['user_name']);
$stmt->execute();
}
}
} else {
echo "Invalid Username or Password";
}
At line A if userA is logged in and another user lets say userB tries to login then it will say userA is currently editing the form. Want to take over ?.
What I want to achieve is if userB takes over the form on clicking ok in the alert box then userA should come in the readonly mode (revoke the write access) and userB should
be given full access (write).
userA => open = true, write = 0
userB => open = true, write = 1
This is what I have tried:
After Line A, I am thinking to add these lines so that the user who is trying to take over should be able to login.
$_SESSION['admin'] = true;
$_SESSION['user_name'] = $username;
$open="true";
$write="1";
$stmt= $connect->prepare("UPDATE trace_users SET open=?, write=? WHERE user_name=?");
$stmt->bind_param('sss', $open, $write, $_SESSION['user_name']);
$stmt->execute();
Problem Statement:
I am wondering what changes I should make in the php code above so that if userA is logged in and userB also want to login and try to take over the form
then there should be no write access for userA, and userB should be given full access i.e.
userA (open=true , write=0)
userB (open=true, write=1)
Note: For no write access, the save button will not appear for the user.

The issue is more in the flow logic, than in the code :
UserA connects (call 1, php grants access : open => 1, write => 1)
UserB connects (call 2, php shows popup to UserB : open => 1)
UserB has to decide to take over (or not) (call 3, php grants write access to UserB and revokes write access of UserA : UserA write => 0, UserB write=>1)
With your code you are managing step 1 and 2.
When UserB decides to take over, the php code of step 2 is already over (your prompted the question). You have to write a dedicated code to get the UserB answer to the question (it is all about the javascript you prepared).
Whatever, you are not checking any write access, so once UserB took over and then logged out, UserA will still be with open connection but without write access. If UserB connects again it will be prompted again to take over something that doesn't exist (UserA write access is gone).

I see two basic problems with your approach:
The trace_users.write column might inadvertently get set to 1 for multiple users unless you're really careful about locking the table before updating it. That's just not a good way to go about tracking who is the "active" editor.
Your line "A" might display some HTML, but until the user confirms yes/no, it won't get executed. That step has to be done somewhere else.
A better way would be to have a single canonical record (probably in another table) track the current admin, and your code can check (or update) that record any time as necessary. Any time a user elects to make a change, PHP would first check whether they're the active editor and if so, commit the update, otherwise deny the update.
Pseudo-code for some server-side functions you'll need:
// When an update is requested:
if ( user is logged in and is the current admin ) {
commit the change;
} else {
Display "sorry" message to user
}
// -----------
// When user "X" logs in:
if ( another user is currently the active admin ) {
Display message: "User Y is currently the admin. Take over?"
} else {
Log in user "X" and set user "X" to current admin
}
// -----------
// When user "X" asks to take over as admin:
log in user "X"
set user "X" to current admin
The only flaw I can see here is that user "A" is logged in and editing and saves changes, user "B" might have the old data loaded in their browser. But that's an issue you didn't ask to solve, so it may not be a problem for you anyway.

How about something like this?
$username = $_SESSION['user_name'];
$open="true";
$write="1";
$stmt=$connect->prepare("UPDATE trace_users SET write=0 WHERE write=1"); // revoke write access of all users
$stmt=$connect->prepare("UPDATE trace_users SET open=:open, write=:write WHERE user_name=:username");
$stmt->bind_param('sss', $open, $write, $username);
$stmt->execute();
That will revoke write-access of all users, then set write-access only for the most recent user to request write-acces.
And you will need to make sure your client is updated about its revocation of write access, so the save button can be hidden when necessary.

Not recommended on a large data set, but a single line option for you is to replace your update with one that includes reference to the username:
$stmt= $connect->prepare("UPDATE trace_users SET open=IF(open=1 OR user_name=?,1,0), write=IF(user_name=?,1,0)");
$stmt->bind_param('sss', $_SESSION['user_name'], $_SESSION['user_name']);
This will, for every record in the table:
Leave open=1 where it is 1
Set open=1 where username is targetUser
Leave open=0 where is is 0 and username is not targetUser
Set write=1 where username is targetUser
Set write=0 where username is not targetUser
All in one hit.
For large data sets, create a separate table to track. There are other smarts you can do using unique or primary keys to ensure only one record gets updated, and then validate "If record got updated (i.e. rowCount() > 0), then I got the flag, if not, someone else got it". Only consider that when you're more used to MySQL.
Warning: "Open" and "Write" are both keywords / reserved words and your SQL may not be portable. Either wrap in back ticks, or preface with something like userOpen, userWrite. I've added back ticks in the example below. Read more here: https://dev.mysql.com/doc/refman/8.0/en/keywords.html
$stmt= $connect->prepare("UPDATE trace_users SET `open`=IF(`open`=1 OR user_name=?,1,0), `write`=IF(user_name=?,1,0)");
$stmt->bind_param('sss', $_SESSION['user_name'], $_SESSION['user_name']);

Related

Validate user's rights

I'm writing an hour registration system for my projectteam at school. Everything is pretty much working, but I can't seem to get the validation of user rights to work.
Validation is done in the acctype field within the user table. If 0 (guest), you can only view the list of hours, if 1 (specialist) you can add your own hours and if 2 (project-manager), you can review the hours users have submitted.
At first I was only using the $account query but instead of selecting them all I selected acctype only.
Does anyone have any idea what am I doing wrong?
$cookie = $_COOKIE['user'];
$account = mysqli_query($conn, "SELECT * FROM user WHERE user = '" . $cookie . "'");
$acctype = mysqli_fetch_assoc($account->acctype);
if(isset($cookie) && $acctype >= 1) {
} else {
}
Jonathan
I believe there's a few things wrong here:
You're reading the cookie before checking if it's set. That's a mistake. You should see if there's a cookie, and THEN read it in. You also don't need to assign it a separate variable.
Note: As I said in my comment, user data should be in a session, not a cookie.
I don't know what your DB schema looks like, but your query is SELECT * FROM user, meaning that if you have an ID, a user name, an access level, and some other things, you're going to get ALL that into the var $acctype, which obviously isn't an integer.
I think the fix is to execute your query, get your results, and then compare the row(s) returned and only check the acctype part:
if ($row['acctype'] >= 1){
}
Documentation: http://us1.php.net/mysqli_fetch_assoc

how to delete mysql table row from mail inbox

I want to delete mysql table row from mail inbox , Is it possible !, If yes how can i delete the table row in my server database from any mail inbox account, please tell me the solution
Table Structure:
id usrname password status usercat
1 xxxxxxx xxxxxxx new 1
2 uuuuuuu uuuuuuu new 5
$del_qry= mysql_query("DELETE FROM table_name WHERE some_column=some_value")
In my site after Registration, the registered person get alert mail and also site admin get registered user detail's mail. So if the admin want to delete the second user(username - uuuuuu) from his mail account.
How can i do this, Please tell me i am new here...
The email you send to the admin will have to contain a link like this:
http://www.example.org/admin/remove_account.php?id=123
Where 123 is the user that was registered and remove_account.php is the script that will be loaded when the link is clicked.
Within the script you would have something like this:
mysql_query("DELETE FROM table_name WHERE id=" . mysql_real_escape_string($_GET['id']));
CAUTION
A few words of caution. The above link should be protected by one of the following:
User & password protection (either using Apache or PHP)
Signature protection (example below)
The signature protection prevents tampering / forging link parameters by adding a signature. Works like this:
$secret = "some reasonably long string of random data";
$id = "123"; // like above, the user id
$sig = hash_hmac('sha1', $id, $secret);
$link = 'http://www.example.org/admin/remove_account.php?' . http_build_query(array(
'id' => $id,
'sig' => $sig,
));
To verify the signature:
$secret = "some reasonably long string of random data";
if (isset($_GET['id'], $_GET['sig'])) {
$calc_sig = hash_hmac('sha1', $_GET['id'], $secret);
if ($calc_sig === $_GET['sig']) {
// your delete query here
}
}
Please note that, although the link protects against someone trying to access your administrative script, if it falls in the wrong hands you're still pretty much screwed. Don't underestimate security :)
I think you must add a link of a page of your site in email of delete with respect of user list. and when user click on link it will redirect to particular page where it will get that user id from url of link and then you can perform delete action. It is necessary to redirect to site page from mail because in mail you can not direct connect with database.
thanks

Php's Mysqli executes statements in random order (very strange)

EDIT: I am very sorry, I found the mistake and it's very stupid. (See answer)
I seem to have a very strange problem. I have a website that uses 3rd party authorization for login. My users use two social networks for this: Facebook and Vkontakte (Russian analogue).
On log in, I search the database for the user with the passed social id (which is, depending on the chosen social network, user's id in FB or VK, for which I have two different columns) and fetch it.
In case a user has accounts in both FB and VK and has logged in through both of them, he or she now has two separate accounts on my site. However, he can join them into one, by logging in through one social network (this will be his master account) and using the 'user_bind' function with another social network.
This function finds the user's another account and relinks all data in the database to the master account. It then deletes the other account and adds its social id to the master account, so that now the user can log in through both social networks. Social id column has a UNIQUE index, naturally.
However, when the script executes, it seems to execute the UPDATE, which adds the social id, before the DELETE statement, which removes the old user. This produces an error, because it attempts to add an existing social id (because the old user is still there).
When I check the database after the script execution, the old user is gone, so I guess that means that the DELETE statement is indeed executed, but with a delay, in which other statements are executed. The MySQL Workbench's log confirms this, though I'm not sure whether it's reliable.
My question is how do I ensure that the DELETE (or any other MySQL statement for that matter) has actually been executed before executing the rest of the script? And why does this happen anyway?
Here's the adequately-commented code (though I will gladly accept an answer which has no code in it and just explains the principle).
The user_bind function:
function user_bind($eSourceType)
{
//$eSourceType can be 'fb' or 'vk', depending on the social network of the secondary account
$usrMe=get_gl_me(); //gets the user's account, through which he is logged in - the master account
if ($eSourceType=='fb') //if the social network that we are binding this account to is Facebook
{
$vSidName='facebook_id'; //name of the column which contains the social id
if (!$usrMe->get_private_property("facebook_id") & $usrMe->get_private_property("vkontakte_id") ) //check if the master account really doesn't have facebook_id set
{
$fb=get_facebook();//gets facebook object (from FB PHP SDK)
$sid=$fb->getUser();//gets user's id in facebook (social id)
}
else
{
error("The account has facebook_id set");
}
}
elseif($eSourceType=='vk')//same as before, but the id is fetched through $_GET, not object
{
$vSidName='vkontakte_id';
if ($usrMe->get_private_property("facebook_id") & !$usrMe->get_private_property("vkontakte_id") ) //check if it's the right account
{
$sid=$_GET['uid'];
}
else
{
error("The account has vkontakte_id set");
}
}
if(!$sid) //if we couldn't retrieve the social id
{
error("Can't bind: \$sid not set.");
}
$idNew=$usrMe->get_id();//get id (database id) of the master account
$usrOld=fetch_user_by_sid($sid, $eSourceType, true); //fetches the 'user' object by the social id we retrieved before
if ($usrOld)//if there is a user with this social id (if there is a secondary account)
{
$idOld=$usrOld->get_id();//get id of the secondary account
$tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments"); //get tables in which we have to relink users
foreach($tblsRelink as $tbl)
{
//update set users_idusers to userid
$sp=new Statement_Parameter; //this is a class from PHP.com: http://php.net/manual/en/mysqli-stmt.bind-param.php. It allows to bind variables to the prepared statement in MySQLi without much pain
$query="UPDATE $tbl SET users_idusers=" . db_stmt_operands($idNew, $sp, 'idNew') . " WHERE users_idusers=". db_stmt_operands($idOld, $sp, 'idOld'); //db_stmt_operands inserts question marks in the query, while binding the variables through Statement_Parameter
$affected_rows=db_submit($query, $sp);//see below for the db_submit() function explanation
}
//delete old user
$sp=new Statement_Parameter; //clear Statement_Parameter
$query="DELETE FROM users WHERE idusers=" . db_stmt_operands($idOld, $sp, 'idOld');
$affected_rows=db_submit($query, $sp);
echo "<br>affected: $affected_rows<BR>"; //this actually returns 1
//lets see if the user was actually deleted
$usrTest=fetch_user_by_sid($sid, $eSourceType, true); //fetch the user by the social id
if($usrTest) //if a user is fetched
{
debug_array($usrTest); //custom implementation of print_r
error("User still exsists. Oh no.");//it always does
}
}
$usrMe->set_private_property($vSidName, $sid);//sets the property 'facebook_id' or 'vkontakte_id' to the social id that we got in the beginning
$usrMe->update();//UPDATE statement, which brings the object's properties in the database up to date (in our case: adds the social id)
//the UPDATE statement doesn't execute because the old user is still there
}
The db_submit function:
function db_submit($query, $sp=NULL)
{
$mysqli = db_connect(); //fetches PHP MySQLi object
if ($stmt = $mysqli->prepare($query)) //if the statement is successfully prepared
{
if($sp)//if there is a Statement_Parameter passed
{
$sp->Bind_Params($stmt); //bind parameters from SP
}
if($stmt->execute())//try to execute the statement
{
//on success
if ($mysqli->insert_id) //if this was an INSERT
{
return $mysqli->insert_id;
}
else //if this was DELETE or UPDATE
{
return $mysqli->affected_rows;
}
}
else
{
//on failure
error("Could not submit: could not execute statement. Query: $query." . $stmt->error); //this kills the script
}
}
else
{
error("Could not submit. Query: $query." . $mysqli->error);
}
}
The thing is: private_properties (including the social ids) or the object 'user' are stored in a separate table ('users_private'), which was linked to the main table ('users') through a foreign key.
I included the 'users_private' table in the array of tables that require relinking:
$tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments");
This resulted in the record in 'users_private' for the old user being relinked to the new user (which now had 2 records - how reckless of me not to make this field UNIQUE). So when the old user got deleted, its associated 'users_private' record was not, because it was now linked to the new user. Naturally, attempt to add the social id was producing an error, because that id already was there, relinked from the old user.
This could be prevented by either
Thinking a bit more about what I was doing (why did I consider 'users_private' a table eligible for relinking?)
More careful database structuring (if a field is supposed to be unique - create a UNIQUE key!)
or, even better, both.

Limiting the number of failed login attemps

I want to limit the failed login attempts. For example, if a specific user attempt to login with wrong username or password 4 times, i should show the CAPTCHA 4th time instead of blocking for some specific time, and keep showing CAPTCHA unless he supplies valid username and password. Once the user has successfully logged in, the login attempt is reset to ZERO.
Is the idea of checking the username instead of IP address OK in security point of view? Can this approach be implemented without using database?, as I think I don't need to store time because i will just show recaptcha?
Please give your opinion.
You don't want to use the database for the 'number of failed logins'-check? Then just use a cookie and check it. Sure, they can remove it, but it's a hassle.
However, I suspect that you already are getting the username and password from the database, why not also fetch the last number of failed logins while you are at it?
if (isset($_POST['submit_login'])) {
if (isset($_POST['username']) && isset($_POST['password'])) {
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
// id = unique primary key
$rs = mysql_query('SELECT id,Username,Password,Failed_logins,IP_address FROM Users WHERE Username = '.$username.'');
$num = mysql_num_rows($rs);
if ($num > 0) {
// I would add a password hash here to $password, and check against the hashed Password from the database
// But let's check the clean passwords
$row = mysql_fetch_array($rs);
if ($password == $row['Password']) {
// Successful login, set session or whatever you need
// Reset failed logins
mysql_query('UPDATE Users SET Failed_logins = 0 WHERE id = '.$row['id'].'');
header('location: success.php');
} else {
// Failed password check
if ($row['Failed_logins'] > 3) {
// Redirect to captcha
header('location: captcha.php');
} else {
$ip = $_SERVER['REMOTE_ADDR'];
if ($row['IP_address'] != $ip) {
// New ip adress, reset failed logins
$failed_logins = 0;
} else {
// Increment failed logins
$failed_logins = $row['Failed_logins']+1;
}
mysql_query('UPDATE Users SET Failed_logins = '.$failed_logins.',IP_address = '.$ip.' WHERE id = '.$row['id'].' ');
} // End check Failed_logins > 3
}
} else {
// No such Username found in database
$error = 'no_such_username';
} // End username check from database
} else {
// Either username or password is missing
$error = 'incomplete_form';
} // end check for username and password
} // end main submit_login check
Something like that.
EDIT:
This is really old code and I see some problems with it now. But, at least you should always use PDO (prepared statements) for inserting data in your database.
What are you protecting?
Random user-only content, username.
Banking information (I doubt..) or other sensitive data, IP might be okay, provided you use ranges.
Are you asking how to do it, or which you should use?
You do need time, how would you otherwise determine if an login attempt has expired? If I would fail to login 4 times over a 10 year time span I would get blocked. You want to block those who attempt multiple login attempts in a short time span.
I suggest you use an database - as you will keep an detailed history of logins at the same time. But an memory based solution like memcached could also suffice.
To elaborate on which security to implement: a combination!
Combine the used username and ip address and store them into your database.
Use the login attempts on your username to directly protect your users from attempts.
Use the ip address information to observe an detect only, and take action if needed.
You should save the attempts in a database and check the user.
The best way to do this is to use databases.
You need to create a separate table in your database, which would store three variables :
(a) IP address (where the person is trying to log in)
(b) Number of login attempts
(c) date/time (or : current-timestamp)
The ONLY problem with this approach is with the first variable : IP address
What if the person is in an Internet Cafe? Or using public Wi-fi? Or Hotspot? Or, a school? office? etc, etc
If you block the IP address, it affects everybody who is trying to log into your website from that location.
This is not a problem if your website concerns something like a bank, or military installation, or the Pentagon.
But, if your website is a business (buy-and-selling, game, whatever), then blocking a specific address will only piss off your customers!

Drupal's profile_save_profile Doesn't Work in hook_cron, When Run by the Server's cron

I have a problem with the following implementation of hook_cron in Drupal 6.1.3.
The script below runs exactly as expected: it sends a welcome letter to new members, and updates a hidden field in their profile to designate that the letter has been sent. There are no errors in the letter, all new members are accounted for, etc.
The problem is that the last line -- updating the profile -- doesn't seem to work when Drupal cron is invoked by the 'real' cron on the server.
When I run cron manually (such as via /admin/reports/status/run-cron) the profile fields get updated as expected.
Any suggestions as to what might be causing this?
(Note, since someone will suggest it: members join by means outside of Drupal, and are uploaded to Drupal nightly, so Drupal's built-in welcome letters won't work (I think).)
<?php
function foo_cron() {
// Find users who have not received the new member letter,
// and send them a welcome email
// Get users who have not recd a message, as per the profile value setting
$pending_count_sql = "SELECT COUNT(*) FROM {profile_values} v
WHERE (v.value = 0) AND (v.fid = 7)"; //fid 7 is the profile field for profile_intro_email_sent
if (db_result(db_query($pending_count_sql))) {
// Load the message template, since we
// know we have users to feed into it.
$email_template_file = "/home/foo/public_html/drupal/" .
drupal_get_path('module', 'foo') .
"/emails/foo-new-member-email-template.txt";
$email_template_data = file_get_contents($email_template_file);
fclose($email_template_fh);
//We'll just pull the uid, since we have to run user_load anyway
$query = "SELECT v.uid FROM {profile_values} v
WHERE (v.value = 0) AND (v.fid = 7)";
$result = db_query(($query));
// Loop through the uids, loading profiles so as to access string replacement variables
while ($item = db_fetch_object($result)) {
$new_member = user_load($item->uid);
$translation_key = array(
// ... code that generates the string replacement array ...
);
// Compose the email component of the message, and send to user
$email_text = t($email_template_data, $translation_key);
$language = user_preferred_language($new_member); // use member's language preference
$params['subject'] = 'New Member Benefits - Welcome to FOO!';
$params['content-type'] = 'text/plain; charset=UTF-8; format=flowed;';
$params['content'] = $email_text;
drupal_mail('foo', 'welcome_letter', $new_member->mail, $language, $params, 'webmaster#foo.org');
// Mark the user's profile to indicate that the message was sent
$change = array(
// Rebuild all of the profile fields in this category,
// since they'll be deleted otherwise
'profile_first_name' => $new_member->profile_first_name,
'profile_last_name' => $new_member->profile_last_name,
'profile_intro_email_sent' => 1);
profile_save_profile($change, $new_member, "Membership Data");
}
}
}
To safely switch users
http://api.drupal.org/api/function/session_save_session/6
<?php
global $user;
$original_user = $user;
session_save_session(FALSE); // D7: use drupal_save_session(FALSE);
$user = user_load(array('uid' => 1)); // D7: use user_load(1);
// Take your action here where you pretend to be the user with UID = 1 (typically the admin user on a site)
// If your code fails, it's not a problem because the session will not be saved
$user = $original_user;
session_save_session(TRUE); // // D7: use drupal_save_session(TRUE);
// From here on the $user is back to normal so it's OK for the session to be saved
?>
Taken from here:
drupal dot org/node/218104
yes i confirm drupal cron user profile is "anonymous" so you have to add the permission de manager user for the "anonymous" user which is not very good in term of security ..
Not quite a random guess ... but close ...
When "real" cron runs code, it runs it as a particular user.
Similarly, when you run the Drupal cron code manually, the code will also be running as a particular user.
My suspicion is that the two users are different, with different permissions, and that's causing the failure.
Does the cron job's user have access to write the database, or read only?
Are there any log files generated by the cron job?
Update: By 'user' above, I'm referring to the user accounts on the host server, not Drupal accounts. For example, on a sandbox system I use for testing Drupal changes, Apache runs under the noone account.
profile_save_profile dosn't check for permissions or as far as I can see do 'global $user' so I don't know if this is the real issue.
$new_member = user_load($item->uid);
This looks wrong user_load should take an array not a uid (unless you are using Drupal7), but if that didn't work the rest of the code would fail too.

Categories