This post covers my notes on User Management and Auth (chapter 6) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

  • Component behavior
  • Hash the password
  • Yii authentication model

Behaviors in Yii are classes implementing the IBehavior interface, and whose methods can be used to extend the functionality of components by being attached to the component, rather than the component explicitly extending the class. Behaviors can be attached to multiple components and components can attach multiple behaviors. => achieve a kind of multiple inheritance for our Yii component classes.

Rather than just adding the logic directly to our User model class, is because our other model classes, Issue and Project, also need this same logic.

In order for a component to use the methods of a behavior, the behavior has to be attached to the component. :

$component->attachBehavior($name, $behavior);

Zii extension library, already has a ready-made behavior that will update date-time columns, create_time and update_time, which we have on each of our underlying tables. This behavior is called CTimestampBehavior.

protected/
models/User.php:
public function behaviors()
{
return array(
'CTimestampBehavior' => array(
'class' => 'zii.behaviors.CTimestampBehavior',
'createAttribute' => 'create_time',
'updateAttribute' => 'update_time',
'setUpdateOnCreate' => true,
),
);
}

This is great, but we need to repeat this in our other model classes. We could duplicate the behaviors() method in each one. Alternatively, we could put this in a common base class and have each of our model classes extend this new base class. Extending the existing behavior to add this extra functionality would probably make the most sense in a real-world application; however, to demonstrate another approach, let's tap into the active record beforeSave event, and do this in a common base class from which all of our AR model classes can extend. This way, we exposure to a couple of different approaches and have more options to choose from when building other own Yii applications.

So, we need to create a new base class for our AR model classes. We'll also make this new class abstract since it should not be instantiated directly.

//TrackStarAR
<?php
abstract class TrackStarActiveRecord extends CActiveRecord
{
     /**
     * Prepares create_user_id and update_user_id attributes before saving.
     */
 
    protected function beforeSave()
    {
 
        if(null !== Yii::app()->user)
            $id=Yii::app()->user->id;
        else
            $id=1;
 
        if($this->isNewRecord)
            $this->create_user_id=$id;
 
        $this->update_user_id=$id;
 
        return parent::beforeSave();
    }
 
    /**
     * Attaches the timestamp behavior to update our create and update times
     */
    public function behaviors() 
    {
        return array(
            'CTimestampBehavior' => array(
                'class' => 'zii.behaviors.CTimestampBehavior',
                'createAttribute' => 'create_time',
                'updateAttribute' => 'update_time',
                'setUpdateOnCreate' => true,
            ),
        );
    }
 
}
 
/*then we change
class User extends CActiveRecord
{
…}
to
class User extends TrackStarActiveRecord
{
…}*/

For example, we see lines like the following in our create and update controller actions for AR models:

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

This is doing a mass assignment of all of the model attributes from the posted form fields. As an added security measure, this only works for attributes that have validation rules assigned for them. You can use the CSafeValidator as a way to mark model attributes that don't otherwise have any validation rules as being safe for this mass assignment. Since these fields are not going to be filled in by the user, and we don't need them to be massively assigned, we can remove the rules. Open up protected/models/User.php and in the rules() method, remove the following two rules:

array('create_user_id, update_user_id', 'numerical',
'integerOnly'=>true),
array('last_login_time, create_time, update_time', 'safe'),

The removal of the rule for the last_login_time attribute above was intentional.

Hash the password This time we'll override the afterValidate() method and apply a basic one-way hash to the password after we validate all the input fields, but before we save the record. User AR class - add the following to the bottom :

/**
     * apply a hash on the password before we store it in the database
     */
    protected function afterValidate()
    {   
        parent::afterValidate();
        //ensure we don't have any other errors
        if(!$this->hasErrors())
            $this->password = $this->hashPassword($this->password);                     
    }
 
    /**
     * Generates the password hash.
     * @param string password
     * @return string hash
     */
    public function hashPassword($password)
    {
        return md5($password);
    }
 
    /**
     * 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->password;
    }

Yii authentication model

Central to the Yii authentication framework is an application component called user, configuration can be seen in the protected/config/main.php file, under the components array element:

'user'=>array(
// enable cookie-based authentication
'allowAutoLogin'=>true,
),

The following sequence diagram depicts the class interaction that occurs during a successful login from the time the form is submitted. diagram yii class interaction

Replace the contents of component UserIdentity.php with the following code:

<?php
 
/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
 
class UserIdentity extends CUserIdentity
{
    private $_id;
 
    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->setState('lastLogin', date("m/d/y g:i A", strtotime($user->last_login_time)));
            $user->saveAttributes(array(
                'last_login_time'=>date("Y-m-d H:i:s", time()),
            ));
            $this->errorCode=self::ERROR_NONE;
        }
        return $this->errorCode==self::ERROR_NONE;
    }
 
    public function getId()
    {
        return $this->_id;
    }
}

And since User model class will do the actual password validation, add the following method to User model class:

/**
* 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->password;
}

Download source code of chapter 7

Leave a Comment

Fields with * are required.

Please enter the letters as they are shown in the image above.
Letters are not case-sensitive.