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?
Related
I am trying to create a form whose structure depends on the parameter in the url. If no parameter is specified in the url, an error message should be displayed. Depending on the id, a database query is performed and the form is filled with data.
example url: http://127.0.0.1/local/group/signin.php?groupid=14
Unfortunately, my form doesn't validate after I submit the form via pressing the action button. It jumps to http://127.0.0.1/local/group/signin.php and because there is no parameter present in the url the error message 'No group found' is displayed.
What have I done wrong here?
signinform.php:
class signinform extends moodleform {
public function definition() {
global $DB;
global $USER;
$mform = $this->_form;
$urlid = $this->_customdata['id']; // get the passed group id
$message = 'No group found';
if(is_null($urlid)){
$mform->addElement('html', '<h3>'.\core\notification::error($message).'</h3>');
}
else{
// build the form, sql query etc.
$this->add_action_buttons(true, 'Submit');
}
}
function validation($data, $files) {
return array();
}
}
signin.php:
$PAGE->set_url(new moodle_url('/local/schedule/signin.php?'));
$PAGE->set_context(\context_system::instance());
$PAGE->set_pagelayout('base');
$PAGE->set_title("Sign up");
$PAGE->set_heading("Sign up for a group");
global $DB;
global $USER;
$urlid = $_GET["id"];
$to_form = array('id' => $urlid); // pass group id to form
$mform = new signinform(null, $to_form);
$homeurl = new moodle_url('/');
if ($mform->is_cancelled()) {
redirect($homeurl, 'Cancelled.'); // Just for testing, never enters here
} else if ($fromform = $mform->get_data()) {
redirect($homeurl, 'Validation in process'); // Just for testing, never enters here
}
echo $OUTPUT->header();
$mform->display();
echo $OUTPUT->footer();
You need to add a hidden field to the form that contains the 'id' that has to be passed to the page, otherwise, when the form is submitted, that id will no longer be present in the params for that page.
e.g. (in definition())
$mform->addElement('hidden', 'id', $urlid);
$mform->setType('id', PARAM_INT);
Also, in Moodle, you should never access $_GET directly - use the wrapper functions required_param() or optional_param(), as these:
Clean the parameter to the declared type
Automatically take parameters from either $_GET or $_POST (which will be important in this case, as the 'id' param will be part of the POST data, when you submit the form)
Handle missing parameters by either applying a default (optional_param) or stopping with an error message (required_param)
So your access to $_GET['id'], should be replaced with:
$urlid = optional_param('id', null, PARAM_INT);
My form contains a model object that contains five child objects that are related using hasMany. When I save the form, I notice that all fields, regardless if they are empty, are saved into the database. Is it possible to set a condition in the beforeSave() callback method to prevent child items that have no values from being saved? I have tried to unset the key in the array containing empty values but the row is still being added into the database.
Here is the code for my 'Mtd' model. The Mtd model contains many Flowratereatments. On my form, I have a checkbox that says 'This is a Flow Rate Based Treatment'. So, if a user clicks on that, then the user can fill it in the fields. However, if the user does not fill it in, I want to prevent a new row from being added with just the foreign key of the Mtd table.
<?php
class Mtd extends AppModel {
public $name = 'Mtd';
public $hasOne = array('Treatmentdesign', 'Volumetreatment');
public $hasMany = 'Flowratetreatment';
function beforeSave() {
if($this->data['Mtd']['is_settling'] != 1){
unset($this->data['Flowratetreatment'][0]);
}
return true;
}
}
?>
Did you tried something like:
class User extends AppModel {
function validates() {
$this->setAction();
#always validate presence of username
$this->validates_presence_of('username');
#validate uniqueness of username when creating a new user
$this->validates_uniqueness_of('username',array('on'=>'create'));
#validate length of username (minimum)
$this->validates_length_of('username',array('min'=>3));
#validate length of username (maximum)
$this->validates_length_of('username',array('max'=>50));
#validate presence of password
$this->validates_presence_of('password');
#validate presence of email
$this->validates_presence_of('email');
#validate uniqueness of email when creating a new user
$this->validates_uniqueness_of('email',array('on'=>'create'));
#validate format of email
$this->validates_format_of('email',VALID_EMAIL);
#if there were errors, return false
$errors = $this->invalidFields();
return (count($errors) == 0);
}
}
?>
in your model
I have used this code:
public function beforeSave() {
if(isset($this->data[$this->alias]['profile_picture'])) {
if($this->data[$this->alias]['profile_picture']['error']==4) {
unset($this->data[$this->alias]['profile_picture']);
}
}
return true;
}
in a previous app, to remove a key from $this->data if the user had not uploaded a file, to prevent the old value being overwritten.
this should work for you (you'll need to adapt it; based on what $this->data contains at this point.
public function beforeSave() {
if(empty($this->data[$this->alias]['the_key'])) {
unset($this->data[$this->alias]['the_key']);
}
//debug($this->data); exit; // this is what will be saved
return true;
}
you mention you tried this already? post your code in your original post.
I have a Notices class which is used to provide one-time message notifications. The notifications are stored in a Kohana session (cookie) and my getter looks like this (echos are there for debugging purposes):
private static function _get($key)
{
$session = Session::instance();
$value = $session->get($key);
echo $key . ': ' . $session->get($key) . '<br />';
$session->delete($key);
echo 'deleted ' .$key. ', value now: ' .$session->get($key). '<br /><br />';
return $value;
}
Now, one page I use this class on is the login page. When a user enters incorrect login info it does what I expect it too: displays an error saying "username/password incorrect". This message should then be deleted as I've called the getter and the getter deletes it from the session.
However, this is not the case. The message is shown on the next page too. And if I go to any other page which displays notifications, the error is shown there too, until a different error is displayed (say if I enter information on another form incorrectly).
This is what is displayed from the echo's on the page:
error: Username/password combination is incorrect.
deleted error, value now:
Which suggests that the value is being deleted, but it's still showing the same thing when I refresh the page.
If it helps this is what the process of setting an error message is like, from a controller I call Notices::error('message'); which calls this method:
public static function error($value = NULL)
{
return Notices::_notice('error', $value);
}
which calls:
private static function _notice($key, $value)
{
// If value is not NULL, then store the notice
if ( ! is_null($value))
{
Notices::_set($key, $value);
return TRUE;
}
else
{
// If value is not NULL. retieve key and format HTML
if ( ! is_null($value = Notices::_get($key)))
{
return "$value";
}
// If the key does not exist return empty string
else
{
return '';
}
}
}
and the setter looks like this:
private static function _set($key, $value)
{
Session::instance()->set($key, $value);
return TRUE;
}
If session settings are playing up on you, check two things:
Your browser cache is not so aggressive that it appears to hold on to remnants of elements unless you force flush it.
You're not also setting the session in the same method, thus having it re-fill the variable when you refresh the page or call the method again.
You should also check out the existing get_once method in the Kohana_Session class that destroys the variable after it's done taking it out of the $_SESSION variable:
/**
* Get and delete a variable from the session array.
*
* $bar = $session->get_once('bar');
*
* #param string variable name
* #param mixed default value to return
* #return mixed
*/
public function get_once($key, $default = NULL)
{
$value = $this->get($key, $default);
unset($this->_data[$key]);
return $value;
}
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();
I use this class (taken from a blog tutorial) to generate unique keys to validate a form:
class formKey {
//Here we store the generated form key
private $formKey;
//Here we store the old form key
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.
$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."' />";
return $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.
return true;
}
else {
//The key is invalid, return false.
return false;
}
}
}
Everything in my website goes through index.php first, so I put this in index.php: $formKey = new formKey();
Then, in every form I put this: <?php $formKey->outputKey(); ?>
That generates this: <input type="hidden" name="form_key" id="form_key" value="7bd8496ea1518e1850c24cf2de8ded23" />
Then I can simply check for if(!isset($_POST['form_key']) || !$formKey->validate())
I have two problems. First: I cant use more than one form per page becouse only the last key generated will validate.
Second: Because everything goes through index.php first, if I use ajax to validate the form, the first time will validate but the second time not, because index.php generates a new key but the pages containing the form does't refresh so the form key is not updated..
I have tried several things but I cant get it to work.. Maybe YOU can update/modify the code/class to get it to work?? Thanks!!!
You could put this into a class, but this is needless complexity. Simple security systems are best because they are easier to audit.
//Put this in a header file
session_start();
if(!$_SESSION['xsrf_token']){
//Not the best but this should be enough entropy
$_SESSION['xsrf_token']=uniqid(mt_rand(),true);
}
//$_REQUEST is used because you might need it for a GET or POST request.
function validate_xsrf(){
return $_SESSION['xsrf_token']==$_REQUEST['xsrf_token'] && $_SESSION['xsrf_token'];
}
//End of header file.
The extra && $_SESSION['xsrf_token'] makes sure this variable is populated. Its there to make sure the implementation fails securely. (Like if you forgot the header file doah! ;)
This following html/php goes in any file you want to protect from XSRF, make sure you have the code above in a header file.
if(validate_xsrf()){
//do somthing with $_POST
}
This is all you need to print out the form, again make sure you call session_start(); before you do anything, it doesn't matter if you call it multiple times.
<input type="hidden" name="xsrf_token" id="form_key" value="<?=$_SESSION['xsrf_token']?>" />
Not tested, but it should work.
class formKey {
//Here we store the generated form key
private $formKey;
//Here we store the old form key
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'];
$this->formKey = $this->generateKey();
$_SESSION['form_key'] = $this->formKey;
}
}
//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.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey() {
return $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.
return true;
}
else {
//The key is invalid, return false.
return false;
}
}
}
Edit: changed back to single key. Just call outputkey() to when needed. Don't create more than one instance of this class.