checkAccess id instead of name

Hi,

I think Yii is the best framework I have used until now. Great work!

I’m working with Role-Based Access Control at the moment and find – when using a database-based system – it requires quite a lot of work to perform a checkAccess() on a user ID instead of a username, as Yii::app()->user->checkAccess($role) uses the username. I could of course use that, but when a user changes his or her username either he or she loses her rights or I have to perform an UPDATE SET oldname=newname on the AuthAssignment table.

Cheers, spr

Welcome aboard!

The RBAC implemented by Yii allows you to use either user ID or username for access assignment and checking. The default implementation of CWebUser::checkAccess() uses its 'id' property to check the access. If you like, you can extend it and change to use 'name' property to do access checking.

Of course, you would need to adjust your DB schema accordingly.

Also, if you design your DB schema well, you don't have to do UPDATE…SET explicitly. The FK referential integrity will ensure the consistency should a PK is changed.

Okay, but Yii::app()->user->id contains the username?

This depends on how you implement UserIdentity class.

By default, username is returned. But you override getId() method in your UserIdentity to make it return an ID instead.

Works like a charm. Maybe you could include this piece of information in the documentation, as it is not very obvious to a starting user.

Cheers!

I'm also currently struggling with this. I want getId() to return the pk, not the username. I guess, i have to store the Pk somewhere in UserIdentity::authenticate(). I don't want to use get/setState() as i don't want the id to be stored in cookies (might be forged…). Maybe someone has a basic example, how to store persistent user data without using set/getState().

I tried now by adding this to UserIdentity::authenticate()

<?php


$_SESSION['username']=$user->id;


and overriding getId:

<?php


    public function getId()


    {


        return $_SESSION['userid'];


    }


But now login doesn't work anymore.

The blog demo has the solution for you. ;)

Thanks Qiang, it works now. I must admit, that the UserIdentity object is still a little unclear to me. It's a bit hard to get an idea, what this object represents.

Maybe you can point me to the right directions on these:

  1. What's the "logical" difference between CWebUser and CUserIdentity?

  2. How is the private $_id in blog tutorial’s CUserIdentiy preserved between requests?

  3. Why did my above approach using $_SESSION instead of a private var not work?

If the answer for 2. is "cookies", then i think it's to easy to forge user id's. What would be the alternative then?

  1. UserIdentity represents a way to authenticate a user. So we can have form-based identity, SSO identity, and so on. CWebUser represents persistent data relevant to the current user. It is more like $_SESSION.

  2. It is serialized and saved in session or cookie, depending on CWebUser setting.

  3. Saving in $_SESSION doesn't ensure the data is available next time you open the browser.

The cookie is equipped with a hash code that can prevent it from being tampered.

I see, thanks for elaborating.

But i still have a problem with 3: I didn't close the browser, i just tried to login but it shows the login page over and over, if i store the $user->id in $_SESSION.

If been a little confused about these things too, thanks for helping clear it up a little for me.

One thing I was wondering, is it possible to access persistent user data in a model sort of way?  So I can for instance get the user's email via:

Yii::app()->user->email

and the rank:

Yii::app()->user->rank

And these attributes should be stored in the session/CWebUser upon login.  The rank attribute should default to 1 if the user is not logged in.

I was able to get this behavior but i'm not sure if it is the Yii way.  This is what I did:

<?php


//UserIdentity


<?php


class UserIdentity extends CUserIdentity


{


	const ERROR_EMAIL_INVALID=3;


	public $id;





	


	public function authenticate() {


		$record=User::model()->findByAttributes(array('username'=>$this->username));


		


		if ($record === null) {


			$this->errorCode = self::ERROR_USERNAME_INVALID;


		} elseif ($record->password !== md5($this->password)) {


			$this->errorCode = self::ERROR_PASSWORD_INVALID;


		} elseif ($record->email_confirmed != null) {


			$this->errorCode = self::ERROR_EMAIL_INVALID;


		} else {


			$this->username = $record->username; //so the username is in the right case


			$this->id = $record->id;


			$this->setState('rank', $record->group_id); //remember rank


			$this->setState('email',$record->email); //remember email


			


			$this->errorCode = self::ERROR_NONE;


		}


		return !$this->errorCode;


	}


	


	public function getId(){


		return $this->id;


	}





}





//I also extended CWebUser


//WebUser:


class WebUser extends CWebUser {


	public $email;


	public $rank = 1;


	


	public function init() {


		parent::init();


		$this->email = $this->getState('email');


		$rank = $this->getState('rank');


		if ($rank != null)


			$this->rank = $rank;


	}	


}


It works but it seems tedious to me as when I want to add a persistent attribute I need to add code in three places.

Jonah: what you did is as expected. The data stored using setState() is saved in $_SESSION or cookie (if auto-login is enabled). The main purpose of doing so is to save one DB query for each request. It has the drawback that the data may be outdated. For example, an admin user changed the rank of a user, while the old rank was still in use by its user (because it was read from session/cookie instead of DB). Therefore, you should not put too much data into identity states. Also note, it is possible that you save the whole user AR object into state. However, you need to make sure the data can be properly unserialized and it doesn't contain sensitive information like password.

Quote

It has the drawback that the data may be outdated. For example, an admin user changed the rank of a user, while the old rank was still in use by its user (because it was read from session/cookie instead of DB).

Yeah, I noticed that when changing the user rankings via phpMyAdmin.  But I will not be doing that when in production mode, so I won't worry about that.  And I don't want it to perform a user quarry on every request (not worth it).

Quote

Also note, it is possible that you save the whole user AR object into state. However, you need to make sure the data can be properly unserialized and it doesn't contain sensitive information like password.

Really?  That's interesting…  I will keep that in mind if I need to have many more persistent attributes.

I mostly wanted to know if you approved of the way I was doing that and that is was not more of a hack. (I wasn't sure if CWebUser was meant to be extended either). Thanks!

Well, the rank could be changed by an admin user of the application, not necessarily through phpmyadmin. This is quite normal in production mode, assuming the application provides a way to change rank.

Yes, CWebUser can also be extended. As a matter of fact, you can override any application component if needed.