Insert the same time, it is possible to happen? - php

Good evening.
I have a doubt about inclusion in the database. A common example is a user account where the user names must be unique.
In the following logic:
User enters data into a form.
Data are collected via $ _POST (or other method)
Before saving the data is checked if the user name is already registered
If not, save the record, if it is, it informs the user that the username that already exists.
Example:
<?php
$username = $_POST['username'];
$query = "SELECT * FROM `user_tbl` WHERE `username` = '{$username}'";
$result = mysql_query($query);
if ( mysql_num_rows ( $result ) > 1 ) {
/* Username already exists */
echo 'Username already exists';
} else {
/* Username doesn't exist */
/* a certain time is elapsed after checking */
/* .. insert query */
}
My question is, in a system with high volume of requests, it is possible that between the time to check whether the user name already exists and the inclusion (if the user name does not exist), another user can do the same action in same time?
UPDATE
I know about the safety issue, use this code (copy and past) to explain to the question is about the possibility of two users do the same thing at the same time. On the issue of unique index, I know how it works, maybe I was not clear on the question, it was only to see if there was a possibility of "simultaneous commands occur." Thanks for the answers.

Besides the already placed correct comments, the solution to this is to use transactions & an unique index:
The unique index guarantees that a value can only exist once.
In your case the index could be:
CREATE UNIQUE INDEX idx_nn_1 ON user_tbl(username);
That way the user can only exist once. If two people insert the same username now at the same time, one of the inserts will fail. That failure you need to catch and handle.

Related

Validate if username exist when editing info PHP and SQL

I have a question and I can't find a correct way to solve my problem.
I have a application where the username is the emailadres. A user can change his/her information on a page called edit.php
I have also a validation in place that is checking if the username exists when editing. This to prevent that user A can edit his username in a username of another existing user. I do this with the following function.
$sql=mysql_query("SELECT username FROM users WHERE username='".mysql_real_escape_string($_POST['username'])."'");
if(mysql_num_rows($sql)>=1) { echo "Exists"; } else { /* update database */ }
This works, only now I have a problem. Because the user can edit in the editform not only the USERNAME, but also PHONE, ZIP, CITY. If a user edits his ZIP, CITY or PHONE I will get logically 'exists', because the username is also posted in my editform.
My question: How can I set it up so that the username only is checked when it is different from the current username of the user. For example, if test#test.com (username) edits his information and it remains test#test.com it won't be checked and when the username test#test.com is edited in test123#test.com it will be checked?
I think I need to set up a double check like if mysql_num_rows($sql)>=1 OR ==
Am I right? any help would be great.
For the new username to be valid, your check should be: there is no row with this username, or the row is actually the current user.
So you need to select the ID in your query (instead of the username, which you don't need) and change your if statement to test "no result, or one result with ID = current user ID".
Of course I'm assuming you have an ID as a primary key of your table, and that this ID is stored in the session for the current user.
Also, consider using PDO instead of mysql_query...
This can be resolved by doing a verify user with ajax before processing the form, using the SQL query you are currently using:
SELECT username FROM users WHERE username='$user'
If ajax query return "true" or "not empty data", user exist and not is usable, else, user is available for use.
A few months ago I did something similar in a Signup form. On "username" field, if the user is available turns green, if not available, it turns red. The verification is done by changing field.
Signup Example with verification
Recommend to use PDO instead of php mysql_query (is deprecated in new PHP versions).

Cannot select all results from a table depending on another table (relational DB) in PHP and MySQL

I am trying to finish this website I am currently creating, but I am kind of stuck.
I want to create a table called "orders" in my DB. I want this table to be related to my users table so that when the user goes to his "orders.php" page (once logged in already) he sees all his current and previous orders.
These would be my table fields/cols:
id
username
ordernumber
description
quantity
total
This is my approach:
Whenever a new order is created, insert all the table fields/cols depending on the user's choice (selected stuff for the order), but the username would be the only value gathered from a $_SESSION or $_COOKIE variable, which holds the username. Then, once the user goes to orders.php, I will execute a query to show all the orders that only that username has ordered. Please note that I do sanitize all my input/output and I do not store sensitive data in my cookies. My system is designed so it only uses the session as the method of authentication, therefore you need to login every time you close the browser but that is fine.
1) Is this a safe approach? Do you have any suggestions/comments?
2) Could you help me construct the query?
I haven't really worked with relational databases, so I am kind of lost. How can I call all the orders from table "orders" where username = "username from the session"?
So far I have this:
"SELECT * FROM orders WHERE username = ? " //(Using PDO)
I know that this will work but my concern is in case of getting a session hijacked or something like that, then a user would be able to retrieve any users' orders, or not?
Thank you for explaining this a little bit further and helping me out!
Cheers!
Be careful! Please don't create a plain text cookie containing a human-readable user id (like user2345995 or OllieJones). It's far too easy for a badguy to fake a cookie like that just by guessing, and then your users' information leaks out.
You're working in php. Therefore you can use php's session mechanism to store your userid and other values. php uses hard-to-guess session ids (SIDs) and stores them in either a cookie or as a sid=1234abcd9875 parameter in URLs.
For the sake of your system's integrity, please read up on this. It's actually a pretty well-designed feature and it's been in the wild for fifteen years or so: it's debugged.
http://php.net/manual/en/session.idpassing.php
If you're using the session system, you basically do this in your first page, your login page.
session_start();
...
$_SESSION['username'] = $username; /* which you get by logging in */
...
On your order lookup page you do something similar to retrieve the username and use it in a query.
session_start();
...
$orderstmt = $pdoconn->prepare("SELECT * FROM orders WHERE username = :username");
$orderstmt->execute( array(':username' => $_SESSION['username']) );
...
while ($row = $orderstmt->fetch()) {
/* use the row's data */
}
$orderstmt->closeCursor();

PHP & MySQL - Checking if database entry does NOT exist?

I'm collecting the birthdates of users in my system, doing so by linking the the user's unique ID to their birthday entry in another table.
To prevent users from accidentally/purposefully entering two birthdate entries for their accounts, I'd like to remove to the entry form for birthdays IF the user has already entered a birthday prior.
For instance:
$value = mysqli_query("SELECT bd_user_id FROM user_birthdate WHERE
bd_user_id="$user_id";");
From that data, how will I be able to return some form value to determine if whether the user's ID has already been index in user_birthdate or not? (Where $user_id = The current user's ID)
Or perhaps I'm taking the wrong approach here? The logic behind it is what's been getting me.
How can I check if whether a value is NOT indexed in a database table?
You normally query the database as you did
$value = mysqli_query("SELECT bd_user_id FROM user_birthdate WHERE bd_user_id="$user_id";");
Than you use mysqli_num_rows(), and check if it returns 0.
$num_rows = mysqli_num_rows($value);
if($num_rows > 0){
//exists
}else{
//doesn't exist
}
**Sorry, as Devon said in your case it's mysqli_num_rows not mysql_num_rows.

PHP/MySQL - Login system - Is it smart to SELECT the whole database?

I am in the process of designing a fairly simple login system, and I currently use the following code when a user attempts to log in to determine whether there is an entry in the database that matches the username that the user tries to log in with. (Later in the code, I check for matching passwords, etc.; I'm not worried about that part.)
Currently, I use SELECT to grab the entire database into a variable ($wholeUserDatabase), and then iterate through it to determine whether the 'username' field matches.
It works fine for now. But my database has three users right now. Will this method of grabbing the whole database into a variable become painfully slow when I release the site to the public and (theoretically) get many more users?
$connection = mysql_connect($mysql_host,$mysql_user,$mysql_password);
mysql_select_db($mysql_database, $connection);
// Take the whole user database, and store it in $wholeUserDatabase.
$wholeUserDatabase = mysql_query("SELECT * FROM myTable")
or die(mysql_error());
$boolFoundUser = false;
/* Iterate once for every entry in the database, storing the current entry
of the database into a variable $currentEntry, which is an array containing
everything related to the one user. */
while($currentEntry = mysql_fetch_array($wholeUserDatabase)) {
/* Does the "username" field of the current entry match the one
the user tried to log in with? */
if ($currentEntry['username'] == $_POST['username']) {
/* If it does, break the loop so that the $currentEntry variable
will contain the information for the user who is trying to log in,
which I will later need to check passwords, etc. */
$boolFoundUser = true;
break;
}
}
mysql_close($connection);
Thanks for any help. Let me know if I need to rethink this part. I hope this can be helpful to other people.
YES! It will be horribly, horribly slow. Do not select the whole database, just select what you need.
I don't understand why you are doing things this way. It kind of defeats the purpose of having a database in the first place. I mean, if you want to do things this way, file i/o would suffice (i.e. wriitng/reading from a plaintext file).
What you want to do is a SELECT * FROM myTable Where username=$username && password==$password...
This is better because (a) you can create indexes on username which would make the database search/find much faster, (b) its far less expensive from i/o and processing perspective as (a) you are not pushing all that data (the entire db) from db to application, (b) mySQL doesn't need to iterate over the entire db if its properly indexed (so faster)...
Regards
It is obviously a very bad idea to get all the users from the database. To get an idea of how much data transfer you will cause. Imagine you getting 10^5 users after you release it. Let's say the schema of the users table is, at least: users(username varchar(30), password varchar(64)). In this case, You will transfer from the DB machine:
10^5 * (30 + 64) * 2 bytes = 18.8 MB of data.
That's for 10^5 users for whom you have only an username and a pass stored in the DB. What if you get lucky and get 10^6 or 10^7 users ?
In general, you will transfer an amount of data falling in this class: O(users)

Loop until passcode is unique

This is for a file sharing website. In order to make sure a "passcode", which is unique to each file, is truely unique, I'm trying this:
$genpasscode = mysql_real_escape_string(sha1($row['name'].time())); //Make passcode out of time + filename.
$i = 0;
while ($i < 1) //Create new passcode in loop until $i = 1;
{
$query = "SELECT * FROM files WHERE passcode='".$genpasscode."'";
$res = mysql_query($query);
if (mysql_num_rows($res) == 0) // Passcode doesn't exist yet? Stop making a new one!
{
$i = 1;
}
else // Passcode exists? Make a new one!
{
$genpasscode = mysql_real_escape_string(sha1($row['name'].time()));
}
}
This really only prevents a double passcode if two users upload a file with the same name at the exact same time, but hey better safe than sorry right? My question is; does this work the way I intend it to? I have no way to reliably (read: easily) test it because even one second off would generate a unique passcode anyway.
UPDATE:
Lee suggest I do it like this:
do {
$query = "INSERT IGNORE INTO files
(filename, passcode) values ('whatever', SHA1(NOW()))";
$res = mysql_query($query);
} while( $res && (0 == mysql_affected_rows()) )
[Edit: I updated above example to include two crucial fixes. See my answer below for details. -#Lee]
But I'm afraid it will update someone else's row. Which wouldn't be a problem if filename and passcode were the only fields in the database. But in addition to that there's also checks for mime type etc. so I was thinking of this:
//Add file
$sql = "INSERT INTO files (name) VALUES ('".$str."')";
mysql_query($sql) or die(mysql_error());
//Add passcode to last inserted file
$lastid = mysql_insert_id();
$genpasscode = mysql_real_escape_string(sha1($str.$lastid.time())); //Make passcode out of time + id + filename.
$sql = "UPDATE files SET passcode='".$genpasscode."' WHERE id=$lastid";
mysql_query($sql) or die(mysql_error());
Would that be the best solution? The last-inserted-id field is always unique so the passcode should be too. Any thoughts?
UPDATE2: Apperenatly IGNORE does not replace a row if it already exists. This was a misunderstanding on my part, so that's probably the best way to go!
Strictly speaking, your test for uniqueness won't guarantee uniqueness under a concurrent load. The problem is that you check for uniqueness prior to (and separately from) the place where you insert a row to "claim" your newly generated passcode. Another process could be doing the same thing, at the same time. Here's how that goes...
Two processes generate the exact same passcode. They each begin by checking for uniqueness. Since neither process has (yet) inserted a row to the table, both processes will find no matching passcode in database, and so both processes will assume that the code is unique. Now as the processes each continue their work, eventually they will both insert a row to the files table using the generated code -- and thus you get a duplicate.
To get around this, you must perform the check, and do the insert in a single "atomic" operation. Following is an explanation of this approach:
If you want passcode to be unique, you should define the column in your database as UNIQUE. This will ensure uniqueness (even if your php code does not) by refusing to insert a row that would cause a duplicate passcode.
CREATE TABLE files (
id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
filename varchar(255) NOT NULL,
passcode varchar(64) NOT NULL UNIQUE,
)
Now, use mysql's SHA1() and NOW() to generate your passcode as part of the insert statement. Combine this with INSERT IGNORE ... (docs), and loop until a row is successfully inserted:
do {
$query = "INSERT IGNORE INTO files
(filename, passcode) values ('whatever', SHA1(NOW()))";
$res = mysql_query($query);
} while( $res && (0 == mysql_affected_rows()) )
if( !$res ) {
// an error occurred (eg. lost connection, insufficient permissions on table, etc)
// no passcode was generated. handle the error, and either abort or retry.
} else {
// success, unique code was generated and inserted into db.
// you can now do a select to retrieve the generated code (described below)
// or you can proceed with the rest of your program logic.
}
Note: The above example was edited to account for the excellent observations posted by #martinstoeckli in the comments section. The following changes were made:
changed mysql_num_rows() (docs) to mysql_affected_rows() (docs) -- num_rows doesn't apply to inserts. Also removed the argument to mysql_affected_rows(), as this function operates on the connection level, not the result level (and in any case, the result of an insert is boolean, not a resource number).
added error checking in the loop condition, and added a test for error/success after loop exits. The error handling is important, as without it, database errors (like lost connections, or permissions problems), will cause the loop to spin forever. The approach shown above (using IGNORE, and mysql_affected_rows(), and testing $res separately for errors) allows us to distinguish these "real database errors" from the unique constraint violation (which is a completely valid non-error condition in this section of logic).
If you need to get the passcode after it has been generated, just select the record again:
$res = mysql_query("SELECT * FROM files WHERE id=LAST_INSERT_ID()");
$row = mysql_fetch_assoc($res);
$passcode = $row['passcode'];
Edit: changed above example to use the mysql function LAST_INSERT_ID(), rather than PHP's function. This is a more efficient way to accomplish the same thing, and the resulting code is cleaner, clearer, and less cluttered.
I'd personally would have write it on a different way but I'll provide you a much easier solution: sessions.
I guess you're familiar with sessions? Sessions are server-side remembered variables that timeout at some point, depending on the server configuration (the default value is 10 minutes or longer). The session is linked to a client using a session id, a random generated string.
If you start a session at the upload page, an id will be generated which is guaranteed to be unique as long the session is not destroyed, which should take about 10 minutes. That means that when you're combining the session id and the current time you'll never have the same passcode. A session id + the current time (in microseconds, milliseconds or seconds) are NEVER the same.
In your upload page:
session_start();
In the page where you handle the upload:
$genpasscode = mysql_real_escape_string(sha1($row['name'].time().session_id()));
// No need for the slow, whacky while loop, insert immediately
// Optionally you can destroy the session id
If you do destroy the session id, that would mean there's a very slim chance that another client can generate the same session id so I wouldn't advice that. I'd just allow the session to expire.
Your question is:
does this work the way I intend it to?
Well, I'd say... yes, it does work, but it could be optimized.
Database
To make sure to not have the same value in the field passcode on the database layer, add a unique key to this:
/* SQL */
ALTER TABLE `yourtable` ADD UNIQUE `passcode` (`passcode`);
(duplicate key handling has to be taken care of than ofc)
Code
To wait a second until a new Hash is created, is ok, but if you're talking heavy load, then a single second might be a tiny eternity. Therefore I'd rather add another component to the sha1-part of your code, maybe a file id from the same database record, userid or whatever which makes this really unique.
If you don't have a unique id at hand, you still can fall back to a random number rand-function in php.
I don't think mysql_real_escape_string is needed in this context. The sha1 returns a 40-character hexadecimal number anyway, even if there are some bad characters in your rows.
$genpasscode = sha1(rand().$row['name'].time());
...should suffice.
Style
Two times the passcode generation code is used in your code sample. Start cleaning this up in moving this into a function.
$genpasscode = gen_pc(row['name']);
...
function gen_pc($x)
{
return sha1($row[rand().$x.time());
}
If I'd do it, I'd do it differently, I'd use the session_id() to avoid duplicates as good as possible. This way you wouldn't need to loop and communicate with your database in that loop possibly several times.
You can add unique constraint to your table.
ALTER TABLE files ADD UNIQUE (passcode);
PS: You can use microtime or uniqid to make the passcode more unique.
Edit:
You make your best to generate a unique value in php, and unique constraint is used to guarantee that at database side. If your unique value is very unique, but in very rare case it failed to be unique, just feel free to give the message like The system is busy now. Please try again:).

Categories