This is a part of my Guide to setting up the CakePHP auth component series.
I found a good basis for a resolution to this issue here . It wasn’t perfect for me but did a nice job of explaining a problem I had with hashed values. I have provided a modified solution below that meets my requirements more specifically (that is to allow password changes only when a password confirm is provided on edit, while requiring a password on add).
A problem I had encountered along the way was due to me not understanding the hash function, I couldn’t get the two hash values to match, this problem is highlighted in the post “Manually hashing password and password validation in CakePHP“:
Security::hash($this->data['User']['passwd'], null, true);
This line makes the password readable by the Auth component, and that in turn removes the need of creating your own. Take a look at the API reference of Security::hash() and you will see that the third parameter is very important, as it tells the hash function to use the “Security.salt” value in the hashing process. In any case, if you need to hash something and be compatible with the Cake’s own Auth and Security, this seems to be the right way.
So make sure you include the second and third parameters of the security function whichever solution you implement.
Below is my version of the code, I have essentially reversed the validation process as I wanted it to only execute if a password confirm was provided (that is the validation wouldn’t be tripped up unless they specifically wanted to change the password, and if the password field itself was blank then nothing should happen ever).
Taken from user.php (the model)
function __construct()
{
parent::__construct();
/*
* Validate on the password field not the password confirmation field
* This ensures that if we enter blank in both then nothing is triggered
* or blank on the confirmation field only then it is rejected using validatePasswdConfirm
*/
$this->validate = array
(
'passwd' => array
(
/* snip other rules */
'match' =>
array
(
'rule' => 'validatePasswdConfirm',
'required' => false,
'allowEmpty' => true,
'message' => __('Passwords do not match', true)
)
),
/*
* On creation when we always want a password, have the form use a normal
* password field and have it validated against it's own special "empty" check
* that is '' that has been hashed (automagically by cake)
*/
'password' => array
(
'rule' => 'validateHashedPassword',
'required' => false,
'allowEmpty' => false,
'message' => __('You must submit a password', true)
)
);
}
function validateHashedPassword($data)
{
if ($data['password'] == Security::hash('', null, true))
{
return false;
}
return true;
}
function validatePasswdConfirm($data)
{
if (isset($this->data['User']['passwd_confirm'])&&
$this->data['User']['passwd'] <> '' &&
$this->data['User']['passwd_confirm'] !== $data['passwd']
)
{
return false;
}
return true;
}
function beforeSave() {
/*
* Ensure that there is a value for the password,
* field it should be ignored if they are not
* providing a value (i.e. no update should take place)
*/
if (isset($this->data['User']['passwd']) && $this->data['User']['passwd'] <> '')
{
$this->data['User']['password'] = Security::hash($this->data['User']['passwd'], null, true);
unset($this->data['User']['passwd']);
}
if (isset($this->data['User']['passwd_confirm']))
{
unset($this->data['User']['passwd_confirm']);
}
return true;
}
Just to be clear too here is my add/edit form (I combine them to save duplication), admin_edit.php:
create('User');?>
input('id');
echo $form->input('email');
//App::import("Vendor", "dbug2");new dbug2($this->data);
if(empty($this->data['User']['id'])){
echo $form->input('password', array('value' => '','autocomplete'=>'off'));
}else{
echo $form->input('User.passwd', array('label' => 'New password','value' => '','autocomplete'=>'off'));
echo $form->input('User.passwd_confirm', array('type' => 'password','label' => 'Confirm new password','value' => '','autocomplete'=>'off'));
};
echo $form->input('firstname');
echo $form->input('surname');
echo $form->input('group_id');
?>
end('Submit');?>
Notice too the attribute:
'autocomplete'=>'off'
This just prevents the field from being auto populated which I was experiencing while testing.