Yii: Разработка приложения с нуля. Часть 2. Создаём новых пользователей. Авторизация, валидация, пароли, соли.

Yii: Разработка приложения с нуля. Часть 2. Создаём новых пользователей. Авторизация, валидация, пароли, соли. Как создать других пользователей, кроме demo в стандартном блоге. Сделать себя администратором.

В Yii блоге, который мы установили по умолчанию, уже модель User (связана с таблицей tbl_user), но ни контроллера, ни файлов видов ещё не создано, а в базе - только один бедняжка "demo". Есть что развивать.

1) С помощью Gii создадим для модели User свою Crud. (Так за секунды мы получим контроллер с необходимыми функциями работы с пользователями + все файлы видов).

Если у вас пока не включен Gii, то надо включить его в работу, прописав в config/main.php:

'modules'=>array(
		// uncomment the following to enable the Gii tool
		
		'gii'=>array(
			'class'=>'system.gii.GiiModule',
			'password'=>'yourpassword',
		 	// If removed, Gii defaults to localhost only. Edit carefully to taste.
			'ipFilters'=>array('127.0.0.1', '::1'),
		),
	),

2) При попытке открыть /index.php/user, выпадает ошибка 500 (говорит, что нет search в модели).

Не проблема. Возьмём этот метод из модели Post. И изменим, что надо и добавим в models/User.php:

public function search()
	{
		$criteria=new CDbCriteria;
		$criteria->compare('username',$this->username,true);
		$criteria->compare('email',$this->email);

		return new CActiveDataProvider('User', array(
			'criteria'=>$criteria,
			'sort'=>array(
				'defaultOrder'=>'username, email DESC',
			),
		));
	}


3) Теперь закомментируем временно accessRules() в контроллер UserController или разрешим demo функции администраторские (потому что этот demo пока у нас один и мы на него возлагаем всю работу).

Создаём от его лица нового пользователя 'admin' (через путь 'index.php/user/create'). Видим что у нас просят зачем-то Salt (надо чтобы сама генерировалась), и кроме того, пароль не кодируется, а сохраняется в базу как мы его ввели. Это надо доработать.

4) В User::rules() дописываем проверку на уникальность email и username:

public function rules()
{
// NOTE: you should only define rules for those attributes that // will receive user inputs. return array(
...
array('email, username', 'unique'),
// The following rule is used by search(). 
// Please remove those attributes that should not be searched. 
array('id, email, username, password', 'safe', 'on'=>'search'),
);
}

5) Удалим из protected/views/user/_form поле 'safe' и добавим поле для повтора пароля

<div class="row"> 
<?php echo $form->label($model,'password_repeat'); ?> 
<?php echo $form->passwordField($model,'password_repeat',array('size'=>60,'maxlength'=>256)); ?> 
<?php echo $form->error($model,'password_repeat'); ?>
</div>

Это обычная практика, когда нового пользователя при регистрации просят ввести пароль повторно, чтобы он не ошибся. Встроенный в Yii класс CCompareValidator поможет нам сравнить пароли в двух полях.

Добавляем в модель User в самый верх переменную $password_repeat:

<?php
class User extends CActiveRecord
{
	public $password_repeat;
...

Мы добавляем в названии арибута _repeat, потому что валидатор так работает. Если он не находит явно указанного поля, с которым производить сравнение, то сравнит с полем такого же названия с добавкой _repeat.

Теперь добавим в User::rules() правило compare, и пропишем password_repeat в безопасные атрибуты, чтобы он мог передаваться при вызове setAttributes(), хотя он и не является полем в таблице tbl_user:

array('password', 'compare'),
array('profile, password_repeat', 'safe'),

6) Вернёмся к закодированию пароля при добавлении в базу данных. Сейчас он не кодируется никак и это небезопасно. Но и тут всё относительно просто: нам поможет метод afterValidate(). В модели Users уже нам разработчики заготовили функции для шифрования, можно написать свои, но я пока оставляю как есть, только добавляю afterValidate():

// После валидации присваиваем пароль и соль.
  public function afterValidate()
  {
 				$this->password = self::hashPassword($this->password, $this->salt);
        if($this->isNewRecord) {
            $salt = self::generateSalt();
            $this->password = self::hashPassword($this->password, $salt);
            $this->salt = $salt;
            //$this->role = 'user';
        }
        return true;
  }

	// Проверка валидности пароля
	public function validatePassword($password)
	{
		return $this->hashPassword($password,$this->salt)===$this->password;
	}

	// Создание хэша пароля
	public function hashPassword($password,$salt)
	{
		return md5($salt.$password);
	}

	/**
	 * Generates a salt that can be used to generate a password hash.
	 * @return string the salt
	 */
	protected function generateSalt()
	{
		return uniqid('',true);
	}

Тут соль генерируется как это сделано по-умолчанию, с помощью функции uniqid(). Но функция генерации соли может быть например, как предлагает timlar на форуме yiiframework.ru

// Генерация "соли". Этот метод генерирует случайным образом слово
    // заданной длины. Длина указывается в единственном свойстве метода.
    // Символы, применяемые при генерации, указаны в переменной $chars.
    // По умолчанию, длина соли 32 символа.
    public function generateSalt($length=32)
    {
        $chars = "abcdefghijkmnopqrstuvwxyz023456789";
        srand((double)microtime()*1000000);
        $i = 1;
        $salt = '' ;

        while ($i <= $length)
        {
            $num = rand() % 33;
            $tmp = substr($chars, $num, 1);
            $salt .= $tmp;
            $i++;
        }
        return $salt;
    } 

Ещё мы немного изменили для кратости метод authenticate в компоненте UserIdentity, посмотрите:

public function authenticate()
	{
		$user=User::model()->find('LOWER(username)=?',array(strtolower($this->username)));
		if($user===null)
			$this->errorCode=self::ERROR_USERNAME_INVALID;
		else if(!$user->validatePassword($this->password))
			$this->errorCode=self::ERROR_PASSWORD_INVALID;
		else
		{
			$this->_id=$user->id;
			$this->username=$user->username;
			$this->errorCode=self::ERROR_NONE;
		}
		return !$this->errorCode;
	}

Цель достигнута, не забудьте восстановить accessRules(), и указать там в правах нужных пользователей. Мы не вводили роли пользователям, пока по простому, для больших приложений удобнее будет взять расширения (например yii-users, yii-user-management).

И добавьте в шаблон, например в column2.php

<?php
			$this->beginWidget('zii.widgets.CPortlet', array(
				'title'=>'Operations',
			));
			$this->widget('zii.widgets.CMenu', array(
				'items'=>$this->menu,
				'htmlOptions'=>array('class'=>'operations'),
			));
			$this->endWidget();
		?>

Это покажет меню, созданные Gii. А в общее меню (компонент userMenu.php):

<li><?php echo CHtml::link('Manage Users',array('user/admin'), $htmlOptions=array('class'=>'admin')); ?></li>



Источник: loco.ru

Комментарии (7)  

7 комментариев

#285
cpentyc говорит:
February 21, 2012 at 10:17 am
Думаю нужно в правилах дать доступ не авторизованному пользователю к действию user/create))))
#286
cpentyc говорит:
February 21, 2012 at 10:52 am
У меня никто авторизоваться не может
#287
cpentyc говорит:
February 21, 2012 at 11:02 am

<?php echo $form->passwordField($model,'password',array('size'=>60,'maxlength'=>128,'value'=>'')); ?>

нужно сбрасывать пароли, то там хэш при неудачной валидации забит

Loco: Верно, но некритично.

#288
cpentyc говорит:
February 22, 2012 at 12:36 pm
  public function afterValidate()
  {
 
$this->password = self::hashPassword($this->password, $this->salt);
        if($this->isNewRecord) {

            $salt = self::generateSalt();

$this->password = self::hashPassword($this->password, $salt);

            $this->salt = $salt;

            //$this->role = 'user';

        }

        return true;

  }

#291
Саша Loco говорит:
February 22, 2012 at 05:22 pm

я делал регистрацию ручную, то есть только админы заводят новых пользователей. А так, да, разрешаете нужные экшены в контроллере для нужных пользователей.

Что в выделенной функции вы хотите сказать?

#305
cpentyc говорит:
February 23, 2012 at 09:52 am

При добавлении пользователя пароль генерится дважды .

public function afterValidate()
  {
 
        if($this->isNewRecord) {
            $salt = self::generateSalt();

$this->parent_id = (Yii::app()->user->parent_id==0?Yii::app()->user->id:Yii::app()->user->parent_id);
            $this->password = self::hashPassword($this->password, $salt);
            $this->salt = $salt;
            //$this->role = 'user';
return true;
        }
$this->password = self::hashPassword($this->password, $this->salt);
        return true;
  }

#308
Саша Loco говорит:
February 23, 2012 at 11:19 am
Может Yii::app()->end(); поставить в конце if (новая запись), чтобы дальше не генерировался пароль ещё раз?

Leave a Comment

Fields with * are required.

Картинка с кодом валидации
Пожалуйста введите символы с картинки. Регистр букв неважен.