Function to set an auth_token - php

In my form I have a hidden field:
<input type="hidden" name="auth_token" value="<?php echo $auth_token; ?>">
This value is also stored in a session and a variable:
$_SESSION['auth_token'] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']); # TODO: put this in a function
$auth_token = $_SESSION['auth_token'];
When the form is submitted the two values are compared. It's a basic form token.
Should this be made into two functions or just one when refactored? set_form_token() and get_form_token(), get_form_token() returning the session value, then I can compare it in my main code. What is the proper way of doing this?
EDIT:
Considering both Joel L and RobertPitt's answers I have made these:
function set_auth_token()
{
if (!isset($_SESSION['auth_token']))
{
$_SESSION['auth_token'] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']);
}
}
function get_auth_token()
{
if (isset($_SESSION['auth_token']))
{
return $_SESSION['auth_token'];
}
else
{
die('No auth token.');
}
}
function check_auth_token()
{
if (array_key_exists('auth_token', $_SESSION) && array_key_exists('auth_token', $_POST))
{
if ($_SESSION['auth_token'] === $_POST['auth_token'])
{
# what happens if user fills the form in wrong first time(?)
$_SESSION['auth_token'] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']);
}
else
{
return false;
}
}
else
{
return false;
}
}
I can then check if check_auth_token returns false or not and then record it after the form has been submitted. Would this be acceptable?

In my app, I actually have the following helper functions for using tokens:
generateToken() // generate and return hash, used in login process.
// hash then saved to session
getToken() // returns user's token from session
tokenField() // shortcut for echo '<input type="hidden" ... value="getToken()" />';
// used in page templates
checkToken() // get token from either 1) $_POST 2) request header or 3) $_GET
// and compare with getToken(). generate error if invalid.
The checkToken() function checks 3 locations because the request can be GET or POST, and either of those could be via AJAX. And I have my AJAX helper automatically insert the token in the header for each request).
This way, I only need to call checkToken() everywhere the check is needed, and can therefore change the impelmentation details quite easily.
For instance, I can start using one-time tokens by changing only getToken() and checkToken().
If you manually compare if (get_form_token() == $token) everywhere in your code, you have no such flexibility.

firstly you should understand exactly what the workflow is, and Joel L explains that very simply.
You should encapsulate the methods in a class to keep everything together, some thing like sp:
class FormTokenizer
{
private $context = "";
public function __construct($auth_token = "auth_token")
{
$this->context = $context;
}
public function generateToken()
{
$_SESSION[form_tokens][$this->context] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']);
return $this;
}
public function getToken()
{
return isset($_SESSION[form_tokens][$this->context]) ? $_SESSION[form_tokens][$this->context] : false;
}
function generateField()
{
return sprintf('<input type="hidden" name="a_%s" value="%s">',$this->context,$this->getToken());
}
public function validateToken()
{
if(isset($_POST["a_" . $this->context]))
{
return $this->getToken() == $_POST["a_" . $this->context];
}
return false;
}
}
and a simple usage would be:
$Token = new FormTokenizer("registration");
if(isset($_POST))
{
if($Token->validateToken() === false)
{
//Token Onvalid
}
}
//Generate a fresh token.
$hidden_input = $Token->generateToken()->generateField();

Related

php token match does not work

i am doing form validator(using tokens).
Page is based on smarty. So in class Articles i have the followin function:
public static function getToken(){
self::$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = self::$token;
}
Then in .tpl file i have inserted the value like this:
<input type="hidden" name="token" value="{Articles::$token}">
After the form is submitted the check is run in index.php:
if(isset($_POST['token']) && $_POST['token'] == $_SESSION['token']){
Articles::addComment($item, $_POST['name'], $_POST['email'], $_POST['comment']);
}
The issue is that I get different tokens. For example, I echoed the $_SESSION['token'] and compared in with the value in the form (view source).
Also after submit i tried to echo both values ($_SESSION ['token'] and $_POST ['token]) they are different as well. Obviously, the form is not submitting.
CSRF tokens need not be mapped 1:1 for users. What if you have more than one form on a page? What if a user has more than one browser window open?
Define something to store 0..n tokens, set a reasonably short validity period on them, and [optionally] define what they are valid for. Eg:
class Token {
protected $id, $expiry;
public function __construct($expiry=300, $id=NULL) {
$this->expiry = time() + $expiry;
if( is_null($id) ) {
// actual secure ID generation.
$this->id = bin2hex(random_bytes(32));
} else {
$this->id = $id;
}
}
public function getID() {
return $id;
}
public function isValid() {
return time() < $expiry;
}
}
class YourApp {
public function newToken() {
$token = new Token();
$_SESSION['tokens'][$token->getID()] = $token;
return $token
}
public function checkToken($token_id) {
if( key_exists($token_id, $_SESSION['tokens']) ) {
$cur = $_SESSION['tokens'][$token_id];
unset($_SESSION['tokens'][$token_id]; // single use only!
return $cur->isValid();
} else {
return false;
}
}
public function flushOldTokens() {
$_SESSION['tokens'] = array_filter(
$_SESSION['tokens'],
function($t){return $t->isValid();}
);
}
}

Variable to another php file

I have a php file(register.php) with a public function register($data) where errors are validated.Then errors are counted and if no errors are found, validation is passed.
register.php:
class ARegister {
public function register($data) {
$user = $data['userData'];
//validate provided data
$errors = $this->validateUser($data);
if(count($errors) == 0) {
//first validation
}
}
public function validateUser($data, $botProtection = true) {
$id = $data['fieldId'];
$user = $data['userData'];
$errors = array();
$validator = new AValidator();
if( $validator->isEmpty($user['password']) )
$errors[] = array(
"id" => $id['password'],
"msg" => Lang::get('password_required')
);
return $errors;
}
The problem is, that I need to get this confirmation of validated data to my other php file (othervalidation.php) where I've made another validation:
othervalidation.php:
<?php
require 'register.php';
if ( !empty($action) ) {
switch ( $action ) {
case 'process_payment':
try {
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
throw new Exception('Validation error');
}
} catch (Exception $e) {
$status = false;
$message = $e->getMessage();
}
}
How can I send the result of $errors variable to my other validation (othervalidation.php)?
I looked at your new code design and here's the new problems I found.
First, in your register function, you use the errors variable as an integer while your validate function returns an array. You got two possibilities here.
You can change your register method to check out if your error array is empty like this:
if(empty($errors)) {
//first validation
}
Count is also valid, but I still prefer empty since it's syntactically clearer. Furthermore, the count function returns 1 if the parameter is not an array or a countable object or 0 if the parameter is NULL. As I said, it is a functional solution in your current case but, in some other contexts, it might cause you unexpected results.
Here in your method declaration, I see that you are expecting a boolean (botProtection).
public function validateUser($data, $botProtection = true) {
But you are supplying an errors parameter
if($instance->validateUser($data, $errors)) {
You don't provide me the declaration of the errors variable, but it is probably not matching the bot protection parameter your function is expecting. PHP is using lose typing, it is useful but, once again, you got to be careful for bugs hard to find. For public function, you should always make sure a way or another that the supplied parameter won't lead to code crash.
In your code, the data parameter seems to be an array. You can use parameter hinting to force the use of array like this:
public function register(array $data) {
public function validateUser(array $data, $botProtection = true) {
And even specific class (as if you where using "instance of" in a condition)
public function register(MyDataClass $data) {
public function validateUser(MyDataClass $data, $botProtection = true) {
Also, you're not even using the botProtection parameter in your validateUser method.
On the same function call:
if($instance->validateUser($data, $errors)) {
you are expecting a Boolean (true or false), but the method returns an array. If you want to use the code the way it is currently designed, you must use it like this
if(!empty($instance->validateUser($data, $errors)) {
Here, I'm not so sure it is necessary to use exception. Ain't it be easier to design your code like this?
if(!empty($instance->validateUser($data, $errors)) {
$message = 'Validation error';
}
In your validate function, is the "isEmpty" function also validating if the client provided a password?
If that's the case you could validate it like this:
if(!in_array($user['password']) or empty($user['password']))
With those corrections, your code should be functional.
Here's a sample of how I would had design your code (considering the code sample provided):
class ARegister {
public function register($data) {
$user = $data['userData']; //don't declare it here, all the user validations must be done in validateUser($data, &$errors)
$errors = array();
if($this->validateUser($data, $errors)) {
//first validation
}
}
/**
* Note: If you are not returing more than one error at the time, $errors should be a string instead of an array.
*/
public function validateUser($data, array &$errors) {
$isValid = false;
if (in_array($data['fieldId']) and in_array($data['fieldId']['password']) and in_array($data['userData'])){
if(!in_array($data['userData']['password']) or empty($data['userData']['password'])){
$errors[$data['fieldId']['password']] = Lang::get('password_required');
}
else{
$isValid = true;
}
}
else{
//an invalid data array had been provided
}
return $isValid;
}
For the next part, if the code is executed directly in the view and you are a beginner, create a procedural external controller file (all functions will be public...). If you are a professional, you MUST create a class to encapsulate the treatment.
You must not do treatment directly in the view. The view is a dumb placeholder for data presentation and collecting client's input. The sole action it must do is display the data sent by the controller and send back the client's input to the controller.
The treatment on data is the controller responsibility.
if (!empty($action) ) {
$errors =array();
switch ( $action ) {
case 'process_payment':
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
//the user is valid, do the treatment
}
else
PageManager::dispayError($errors);
}
unset($instance);
}
}
Here's an example how you can centralize your error display
/**
* Can be more complexe than that, but I'm at my father's home at four hundred kms away from Montreal right now..
*/
public static function dispayError($errors, $size = 4){
if (is_numeric($size)){
if ($size < 0){
$size = 1;
}
elseif($size > 5){
$size = 5;
}
}
else{
$size = 4;
}
if (is_scalar($errors)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $errors . '</h' . $size . '><br>';
}
elseif (is_array($errors)){
foreach ($errors as $error){
if (is_scalar($error)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $error . '</h' . $size . '><br>';
}
}
}
}
Of course, you can also support many kind of message:
public static function dispayError($errors, $size = 4){
self::displayMessage("ERROR_MESSAGE", $errors, $size=4);
}
private static displayMessage($class, $messages, $size=4)
Well, took me two hours to write that. I hope you have now enough material to build an efficient, reusable and, no less important, safe code design.
Good success,
Jonathan Parent-Lévesque from Montreal
You can try something like this:
class ARegister {
private $error = 0;
public function register($data) {
if (!$this->validateUser($data)){
$this->error++;
}
}
public function getErrorCount(){
return $this->error;
}
public resetErrorCount(){
$this->error = 0;
}
Or pass the error by reference:
public function register(&$error, $data) {
if (!$this->validateUser($data)){
$error++;
}
}
Personally, I would do all the validation in the same method (in the class for encapsulation), use an error message parameter (passed by reference) to return why the validation failed and use the return statement to return true or false.
class MyClass{
public function validation(&$errorMessage, $firstParameter, $secondParameter){
$success = false;
if (!$this->firstValidation($firstParameter)){
$errorMessage = "this is not working pal.";
}
elseif (!this->secondeValidation($firstParameter)){
$errorMessage = "Still not working buddy...";
}
else{
$success = true;
}
return $success;
}
private function firstValidation($firstParameter){
$success = false;
return $success;
}
private function secondeValidation($secondParameter){
$success = false;
return $success;
}
}
In your other file:
<?php
$instance = new MyClass();
$errorMessage = "";
if ($instance->validation($errorMessage, $firstParameter, $secondParameter)){
echo "Woot, it's working!!!";
}
else{
echo $errorMessage;
}
?>
Is one of these code solutions fit your needs?
Jonathan Parent-Lévesque from Montreal

Safari is causing session variables to be changed

I'm verifying a form input and making sure the user doesn't submit the form twice. I'm doing this with the following class:
<?php
//You can of course choose any name for your class or integrate it in something like a functions or base class
class formKey
{
//Here we store the generated form key
private $formKey;
//Here we store the old form key (more info at step 4)
private $old_formKey;
//The constructor stores the form key (if one excists) in our class variable
function __construct()
{
//We need the previous key so we store it
if(isset($_SESSION['form_key']))
{
$this->old_formKey = $_SESSION['form_key'];
}
}
//Function to generate the form key
private function generateKey()
{
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
//See http://www.php.net/mt_rand for a precise description of the function and more examples.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey()
{
//Generate the key and store it inside the class
$this->formKey = $this->generateKey();
//Store the form key in the session
$_SESSION['form_key'] = $this->formKey;
//Output the form key
echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
}
//Function that validated the form key POST data
public function validate()
{
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey)
{
//The key is valid, return true.
unset($_SESSION['form_key']);
return true;
}
else
{
//The key is invalid, return false.
return false;
}
}
}
?>
This is output to the form like this then:
include STYLESHEETPATH . '/formkey.class.php';
$formKey = new formKey();
$formKey->outputKey();
If I echo out the old_formKey and the post form_key they are the exact same and it works in every browser other than Safari. If I check these in Safari, the old_formKey is always different. Why would this be?

Duplicate entries in MySQL after performing a check (PHP)

Lately, I have been getting some strange duplicate entries in my MySQL database. Entries in this table are inserted during a PUT request to a PHP page. The table contains 3 fields with no external references:
manga_id (primary key; auto increment) - bigint(20);
name - varchar(255);
manga_cid - varchar(255).
The PHP code is the following:
class MangaHandler {
private function getMangaFromName($name) {
$id = $this->generateId($name);
$mangas = new Query("SELECT * FROM tbl_manga WHERE manga_cid = '" . $this->conn->escapeString($id) . "'", $this->conn);
if(!$mangas || $mangas->hasError()) {
logError("getMangaFromName($name): " . $this->conn->getError());
return null;
}
if($mangas->moveNext()) {
return $mangas->getRow();
}
return null;
}
private function addManga($name) {
$manga_row = null;
$error = false;
$cid = $this->generateId($name);
$sql = sprintf("INSERT INTO tbl_manga(name, manga_cid) VALUES ('%s', '%s')", $this->conn->escapeString($name), $this->conn->escapeString($cid));
if(!$this->conn->execute($sql))
$error = true;
// some more code ...
if($error) {
logError("addManga($name): " . $this->conn->getError());
}
return $manga_row;
}
public function addMangaSourceAndFollow($name, $url, $source_id, $user_id, $stick_source = false, $stick_lang = 'English') {
// validate url
$manga = $this->getMangaFromUrl($url, $source_id);
if(!$manga) {
$manga = $this->getMangaFromName($name);
if(!$manga) $manga = $this->addManga($name);
// ...
}
// ...
return true;
}
}
class MangaRestService extends CommonRestService
{
public function performPut($url, $arguments, $accept, $raw) {
header('Content-type: application/json');
header("Cache-Control: no-cache, must-revalidate");
$json = json_decode($raw, true);
// some input validation and auth
$ms = new MangaHandler();
try {
$ret = $ms->addMangaSourceAndFollow(null, $json['url'], $source['source_id'], $user['user_id'], $enforce == 1);
// throw exception if some ret is invalid
// return proper json response
} catch(Exception $e) {
$conn->rollback();
logError("MangaRestService.performPut($url, [" . implode("; ", $arguments) . "], $accept, $raw): " . $e->getMessage());
echo RestResponse::getSomeErrorResponse()->toJSON();
}
}
}
$serv = new MangaRestService();
// performs some checks and invokes the appropriate GET, POST, PUT or DELETE method
// in this case it's the performPut method above
$serv->handleRawRequest();
The manga name gets filtered (only alphanumeric chars, underscore and some more characters are allowed) and becomes the manga_cid (which should be unique in the table).
The code basically checks if a manga with a specific manga_cid exists. Only if it doesn't, a new entry is inserted. I have tested the code and it works, however, after deploying the app., I have been getting entries with duplicate manga_cid (sometimes more than 2). This is a rare occurrence, as most entries remain unique. Also, I have no errors in my logs.
Could it be that for some reason, multiple HTTP PUT requests are being executed concurrently and, since there is no synchronization, INSERT gets called multiple times?
I find it very unlikely, specially because the app only lets you press the button that performs this request once before it disappears.
I have tried using MySQL transactions but it didn't solve the problem. I know that setting the field as unique will probably allow me to avoid these duplicate entries, however I would have to perform some heavy maintenance on the database in order to remove the duplicate entries first. Although this table has a simple structure, the manga_id is referenced in several other tables.
Also, I am curious as to why this is happening :-)
Just in case, here's the Query class:
class Query extends QueryBase
{
function Query($query, &$conn)
{
$this->recordset = array();
$this->has_error=0;
$regs = mysqli_query($conn->getConnection(), $query);
if(!$regs)
{
$this->has_error=1;
return;
}
$index = 0;
$this->current_index=-1;
while(($row = mysqli_fetch_array($regs, MYSQL_ASSOC)))
{
$this->recordset[$index]=$row;
$index++;
}
mysqli_free_result($regs);
}
public function moveNext()
{
if($this->current_index<(sizeof($this->recordset)-1))
{
$this->current_index++;
return 1;
}
else
return 0;
}
public function moveBack()
{
if($this->current_index>=1)
{
$this->current_index--;
return 1;
}
else
return 0;
}
public function recordCount()
{
return sizeof($this->recordset);
}
public function get($field)
{
return $this->recordset[$this->current_index][$field];
}
public function getRow()
{
return $this->recordset[$this->current_index];
}
public function hasError()
{
return $this->has_error;
}
}
Thank you for your help.
It sounds as though you are executing that block of code more than once, can you post the rest of the code? The code that deals with the 'put'?
As a general rule, if the process for checking for the existence of a value and insertion of a value are separate, there can be double entries due to someone accidentally clicking the submit button more than once (for example - or other action? image click...)
It's often a good practice to embed a duplicate submission check so that double submissions are avoided.
I'm not sure if this will fix it for you, but it would be better to return "false;" instead of returning "null" upon failure from your getManageFromName() method.
Another thing to check would your hasErrors() method; it might be returning errors for some reason sometimes?
You might be better off to write it like this:
private function getMangaFromName($name) {
$id = $this->generateId($name);
$mangas = new Query("SELECT * FROM tbl_manga WHERE manga_cid = '" . $this->conn->escapeString($id) . "'", $this->conn);
if($mangas->moveNext()) {
return $mangas->getRow();
} else {
if(!$mangas || $mangas->hasError()) {
logError("getMangaFromName($name): " . $this->conn->getError());
}
return null;
}
}
// Then check it like this:
$manga = $this->getMangaFromName($name);
if ( empty($manga) ) {
$manga = $this->addManga($name);
}

Running _remap() once

When a user is logged in, I would like them to be able to visit http://website.com/user and be taken to http://website.com/1/johndoe, where 1 is their user ID, and johndoe is their user name.
I'm trying to use _remap() to catch all attempts at http://website.com/user/, so even incomplete URIs like http://website.com/user/1 or http://website.com/user/1/joh are redirected to http://website.com/user/1/johndoe.
Here's what I've tried:
class User extends CI_Controller {
function index($uID, $user) {
echo $uID;
echo $user;
}
function _remap() {
$uID = 3;
$user = 'johndoe';
//redirect('user/'.$uID.'/'.$user); // Updates URI, but redirect loop
//$this->index($uID, $user); Works, but doesn't update the URI
}
}
I could of course detect the method first, and do something like this:
function _remap($method = '') {
if ($method != 'view') {
$uID = 3;
$user = 'johndoe';
redirect('user/view/'.$uID.'/'.$user);
}
}
function view($uID, $user) {
echo $uID;
echo $user;
}
But then I think the URI would look like http://website.com/user/view/1/johndoe, and I'd rather view was excluded. How should I go about this problem?
If you have a _remap() method - it will always be called, so redirecting to user/anything will still call _remap() on the next request, so not only do you need to catch the router method and its parameters - you must do it if you want to use _remap() in a way that makes any sense:
public function _remap($method, $args)
{
if ($method === 'user' && (empty($args) OR ! ctype_digit($args[0])))
{
// determine and handle the user ID and name here
}
else
{
return call_user_func_array(array($this, $method), $args));
}
}
The solution I use is:
$route['user/(:num)/:any'] = 'user/view/$1';
$route['user/(:num)'] = 'user/view/$1';
Really, the username should only be for SEO purposes and in which case, should not be passed to the action. You will of course be able to access the username from the UserID when you look up the user anyway, so I feel it's redundant.
The above will match
/user/1/jdoe
/user/1
but will only pass 1 to your user/view action.
Edit: With your comment in mind:
$route['user/(:num)/(:any)'] = 'user/view/$1/$2';
$route['user/(:num)'] = 'user/view/$1';
function view($UserID, $UserName = null) {
// Load the model and get the user.
$this->model->load('user_model');
$User = $this->user_model->GetByUserID($UserID);
// If the user does not exist, 404!
if (empty($User)) {
show_404();
return;
}
// If the UserName does not exist, or is wrong,
// redirect to the correct page.
if($UserName === null || strtolower($User->UserName) != strtolower($UserName)) {
redirect("user/$UserID/{$User->UserName}");
return;
}
}
The above will accept the username as the parameter, however if it is not supplied or if it is not correct, it will redirect to the correct url and continue.
Hopefully this solves your problem?

Categories