CakePHP: Securely setting a default value on a form - php

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;
}
}

Related

Cakephp 3 - How to retrieve current logged user in a 'Table' class during validation process?

I'am using CakePhp3 for my website and I have to inject some custom validation logic based on the current user Id when I'am creating or modifying an entity.
The basic case is "Is the user allow to change this field to this new value" ? If' not, I want to raise a validation error (or an unauthorized exception).
In cakephp, for what I'am understanding, most of the application and businness rules must be placed on Models or 'ModelsTable'of the ORM. But, in this classes, the AuthComponent or the current session is not available.
I don't want to call manually a method on the entity from the controller each time I need to check. I would like to use a validator, something like :
$validator->add('protected_data', 'valid', [
'rule' => 'canChangeProtectedData',
'message' => __('You're not able to change this data !'),
'provider' => 'table',
]);
Method on ModelTable :
public function canChangeProtectedData($value, array $context)
{
\Cake\Log\Log::debug("canChangeProtectedData");
// Find logged user, look at the new value, check if he is authorized to do that, return true/false
return false;
}
I cakephp < 3, the AuthComponent have a static method 'AuthComponent::user()' that is not available anymore. So, how Can I do that in CakePhp 3 ?
Thank you for any response.
EDIT - Adding more details
So here are more details. In case of an REST API. I have an edit function of an entity. The "Article" Entity.
This Article has an owner with a foreign key on the column named "user_id" (nothing special here). My users are organized in groups with a leader on the group. Leaders of groups can change article's owner but "basics" users can't do it (but they can edit their own articles). Admin users can edit everything.
So the edit method must be available for any authenticated user, but changing the "user_id" of the entity must be allowed and checked depending the case (if I'am admin yes, if I'am leader yes only if the new Id is one of my group and if I'am basic user no).
I can do this check on the controller but if I want this rule to be checked everywhere in my code where an Article is modified (in another method than the "Edit" of ArticlesController). So for me the Model seems the good place to put it no?
Authentication vs Authorisation
Authentication means identifying an user by credentials, which most of the time boils down to "Is a user logged in".
Authorisation means to check if an user is allowed to do a specific action
So don't mix these two.
You don't want validation you want application rules
Taken from the book:
Validation vs. Application Rules
The CakePHP ORM is unique in that it uses a two-layered approach to
validation.
The first layer is validation. Validation rules are intended to
operate in a stateless way. They are best leveraged to ensure that the
shape, data types and format of data is correct.
The second layer is application rules. Application rules are best
leveraged to check stateful properties of your entities. For example,
validation rules could ensure that an email address is valid, while an
application rule could ensure that the email address is unique.
What you want to implement is complex application logic and more than just a simple validation, so the best way to implement this is as an application rule.
I'm taking a code snippet from one of my articles that explains a similar case. I had to check for a limitation of languages (translations) that can be associated to a model. You can read the whole article here http://florian-kraemer.net/2016/08/complex-application-rules-in-cakephp3/
<?php
namespace App\Model\Rule;
use Cake\Datasource\EntityInterface;
use Cake\ORM\TableRegistry;
use RuntimeException;
class ProfileLanguageLimitRule {
/**
* Performs the check
*
* #link http://php.net/manual/en/language.oop5.magic.php
* #param \Cake\Datasource\EntityInterface $entity Entity.
* #param array $options Options.
* #return bool
*/
public function __invoke(EntityInterface $entity, array $options) {
if (!isset($entity->profile_constraint->amount_of_languages)) {
if (!isset($entity->profile_constraint_id)) {
throw new RuntimeException('Profile Constraint ID is missing!');
}
$languageLimit = $this->_getConstraintFromDB($entity);
} else {
$languageLimit = $entity->profile_constraint->amount_of_languages;
}
// Unlimited languages are represented by -1
if ($languageLimit === -1) {
return true;
}
// -1 Here because the language_id of the profiles table already counts as one language
// So it's always -1 of the constraint value
$count = count($entity->languages);
return $count <= ($languageLimit - 1);
}
/**
* Gets the limitation from the ProfileConstraints Table object.
*
* #param \Cake\Datasource\EntityInterface $entity Entity.
* #return int
*/
protected function _getConstraintFromDB(EntityInterface $entity) {
$constraintsTable = TableRegistry::get('ProfileConstraints');
$constraint = $constraintsTable->find()
->where([
'id' => $entity['profile_constraint_id']
])
->select([
'amount_of_languages'
])
->firstOrFail();
return $constraint->amount_of_languages;
}
}
I think it is pretty self-explaining. Make sure your entities user_id field is not accessible for the "public". Before saving the data, just after the patching add it:
$entity->set('user_id', $this->Auth->user('id'));
If you alter the above snippet and change the profile_constraint_id to user_id or whatever else you have there this should do the job for you.
What you really want is row / field level based authorisation
Guess you can use ACL for that, but I've never ever had the need for field based ACL yet. So I can't give you much input on that, but it was (Cake2) and still is (Cake3) possible. For Cake3 the ACL stuff was moved to a plugin. Technically it is possible to check against anything, DB fields, rows, anything.
You could write a behavior that uses the Model.beforeMarshal event and checks if user_id (or role, or whatever) is present and not empty and then run a check on all fields you want for the given user id or user role using ACL.
You could probably use this method PermissionsTable::check() or you can write a more dedicated method does checks on multiple objects (fields) at the same time. Like I said, you'll spend some time to figure the best way out using ACL if you go for it.
UX and yet another cheap solution
First I would not show fields at all an user is not allowed to change or enter as inputs. If you need to show them, fine, disable the form input or just show it as text. Then use a regular set of validation rules that requires the field to be empty (or not present) or empty a list of fields based on your users role. If you don't show the fields the user would have to temper the form and then fail the CSRF check as well (if used).
I don't think you need to validate in the table. I just thought of a way to do it in the controller.
In my Users/Add method in the controller for instance:
public function add()
{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->data);
//check if user is logged in and is a certain user
if ($this->request->session()->read('Auth.User.id') === 1) {
//allow adding/editing role or whatever
$user->role = $this->request->data('role');
} else {
$user->role = 4;//or whatever the correct data is for your problem.
}
if ($this->Users->save($user)) {
$this->Flash->success(__('You have been added.'));
} else {
$this->Flash->error(__('You could not be added. Please, try again.'));
}
}
$this->set(compact('user'));
$this->set('_serialize', ['user']);
}

CakePHP: authorizing actions based on belongsTo relationships

Let's keep it simple.
A project has just two models:
User (hasMany Project)
Project (belongsTo User)
Users are only allowed to perform actions on the projects which they own. No one else's.
I know how to manually check who the logged in user is and whether or not he/she owns a specific project, but is there a better, more global way to do this? I'm looking for a more D.R.Y. way that doesn't require repeating the same validation inside multiple actions. For example, is there a config setting like maybe...
Configure::write('Enforce_belongs_to', true);
...or maybe a setting/option on the Auth component.
Maybe this is crazy, but I figured I'd ask.
Adding to Nunser's answer, here would be a general concept of how the behavior would be. You can then attach it to the applicable model.
class StrongBelongBehavior extends ModelBehavior
{
public function beforeFind( Model $Model, $query = array() ) {
$query['conditions'] = array_merge( (array)$query['conditions'], array( $Model->alias.'.user_id' => CakeSession::read("Auth.User.id" ) );
return $query;
}
public function beforeSave( Model $Model ) {
$projectId = Hash::get( $Model->data, 'Poject.id' );
if( $projectId ) {
$Model->loadModel('UserProject'); // UserProject is a custom model
$canEdit = $Model->UserProject->projectIDExists( $projectId ); // returns true if projectId belongs to the current user
if ( ! $canEdit ) {
return false;
}
}
return true;
}
}
I'm not sure if what I'm answering is the best-utermost-dry-it's-almost-dehydrating approach, but is the simplest thing I could think of.
In the Project model, create a function that return an array of project ids associated to an user.
class Project extends AppModel {
public function getByUserId($userId) {
$projectsArray = array();
if ($userId != "valid")
//do all the checks, if it's not null, numeric, if the id exists, etc
$projects = $this->Project->find('all', array('conditions'=>
array('user_id'=>$userId)));
if (!empty($projects)) {
foreach($projects as $i => $project)
$projectsArray[] = $project['Project']['id'];
}
return $projectsArray;
}
}
You mention a find('first') in your comment, but I'm assuming you want all the projects related to the user instead of just the first. If not, it's a simple modification of that function. Also, I'm just getting the ids, but you may want an $id=>$name_project array, up to you.
Now, I don't know what you mean by "only allowed to perform actions", is it just edits that are restricted? Or lists or views should be restricted and not even shown to the user if the project is not his/hers?
For the first case, restrict editing, modify beforeSave.
public function beforeSave($options = array()) {
if(!$this->id && !isset($this->data[$this->alias][$this->primaryKey])) {
//INSERT
//not doing anything
} else {
//UPDATE
//check if project inside allowed projects array
$allowed = $this->getByUserId(CakeSession::read("Auth.User.id"));
if (!in_array($this->id, $allowed))
return false; //or throw error and catch it in the controller
}
return true;
}
The code is untested, but in general terms, you prevent the edit of a project that is not "the user's" just before the update of the record. I assume the insert of new projects is free for everyone. According to this post, all saving functions except saveAll pass through this filter first, so you will need to overwrite the saveAll function and add a validation similar to the one in beforeSave (as explained in the answer there).
And for the second part, filtering results so the user isn't even aware that there are other projects instead of his/hers, change beforeFind. The docs talk about restricting results based on user's roles, so I guess we're on the right track.
public function beforeFind($queryData) {
//force the condition
$allowed = $this->getByUserId(CakeSession::read("Auth.User.id"));
$queryData['conditions'][$this->alias.'.user_id'] = $allowed;
return $queryData;
}
Since the $allowed array has just id values, it'll work like an IN clause, but if you change that array structure, be sure to modify these functions accordingly.
And that's it. I'm thinking about the more basic cases here, edit, view, delete... Ups, delete... change the beforeDelete function also, to avoid any evil users who want to delete others property. The logic remains the same (check if project id is in allowed array, if not, return false or throw error), so I won't add the example of that function here. But that's the basic stuff. If for some reason you want to have the allowed projects in the controller, call the getByUserId function in beforeFilter and handle that ids array there. You can even store it in session, but you'll have to have in mind maintaining that session when adding or deleting projects.
If you want a superadmin that can see and edit everything, just add a condition in getByUserId that checks the role of the user, and if it is an admin, return all projects.
Also, keep in mind: maybe Project has many... subprojects (not much imagination), and so, the user related to the project can add subprojects, but the same evil user as before modifies the hidden project_id that subproject has and edits it. In that case, I recommend you also add a validation in Subproject to avoid actions on models related to Project that are not his. If you have the Security component in place and the edit and delete actions can just be reached by forms, this is a minor thing because Security Component well used avoids form tampering. But give it a thought to see if you need to validate "Subproject" instances also.
As Ayo Akinyemi mentioned, you can use all this as a behavior. I haven't personally done so, but it meets the requirements, all the callbacks modified here are what you modify in a behaviour. You'll have to encapsulate the logic, column names (need to be variable an not set hardcoded, like user_id), etc, but it will be reusable in any other cake project you'll have. Something like StrongBelongBehavior or MoreDRYBehavior. And share it if you do it :)
I'm not sure if Auth component has some way of doing what you want, but that would be the best option I guess. Until some enlightens me (I haven't investigate much this issue), this is the solution I'd use.

Working with related model in CakePHP

This is a fairly basic question about CakePHP, but since my knowledge of this framework is rather rusty, it is making me lose a lot of time.
I have a ManyToMany relation between Guest and Present. Whenever a new Guest is created and associated with a present, I would like to mark the Present as taken. If the present is already taken, some error should arise. The reason why I am not just declaring that a Guest hasMany Presents is because in the future things may change and more than one guest could associate to a present, so I prefer to avoid a Db migration.
My Guest::add() action looks like follows. It is called with a POST with the data of a new Guest and the id of an existing Present.
public function add() {
if ($this->request->is('post')) {
$id = $this->request->data['Present']['id'];
$this->Guest->create();
$present = $this->Guest->Present->findById($id);
if ($present['Present']['taken']) {
throw new ForbiddenException();
}
if ($this->Guest->save($this->request->data)) {
if ($this->Guest->Present->saveField('taken', true)) {
// Give the guest a uuid and proceed with a welcome message
$this->Guest->read();
$this->set('uuid', $this->Guest->data['Guest']['uuid']);
}
}
}
else {
throw new ForbiddenException();
}
}
What happens is that a new Guest is created (correct) and associated with the given present (correct) but when I save the taken field a new present is created instead of modifying the given one.
What is the correct way to proceed to update the current Present?
If it is of any help, I am using CakePHP 2.0
For obtaining the model data by the primary key it's better to use theIn addition read method:
$present = $this->Guest->Present->read(null, $id);
The read method sets the model's id attribute so that further calls to other methods affect the same data record, rather than creating a new one. This should solve the problem you are having.
Model callbacks tend to be better suited for these situations. You could add a beforeSave callback to the Guest class to checks if the present is already taken, and not allow the creation if it is. This way the model logic is left in the model layer and you don't need to do any extra work e.g. if the constraint has to be enforced also when existing Guests are saved, or created from different controllers or actions.
It sounds like the ID of the model you are trying to save is losing scope. You should be able to resolve your issue by updating your code:
...
if ($this->Guest->save($this->request->data)) {
$this->Guest->Present->id = $id;
if ($this->Guest->Present->saveField('taken', true)) {
...

PHP advice please. (Kohana experience a plus)

I've been working with PHP for about a year, but I do it as a hobby. I dont have anybody I can go to as a teacher or a mentor to give me advice on what I may be doing completely wrong, or what I could do better. I've done quite a few different things within that year, so I wouldnt consider myself a complete noob.
Anyways, I have just started using a framework (Kohana), and there really arent that many tutorials out there, so I'm not entirely sure if I'm doing things in a good way.
I have a few code snippets that I would like to post to get some feedback pertaining to what I just said.
For Starters
User Controller
class User_Controller extends Template_Controller{
public function register()
{
// logged in users cant register
if($this->logged_in)
{
url::redirect('user/profile');
}
// initially show an empty form
$form = $errors = array
(
'username' => '',
'email' => '',
'password' => '',
'gender' => '',
'dob_month' => '',
'dob_day' => '',
'dob_year' => '',
'date_of_birth' => '',
'captcha' => '',
'registration' => ''
);
// check for a form submission
if($this->input->post('register'))
{
// get the form
$post = $this->input->post();
// prepare the data for validation
$post['date_of_birth'] = "{$post['dob_year']}-{$post['dob_month']}-{$post['dob_day']}";
// create a new user
$user = ORM::factory('user');
// validate and register the user.
if($user->register($post, TRUE))
{
// SEND EMAIL
// login using the collected data
if(Auth::instance()->login($post->username, $post->password, TRUE))
{
// redirect the user to the profile page
//url::redirect("user/profile/{$user->id}");
}
}
// get validation errors and repopulate the form
$form = arr::overwrite($form, $post->as_array());
$errors = arr::overwrite($errors, $post->errors('registration_errors'));
}
// template variables
$this->template->title = 'Sign Up';
$this->template->body = new View('layout_1');
// layout variables
$this->template->body->left = new View('user/registration_form');
$this->template->body->right = 'Right Side Content';
// registration form variables
$this->template->body->left->form = $form;
$this->template->body->left->errors = $errors;
$this->template->body->left->captcha = new Captcha('register');
}
}
Register Function within User_Model
class User_Model extends ORM{
public function register(array& $user, $save = FALSE)
{
$user = new Validation($user);
// logged in users cant register
if(Auth::instance()->logged_in())
{
$user->add_error('registration', 'logged_in');
return FALSE;
}
// trim everything
$user->pre_filter('trim')
// everything is required
->add_rules('*', 'required')
// username must be 5 - 30 alphanumeric characters and available
->add_rules('username', 'length[5,30]', 'valid::alpha_numeric', array($this, 'username_available'))
// email must be valid format and available
->add_rules('email', 'valid::email', array($this, 'email_available'))
// password must be 5 - 15 characters and alpha dash
->add_rules('password', 'length[5,15]', 'valid::alpha_dash')
// gender must be either male or female. capitalize first letter
->add_rules('gender', array($this, 'valid_gender'))
->post_filter('ucfirst', 'gender')
// dob must be a valid date, and user must be old enough.
->add_callbacks('date_of_birth', array($this, 'check_dob'))
// captcha must be entered correctly.
->add_rules('captcha', 'Captcha::valid');
// add the registration date
$this->registration_date = date::unix2mysql(); // helper function transforms the current unix to mysql datetime format
// validate the information. an ORM function.
$result = parent::validate($user, $save);
// was the user info valid?
if($result === TRUE)
{
// was the user saved?
if($save === TRUE)
{
// add a login role
$this->add(ORM::factory('role', 'login'));
$this->save();
}
}
else
{
$user->add_error('registration', 'failed');
}
return $result;
}
}
Mostly all my models follow the same format when validating info.
I have some other things I would appreciate feedback on as well, but I dont want to overwhelm anybody.
Thanks a lot for your time
EDIT: I'm sorry, I should've posted both the user controller and model. I've been reading alot about how models should be fat, and controllers should be skinny. Thats why I created a register function in the model to validate the info instead of doing so within the controller. The register function takes an array, but turns that array into a validation object so that I can retrieve the user input, and the errors. I've seen a few tutorials on Kohana where it was done this way.
First, I would not put the register() method into the User model. A model should be a representation of the object in the database and generally only contains your "CRUD" methods (create, retrieve, update, delete), getter and setter methods, and maybe some static helper methods related to the model. By putting your register() method into the model, you're making the model do presentation logic that should really be done by a User controller, since this is a response to a user action. A controller handles user actions, validates those user actions, then updates the model if the validation is successful.
In your example, the user is attempting to create a new account. He fills out a form and clicks submit. The form's POST action should point to a controller's method, like /user/register, and that method will use the Validation library to validate the form data sent by the user. Only if that data validates successfully should you create a User model instance, set the properties of that model to what the user input, and then use the model's save() method to save to the database. If the validation fails, you report the error to the user and you don't create a User model at all since you don't have a valid data set to create a model with yet.
Next, you are checking to see if the user is logged in. Again, this should be in the controller, not the model. Besides that, the user should not be able to get to this register process in the first place if he is already logged in. The controller method that creates the user registration form view should check to see if the user's logged in, and if he is, then he should be redirected to another page. Even if the user is playing tricks and manages to submit the form (maybe he logged in via another window while having the form open in an old window), your register method should check for that first and not create a $user Validation object yet.
I can see in your code that there are some confusing items based on your model set up. For example, you're passing the $user array into the method, which I presume is the form data. But you're using the "pass by reference" operator (&) which is unnecessary in PHP5 since all objects are now passed by reference. But after that you're recasting $user as a Validation object. Are you using the $user Validation object elsewhere and require it to be passed by reference? If so, that's another flaw in the logic as all of this processing needs to be in the controller and the $_POST values can be used directly in the controller instead of having to pass around a Validation object.
Later on, you're validating the user information with parent::validate($user, $save). Why is the validate() method being called on parent as a static method? If this is a model, it should be extending Kohana's core Model class, and "parent" references the Model class. Is your model extending the Validation class? Also, why are you passing in the $user Validation object to the validation() method? Doing that is required if you need to do recursion (to validate elements again after making changes from previous filters), but it looks like you're not doing anything to require recursion. You should be calling validate() on the $user Validation object:
$user->validate();
without any arguments. The validation errors will become part of the $user object, so you can check for errors using
$user->errors();
Finally, while Kohana allows you to use method chaining, I would not use one long chain to set up the rules and other items for the validation. It's confusing and may cause debugging to be difficult. Put each of those on its own line and perform each directly on the $user object.
I dont know Kohanna so im not sure what the lay of the land is on their MVC separation but typically i would make register an action on a controller. The main thing i disagee with in your code is that the Model is coupled to the Authentication system internally. The authentication check should be made outside the class and the control flow decision should be made outside as well, OR the result of the authentication check should be passed in to the Model for use in its internal operation.
Typically i might do something like the following pseudo code:
// in my controller class for User or whatever
public function registerAction()
{
// get the form data from the request if its POST, ortherwise a blank array
$userData = $this->getRequest('user', array(), 'POST');
// create a user
$user = new User($userData);
if(Auth::instance()->logged_in())
{
// we are logged in add an error to the user object for use by the view
$user->getValidator()->add_error('registration', 'logged_in');
}
elseif($user->is_valid())
{
// user data is valid, set the view with the success message
$user->save();
$this->setView('register_success');
}
/**
* render the designated view, by default this would be the one containing the
* registration form which displays errors if they exist - however if we success-
* fully registered then the view with the success message we set above will be
* displayed.
*/
$this->render();
}

How to prevent double / duplicate form submissions in CakePHP?

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']);

Categories