Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yii, best way to implement "user change of password"

I'm using Yii for an application, I'm writing a very simple user management, like registering, deleting and updating users... For updating the existing user I need to check the old password first before change it to the new inserted password. So here is the fields I have in the form:

username:----
old_password:---
new_password:---

and my user table looks like this:

id, username, password

How can I validate the old_password before updating it with the new_password? I know the usual php coding, but I want to know if there are any Yii tricks that does this automatically...

Thanks in advance

like image 634
mahsa.teimourikia Avatar asked Nov 05 '12 14:11

mahsa.teimourikia


3 Answers

You should not pollute your model with rubbish. Please, always have in mind these basic MVC principles:

  • Your controller must not be aware of your model's implementation.
  • Don't pollute your model with stuff not connected with your application's business model.

Always create reusable code, make your code "DRY" (Don't repeat yourself)

By the way, what is the purpose of the username field? Since the form would be available to the logged user only, the username can be accessed already with Yii::app()->user.

<?php
// models/ChangePasswordForm.php

class ChangePasswordForm extends CFormModel
{
    /**
     * @var string
     */
    public $currentPassword;

    /**
     * @var string
     */
    public $newPassword;

    /**
     * @var string
     */
    public $newPasswordRepeat;

    /**
     * Validation rules for this form.
     *
     * @return array
     */
    public function rules()
    {
        return array(
            array('currentPassword, newPassword, newPasswordRepeat', 'required'),
            array('currentPassword', 'validateCurrentPassword', 'message'=>'This is not your password.'),
            array('newPassword', 'compare', 'compareAttribute'=>'validateNewPassword'),
            array('newPassword', 'match', 'pattern'=>'/^[a-z0-9_\-]{5,}/i', 'message'=>'Your password does not meet our password complexity policy.'),
        );
    }

    /**
     * I don't know your hashing policy, so I assume it's simple MD5 hashing method.
     * 
     * @return string Hashed password
     */
    protected function createPasswordHash($password)
    {
        return md5($password);
    }

    /**
     * I don't know how you access user's password as well.
     *
     * @return string
     */
    protected function getUserPassword()
    {
        return Yii::app()->user->password;
    }

    /**
     * Saves the new password.
     */
    public function saveNewPassword()
    {
        $user = UserModel::findByPk(Yii::app()->user->username);
        $user->password = $this->createPasswordHash($this->newPassword);
        $user->update();
    }

    /**
     * Validates current password.
     *
     * @return bool Is password valid
     */
    public function validateCurrentPassword()
    {
        return $this->createPasswordHash($this->currentPassword) == $this->getUserPassword();
    }
}

example controller action:

public function actionChangePassword()
{
    $model=new ChangePasswordForm();
    if (isset($_POST['ChangePasswordForm'])) {
        $model->setAttributes($_POST['ChangePasswordForm']);
        if ($model->validate()) {
            $model->save();
            // you can redirect here
        }
    }

    $this->render('changePasswordTemplate', array('model'=>$model));
}

example template code:

    <?php echo CHtml::errorSummary($model); ?>

    <div class="row">
        <?php echo CHtml::activeLabel($model,'currentPassword'); ?>
        <?php echo CHtml::activePasswordField($model,'currentPassword') ?>
    </div>

    <div class="row">
        <?php echo CHtml::activeLabel($model,'newPassword'); ?>
        <?php echo CHtml::activePasswordField($model,'newPassword') ?>
    </div>

    <div class="row">
        <?php echo CHtml::activeLabel($model,'newPasswordRepeat'); ?>
        <?php echo CHtml::activePasswordField($model,'newPasswordRepeat') ?>
    </div>

    <div class="row submit">
        <?php echo CHtml::submitButton('Change password'); ?>
    </div>

    <?php echo CHtml::endForm(); ?>
</div><!-- form -->

The template should be easy enough to create. This code, with some minor tweaks, is ready to be copied & pasted to another Yii project.

like image 182
emix Avatar answered Oct 20 '22 10:10

emix


Its simple create a action that has logic for update pass.

Make target for form to new action in this case actionChangePass and validate there the way you want .

A rough example can be put like this

    public function actionChangePass($id)
    {  
    $user = loadModel($id)
    if(md5($_POST['User']['old_password']) === $user->password)
    {
       $user->setScenario('changePassword');
       $user->attributes = $_POST['User'];                
       $user->password = md5($_POST['User']['new_password']);
       if($user->save())
         Yii::app()->user->setFlash('passChanged', 'Your password has been changed <strong>successfully</strong>.');
    }            
    else
    {
      Yii::app()->user->setFlash('passChangeError', 'Your password was not changed because it did not matched the <strong>old password</strong>.');                    
    }  
 }

Also make sure you have $old_password in your user User Model. Also you can do some validations in rules of model to make new password required

there can be some different ways too but i do it like this

Also create your custom validation scenario changePassword

like image 4
Afnan Bashir Avatar answered Oct 20 '22 10:10

Afnan Bashir


Here is what I personally like to do. It is a complicated version of this. Add to model two fields that will help you process the password. Note these two fields do not exists in database and are not present in Gii generated code. Something like

class UserModel extends CActiveRecord
{
    /*Password attributes*/
    public $initial_password;
    public $repeat_password;
    //..................
}

In the form, do not associate the actual password field in the database with any input. The two field in database should be associated with these two fields. The short version of the form becomes:

    <?php echo $form->errorSummary($model); ?>


    <?php echo $form->passwordFieldRow($model,'initial_password',array('class'=>'span5','maxlength'=>128)); ?> 

    <?php echo $form->passwordFieldRow($model,'repeat_password',array('class'=>'span5','maxlength'=>128)); ?>  

Now how do I know that user changed password? I simply check in beforeSave() if the two fields are empty and compare them and then change the password. If they are empty then I just skip the whole thing altogether. So simple version of beforeSave is:

/**
     * Called before saving the model
     */
    protected function beforeSave()
    {
        if(parent::beforeSave())
        { 
            if($this->isNewRecord)
            { 
                $this->password = HashYourPass($this->initial_password); 
            }
            else
                { 
                    //should we update password?
                    if($this->initial_password !== '')
                    {
                        //encrypt password and assign to password field
                        $this->password = HashYourPass($this->initial_password);
                       //Check old password match here
                    }
                }
            return true;
        }
        else
            return false;
    }

Now according to your question, one thing is missing. Checking old password! You can add new Model fields called aold password and its form input control. Then in beforesave method (as indicated by comment) you can compare the input with actual password field from the database and if they match then do change password.

You can add them as validation rules with scenarios but I found it complicated somehow and with little time at hand I went with this method.

like image 1
Stefano Mtangoo Avatar answered Oct 20 '22 09:10

Stefano Mtangoo