For a project I’m working, users can have up to five roles.
It just occurred to me that if an admin were to go in and change a user’s role, the role wouldn’t be updated until the user logged out and back in, because the assignRole() operations are in UserIdentity.
I took a look at the cookbook here: http://www.yiiframework.com/doc/cookbook/60/ - and liked the idea. Now, I’ve been scratching my head trying to figure out how to use a similar idea to load user roles “on the fly”, to avoid the scenario I described above.
If the scenario I described is one that happens (I’m assuming a user’s roles get stored in a session, and not loaded dynamically each time from the database every time their role is checked), does anybody know of a way to do something similar to the cookbook page - except for roles? I could see that functionality being combined with what’s described on the cookbook page as an extension - for live database-managed user params and roles, rather than login-assigned params and roles.
and then you can look search through the bottom of the page for your rbac table names. You can see how many queries are being done on them, and I bet you are getting a re-request for every check you do. You will have to do one of the following: either explicity check permissions before you do stuff with
Or you will have to override some functionality in your controllers to check automatically. If you have to go this route, I would use one of the extensions that has a management interface built in already rather than doing the work yourself. I highly recommend srbac extension if you are doing rbac. It is a little daunting to install at first, but within the hour you should be rocking and rolling.
I’m actually using SRBAC - great extension! Perhaps I didn’t phrase my question clearly, or didn’t understand your response: I agree that a query is being done every time checkAccess() or something similar is called; but I think what happens is
the roles get assigned into a session at the user login by UserIdentity
whenever checkAccess() (or something similar) is called, it looks in the database for permissions, but compares those to the roles already stored in the session.
What I’m looking for is a way that when checkAccess() (or similar) is called, instead of looking at the session-stored roles (assigned on login) and comparing those to the RBAC hierarchy in the database, it will instead re-check the database and dynamically load the user’s roles based on whatever criteria in the database resulted in that user being assigned those roles during login.
This way, if an admin gives a user role A while that user is logged in, they won’t need to log out and then back in to get that role assigned to their session data.
I understand the question more now, but I don’t see that happening on my test app. I have a skeleton app that I threw srbac on. I was able to do the following successfully (I was logged in as superuser in one browser and a regular user in another).
As superuser, I revoked permissions to do some operations from superuser. Superuser account was immediately rejected access to these areas. I reassigned my access, and I was immediately allowed back in.
As a normal user, I accessed some section of the site. As the superuser, I revoked roles from the normal user. The normal user was immediately forbidden access to those parts of the site. As superuser, I re-added the role. Normal user was immediately able to access these parts again.
There was no logging out or logging back in at any point in this test.
Can you help me understand what you are seeing that makes you think role information is stored somewhere in session? Perhaps post your user object code and your useridentity object code so I can see what is going on?
How were you adding and revoking the roles? By changing the database flag (or criteria) taken by assignRole(), or by calling revoke() for that user? I would expect revoke() to work, but I was hoping to not have to add assignRole() and revoke() actions in the User controller or model…
I was using the front end included in srbac. I do not have any rbac related functions in my user model (active record), or in my user identiy class. The only functions I have added to my CWebUser class (extended for my project) have to do with the concept of a superuser, but nothing like revoke or assignrole.
Can you maybe post your main config file (without db info) ?
Hmm… so am I correct in saying that you assign roles to your users through SRBAC? My users don’t have roles assigned until they log in, based on their DB - so when they’re logged out, there isn’t anything in the database to reflect that they ever had a role. Of course the roles exist, just not attached to a user.
Absolutely right! I think we are getting somewhere. I assume you are using a custom CWebUser class then? Any chance you could post that and maybe your UserIdentity class as well? I am thinking that something you are using is caching somewhere, and the answer is to change the functionality to use a table that stores user->roles temporarily, maybe based on session id, and this table can be cleaned after x amount of inactivity, so you have a place to check on every page load, but you don’t persist user->roles after sessions are up.
I’m not using a custom CWebUser class - yet; although I was considering creating one so Yii::app()->user->someVariable would load straight from the database in real time, rather than being stored in session. Here’s my UserIdentity class as of now:
<?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;
/**
* Authenticates a user.
* @return boolean whether authentication succeeds.
*/
public function authenticate() {
$user=User::model()->find('LOWER(email)=?',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->user_oid;
$this->username=$user->email;
$auth=Yii::app()->authManager; //initializes the authManager
function assignRole($role,$name,$id) {
if($role==1) {
if(!Yii::app()->authManager->isAssigned($name,$id)) {
if(Yii::app()->authManager->assign($name,$id)) {
Yii::app()->authManager->save();
}
}
}
}
$cpadminStatus=0;
if(Involved::model()->count('is_cpadmin=1 AND user_fk='.$this->_id)!=0) {
$cpadminStatus=1;
}
$reviewStatus=0;
if(MakesReview::model()->count('user_fk='.$this->_id)!=0) {
$reviewStatus=1;
}
assignRole($user->is_appadmin,'appadmin',$this->_id);
assignRole($cpadminStatus,'cpadmin',$this->_id);
assignRole($user->is_super,'super',$this->_id);
assignRole($user->is_volunteer,'volunteer',$this->_id);
assignRole($reviewStatus,'reviewer',$this->_id);
$this->setState('appadmin', $user->is_appadmin);
$this->setState('super', $user->is_super);
$this->setState('volunteer', $user->is_volunteer);
$this->setState('cpadmin', $cpadminStatus);
$this->setState('reviewer',$reviewStatus);
$this->errorCode=self::ERROR_NONE;
}
return $this->errorCode==self::ERROR_NONE;
}
/**
* @return integer the ID of the user record
*/
public function getId() {
return $this->_id;
}
}
Now, where I use setState, I was assigning that because I’d use
<?php if(Yii::app()->user->appadmin) : ?>
and
<?php endif; ?>
to decide whether certain parts of views and controller actions should be echoed, much like checkAccess(). I’m not sure if that’s the best way, but deadlines were coming up fast and I needed something to work with. That’s the part I’d change if I use a custom CWebUser class - instead of assigning those to state, loading them directly from the database so they are updated in real-time.
setState and getState are definitely not what you want, as they save things to the session.
I think the first thing to try is to make a class sClarkUser entends CWebUser just like you said, and then move the functionality to that class so you have more control over it. Then if you decide there is too much DB activity, you can always cache however you want in your own code, and for whatever time period. Did this help? We’ve been at it so long I’m not sure if just the storage location is enough help or I can help with something else, too.
Well… that wasn’t the part I was interested in, for this topic, so much: I was more interested in what happens with the assignRole() section of the code. Somewhere there must be something like getRole() (not stated like that, but that’s how the access checking happens). I essentially need getRole() to either check the database every time it is activated (or a frequently updated cache) and apply a business rule, or assignRole() and revoke() to be called every time a user’s relevant database information is updated (that feels like messy and more unmaintainable code, although it could potentially be more efficient).
Thanks for your help though! - it’s greatly appreciated
It looks like assign() assigns stuff to the user, and save() makes it persistent. Maybe try
function assignRole($role,$name,$id) {
if($role==1) {
if(!Yii::app()->authManager->isAssigned($name,$id)) {
if(Yii::app()->authManager->assign($name,$id)) {
/* see if losing the following line makes things not persistent anymore
Yii::app()->authManager->save();
*/
}
}
}
}
Then you can use this wherever you need it (and it is used implicitly by Yii all over the place)