Change values before put them in the DB

Hi

I’m building an app with an authentication based on the Blog Demo of Yii.

The DB table "users" have fields id, username, password and salt.

I want to permit to admin to manage this table. So, with Gii, I generate CRUD for this table.

Naturally, on "create" or "update", the CRUD does not apply MD5 to password nor generate the salt key.

I can’t find out where I change the data to be sended to DB. I thought that was on the model but I don’t see where I can do that:


<?php


class Users extends CActiveRecord

{

	/**

	 * The followings are the available columns in table 'tbl_user':

	 * @var integer $id

	 * @var string $username

	 * @var string $password

	 * @var string $salt

	 */


	/**

	 * Returns the static model of the specified AR class.

	 * @return CActiveRecord the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'users';

	}


	/**

	 * @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(

			array('username, password', 'required'),

			array('username, password', 'length', 'max'=>128),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Id',

			'username' => 'Username',

			'password' => 'Password',

			'salt' => 'Salt',

		);

	}


	/**

	 * Checks if the given password is correct.

	 * @param string the password to be validated

	 * @return boolean whether the password is valid

	 */

	public function validatePassword($password)

	{

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

	}


	/**

	 * Generates the password hash.

	 * @param string password

	 * @param string salt

	 * @return string hash

	 */

	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);

	}


	/**

	 * Retrieves a list of models based on the current search/filter conditions.

	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.

	 */

	public function search()

	{

		// Warning: Please modify the following code to remove attributes that

		// should not be searched.


		$criteria=new CDbCriteria;


		$criteria->compare('userid',$this->userid,true);

		$criteria->compare('username',$this->username,true);


		return new CActiveDataProvider(get_class($this), array(

			'criteria'=>$criteria,

		));

	}

}

I believe that this is a rookie thing, but if someone can help me, I will apreciate.

Thanks

I havent done any work with authentication in Yii so cant offer solution. However, check out this link @ http://yiiframework.ru/doc/blog/en/prototype.auth - it talks about your problem.

Thanks nguyendh

But that I already have on my code. The authentication is running well on my app. It is based on the blog demo, as I said. And as in the demo is running well, on my app is also running well. The 2 users that was already created on the demo (admin and demon) can login and logout.

What I am trying to do is to permit to admin to manage the users, to add, delete, and update users information. For that, I used the CRUD generation form my users table.

And I can now add, delete and update users. The problem is that I want to apply MD5 do the password when saving it to database. Also, I want to generate the "salt" key for later apply to password for generating the MD5.

http://code.activestate.com/recipes/576894-generate-a-salt/ -> generate a random salt key

what stop you from using $user->password = $user->hashPassword($password,$salt) ?

Hi nguyendh

Thanks again for your answer

Nothing stop me from using your example. My problem is where I use that. In which function/method? It is after post but where?

Sorry, I know that this must be total rookie question, but I am lost with this. :-[

As you can see on the code that I posted, I already have the functions that generate the salt key and that apply md5 in conjunction with the salt key. These functions were already presente in the Blog Demo. But they are only used to make login. The demo blog does not have a users manage module.

in your actionCreate/actionUpdate in the controller where your admin create/update user.

simply add this method in your User Model like

[color="#1C2837"][size="2"][color="#000088"]public[/color] [color="#000088"]function[/color][color="#000000"] beforeSave[/color]color="#666600"[/color] [color="#666600"]{[/color][color="#000000"]

    &#036;pass [/color][color=&quot;#666600&quot;]=[/color][color=&quot;#000000&quot;] md5[/color][color=&quot;#666600&quot;]([/color][color=&quot;#000000&quot;]&#036;this[/color][color=&quot;#666600&quot;]-&gt;[/color][color=&quot;#000000&quot;]password[/color][color=&quot;#666600&quot;]);[/color][color=&quot;#000000&quot;]


    &#036;this[/color][color=&quot;#666600&quot;]-&gt;[/color][color=&quot;#000000&quot;]password [/color][color=&quot;#666600&quot;]=[/color][color=&quot;#000000&quot;] &#036;pass[/color][color=&quot;#666600&quot;];[/color] [color=&quot;#000088&quot;]return[/color] [color=&quot;#000088&quot;]true[/color][color=&quot;#666600&quot;];[/color] [color=&quot;#666600&quot;]}[/color][/size][/color]

thanks bipu

It’s that that I was looking for.

Now I have other question.

As you can see on the code that I posted before, I have a protected function “generateSalt” that can generate a Salt key. But how can I use it? I’m not too strong with the use of objects and classes so I don’t know exacly how to call that function.

I already tried:

$this->salt = generateSalt();

$this->salt = this->generateSalt();

$this->salt = parent::generateSalt();

I already change the function from protected to public, but it always give some kind of error.

Sorry, forget the last post… my mistake

I miss a "$" before "this".

Thank you again for your help.

append [color=#1C2837][font=monospace][size=2][color=#660066]Yii[/color][color=#666600]::[/color][color=#000000]app[/color]color=#666600->[/color][color=#000088]params[/color][color=#666600][[/color][color=#008800]"salt"[/color][color=#666600]] to pass attribute ,[/color][/size][/font][/color]

Hi Liurai,

Maybe you should take a look at the codes of existing authentication extensions, and learn how they deal with the hashing of the password.

I’m using “yii-user”, and as far as I can see, it is hashing the password just before it calls User::save() in the controllers. Not in the model codes.

@bipu: I think a simple hashing of the password in User::beforeSave() may not work, because the password could be already hashed when you call save() from the actionUpdate() method.

The following is the actionUpdate() method of the AdminController of yii-user:




public function actionUpdate()

{

	$model=$this->loadModel();

	$profile=$model->profile;

	$this->performAjaxValidation(array($model,$profile));

	if(isset($_POST['User']))

	{

		$model->attributes=$_POST['User'];

		$profile->attributes=$_POST['Profile'];

			

		if($model->validate()&&$profile->validate()) {

			$old_password = User::model()->notsafe()->findByPk($model->id);

			if ($old_password->password!=$model->password) {

				$model->password=Yii::app()->controller->module->encrypting($model->password);

				$model->activkey=Yii::app()->controller->module->encrypting(microtime().$model->password);

			}

			$model->save();

			$profile->save();

			$this->redirect(array('view','id'=>$model->id));

		} else $profile->validate();

	}

	$this->render('update',array(

		'model'=>$model,

		'profile'=>$profile,

	));

}



As you will notice, it reads the old password from the DB table and compares it with the new one that the admin user has input. If they are different, then it hashes the password before it calls User::save(). If they are the same, i.e., if the admin user hasn’t changed the password, then there’s no need to hash the password, because it’s already hashed.

It might seem strange to use a hashed password for the input form of an update method, but I think it’s okay. We just need to know whether the admin user has wanted to change the existing password or not. And the admin user doesn’t need to know the old password, or, more precisely, he should not know the original password in a plain text.

Hi Liurai, I am not sure if this will help you but, here it goes:

User authentication was the first thing I did with Yii, and I did it in a different way as the examples. I had an extra field in the login for (accountName in addition to userName + password), so I used the examples as a general guide.

I have these functions on my User model:


   public function __set($name, $value) {

      switch ($name) {

      case 'password':

         $value = self::hashPassword($value);

         break;   

      case 'password2':

         $this->_password2 = self::hashPassword($value);

         return;

      }

      parent::__set($name, $value);

   }

   public static function hashPassword($password) {

      return md5($password . self::PASSWORD_SALT . md5($password));

   }

The first function changes the setter for password, so each time you do:


$user->password = 'something'

the password will be hashed (It will do the same with password2, that I use for password confirmation on the change password form)

The second function hashes the password. I am using the same self::PASSWORD_SALT for every user. If you want to have different salt for each user, you can change the code to something like:


   public function __set($name, $value) {

      switch ($name) {

      case 'password':

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

         break;   

      case 'password2':

         $this->_password2 = self::hashPassword($value);

         return;

      case 'salt':

         if ($this->isNewRecord) { // In your case I think that if is not new record, salt comes from the db

            return 'new salt';

         }

      }

      parent::__set($name, $value);

   }

   public static function hashPassword($password, $salt) {

      return md5($password . $salt . md5($password));

   }

So far this approach has worked for me. Notice that:


$a = 'hello';

$user->password = $a;

$b = $user->password;

if ($a == $<img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='B)' /> {

   echo 'This will not be printed because $a != $b';

}

In other words you will lose the password that the user enters after you put it in $user->password. But that has not been a problem to me so far (and you can always save that in a protected field $originalPassword on the user model)

Good luck

rurbinac

Hi rurbinac

Thanks for showing me your approach. Meanwhile and using some lines of the example of user "softark", I think that I already solve my problem, and for now (and for my actual needs) it is working. Thank you

Hi softark

Thanks to you too.

You are right when calling my attention that the code should be on the controller and not on the model. I had it on the model and it worked on “Create user” function. But when I started to think on “update user” function, I saw that I had a problem, because that thing that you said: on “update user”, the password is already hashed. It doesn’t need to be hashed again.

So, I tooked some ideas from your example and make the following changes to my controller:


			$model->salt = $model->generateSalt();

			$model->password = $model->hashPassword($model->password, $model->salt);

added these 2 lines to actionCreate


	public function actionCreate()

	{

		$model=new Users;


		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);


		if(isset($_POST['Users']))

		{

			$model->attributes=$_POST['Users'];

			$model->salt = $model->generateSalt();

			$model->password = $model->hashPassword($model->password, $model->salt);

			if($model->save())

				$this->redirect(array('view','id'=>$model->userid));

		}


		$this->render('create',array(

			'model'=>$model,

		));

	}



and added the following code to actionUpdate


			$old_password = Users::model()->findByPk($id);

			if ($old_password->password!=$model->password) {

				$model->salt = $model->generateSalt();

				$model->password = $model->hashPassword($model->password, $model->salt);

			}




	public function actionUpdate($id)

	{

		$model=$this->loadModel($id);


		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);


		if(isset($_POST['Users']))

		{

			$model->attributes=$_POST['Users'];

			$old_password = Users::model()->findByPk($id);

			if ($old_password->password!=$model->password) {

				$model->salt = $model->generateSalt();

				$model->password = $model->hashPassword($model->password, $model->salt);

			}

			if($model->save())

				$this->redirect(array('view','id'=>$model->userid));

		}


		$this->render('update',array(

			'model'=>$model,

		));

	}