Достаточно часто на тематических форумах встречаются вопросы новичков об организации хранения паролей пользователей в модели User. Вопрос звучит примерно так: «У меня в модели User есть поле password, в котором я храню хэш пароля. Как мне сделать так, чтобы пароль не перезаписывался при каждом сохранении модели?» Вопрос любопытный и для других фреймворков.

Действительно, многие начальные мануалы советуют хэшировать пароль перед первым сохранением модели (в методе beforeSave):

class User extends CActiveRecord
{
    // ...
 
    protected function beforeSave()
    {
        if (parent::beforeSave())
        { 
            // если это новая запись
            if ($this->isNewRecord)
            {            
                $this->salt = $this->generateSalt();
                $this->password = $this->hashPassowrd($this->password, $this->salt);
            }
 
            return true;
        }
        else
            return false;
    }      
 
    // ...
}

Это самый примитивный способ не позволяет легко менять пароль в дальнейшем без непосредственного присваивания хеша пароля в коде контроллера. Мы же стремимся сохранить контроллер примитивным:

protected function actionSettings()
{
    $model = $this->loadModel();    
    $model->scenario = 'settings'; // указываем нужный сценарий для правил валидации
 
    if (isset($_POST['User'])
    {
        $model->attributes = $_POST['User'];
 
        if ($model->save())
        {
            Yii::app()->user->setFlash('settings-form', 'Сохранено!');
            $this->refresh();
        }
    }
 
    $this->render('settings', array('model'=>$model));
}

Как вариант, можно запоминать старый пароль в приватной переменной и сравнивать с новым при каждом сохранении. Если пароль другой, то заменить его на хэш:

class User extends CActiveRecord
{
    private $_old_password; // для хранения хэша старого пароля
 
    // ...
 
    protected function afterFind() // при чтении из базы
    {
        $this->_old_password = $this->password;
        parent::afterFind();
    }     
 
    protected function beforeSave()
    {
        if (parent::beforeSave())
        {
            if ($this->password !== $this->_old_password)
            {
                $this->salt = $this->generateSalt();
                $this->password = $this->hashPassword($this->password, $this->salt);
            }            
            return true;
        }
        else
            return false;
    }    
 
    // ...
}

Вроде бы всё работает, но попробуйте вывести форму

<?php $form=$this->beginWidget('CActiveForm'); ?>
    <?php echo $form->textField($model,'username'); ?><br />        
    <?php echo $form->textField($model,'email'); ?><br />        
    <?php echo $form->passwordField($model,'password'); ?><br />
    <?php echo CHtml::submitButton('Сохранить'); ?>
<?php $this->endWidget(); ?>

…и Вы увидите, что поле пароля будет заполнено кучей точек. Там действительно будет содержимое поля $model->password. Таким образом, в поля формы будут выведены текущий логин, email и хэш пароля, а это небезопасно.

Мы должны иметь возможность задавать новый пароль и не должны иметь прямого доступа из формы к старому!

Добавим в модель пару полей:

class User extends CActiveRecord
{    
    public $new_password;
    public $new_confirm;
 
    public function rules()
    {
        return array(
            array('username, email', 'required'),
            array('username', 'match', 'pattern'=>'#^[a-zA-Z0-9_\.-]+$#', 'message'=>'Логин содержит запрещённые символы'),
            array('email', 'email', 'message'=>'Неверный формат E-mail адреса'),
            array('username, email', 'unique', 'caseSensitive'=>false),
            array('email, username, new_password, new_confirm', 'length', 'max'=>255),
            array('new_password', 'length', 'min'=>6, 'allowEmpty'=>true),
            array('new_confirm', 'compare', 'compareAttribute'=>'new_password', 'message'=>'Пароли не совпадают'),
 
            // Register
            array('new_password', 'required', 'on'=>'register'),
 
            // Settings
            array('username', 'unsafe', 'on'=>'settings'),
        );
    }
 
    public function attributeLabels()
    {
        return array(
            // ...
            'new_password' => 'Новый пароль',
            'new_confirm' => 'Подтвердите новый пароль',
        );
    }
 
    protected function beforeSave()
    {
        if (parent::beforeSave())
        {
            if ($this->new_password)
            {
                $this->salt = $this->generateSalt();
                $this->password = $this->hashPassword($this->new_password, $this->salt);
            }            
            return true;
        }
        else
            return false;
    } 
}

Здесь мы добавили отдельное поле для ввода нового пароля. Исходное поле password объявили небезопасным (его теперь нельзя перезаписать через безопасное присваивание атрибутов). В методе beforeSave() модели мы смотрим, введён ли новый пароль в поле new_password. Если введён, то записываем его хэш в поле password для записи в БД.

Теперь в формах регистрации (сценарий «register») и настроек профиля (сценарий «settings») нужно использовать поле new_password вместо password;

<?php $form=$this->beginWidget('CActiveForm'); ?>
    <?php echo $form->textField($model,'username'); ?><br />        
    <?php echo $form->textField($model,'email'); ?><br />        
    <?php echo $form->passwordField($model,'new_password'); ?><br /> 
    <?php echo $form->passwordField($model,'new_confirm'); ?><br /> 
    <?php echo CHtml::submitButton('Сохранить'); ?>
<?php $this->endWidget(); ?>

Если теперь ввести пароль в это поле, то он правильно сохранится в модели, а поле ввода пароля во всех формах будет пустым. Контроллер вообще не изменился. Всю работу делает модель.

 

Взято отсюда

Комментарии

comments powered by Disqus