Programmatically flushing a single user session

Depending on different IPN requests I am adding/removing users to/from a user group that is eligible for passing a paywall via a rest handler:

if ($grantAccess) {
    if (!$user->isMember($group->get('id'))) {
        $user->joinGroup($group->get('id'));
    }
    $action = 'granted';
} else {
    if ($user->isMember($group->get('id'))) {
        $user->leaveGroup($group->get('id'));
    }
    $action = 'revoked';
}

The whole logic is working, but the actual access perms/session of the user are not getting updated unless the user does a re-login. Looking at the leaveGroup and joinGroup code it only unsets the current session permissions:

unset($_SESSION["modx.user.{$this->get('id')}.userGroupNames"],
                    $_SESSION["modx.user.{$this->get('id')}.userGroups"]);

And since I am running this from a different process than where the actual user session is in, this won’t work.

How can achieve the desired results?

I’m not sure I understand what exactly you are trying to do.


With the default settings, MODX saves the sessions to the database table modx_session. But I don’t think it’s possible to identify the rows of a given user.

You probably need a solution like smartSessions, that uses a custom session handler and adds a new column user_id to the (new) session table (modx_smart_sessions). Then you could delete the entries of a certain user, so that the user is forced to log-in again.

It’s also not advisable to associate a user_id with a session, as that then becomes a vector for security attacks should someone gain access to the database. In fact, we just deprecated and stopped recording the session_id in the user_attributes table because it was reported as a potential security vulnerability.

Sorry, my bad. Let me try it again:

Workflow

  • user is logged in and has access to paid content (via user group paid)
  • payment gateway calls modRest route with an IPN that payment was cancelled
  • my handler removes this user via leaveGroup
    => Problem: this user still has access to the paid content until he logs out and logs in again.

I guess the session perms are not updated because leaveGroup is trying to unset those on the actual $_SESSION. However, that only affects the current PHP session running the code (my REST handler), not the actual user session in their browser. So the user’s permissions are not refreshed:

unset($_SESSION["modx.user.{$this->get('id')}.userGroupNames"],
                    $_SESSION["modx.user.{$this->get('id')}.userGroups"]);

Would it be a valid solution to set session_stale on this user after the groups have been changed? Like so…

if ($user->isMember($group->get('id'))) {
 $user->leaveGroup($group->get('id'));
}

$user->set('session_stale', serialize(['web']));
$user->save();

I don’t see that session_stale actually does anything. It is set in multiple places, but never used by anything as far as I can see.

Dang it. That’s true.

However I found a work around:

I created two plugins (note: I am misusing remote_key field here):

setReloginFlag:

<?php
switch ($modx->event->name) {
    case 'OnUserRemoveFromGroup':
    case 'OnUserAddToGroup':
        if (!$user) return '';

        // Set flag for relogin to flush permissions
        $user->set('remote_key','1');
        $user->save();
        
        break;
}

reloginUser

<?php
switch ($modx->event->name) {
    case 'OnLoadWebDocument':
        if($modx->user->hasSessionContext($modx->context->get('key')) && $modx->user->remote_key == 1){
            // remove session
            $user = $modx->getUser();
            $key = "modx.user.{$user->id}";
            $keyLength = strlen($key);
            foreach ($_SESSION as $sKey => $sVal) {
                if (substr($sKey, 0, $keyLength) === $key) unset($_SESSION[$sKey]);
            }
        
            $user->addSessionContext('web');
            $_SESSION["modx.web.user.token"]= $user->generateToken('web');
            $ext['needToRefreshSession'] = 0;
            $user->set('remote_key',null);
            $user->save();
        }
        break;
}

Just need to make sure that OnUserRemoveFromGroup and OnUserAddToGroup are invoked by the script. Afaik leaveGroup and joinGroup are not invoking those events.