I am working on an application that I need to post a secret variable. I wrote this code.
<form target="_blank" action="/validate/lista.php" method="POST">
<input type="hidden" name="evento" value="<?php echo $pname ?>" />
<button class="btn btn-block btn-md btn-outline-success">Lista</button>
</form>
My problem is that if the user inspect the element with chrome or whatever, he can see the value and change it before POST.
I could use SESSION but every user has a different session ID and this way I would need to POST the session ID (because they are separete applications), which I think is not secure. Or is it ok?
How can I prevent this? I am new to programming...
Thank you
Maintain HTML Form State safely ('Conversation' Tracking)
Keep track of the 'state' of an HTML Form as it is processed by the client and the server.
The typical 'conversation' is:
Send a new form to the client, often for a specific user who has to login.
The client enters data and returns it.
It is validated and may be sent out again.
The data changes are applied.
the client is informed of the result.
It sounds simple. Alas, we need to keep track of the 'state' of the form during the 'conversation'.
We need to record the state in a hidden field. This can open us up to various 'failure modes'.
This answer is one method of reliably keeping track of the 'conversations'.
Including people being 'malicious'. It happens. ;-/
This is a data change form so we don't want it applied to the wrong person.
There are various requirements:
Sensible ones:
prevent a form being processed twice
Ask a user to confirm the data if the form is too old
Malicious ones:
Changing the form to appear to be from a different user
Using an old copy of the form
Changing other hidden data to corrupt the user data
Now, we cannot prevent the client changing the hidden data, or storing it to replay later. etc.
What to do?
We need to ensure that if it is changed then we can detect that it is tampered with and tell the user about it. We do nothing.
If they send us an old stored valid copy then we can detect that as well.
Is there a simple way of doing this? Oh yes! :)
Answers:
Give each form a unique id: makes it easy to determine if we have already seen it.
Give each form a timestamp of when it was first created.
we can then decide the max age we allow to use it.
If it is too old then we just copy the entered data to a new form and ask the user to confirm it. see Captcha :)
When we process the form we store the form id.
The first check before processing a form is to see if we have already processed it
Identifying 'tampering'?
We encrypt it with AES! :) Only the server needs to know the password so there are no client issues.
If it is changed then the decrypt will fail and we just issue a new form to the user with the data input on it. :)
Is it a lot of code? Not really. And it makes forms processing safe.
One advantage is that has the protection for the CSRF attack built in so no separate code needed.
Program Code (FormState Class)
<?php
/**
* every 'data edit' form has one of these - without exeception.
*
* This ensures that the form I sent out came from me.
*
* It has:
* 1) A unique #id
* 2) A date time stamp and a lifetime
*
* Can be automatically generated and checked.
*/
class FormState {
const MAX_FORM_AGE = 600; // seconds
const ENC_PASSWORD = '327136823981d9e57652bba2acfdb1f2';
const ENC_IV = 'f9928260b550dbb2eecb6e10fcf630ba';
protected $state = array();
public function __construct($prevState = '')
{
if (!empty($prevState)) {
$this->reloadState($prevState); // will not be valid if fails
return;
}
$this->setNewForm();
}
/**
* Generate a new unique id and timestanp
*
* #param $name - optional name for the form
*/
public function setNewForm($name = '')
{
$this->state = array();
$this->state['formid'] = sha1(uniqid(true)); // each form has a unique id
$this->state['when'] = time();
if (!empty($name)) {
$this->setAttribute('name', $name);
}
}
/**
* retrieve attribute value
*
* #param $name attribute name to use
* #param $default value to return if attribute does not exist
*
* #return string / number
*/
public function getAttribute($name, $default = null)
{
if (isset($this->state[$name])) {
return $this->state[$name];
} else {
return $default;
}
}
/**
* store attribute value
*
* #param $name attribute name to use
* #param $value value to save
*/
public function setAttribute($name, $value)
{
$this->state[$name] = $value;
}
/**
* get the array
*/
public function getAllAttributes()
{
return $this->state;
}
/**
* the unique form id
*
* #return hex string
*/
public function getFormId()
{
return $this->getAttribute('formid');
}
/**
* Age of the form in seconds
* #return int seconds
*/
public function getAge()
{
if ($this->isValid()) {
return time() - $this->state['when'];
}
return 0;
}
/**
* check the age of the form
*
*#param $ageSeconds is age older than the supplied age
*/
public function isOutOfDate($ageSeconds = self::MAX_FORM_AGE)
{
return $this->getAge() >= $ageSeconds;
}
/**
* was a valid string passed when restoring it
* #return boolean
*/
public function isValid()
{
return is_array($this->state) && !empty($this->state);
}
/** -----------------------------------------------------------------------
* Encode as string - these are encrypted to ensure they are not tampered with
*/
public function asString()
{
$serialized = serialize($this->state);
$encrypted = $this->encrypt_decrypt('encrypt', $serialized);
$result = base64_encode($encrypted);
return $result;
}
/**
* Restore the saved attributes - it must be a valid string
*
* #Param $prevState
* #return array Attributes
*/
public function fromString($prevState)
{
$encrypted = #base64_decode($prevState);
if ($encrypted === false) {
return false;
}
$serialized = $this->encrypt_decrypt('decrypt', $encrypted);
if ($serialized === false) {
return false;
}
$object = #unserialize($serialized);
if ($object === false) {
return false;
}
if (!is_array($object)) {
throw new \Exception(__METHOD__ .' failed to return object: '. $object, 500);
}
return $object;
}
public function __toString()
{
return $this->asString();
}
/**
* Restore the previous state of the form
* will not be valid if not a valid string
*
* #param $prevState an encoded serialized array
* #return bool isValid or not
*/
public function reloadState($prevState)
{
$this->state = array();
$state = $this->fromString($prevState);
if ($state !== false) {
$this->state = $state;
}
return $this->isValid();
}
/**
* simple method to encrypt or decrypt a plain text string
* initialization vector(IV) has to be the same when encrypting and decrypting
*
* #param string $action: can be 'encrypt' or 'decrypt'
* #param string $string: string to encrypt or decrypt
*
* #return string
*/
public function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = self::ENC_PASSWORD;
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$secret_iv_len = openssl_cipher_iv_length($encrypt_method);
$secret_iv = substr(self::ENC_IV, 0, $secret_iv_len);
if ( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
} else if( $action == 'decrypt' ) {
$output = openssl_decrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
}
if ($output === false) {
// throw new \Exception($action .' failed: '. $string, 500);
}
return $output;
}
}
Example Code
Full Example Application Source Code (Q49924789)
Website Using the supplied Source Code
FormState source code
Do we have an existing form?
$isExistingForm = !empty($_POST['formState']);
$selectedAction = 'start-NewForm'; // default action
if ($isExistingForm) { // restore state
$selectedAction = $_POST['selectedAction'];
$formState = new \FormState($_POST['formState']); // it may be invalid
if (!$formState->isValid() && $selectedAction !== 'start-NewForm') {
$selectedAction = "formState-isWrong"; // force user to start a new form
}
} else {
$_POST = array(); // yes, $_POST is just another PHP array
$formState = new \FormState();
}
Start New Form
$formState = new \FormState();
$_POST = array();
$displayMsg = "New formstate created. FormId: ". $formState->getFormId();
Store UserId (Database Id) in the FormState
$formState->setAttribute('userId' $userId);
Check a form being to old?
$secsToBeOutOfDate = 3;
if ($formState->isOutOfDate($secsToBeOutOfDate)) {
$errorMsg = 'Out Of Date Age: '. $secsToBeOutOfDate .'secs'
.', ActualAge: '. $formState->getAge();
}
Reload State from the form hidden field.
$formState = new \FormState('this is rubbish!!');
$errorMsg = "formState: isValid(): ". ($formState->isValid() ? 'True' : 'False');
Check if a form has already been processed.
if (isset($_SESSION['processedForms'][$formState->getFormId()])) {
$errorMsg = 'This form has already been processed. (' . $formState->getFormId() .')';
break;
}
$_SESSION['processedForms'][$formState->getFormId()] = true;
$displayMsg = "Form processed and added to list.";
While looking into this question I came up with the following solution that is called from canDelete() in an extension to File:
protected function isFileInUse()
{
$owner = $this->getOwner();
$dataObjectSubClasses = ClassInfo::subclassesFor('DataObject');
$classesWithFileHasOne = [];
foreach ($dataObjectSubClasses as $subClass) {
$hasOnes = array_flip($subClass::create()->hasOne());
if (array_key_exists($owner->class, $hasOnes)) {
$classesWithFileHasOne[$subClass] = $hasOnes[$owner->class];
}
}
$threshold = (Director::get_current_page()->class == 'AssetAdmin') ? 1 : 2;
$uses = 0;
foreach ($classesWithFileHasOne as $class => $relation) {
$uses += count($class::get()->filter("{$relation}ID", $this->owner->ID));
if ($uses >= $threshold) {
return true;
}
}
return false;
}
There is one edge case I can't get around though. If, say, a featured image is changed on a blog post then if there is exactly one other use of the same image then with this approach it will still allow it to be deleted. This is because until the page is saved the current change doesn't count towards uses of the image.
The threshold is set differently in CMS Pages and the Media Manager to allow an image to be deleted from within the page that is using it.
Is there a way that I can access the containing page (or other element - we're using Elemental) from within my File extension to see if its associated image has changed?
This is the solution I eventually came up with. I'm not entirely happy with having to inspect the request but couldn't see any other solution:
public function canDelete($member = null)
{
return !$this->isFileInUse();
}
/**
* Check if the file is in use anywhere on the site
* #return bool True if the file is in use
*/
protected function isFileInUse()
{
$owner = $this->getOwner();
$dataObjectSubClasses = ClassInfo::subclassesFor('DataObject');
$classesWithFileHasOne = [];
foreach ($dataObjectSubClasses as $subClass) {
$hasOnes = array_flip($subClass::create()->hasOne());
if (array_key_exists($owner->class, $hasOnes)) {
$classesWithFileHasOne[$subClass] = $hasOnes[$owner->class];
}
}
$threshold = ($this->isAssetAdmin() || ($this->isFileAttach($classesWithFileHasOne))) ? 1 : 2;
$uses = 0;
foreach ($classesWithFileHasOne as $class => $relation) {
$uses += count($class::get()->filter("{$relation}ID", $this->owner->ID));
if ($uses >= $threshold) {
return true;
}
}
return false;
}
/**
* Are we in the asset manager rather than editing a Page or Element?
* #return bool
*/
protected function isAssetAdmin()
{
return 'AssetAdmin' === Director::get_current_page()->class;
}
/**
* Is the current action attaching a file to a field that we're interested in?
* #param array $classesWithFileHasOne Classes with a relationship we're interested in and the name of the
* relevant field
* #return bool
*/
protected function isFileAttach($classesWithFileHasOne)
{
$controller = Controller::curr();
$field = $controller->request->allParams()['FieldName'];
return (preg_match('/attach$/', $controller->requestParams['url']) &&
($controller->action == 'EditForm')
&& (in_array($field, array_values($classesWithFileHasOne))));
}
I am using SimpleBrowser that is a part of SimpleTest PHP framework.
The idea is to imitate user interactions with the website and record returned HTML code into a file for further comparison. But something goes wrong here as empty HTML is sometimes returned.
getTransportError() returns Nothing fetched
It happens in completely random places and I can't use back() function because most pages are submitted forms.
require_once('simpletest/browser.php');
class TesterBrowser extends SimpleBrowser
{
/**
* Test the page against the reference. If reference is missing, is it created
* Uses md5 checksum to check if files are identical
*
* #param string $forcename Optional. Substitude autogenerated filename.
* #param boolean $forceRef Optional. Force file to be saved as the reference
*
* #access public
*
* #return void
*/
public function testPage($forcename = "")
{
//who called me?
//$callers=debug_backtrace();
//$whocalledme = $callers[1]['function'];
//get the current source
$html = $this->getContent();
//generate filename
$filename = empty($forcename) ? preg_replace('/[^\w\-'. ''. ']+/u', '-', $this->getUrl()) : $forcename;
$filename .= ".html";
//is there a gauge?
if(file_exists("ref/".$filename) && filesize(dirname(__FILE__)."/ref/".$filename) > 0)
{
//is there a difference
file_put_contents(dirname(__FILE__)."/actual/".$filename, $html);
if(filesize(dirname(__FILE__)."/actual/".$filename) == 0)
{
return false;
}
if(md5_file(dirname(__FILE__)."/actual/".$filename) != md5_file(dirname(__FILE__)."/ref/".$filename))
{
echo $this->getUrl() . " (" . $filename . ") has changed \r\n";
}
}
else
{
file_put_contents(dirname(__FILE__)."/ref/".$filename, $html);
if(filesize(dirname(__FILE__)."/ref/".$filename) == 0)
{
return false;
}
}
return true;
}
/**
* Output the string to the terminal
*
* #param mixed $string String to output
*
* #access public
*
* #return void
*/
public function output($string)
{
echo date("d-m-Y H:i:s") . " - $string... \r\n";
//update date so that it will be the same on every page
exec('date -s "24 JUN 2013 10:00:00"');
}
/**
* Restore the server date using external NTP server
*
* #access public
*
* #return void
*/
public function restoreDate(){
$this->output("Restoring the date&time from NTP server");
exec("ntpdate 0.uk.pool.ntp.org");
exec("hwclock -systohc");
}
}
And the way tests are performed:
class Tester
{
public $browser = null;
const BASEURL = "http://ticketing/";
function __construct(){
$this->browser = new TesterBrowser();
$this->browser->setConnectionTimeout(180);
//get the list of class method to be run
$methods = array();
foreach(get_class_methods($this) as $var)
{
if(0 === strpos($var, 'test')) //they all start with test
{
$methods[] = $var;
}
}
$methods[] = "cleanUp";
//now we need to run these methods
foreach($methods as $m){
while($this->$m() == false){
$this->browser->output("Empty page, trying again");
sleep(5);
}
}
}
//index page
function testGetIndexPage()
{
$this->browser->output("Getting index page");
$this->browser->get(self::BASEURL);
return $this->browser->testPage();
}
//try to enter wrong password
function testWrongPassword()
{
$this->browser->output("Entering wrong credentials");
$this->browser->setField("username", "wrong");
$this->browser->setField("password", "wrong");
$this->browser->clickSubmitByName("submit");
return $this->browser->testPage("wrong-credentials");
}
//Delete ticket though admin
function testDeleteTicketThroughAdmin()
{
$this->browser->output("Deleting the ticket through admin page");
$this->browser->setField("bulk[]", "375341");
$this->browser->setField("bulkaction", "delete");
$this->browser->clickSubmit("Do Action");
return $this->browser->testPage("deleted-ticket-admin");
}
//Restore the date
function cleanUp()
{
$this->browser->restoreDate();
return true;
}
}
$tester = new Tester();
There are of course much more test performed and this is a stripped version.
I have googled a lot about this problem, there seems to be no adequate documentation whatsoever.
Solved. It was a timeout issue although for some reason no adequate error message is implemented.
$this->browser->setConnectionTimeout(180);
I would like to make a PHP if condition code that will check if the last 10 articles or 10 minutes from the article reading by the user have already elapsed.
E.g.
A user open a page with id = 235 (this id value is in the url localhost/article/235 )
and this id value will be saved in session with a current timestamp and maybe his IP address
Then he read another article and the same will happen.
I need to remember the clicked stuff for another ten clicks and then reset that only for the first row. E.g. after the 10th click the id and timestamp will not became 11th row but will replace the 1st row in the list.
The php condition in CodeIgniter will then check these values and will update the article hit counter value in the articles table and column counter like this:
$this->db->where('id', $id);
$this->db->set('counter', 'counter+1', FALSE);
$this->db->update('articles');
But before calling this code I need to make this check from the session?
How to do that?
I think storing e.g. 10 entries in the session with timestamps per user will be enough.
Just don't save the same page in the session twice.
And the condition will check the current timestamp with the saved one and if it is more than e.g. 10 minutes or the user have read/clicked another 10 articles it will allow the update counter php code.
I don't need to have this bulletproof. Just to disable the increment using browser's refresh button.
So, if he wants to increment the counter he will need to wait ten minutes or read another 10 articles ;)
You should definitely go for Sessions. It saves you bandwidth consumption and is much easier to handle. Unless, of course, you need the data on the client-side, which, by your explanation, I assume you don't. Assuming you went for sessions, all you gotta do is store an array with the data you have. The following code should do it:
$aClicks = $this->session
->userdata('article_clicks');
// Initialize the array, if it's not already initialized
if ($aClicks == false) {
$aClicks = array();
}
// Now, we clean our array for the articles that have been clicked longer than
// 10 minutes ago.
$aClicks = array_filter(
$aClicks,
function($click) {
return (time() - $click['time']) < 600; // Less than 10 minutes elapsed
}
);
// We check if the article clicked is already in the list
$found = false;
foreach ($aClicks as $click) {
if ($click['article'] === $id) { // Assuming $id holds the article id
$found = true;
break;
}
}
// If it's not, we add it
if (!$found) {
$aClicks[] = array(
'article' => $id, // Assuming $id holds the article id
'time' => time()
);
}
// Store the clicks back to the session
$this->session
->set_userdata('article_clicks', $aClicks);
// If we meet all conditions
if (count($aClicks) < 10) {
// Do something
}
I assumne that $clicks is an array with up to ten visited articles. The id is used as key and the timestamp as value. $id is the id of the new article.
$clicks = $this->session->userdata('article_clicks');
//default value
$clicks = ($clicks)? $clicks : array();
//could be loaded from config
$maxItemCount = 10;
$timwToLive= 600;
//helpers
$time = time();
$deadline = $time - $timeToLive;
//add if not in list
if(! isset($clicks[$id]) ){
$clicks[$id] = $time;
}
//remove old values
$clicks = array_filter($clicks, function($value){ $value >= $deadline;});
//sort newest to oldest
arsort($clicks);
//limit items, oldest will be removed first because we sorted the array
$clicks = array_slice($clicks, 0, $maxItemCount);
//save to session
$this->session->>set_userdata('article_clicks',$clicks)
Usage:
//print how mch time has passed since the last visit
if(isset($clicks[$id]){
echo "visited ".($time-$clicks[$id]). "seconds ago." ;
} else {
echo "first visit";
}
EDIT: you have to use arsort not rsort or the keys will be lost, sorry
Based on Raphael_ code and your question you can try this:
<?php
$aClicks = $this->session
->userdata('article_clicks');
$nextId = $this->session->userdata('nextId');
// Initialize the array, if it's not already initialized
if ($aClicks == false) {
$aClicks = array();
$nextId = 0;
}
// Now, we clean our array for the articles that have been clicked longer than
// 10 minutes ago.
$aClicks = array_filter($aClicks, function($click) {
return (time() - $click['time']) < 600; // Less than 10 minutes elapsed
}
);
// We check if the article clicked is already in the list
$found = false;
foreach ($aClicks as $click) {
if ($click['article'] === $id) { // Assuming $id holds the article id
$found = true;
break;
}
}
// If it's not, we add it
if (!$found) {
$aClicks[$nextId] = array(
'article' => $id, // Assuming $id holds the article id
'time' => time()
);
$nextId++;
$this->session->set_userdata('nextId', $nextId);
}
$this->session->set_userdata('article_clicks', $aClicks);
if (count($aClicks) > 10 && $nextId > 9) {
$this->session->set_userdata('nextId', 0);
echo "OK!";
}
?>
I hope I understood correctly what you need.
Usage:
$this->load->library('click');
$this->click->add($id, time());
The class API is very simple and the code is commented. You can also check if an item expired(), if exists() and you can get() item saved time.
Remember that:
Each item will expire after 10 minutes (see $ttl)
Only 10 items are saved in session (see $max_entries)
class Click
{
/**
* CI instance
* #var object
*/
private $CI;
/**
* Click data holder
* #var array
*/
protected $clicks = array();
/**
* Time until an entry will expire
* #var int
*/
protected $ttl = 600;
/**
* How much entries do we store ?
* #var int
*/
protected $max_entries = 10;
// -------------------------------------------------------------------------
public function __construct()
{
$this->CI =& get_instance();
if (!class_exists('CI_Session')) {
$this->CI->load->library('session');
}
// load existing data from user's session
$this->fetch();
}
// -------------------------------------------------------------------------
/**
* Add a new page
*
* #access public
* #param int $id Page ID
* #param int $time Added time (optional)
* #return bool
*/
public function add($id, $time = null)
{
// If page ID does not exist and limit has been reached, stop here
if (!$this->exist($id) AND (count($this->clicks) == $this->max_entries)) {
return false;
}
$time = !is_null($time) ? $time : time();
if ($this->expired($id)) {
$this->clicks[$id] = $time;
return true;
}
return false;
}
/**
* Get specified page ID data
*
* #access public
* #param int $id Page ID
* #return int|bool Added time or `false` on error
*/
public function get($id)
{
return ($this->exist($id)) ? $this->clicks[$id] : false;
}
/**
* Check if specified page ID exists
*
* #access public
* #param int $id Page ID
* #return bool
*/
public function exist($id)
{
return isset($this->clicks[$id]);
}
/**
* Check if specified page ID expired
*
* #access public
* #param int $id Page ID
* #return bool
*/
public function expired($id)
{
// id does not exist, return `true` so it can added
if (!$this->exist($id)) {
return true;
}
return ((time() - $this->clicks[$id]) >= $this->ttl) ? true : false;
}
/**
* Store current clicks data in session
*
* #access public
* #return object Click
*/
public function save()
{
$this->CI->session->set_userdata('article_clicks', serialize($this->clicks));
return $this;
}
/**
* Load data from user's session
*
* #access public
* #return object Click
*/
public function fetch()
{
if ($data = $this->CI->session->userdata('article_clicks')) {
$this->clicks = unserialize($data);
}
return $this;
}
public function __destruct()
{
$this->save();
}
}
You could easily wrap that into a class of it's own that serializes the information into a string and that is able to manipulate the data, e.g. to add another value while taking care to cap at the maximum of ten elements.
A potential usage could look like, let's assume the cookie last would contain 256 at start:
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(10), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(20), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(30), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(40), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(50), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(60), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(70), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(80), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(90), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(100), "\n";
And the output (Demo):
10,256
20,10,256
30,20,10,256
40,30,20,10,256
50,40,30,20,10,256
60,50,40,30,20,10,256
70,60,50,40,30,20,10,256
80,70,60,50,40,30,20,10,256
90,80,70,60,50,40,30,20,10,256
100,90,80,70,60,50,40,30,20,10
A rough implementation of that:
class StringQueue implements Countable
{
private $size = 10;
private $separator = ',';
private $values;
public function __construct($string) {
$this->values = $this->parseString($string);
}
private function parseString($string) {
$values = explode($this->separator, $string, $this->size + 1);
if (isset($values[$this->size])) {
unset($values[$this->size]);
}
return $values;
}
public function add($value) {
$this->values = $this->parseString($value . $this->separator . $this);
return $this;
}
public function __toString() {
return implode(',', $this->values);
}
public function count() {
return count($this->values);
}
}
It's just some basic string operations, here with implode and explode.
I am using codeigniter on the backend .
Recently i heard about the term "CSRF" and decided to protect the requests.
Almost all actions on my site is through Ajax & some times i am creating/appending page content using DOM manipulation [here the doubt, how can i inject the CSRF token to the view files ?]
Okkey ,after that how can i validate that ?
Assume that i added these values as token and passed to server ,then can i use constructors to check & validate this ?
Ex :
Class Cl_Controller extends Ci_controller
{
function __construct()
{
//loading libraries,models,helpers etc...
if (isset($this->input->get_post("CSRF_TOKEN")) || _another_condition_)
{
// The CSRF TOKEN is invalid or null ,the action cannot be done...
}
}
function register()
{
//some codes...
}
function delete_user()
{
//some codes
}
}
Is it possible to do some thing link this ?
Please suggest me some good ideas & usual practices.
Thank you.
For my ajax calls I usually perform two checks;
Make sure it is an ajax request, using a small helper file.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('ajax_check')) {
/**
* Check AJAX
*
* Checks to see if you (or the royal I) are dealing with an AJAX call.
*
* #return boolean
*/
function ajax_check() {
if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
return TRUE;
} else {
show_404();
return FALSE;
}
}
}
if ( ! function_exists('ajax_response')) {
/**
* JSON Response Wrapper
*
* Wraps up any data nicely for sending back to an ajax call
*
* #return string
*/
function ajax_response($status, $data) {
if (!is_array($data)) {
$data = array();
}
// Set the JSON header appropriately
header('Content-Type: application/json');
// Echo out the array into json
echo json_encode(array_merge(array('status' => $status), $data));
exit;
}
}
if ( ! function_exists('ajax_force_fail')) {
/**
* Force AJAX Failure
*
* If you ever need to, force an AJAX to fail
*/
function ajax_force_fail() {
$_ci =& get_instance();
$_ci->output->set_status_header(500);
}
}
Usage like;
public function some_function() {
$this->load->helper('ajax');
ajax_check();
try {
// do something
ajax_response('success', array('data' => $some_var));
} catch (Exception $e) {
ajax_response('failure', array('data' => $e->getMessage()));
}
}
And a similar approach to xsrf.
File:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('xsrf_get_token')) {
/**
* Get XSRF Token
*
* Returns a token that exists for one request that verifies that
* the action was executed by the person that requested it
*
* #return string
*/
function xsrf_get_token() {
$ci =& get_instance();
if ($ci->session->userdata('xsrf_hash')) {
$token = $ci->session->userdata('xsrf_hash');
} else {
// Generate the token
$token = sha1(microtime().$ci->uri->uri_string());
// Set it in the session
$ci->session->set_userdata('xsrf_hash', $token);
}
//Return it
return $token;
}
}
if ( ! function_exists('xsrf_get_token_field')) {
/**
* Get XSRF Token Field
*
* Returns an xhtml form element to include xsrf token.
* You can specify the id/name attribute of the input.
* Has a dependancy to get_xsrf_token().
*
* #param string The id/name to be used
* #return string
*/
function xsrf_get_token_field($name='auth_token') {
return '<input type="hidden" id="'.$name.'" name="'.$name.'" value="' .xsrf_get_token(). '" />';
}
}
if ( ! function_exists('xsrf_delete_token')) {
/**
* Delete XSRF Token
*
* Deletes the xsrf token
*
* #return boolean
*/
function xsrf_delete_token() {
$ci =& get_instance();
if ($ci->session->userdata('xsrf_hash')) {
$ci->session->unset_userdata('xsrf_hash');
return TRUE;
} else {
return FALSE;
}
}
}
if ( ! function_exists('xsrf_check_token')) {
/**
* Get XSRF Token Field
*
* Checks that the token is still valid, returns true if so.
* Deletes old token after valid or fail.
* Has a dependacy to xsrf_delete_token()
*
* #param string The challenge token
* #return boolean
*/
function xsrf_check_token($challenge_token) {
// CI
$ci =& get_instance();
// Get the stored token
$token = $ci->session->userdata('xsrf_hash');
// Delete the old token
xsrf_delete_token();
// Returns if the token is the right token
return ($token == $challenge_token);
}
}
Usage (controller);
public function some_other_function() {
$this->form_validation->set_rules('username', 'Username', 'required|callback_check_token');
if($this->form_validation->run() == TRUE ) {
// do something
} else {
// something else
}
}
// callback function
public function check_token($val) {
if (xsrf_check_token($val) == TRUE) {
return TRUE;
} else {
$this->form_validation->set_message('check_token', 'Oops');
return FALSE;
}
}
In view;
<form action="" method="post">
<?php echo xsrf_get_token_field(); ?>
...
</form>