Making a secure scoring system in PHP - php

On my website, I display 5 questions (MCQs) per page and when the user requests new page, I am calling a script score_update() with the score of this page and then presenting him with the next page.
The scoreUpdate() script is something like
<?php
//connect to database
//update the score
?>
The problem is that the user may refresh the page and the score may be updated twice or the number of times he refreshes the page or he may directly call the script by viewing the source code.
How can I implement this system?I need an idea.
EDIT
Here is my database schema
user
------------------------------------------
user_id | username | password | points
------------------------------------------
PS :The user may attempt the same question again at some point in future. There is no restriction on it. So no need to keep track of questions attempted by him. He must get marks only if he attempted the question and knocked it right. Hope I am clear.

I would recommend saving the user's state in your database. You should add another table in order to do so.
-----------------------------------
user_id | question_id | answer
-----------------------------------
When a user answers a question you can check if the user had already answered this question.
If so, update his answer and if it's the correct answer update him score. This method works assuming you won't present the same question again if the user already answered it correctly.
If you want to use questions multiple times I recommend another method.
Use 2 tables:
----------------------------
user_id | questionnaire_id
----------------------------
and
------------------------------------------
questionnaire_id | question_id | answer
------------------------------------------
Each questionnaire is unique and contains some questions - the answer to each question is empty at the start. Generate new questionnaire each time the user gets new questionnaire and save his answers per questionnaire. This way you can make sure the user can't submit the same questionnaire results twice (or more). If it's the first time the user submit this questionnaire you can update the score, if not, do nothing.
To make sure the user does not change his questionnaire_id manually you can save it in a session on the server so the user won't have access to it.

I would suggest using form keys, also known as NONCE.
This means that each time a submission is made, a new form key (NONCE) is generated.
Each NONCE can only be used once and the NONCE must be valid for the form submission to work.
Most modern frameworks have something like this built in as standard.
See this article for a more in depth explanation of the idea:
http://net.tutsplus.com/tutorials/php/secure-your-forms-with-form-keys/
And this section of the Symfony2 CSRF protection on forms which uses the same technique:
http://symfony.com/doc/current/book/forms.html#csrf-protection

There are different possible solutions for problems like this. It is basically the same with visitor counters or polls.
Atleast you have to store your information somewhere if there user as already triggered that script and redentify him on every page call.
The first and best method is a user account to login and save it in the PHP $_SESSION or directly in the database linked to the user_id / account_id. But this if your page doesnt have a login right now this is too much for a smaller problem I guess. But if you have already one login panel this is by far the best solution.
Another method is to save a cookie which may be a legal problem in some countries lately if the user doesnt agree to that before hand and cookies can be deleted so there it is easy to manipulate.
You can also save the users IP Address: Harder to manipulate (requires restart of internet and such and noone will do that a dozen times to fake your score counter) but if multiple people are sharing the same internet connection only one of them can achive one score.
All of them have different advantages and disadvantages. Depending on how paranoid you are you could also combine multiple of them if you want to make cheating / abusing harder but that decision is up to you.

Consider the following setup;
users
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| user_id | smallint(5) | NO | PRI | NULL | auto_increment |
| username | varchar(10) | NO | | NULL | |
+------------+-------------+------+-----+---------+----------------+
... You'll have more columns, but you get the idea
-
questions
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| qid | smallint(5) | NO | PRI | NULL | auto_increment |
| question | varchar(10) | NO | | NULL | |
| votes | smallint(5) | NO | | 0 | |
+----------+--------------+------+-----+---------+----------------+
-
votes
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| qid | smallint(5) | NO | | NULL | |
| user_id| smallint(5) | NO | | NULL | |
+--------+-------------+------+-----+---------+-------+
In this setup, I'm userid 1 and voting for question id 1
When a user votes, their vote is placed within votes
INSERT INTO `votes` (`qid`,`user_id`) VALUES (1, 1);
To check they've already voted, simply do;
SELECT `user_id` FROM `votes` WHERE (`user_id`=1) AND (`qid`=1);
If that query returns any rows, we know the user has already voted, and we shouldn't process the duplicate vote.
Of course this only restricts us to one type of voting - positive, or negative - whichever you decide to track. We can adapt votes to store the type of vote it is;
ALTER TABLE votes ADD type ENUM('up', 'down') NOT NULL DEFAULT 'up';
Which will make our table structure to the following;
+---------+-------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------------+------+-----+---------+-------+
| qid | smallint(5) | NO | | NULL | |
| user_id | smallint(5) | NO | | NULL | |
| type | enum('up','down') | NO | | up | |
+---------+-------------------+------+-----+---------+-------+
And, again, adapt the lookup query;
SELECT `user_id` FROM `votes` WHERE (`user_id`=1) AND (`qid`=1) AND (`type`='up');

Check the $_SERVER['HTTP_REFERER'] value.
If it's the same the page: is reloaded. (do nothing)
If it is the previous: update database
If it is another domain: illegal access (redirect to first question)

The most foolproof system I see is based on tracking the whole lifetime of a given quizz.
If you store a "current question number" associated with the user and this particular quizz, you can easily filter out duplicate responses:
update_score ($question_number, $choice)
if current question for this quizz and user is not set to $question_number
ignore request
else
set choice for this specific question and update score
increment current question (possibly reaching the end of the quizz)
When the last question is answered, the final score is displayed/recorded and the "current question" reset to 0.
If the user wants to retry the test, current question is set to 1 and the whole process restarts.
If the user wants to cancel the current test and restart, he/she can do so by going back to quizz start page.
So any attempt to submit a second answer to the same question would fail (be it from accidental refresh or malicious attempts), until the quizz is finished and you can start back with question 1.

You can use a toggle session variable approach (name it as flag),which is the simplest and has a good level of security against duplicate requests.
Make a script called updateScore.php .When the user login set the flag=1 ,which means when the next request comes for updation,process it in updateScore.php and at the end of ths script make flag=0. When the next page appears again make flag=1.This way you alternate the values and also set a maximum update limit in your script,say, in your case you have 5 questions so you can set it to 50 (+10 per question). You can take more complicate values of flag to reduce guess chances.

Related

MySql-How to parse date which is attached with another string

I have a database named as DB in which there is a table named as log. log table have following structure.
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | bigint(64) | NO | PRI | NULL | auto_increment |
| BCReq | varchar(400) | YES | | NULL | |
| BCRes | varchar(400) | YES | | NULL | |
| ServerReq | varchar(400) | YES | | NULL | |
| ServerRes | varchar(400) | YES | | NULL | |
+----------------+--------------+------+-----+---------+----------------+
and the value of BCReq field of one of the record is look like this:
uId-->xxxxxx/regiodId-->yyy/videoTitle-->bcdes
/location-->Asia Pacific/BCreqtime-->24-07-2014
10:30:16/Exception-->null
Now i want to fetch the data from log table on the basis of date .is it possible? if yes then how? Please comment if any more data required.Thanks in advance.
However, despite my dislike for the way the data has been modeled.
As developers, we don't sell code...we offer solutions :P
Requirement:
Fetch the data from log table on the basis of date
SQL LIKE statement it.
DBAs will probably want to kill me for suggesting this, because you're perform a LIKE search on a what is probably a non-indexed column. For example
SELECT * FROM log WHERE BCReq LIKE '%24-07-2014%'
But it will give you all records of log on that day, assuming you want to fetch it by day.
I'm sure you can extend it to search by hour, minute, second or even month.
Also, a DBA behind me just blanched at what I posted :P
So here's me trying to redeem myself in his eyes.
Perform your query using a non-transactional connection, so you don't lock the table for other people.
But all in all, the data model needs to be reviewed and addressed to cater for this.
Additional information to address your search between 2 dates issue:
If you wish to get data within 2 dates, due to your data model, in that the field BCReq is difficult to search, a viable solution is to have a developer write up an application or script to grab that data.
A sample solution (pseudo code) could be:
Grab 2 dates from user. Let's call them start_date && end_date
Format those to dates to DD-MM-YYYY format.
Determine the days_between those start_date and end_date
For every day between start_date and end_date
Run SQL query above to retrieve data
Store data retrieved from each iteration to output
Do something with your output

MySQL table structure with one row. Best handling for management and insertion

I'm just creating a social network for practising my skills (PHP, HTML, CSS, JavaScript, etc.).
Now when designing the database layout a question appears, that I'm unhappily not able to solve.
I have a table called UserMain:
+------------+---------------------+
| Field | Type |
+------------+---------------------+
| u_id | bigint(20) unsigned |
| u_email | varchar(256) |
| u_password | varchar(30) |
| u_data | varchar(25) |
| u_friends | varchar(28) |
+------------+---------------------+
For storing the general data, that's being input when registering.
I wanted to separate the users data (prename, surename, sex, birthday, etc.) into another table called data and of course the relationship between users in a table called friends. So I decided to create a data- and friends-table for every user, via php using the u_id above and I came up with something like this, [u_id]_data:
+------------+---------------------+
| Field | Type |
+------------+---------------------+
| u_prename | varchar(20) |
| u_surname | varchar(20) |
| u_sex | boolean |
| u_birthday | DATE |
| u_avatar | varchar(28) |
+------------+---------------------+
Now I don't want to attach value at the friends table, because the problem starts obviously with the [u_id]_data table. A user just has one pre- and surname, etc., so it is a 1-row-table. Now the question:
How do I handle the input of the table in relation to the primary key?
For me, creating a new "id int not null auto_increment pk" seems needless for a single row, so I don't know what combination of columns to use for the primary key.
Maybe you know better implementations of this design, but please consider the following:
It doesn't matter what new implementation you have, the only thing I don't want to have is a table called data in which I have the data of all users.
Alright, I maybe have a bad opinion about MySQL or I'm not really good informed, but my idea of just having multiple data-tables comes from performance reasons.
My idea when changing or inserting data:
GetTheUsersId (Searching User-Table for the Id. That could take a
littlebit if I would have ... let's say 10,000,000 users)
When having the [u_id] I just can use the data-table to find what I'm searching for.
With a table that is made up of (again) 10,000,000 rows it would take longer. Now don't start laughing as I'm taking the abstraction and dimensions to a level far away. It's just for supporting the idea of saving performance.
the only thing I don't want to have is a table called data in which I have the data of all users.
Please tell us more about why this is not an option? It's a perfectly valid way to store user data.
But to answer your question, you probably don't need any key at all if there is only one row in a table. You are going to refer to the row by the table name in most cases anyways:
SELECT * FROM [uid]_data ...
SELECT * FROM [uid]_data JOIN ...
UPDATE [uid]_data ...
INSERT INTO [uid]_data ...
DELETE FROM [uid]_data --You're probably going to want to DROP the table as well

Storing user login-logout time into database with it's difference

I want to store user's login history on my site.
table:
mysql> desc user_stat;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | varchar(64) | YES | | NULL | |
| login | varchar(64) | YES | | NULL | |
| logout | varchar(64) | YES | | NULL | |
| diff | varchar(64) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
When user log In I perform insert.
insert into user_stat(id,login) values('".$id."','".NOW()."');
But for logout situation is confusing. As user may have logged in twise in two diff browser. So how can I maintain logout time? something like session management (though I have not used session var for login logout). JS sdk for login logout I used : http://jsfiddle.net/YCFpH/
Also How can I get the differene of logout and login time in diff column?
One way is to convert string of data-time into number and getting differece of it. But I dont what that method.
Anyother solution for data time diff?
But for logout situation is confusing. As user may have logged in twise in two diff browser. So how can I maintain logout time?
Each Login must have some sort of session identifier. Otherwise, you will never properly correlate your logout time with the correct login time. There are any number of ways to get this and that will depend on your web server platform. At the very least, you can create a uid on the client (see Create GUID / UUID in JavaScript? ) and pass the uid in when the user logs out.
Additionally, if you are not sorting by or joining by the difference time, login and logout should likely be datetime datatypes and the difference simply calculated each time you want it instead of stored. For example.
set #time1 = now();
set #time2 = date_add(now(), interval 30 second);
select #time1, #time2, (timediff(#time1,#time2))
Now you get the difference between the times in seconds.
2013-12-26 14:17:32 2013-12-26 14:18:02 -00:00:30

Like and Unlike System in PHP

I am developing a community site for high school students. I am trying to implement a like and unlike system using PHP. Heres what I have got :
A table named likes in MySQL with 3 columns namely app_id VARCHAR(32), user VARCHAR(12), dormant VARCHAR(6).
UNIQUE(app_id,user)
When a person likes a page on my site, a row is either inserted or updated in the likes table with dormant = false.
When a person unlikes a page, the row present is again updated with dormant = true. This is an alternative to deleting the row as it is a bit intensive for a speedy work of likes and unlikes.
I want to know, if I should go for deleting the row instead of updating it, when someone unlikes the page.
Dont Delete the row. Every data you can gather its a valuable data point.
I would say you should create a new record for every unlike also.
These data will be usefull to you in the future to figure out user behaviour.
Some ppl might like smth now and then unlike it , then like it again and so on.
Maybe in the future u would like to see why so many people who liked an item suddely unliked it then liked it again.
So i say gather as much data as you can.
Sounds like premature optimization. Don't do that.
Design your application as you want to use it /as it should work. When it gets busy, find out the bottlenecks and fix them.
If you want to design your application for scalability to the millions, consider using a different database engine / programming platform altogether.
Looks like you haven't record the number of user liked or unliked the pages. In this case, LIKES should be a many table and there should be another table called APPS (or any name you wish) to store pages:
**USER**
+---------+-------+-----+
| user_id | name | ....|
+---------+-------+-----+
| 1 | ... | ... |
+---------+-------+-----+
| 2 | ... | ... |
+---------+-------+-----+
**APPS**
+---------+-------+-----+
| app_id | name | ....|
+---------+-------+-----+
| 1 | ... | ... |
+---------+-------+-----+
| 2 | ... | ... |
+---------+-------+-----+
**LIKES**
+---------+-------+----------+----------+
| like_id |user_id| app_id | is_liked |
+---------+-------+----------+----------+
| 1 | 1 | 2 | 1 |
+---------+-------+----------+----------+
| 2 | 1 | 3 | 0 |
+---------+-------+----------+----------+
Where you can toggle if the user click like( is_liked = 1) or unlike( is_liked = 0) the page

PHP/MYSQL: Storing a list or massive table

I am still new to PHP and I was wondering which alternative would be better or maybe someone could suggest a better way.
I have a set of users and I have to track all of their interactions with posts. If a users taps on a button, it will add the post to a list and if they tap it again, it will remove the post, so would it be better to:
Have a column of a JSON array of postIDs stored in the table for each user (probably thousands).
-or-
Have a separate table with every save (combination of postID and userID) (probably millions) and return all results where the userID's match?
For the purposes of this question, there are two tables: Table A is users and Table B is posts. How should I store all of the user's saved posts?
EDIT: Sorry, but I didn't mention that posts will have multiple user interactions and users will have multiple post interactions (Many to Many relationship). I think that would affect Bob's answer.
This is an interesting question!
The solution really depends on your expected use case. If each user has a list of posts they've tagged, and that is all the information you need, it will be expedient to list these as a field in the user's table (or in their blob if you're using a nosql backend - a viable option if this is your use case!). There will be no impact on transmission time since the list will be the same size either way, but in this solution you will probably save on lookup time, since you're only using one table and dbs will optimize to keep this information close together.
On the other hand, if you have to be able to query a given post for all the users that have tagged it, then option two will be much better. In the former method, you'd have to query all users and see if each one had the post. In this option, you simply have to find all the relations and work from there. Presumably you'd have a user table, a post table and a user_post table with foreign keys to the first two tables. There are other ways to do this, but it necessitates maintaining multiple lists and cross checking each time, which is an expensive set of operations and error-prone.
Note that the latter option shouldn't choke on 'millions' of connections, since the db should be optimized for this sort of quick read. (pro tip: index the proper columns!) Do be careful about any data massage, though. One unnecessary for-loop will kill your performance.
For the purposes of this question, there are two tables: Table A is users and Table B is posts. How should I store all of the user's saved posts?
If each user has a unique ID of some sort (primary key), then ad a field to each post that refers to the unique ID of the user.
mysql> describe users;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| email | varchar(200) | YES | | NULL | |
| username | varchar(20) | YES | | NULL | |
+----------+------------------+------+-----+---------+----------------+
mysql> describe posts;
+---------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| user | int(11) unsigned | NO | | NULL | |
| text | text | YES | | NULL | |
+---------+------------------+------+-----+---------+----------------+
Then to get posts for a user, for example:
SELECT text
FROM posts
WHERE user=5;
Or to get all the posts from a particular organization:
SELECT posts.text,users.username
FROM posts,users
WHERE post.user=users.id
AND users.email LIKE '%#example.com';
I think it would make sense to keep a third table that would be all the post status data.
If your user interface shows, say, 50 posts per page, then the UI only needs to keep track of 50 posts at a time. They'll all have unique IDs in your database, so that shouldn't be a problem.

Categories