How to prevent double / duplicate form submissions in CakePHP? - php

I discovered the Security Component in CakePHP helps to prevent CSRF by adding tokens as hidden values to forms.
What I was wondering is if there was anyway to prevent duplicate form submissions using this Component or some other component/helper?
In previous projects, I used a unique hash saved in a session, which is read then deleted upon submit. A repeated submit would have that same hash and an error would be produced.
thanks

I've puted onClick event that disables the button like this:
<?= $this->Form->button('Salvar', [
'value' =>'Submit',
'onClick' => 'form.submit();this.disabled=true'
])
?>

You could implement the same type of thing in Cake as you've done before.
On submit, set a session variable that marks that form as having been submitted. Make sure to put an expiry time after it (within a few seconds should do the trick). If the session variable is there when you process the form (and you're within that expiration time), then you've got a resubmit, so don't save the form data.
I'd recommend doing this within the save(..) method of your model, so you don't need to worry about adding it in multiple code locations.

There is a feature in CakePHP 2.x in the security component that allows you to choose to either use the same security token till it expires or just once.
Place this in your controllers beforeFilter method:
$this->Security->csrfUseOnce = true;
Find more information here

#DoctorFox has already answered it with csrfUseOnce = true, but this will throw you in blackholes that you still have to manage. So the complete solution for me is :
class YourAppController extends AppController {
public $helpers = array('Html', 'Form');
public $components = array('Security');
public function beforeFilter() {
$this->Security->csrfUseOnce = true;
$this->Security->blackHoleCallback = 'blackhole';
}
public function blackhole($type) {
$this->redirect(array('action' => 'index'));
}
If there is no redirection, you are still open for double form submission.
Ref : CakePHP security component

Just do PRG Pattern..It's very simple right?! Well, at least that's what everyone says but no one posts a clear answer! It took me a week of search and digging and then the "Newbie" decided to do something on his own! Here is one way to do it in cakephp (I use 2.0.5):
Regardless of code here is the logic in steps:
1- set data
2- validate (do NOT create() yet)
3- write $this->request->data to a session variable
4- redirect to a saveData action
Inside saveData action:
5- read & save the session's variable
6- DELETE session's variable
7- create()
8- save data to model
9- redirect
Here is an example of how your code might look like.
**Attn: "ourController" and "ourModel"
public function add() {
if ($this->request->is('post')) {
if (isset($this->request->data)) {
$this->ourModel->set($this->request->data);
if ($this->ourModel->validates()) {
$this->Session->write('myData', $this->request->data);
$this->redirect(array('controller' => 'ourController',
'action' => 'saveData',
'ourModel' //optional but recommended
)
);
} else {
$this->Session->setFlash('ourModel could not be saved.');
}
}
.....//the rest of add() function
}
Then you should be redirected (on validation) to this function that redirects you again to index action or wherever your logic takes you!
public function saveData($model) {
$myData = $this->Session->read('myData');
$this->Session->delete('myData'); //extremely important
$this->$model->create();
if ($this->$model->save($myData))
// or $myData[$model] if you are dealing with multiple models
{
$this->Session->setFlash(__($model.' have been saved successfully'));
$this->redirect(array('controller' => 'ourController',
'action' => 'index'
)
);
}
} else{
$this->Session->setFlash(__($model.' could not be saved'));
}
}
}
A simple self-redirect might work but in most cases you want to redirect to a different view (e.g. to another form or to index view)
I hope this elaboration helps save time on others so not to have to waste a whole week (as in my case) just to do such functionality server-side!

Don't know about cake, but try to not display content on a POST request, do a self redirect. The double post will get solved.

The Security component should work, furthermore, you can also unset the data just after the post:
unset($this->data['yourModel']);

Related

code igniter 4, csrf problem after echo view and exit

my main purpose to include some pagedata on BaseController, To do so, i decided to put a protected function under basecontroller and i used it to display pages ( instead of return view('page') );
example of my code,
Under BaseController
protected function display($page = NULL,$data = NULL){
global $settings;
$general_data = array(
'activeGoogleAnalytics' => $settings['activeGoogleAnalytics']
);
$data = array_merge($data,$general_data);
echo view($page,$data);
exit; // if exit is removed, csrf error is not appear, else there is csrf error while submitting forms
}
Then, i use display function as follows in my controllers,
public function index()
{
//get some data
$this->display('login/index',array(
'someData' => '$someData',
));
}
This is actually working fine, i am merging two different arrays, and some content which are same in all pages are assigned to data array from BaseController.
My problem is whenever i submit form, i get csrf error "You cannot Perform this action". Even if i remove csrf protection from forms i got this error.
I found solution to the problem, by removing exit; from display function. But i am not sure what causes this problem or it is right decision. Because codeigniter is a framework, there should be better way of assigning page data ( which is same in all pages like website settings ).
Without using exit after echo, probably i ll face with AJAX problems in the future. Hence, i want to keep exit, or i want to learn easy way of assigning some data to view 's array from BaseController for all pages.

Laravel send multiple posts but save only the last one to database

In Laravel I'm developing a web app where users will answer true or false questions on separate pages. On each page I'd like to somehow save their answer and in the end save the results to the database.
I could make a POST form on each page which would increment 'points' field in the database if the answer was correct. But that would require many accesses to the database by each user as answers would be saved on each page. Is there a way to store their points to a variable on the server and then save that variable to the database in the end?
I've thought of saving points to session but that wouldn't be safe as sessions can be modified by users.
Right now my controller only returns the intro page.
class QuizController extends Controller {
public function index() {
return view("quiz.pages.1");
}
public function addPoint() {
$points++;
}
public function getPoints() {
return $points;
}
}
And a route to redirect to the next pages.
Route::get('quiz/{page}', function($page) {
return View::make('quiz.pages.' . $page);
});
The way i would achieve this in my opinion is to send hidden inputs to your pages and not save() them until the end. So it would look something like this.
firstpage.blade.php
<input type="text" name="yourinputname">
So then you can pass this input from your controller to the next page without saving it. Something like:
public function index(Request $request) {
$yourVariablename = new YourModelName;
$yourVariableName->yourDatabaseColumnName = $request->input('yourinputname');
return view("quiz.pages.1", [
'anyName' => $yourVariableName
]);
}
Notice there is no save() on the controller yet because we want to save it at the end so we just store it. From this points onward you can just do something like this in the next pages until you get to the point where you want to save.
anotherpage.blade.php
<input type="hidden" name="yourinputname">
When you gather all the data, just match them with your database columns and then save() it. I hope it solves your problem.

CakePHP: Securely setting a default value on a form

In CakePHP 2.4, how does one securely set part of a form to a default value?
On my user registration view, I'm currently setting the default group_id to 3 with this- but it seems like it would be a huge security hole to do it in the view, should anyone forge a form.
<?php echo $this->Form->hidden('group_id', array('value'=>'3')); ?>
My current register method:
public function register() {
if ($this->request->is('post')) {
$this->User->create();
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__('The user has been saved'), 'flash/success');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The user could not be saved. Please, try again.'), 'flash/error');
}
}
}
Don't set the value in the form but before you save the data. Only set values in hidden fields if it is required to be done in the view layer, if not always set such values in the controller, better model. Remember: Fat models, skinny controllers.
You should always use the Security component in your projects to avoid form tampering and other attacks.
Have a read about the security component here http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#form-tampering-prevention
By default SecurityComponent prevents users from tampering with forms.
It does this by working with FormHelper and tracking which files are
in a form. It also keeps track of the values of hidden input elements.
All of this data is combined and turned into a hash. When a form is
submitted, SecurityComponent will use the POST data to build the same
structure and compare the hash.
Add this component to your add model.
You can save the value in beforeSave() as well, you just need to check if the record is going to be a new one or if it already exists by checking if the id is present.
public function beforeSave($options = array()) {
if (empty($this->id) && empty($this->data[$this->alias][$this->primaryKey])) {
$this->data[$this->alias]['group_id'] = 3;
}
return true;
}
Usually the id is only present when the record exists, if you want to make it more solid you can check exists() for that id as well to be totally sure it's not already there.
Another best practice hint:
$this->data[$this->alias]['group_id'] = 3;
Sucks. Nobody ever knows what 3 is. If the groups are not changing it is better to have an empty, tableless model or a simple class with constants:
$this->data[$this->alias]['group_id'] = UserGroup::USER;
$this->data[$this->alias]['group_id'] = UserGroup::ADMIN;
$this->data[$this->alias]['group_id'] = // You get the idea...
If the groups are dynamic for some reason I guess they still have some kind of identifier:
$this->data[$this->alias]['group_id'] = $this->UserGroup->getDefaultGroupId();
This should be done in your User model. There is a model's callback called beforeSave you can use it to add the group_id value. Don't do it from the view.
//in User.php
public function beforeSave(){
$this->data['User']['group_id'] = 3;
}
See more in the official documentation.
Edited:
To know the current action we will use the Router object according to the following question's answer: $this->params returns null in cakephp model
So, we may modify the previous code to be:
//in User.php
public function beforeSave(){
$params = Router::getParams();
if ($params['action'] == 'register'){
$this->data['User']['group_id'] = 3;
}
}

CakePHP - Controller or No Controller?

I am currently building a web app which has two models, Donor and Donation Models respectively. It has multiple user roles. When the staff user first registers a donor, I want him to be redirected to another form which allows him to fill in the Donation details(the donor is registered once the first donation is successful).
Firs of all, should I create a donation controller, from which I would redirect the user using:
return $this->redirect(array('controller'=>'donations','action'=>'add'));
For the above to work, it requires me to save the newly registered donor's id in a session like so :
$this->Session->write('id', $this->Donor->id);
So the user is redirected to 'donations/add' in the url, and this works fine.. However I think this has some flaws. I was wandering whether I should create another action inside the Donor controller called 'add_donation', which will have its respective 'View'. The idea is to be able to form a url of the sort : 'donors/add_donation/4' (4 being the donor_id ! )
This URL follows this construct: 'controller/action/id'
If anyone could shed some light on best practices, or describe any caveats to my solution(the former, using session etc.) , please do help a brother out! Ill be deeply indebted to you! Thanks in advance!
After you saved the data you can do this in the DonorsController:
$this->redirect(array(
'controller' => 'donations',
'action' => 'add',
$this->Donor->getLastInsertId()
));
There is no need to return a redirect, it's useless because you get redirected. Notice that we pass the last inserted record id as get param in the redirect. The redirect method of the controller calls by default _stop() which calls exit().
CakePHP3: There is a discussion about changing that default behavior in 3.0. Looks like in CakePHP 3.0 the redirect() won't exit() by default any more.
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
I would not use the session here, specially not by saving it to a totally meaningless and generic value named "id". If at all I would use always meaningful names and namespaces, for example Donor.lastInsertId as session key.
It's not always clear where to put things if they're related but the rule of thumb goes that things should go into the domain they belong to, which is pretty clear in this case IMHO.
Edit:
Leaving this edit here just if someone else needs it - it does not comply with the usage scenario of the asker.
If you have the user logged in at this stage, modify the add function to check if the userId passed is the same as the one logged in:
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
if ($this->Auth->user('id') != $donorId) {
throw new InvalidArgumentException();
}
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
You can use also the same controller using more models with uses.
Or you can also to ask to another controller with Ajax and morover to get response with Json.

Using session variables versus Request parameters when persisting data between pages in Zend Framework PHP

I’m trying to better understand what the best method would be to persist data between requests in this scenario (using Zend Framework):
Say I have an Events controller and the default (index) view displays any existing Announcements (if there are any), and a link to Add a new Announcement (Both Event and Announcement are arbitrary objects). I’m trying to retrieve the eventId so I can associate the new Announcement with it when saving it to the database. Compositionally, an Event consists of 0 to many Announcements. From my limited understanding of the Zend Framework, I see two main options.
Option one: Make the URL something like ‘/event/addAnnouncement/eventId/5’, which makes retrieving the eventId easy via route/path parameters.
Option two: In the indexAction of the controller, save the eventId to a session variable, which can then be retrieved in the addAnnouncementAction of the Event controller. This way the Add Announcement link would simply be ‘/event/addAnnouncement/’.
Can anyone shed some light on which of these two ways is better, or if there is another way I’m not aware of?
As always, any help is much appreciated. Thanks.
The question to ask yourself is, how long do you need to persist the data? If you only need to save the data to pass it to the next action you can use POST or GET, the GET would pass through the url and the POST would not(typically).
The example you presented would suggest that you need to persist the data just long enough to validate, filter and process the data. So you would likely be very satisfied passing the few pieces of data around as parameters(POST or GET). This would provide the temporary persistence you need and also provide the added benefit of the data expiring as soon as a request was made that did not pass the variables.
A quick example (assume your form passes data with the POST method):
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost()){
$data = $form->getValues();//filtered values from form
$model = new Appliction_Model_DbTable_MyTable();
$model->save($data);
//but you need to pass the users name from the form to another action
//there are many tools in ZF to do this with, this is just one example
return $this->getHelper('Redirector')->gotoSimple(
'action' => 'newaction',
array('name' => $data['name'])//passed data
);
}
}
if you need to persist data for a longer period of time then the $_SESSION may come in handy. In ZF you will typically use Zend_Session_Namespace() to manipulate session data.
It's easy to use Zend_Session_Namespace, here is an example of how I often use it.
class IndexController extends Zend_Controller_Action {
protected $_session;
public function init() {
//assign the session to the property and give the namespace a name.
$this->_session = new Zend_Session_Namespace('User');
}
public function indexAction() {
//using the previous example
$form = new Application_Form_MyForm();
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost()){
$data = $form->getValues();//filtered values from form
//this time we'll add the data to the session
$this->_session->userName = $data['user'];//assign a string to the session
//we can also assign all of the form data to one session variable as an array or object
$this->_session->formData = $data;
return $this->getHelper('Redirector')->gotoSimple('action'=>'next');
}
}
$this->view->form = $form;
}
public function nextAction() {
//retrieve session variables and assign them to the view for demonstration
$this->view->userData = $this->_session->formData;//an array of values from previous actions form
$this->view->userName = $this->_session->userName;//a string value
}
}
}
any data you need to persist in your application can sent to any action, controller or module. Just remember that if you resubmit that form the information saved to those particular session variables will be over written.
There is one more option in ZF that kind of falls between passing parameters around and storing data in sessions, Zend_Registry. It's use is very similar to Zend_Session_Namespace and is often used to save configuration data in the bootstrap (but can store almost anything you need to store) and is also used by a number of internal Zend classes most notably the flashmessenger action helper.
//Bootstrap.php
protected function _initRegistry() {
//make application.ini configuration available in registry
$config = new Zend_Config($this->getOptions());
//set data in registry
Zend_Registry::set('config', $config);
}
protected function _initView() {
//Initialize view
$view = new Zend_View();
//get data from registry
$view->doctype(Zend_Registry::get('config')->resources->view->doctype);
//...truncated...
//Return it, so that it can be stored by the bootstrap
return $view;
}
I hope this helps. Pleas check out these links if you have more questions:
The ZF Request Object
Zend_Session_Namespace
Zend_Registry
Option 1 is better, although in your example this is not a POST (but it could be done with a POST).
The problems with option 2 are:
If a user had multiple windows or tabs open at the same time, relating to different events, how would you track which event ID should be used?
If a user bookmarked the add event page and came back later, the session var may not be set
Option 2 is also a little more complicated to implement, and adds a reliance on sessions.

Categories