How to catch 'Property "PortalWebUser.login_type" and then redirect user to logout

Here is the following scenario:

  1. User logs to the site, and has a cookie set for Remember me.

  2. We have some state variables set against this user.

A. We do some changes on the site, like we introduce new state variables.

  1. When the users are coming back, with their cookies active, they are recognized and logged in. But since their state doesn’t contain the new state variables, the system crashes with the following message:

[size=“2”][color="#454545"][font=“arial, helvetica, sans-serif”]‘CException’ with message ‘Property “PortalWebUser.login_type” is not[/font][/color][/size] [color="#454545"][font=“arial, helvetica, sans-serif”][size=“2”]defined.’ [/size][/font][/color]

So the question is, how to detect such exception, as when we detect such, we want to redirect the user to the logout page, so they must log in again.

Something like this?


class WebUser extends CWebUser

{


   public $possibleStates = array(

      'login_type',

      'something_else',

   );


   public function init()

   {

      parent::init();

      $this->verifyStates();

   }


   public function afterLogin($fromCookie)

   {

      $this->verifyStates();

   }


   public function verifyStates()

   {

      // Check if user is logged in and all $possibleStates are available.

      // If one is not available, logout the user or load the state dynamically if possible.

   }


}

What do you think about reusing afterLogin()

something like:




/**

     * This method is called after the user is successfully logged in. You may override this method to do some postprocessing (e.g. log the user login IP and time; load the user permission information).

     * @param type $fromCookie 

     */

    public function afterLogin($fromCookie) {


        parent::afterLogin($fromCookie);


        if ($fromCookie) {

            // we need to refresh user state variable

            $user = user::model()->findByPk($this->id);

            if (empty($user)) {

                // user was not found in the database, perform logout

                Yii::app()->controller->redirect('authorize/logout');

            } else {

                $user->saveState($this);//this is the key

            }

        }


        $this->logUserLoginHistory();

    }

What do you think of calling

$user->saveState($this);

at this step?

Well a new state might be available while a user has a active session. So doing the verification in afterLogin only is not enough?

A flow question: in init() after calling parent, do we have access to states? Eg: are they loaded from cookie?

Yes. You could do all the checking in init(), but you might want to do something different in case the user just logged in (instead of having an active session). This you can only do in afterLogin. CWebUser::init does makes sure afterLogin will be called, so in my example verifyStates would only be called once (if you stop the execution properly by redirecting the user).

For the case that everything is okay (user will not be redirected) you could add bool $statesVerified and set it to true to make sure the verification does not occur twice.

I have several problems with this.

If init calls verifyStates, that checks if the user exists, if not redirects to logout.

The problem is that this for not logged in users, causes a redirect loop. They are always redirect to logout, in a loop.

Like I wrote in code sample "Check if user is logged in". So just wrap your code in !$this->isGuest.

That helped, thanks.

But I still have problems.

I have submodules like admin. And for some reason the admin login, triggers the front-end PortalWebUser class, and the verifyStates does run, and since they are different states on admin, it simply redirects the user to the logout link of the front-end.

Both use and reference: Yii::app()->user

You could do the following:


'user' => array(

   'class' => 'PortalWebUser',

),

'adminUser' => array(

   'class' => 'AdminWebUser',

),

Then in AdminModule::init()


Yii::app()->setComponent('user', Yii::app()->adminUser);

Or within the custom webuser class you could check whether the module of the current controller is an instance of AdminModule (Yii::app()->controller->module).

I will try the above, but I already have in adminmodule:init()


// app uses separate login for admin

        $this->setComponents(array(

            'errorHandler' => array(

                'errorAction' => "admin/adminDefault/error"),

            'user' => array(

                'class' => 'RWebUser',

                'loginUrl' => Yii::app()->createUrl("{$this->id}/adminDefault/login"),

            )

        ));

but still permissions are checked against Yii::app()->user? So as far I see they conflict.

Are this accessed by Yii::app()->admin->user ?

Yes, by Yii::app()->getModule(‘admin’)->user. You could add to yout init():


Yii::app()->setComponent('user', $this->user);

Then Yii::app()->user is an instance of RWebUser. But this can lead to problems if you access the admin module from the main application…

As I wrote you could go another way and use only 1 WebUser class. In there you could do:


if (Yii::app()->controller->module instanceof AdminModule)

{


}

else

{


}

I managed to put this together, and it works now.

However I have another issue:

I have a preFilter setup. And there I have a condition like:




if (Yii::app()->user->hasState('login_type')) {

            if (Yii::app()->user->login_type == user::LOGIN_TYPE_IMPERSONATE) {

                return true;

            }

        }

it should skip the filter.

The problem is that it seams the preFilter runs before any states are set, so this condition is never met.

How can I get the proper user object at this stage?