You are viewing revision #2 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version or see the changes made in this revision.
I had some troubles with the password confirmation field for when adding updating user records, so i thought that i should share the way i got it working.
The scenario is the basic one, you have a database table (say user) and this table has a field called password, which is a sha1/md5/etc hash of the user password.
This is the workflow:
When you create a new user, the password needs to be hashed and saved, but when you update a user record, if the same scenario happens, we end up with a hash of the user hashed password, and we don't want this. Instead, on update, we will empty the user password from the model object, store it temporary in another variable then check to see if the password has been submitted in the form, if it was, it means the user password needs to be updated, therefore we need to hash the password(which is plain text now), if it wasn't submitted, then it means it doesn't need to be updated therefore, we restore it from the temporary variable.
So, here we go, the model:
<?php if ( ! defined('YII_PATH')) exit('No direct script access allowed');
class User extends CActiveRecord
{
// holds the password confirmation word
public $repeat_password;
//will hold the encrypted password for update actions.
public $initialPassword;
/**
* @return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
//password and repeat password
array('password, repeat_password', 'required', 'on'=>'resetPassword, insert'),
array('password, repeat_password', 'length', 'min'=>6, 'max'=>40),
array('password', 'compare', 'compareAttribute'=>'repeat_password'),
);
}
public function beforeSave()
{
// in this case, we will use the old hashed password.
if(empty($this->password) && empty($this->repeat_password) && !empty($this->initialPassword))
$this->password=$this->repeat_password=$this->initialPassword;
return parent::beforeSave();
}
public function afterFind()
{
//reset the password to null because we don't want the hash to be shown.
$this->initialPassword = $this->password;
$this->password = null;
parent::afterFind();
}
public function saveModel($data=array())
{
//because the hashes needs to match
if(!empty($data['password']) && !empty($data['repeat_password']))
{
$data['password'] = Yii::app()->user->hashPassword($data['password']);
$data['repeat_password'] = Yii::app()->user->hashPassword($data['repeat_password']);
}
$this->attributes=$data;
if(!$this->save())
return CHtml::errorSummary($this);
return true;
}
}
When the user is created, we do it with the "insert" scenario, meaning that the password is required, but when we update it, we do it with the "update" scenario, meaning that the password is not required anymore, therefore when the form is submitted, the password fields can be empty, and the validation won't fail. This allows us to restore the hashed password from the temporary variable.
Just as a side note, here is how my controller methods looks:
public function actionCreate()
{
$user=new User('insert');
$this->saveModel($user);
$this->setViewData(compact('user'));
$this->render('create', $this->getViewData());
}
public function actionUpdate($id)
{
$user=$this->loadModel($id);
$user->scenario='update';
$this->saveModel($user);
$this->setViewData(compact('user'));
$this->render('update', $this->getViewData());
}
protected function saveModel(User $user)
{
if(isset($_POST['User']))
{
$this->performAjaxValidation($user);
$msg = $user->saveModel($_POST['User']);
//check $msg here
}
}
And this is the rendering form:
<section>
<?php echo $form->labelEx($user,'password'); ?>
<div>
<?php echo $form->passwordField($user,'password',array('maxlength'=>40)); ?>
<?php echo $form->passwordField($user,'repeat_password',array('maxlength'=>40)); ?>
</div>
<?php echo $form->error($user,'password'); ?>
</section>
Hope it helps :)
both passwords are required in update case also
Great effort.. I followed your code but both passwords are also required in update case, please help me.
Thanks
Share the model
Please use something like pastebin.com and share your model so that i can take a look at.
L.E: also please make sure that in your controller you set the scenario to "update" after you load the model.
from mail
Got an email for this article:
In your afterFind() function, you reset the password to null. How did you deal with that in your UserIndentity.php file? Before attempting to use your code, my logins were working just fine with this code: if($user->password!==$user->encrypt($this->password)) { $this->errorCode=self::ERROR_PASSWORD_INVALID; } else { $this->_id = $user->id; etc... but since password is null, my login now fails. Any ideas?
For who is having this issue, the login form must use a CForm model and define the rules() for the login fields there, also the validation will be made by the CForm model, and if the validation passes, instantiate a new User and assign it the username/email and password.
About scenario's
Default scenario when you create a model with the new keyword is insert, and when you do find() the scenario is update. No need to set them again. ;)
http://www.yiiframework.com/doc/api/1.1/CActiveRecord#__construct-detail
http://www.yiiframework.com/doc/api/1.1/CActiveRecord#populateRecord-detail
Simplified version
Thanks for posting this - I've used it as a starting point and come up with a simplified version. Here's how I've done it:
Then in my model beforeSave(), I just have:
//add the password hash if it's a new record if ($this->getIsNewRecord()) { //creates the password hash from the plaintext password $this->password_hash = Yii::app()->user->hashPassword($this->password); } else if (!empty($this->password)&&!empty($this->repeat_password)&&($this->password===$this->repeat_password)) //if it's not a new password, save the password only if it not empty and the two passwords match { $this->password_hash = Yii::app()->user->hashPassword($this->password); }
Yii::app()->user->hashPassword() is a custom hashing function I got from another wiki article here.
Wrong. Wrong, wrong, wrong!
I have no idea why you think
$data['password'] = Yii::app()->user->hashPassword($data['password']);
is the correct thing to do, when the result of this will be
> Exception saving data: CWebUser and its behaviors do not have a method or
closure named "hashPassword".
Do your homework
@heyho - you do realize this is just an example, right? I have no idea how one prefer to hash the password, so that was just a plain example:)
Most of the people reading this got the point, except you ;)
Title cannot be blank.
If you intend it as an example, use an example class, and not a framework class.
Nope
Instead of bitching around, you are welcome to edit the entry or create a new one with a better approach, I'm sure you can come up with something better ;)
Updated tutorial with old, new and repeat password
For more security, required also old password, new password and repeat password.
http://www.yiiframework.com/wiki/718/change-password-with-tbactiveform
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.