We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 41592 ☆ A M B ☆
    • 83 Posts
    Hello to all,

    I dealt with the management of user groups and related ACLs.

    I found that when you change / add / remove a user from a group, they will need to login / logout to update their session to apply the new rules.

    This can be particularly difficult for users, as it is necessary to communicate the need to log out (argh).

    Describing a context of use:
    1) We have 3 modUserGroup. Each group identifies a subscription with associated different features: Gold, Premium, Platinum.

    2) A user belonging to the Gold group signs a new subscription. This will have to change from the Gold group to the Premium group. eg through
    $user->joinGroup('Premium') and $user->leaveGroup('Gold')


    3) The User Dashboard has different features depending on the membership of a particular group.

    4) Once the group has been changed, the user still sees the dashboard associated with the Gold group until he updates his session (login / logout)


    To work around this problem I currently use a workaround to automatically update the user's session. It works, but I do not like it (example because I need an endpoint for the user to trigger it).

    My question is:
    - Why is this update not automatically applied?

    And again:
    - Why, in
    moduser/getUserGroupNames()
    and
    moduser/getUserGroups()
    , we check first the existence of the $_SESSION variable and after (if there is no $_SESSION) do we query the modUserGroup?

    - Why in
    moduser/isMember($groups,$matchAll = false)
    we use
    $groupNames= $this->getUserGroupNames();
    to check if user is member of group?
    $_SESSION["modx.user.{$id}.userGroupNames"]
    is different than
    $_SESSION["modx.user.{$id}.userGroups"]
    and sometimes it's not equal

    Thanks!

      Francesco Mussoni | MODX Ambassador | Skype: solidusite2 | Email: [email protected]
      • 3749
      • 24,544 Posts
      I can't answer most of those questions, but this might work (or not):

      In a plugin connected to OnUserAddToGroup or OnUserRemoveFromGroup:

      unlink('core/cache/context_settings/manager/context.cache.php');
      return;


      (Change 'manager' if you've renamed the manager folder.)


      If that doesn't work, you might be able use the same plugin to alter the $_SESSION array manually (if you can figure out its structure) to change the user's group membership.
        Did I help you? Buy me a beer
        Get my Book: MODX:The Official Guide
        MODX info for everyone: http://bobsguides.com/modx.html
        My MODX Extras
        Bob's Guides is now hosted at A2 MODX Hosting
        • 41592 ☆ A M B ☆
        • 83 Posts
        Quote from: BobRay at Mar 26, 2018, 01:13 PM
        I can't answer most of those questions, but this might work (or not):

        In a plugin connected to OnUserAddToGroup or OnUserRemoveFromGroup:

        unlink('core/cache/context_settings/manager/context.cache.php');
        return;


        (Change 'manager' if you've renamed the manager folder.)


        If that doesn't work, you might be able use the same plugin to alter the $_SESSION array manually (if you can figure out its structure) to change the user's group membership.

        It dosen't work.

        Some precisation:
        I said "Dashboard" but I was not necessarily referring to the mgr context. Suppose you have created a dashboard for users in the web context as well.

        However, deleting the cache file affects all visitors to the site.

        The problem is also in the modx_session table.

        I wrote this topic not to find a solution, but to understand the logic behind these functions.

        That's my workaround for the web context:

        if($ext['needToRefreshSession']){
            // remove session
            $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;
            $profile->set('extended',$ext);
            $profile->save();
        }


        in this way a silent logout / login is performed [ed. note: solidusite last edited this post 8 years, 6 months ago.]
          Francesco Mussoni | MODX Ambassador | Skype: solidusite2 | Email: [email protected]
          • 3749
          • 24,544 Posts
          That look like a great solution. How is that code triggered? How is $ext['needtoRefreshSession'] set above this code?

          Do you mind if I do a blog post on this?
            Did I help you? Buy me a beer
            Get my Book: MODX:The Official Guide
            MODX info for everyone: http://bobsguides.com/modx.html
            My MODX Extras
            Bob's Guides is now hosted at A2 MODX Hosting
            • 41592 ☆ A M B ☆
            • 83 Posts
            Quote from: BobRay at Mar 27, 2018, 03:48 PM
            That look like a great solution. How is that code triggered? How is $ext['needtoRefreshSession'] set above this code?

            Do you mind if I do a blog post on this?

            Of course not smiley

            the variable is set each time a group is changed. It can be triggered through a plugin or a snippet depending on your needs.

            The main problem is that the code to perform silent logout / login must be triggered through a snippet; and this snippet should be called somewhere. Adding code anywhere (or inside a plugin) is not a great solution since it makes the general execution code heavy.

            In addition, there may be situations in which the user does not navigate directly to the endpoint that triggers the function.

            Example through an email that notifies the change of group, and maybe contains a link to a page protected and reserved for the new group.

            To overcome this situation I created a more generic snippet to manage the refresh and redirect.

            connector snippet:
            $redirect_url = $modx->getOption('redirect_url',$scriptProperties,$modx->getOption('redirect_url',$_REQUEST));
                    if(!$modx->user->isAuthenticated('web')){
                        // Send to unauth page -> invite to login
                        return $modx->sendRedirect($redirect_url);
                    }
                    $user = &$modx->user;
                    $profile = $modx->user->getOne('Profile');
                    $ext = $profile->get('extended');
                    /**
                     * verify session
                     */
                    if($ext['needToRefreshSession']){
                        // remove session
                        $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;
                        $profile->set('extended',$ext);
                        $profile->save();
                    }
                    return $modx->sendRedirect($redirect_url);
            


            Example of link:
            [[!~[[++inp.connectorPageID]]? &scheme=`full` &action=`refreshAndRedirect` &redirect_url=`[[~[[++inp.exampleOfDestinationPageID]]? &scheme=`full`]]`]]
            
              Francesco Mussoni | MODX Ambassador | Skype: solidusite2 | Email: [email protected]
              • 3749
              • 24,544 Posts
              Thanks, smiley

              If there's an event (e.g. click or submit), that you can use to trigger things, it might make sense to use JavaScript with an Ajax call to a processor with your code.

              Just a thought: instead of bothering with the user's extended profile fields, how about just setting $_SESSION['needToRefreshSession'] ? Then your code could go in a plugin attached to OnHandleRequest or some other generic event that occurs before page permissions are checked.
                Did I help you? Buy me a beer
                Get my Book: MODX:The Official Guide
                MODX info for everyone: http://bobsguides.com/modx.html
                My MODX Extras
                Bob's Guides is now hosted at A2 MODX Hosting
                • 41592 ☆ A M B ☆
                • 83 Posts
                Quote from: BobRay at Mar 28, 2018, 01:20 PM

                Just a thought: instead of bothering with the user's extended profile fields, how about just setting $_SESSION['needToRefreshSession'] ? Then your code could go in a plugin attached to OnHandleRequest or some other generic event that occurs before page permissions are checked.

                absolutely! Speaking/Brainstorming with you was clarifying. I will refactor it immediately smiley
                  Francesco Mussoni | MODX Ambassador | Skype: solidusite2 | Email: [email protected]
                  • 41592 ☆ A M B ☆
                  • 83 Posts
                  Quote from: BobRay at Mar 28, 2018, 01:20 PM
                  Thanks, smiley
                  Just a thought: instead of bothering with the user's extended profile fields, how about just setting $_SESSION['needToRefreshSession'] ? Then your code could go in a plugin attached to OnHandleRequest or some other generic event that occurs before page permissions are checked.

                  Now I remember, there's a problem with this solution.
                  How can you set user $_SESSION if you change his groups from Modx manager with your admin account? That's why I used User Extended Field.

                  it is intriguing to attach the control to the onHandleRequest function, but I would not want to burden the general load too much. I explain:
                  First of all you should always request the user profile:
                   $user->getOne('Profile');

                  then you have to request the extended field
                  $ext = $profile->get('extended') or manually execute the $ext = json_decode($user->extended, 1);

                  Finally you have to check for the existence of $ext['needToRefreshSession']

                  It is very expensive, especially if the Extended Fields table is very large
                    Francesco Mussoni | MODX Ambassador | Skype: solidusite2 | Email: [email protected]
                    • 3749
                    • 24,544 Posts
                    How can you set user $_SESSION if you change his groups from MODX manager with your admin account? That's why I used User Extended Field.

                    Duh. Absolutely correct.

                    Another thought, the remote_key (string) field of the user object is probably unused. If you put your code in a plugin attached to one of the Login events, you'd already have the user object and could check that field quickly and do your magic only when necessary.
                      Did I help you? Buy me a beer
                      Get my Book: MODX:The Official Guide
                      MODX info for everyone: http://bobsguides.com/modx.html
                      My MODX Extras
                      Bob's Guides is now hosted at A2 MODX Hosting
                      • 41592 ☆ A M B ☆
                      • 83 Posts
                      mm, interesting ... just one thing: better put the function in the OnLoadWebDocument event instead of Login's Event, because the user may already have an active session and then be already logged in. Agree?
                        Francesco Mussoni | MODX Ambassador | Skype: solidusite2 | Email: [email protected]