[Rt-commit] rt branch 5.0/non-blocking-sessions created. rt-5.0.3-82-g6ff59c8f38

BPS Git Server git at git.bestpractical.com
Tue Aug 30 19:02:40 UTC 2022


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/non-blocking-sessions has been created
        at  6ff59c8f3875b30cda5b281d1258bbd3e49957dd (commit)

- Log -----------------------------------------------------------------
commit 6ff59c8f3875b30cda5b281d1258bbd3e49957dd
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Aug 19 16:56:21 2022 -0400

    Make the RT session non-blocking in a request
    
    Isolate the tied session interaction with Apache::Session
    from the full RT web request processing to avoid blocking
    requests on the session. This allows multiple requests
    to access the session at once. This should only be done
    where most requests are reads. If multiple requests
    write to the session, this will cause unpredictable
    results.

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 7384c92a91..336a080f3f 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -445,6 +445,12 @@ sub DoAuth {
             $session->{'CurrentUser'}->Load($UserObj->Id);
         }
 
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'CurrentUser',
+            SessionValue => $session->{'CurrentUser'},
+        );
+
         ####################################################################
         ########## Authentication ##########################################
         ####################################################################
@@ -475,11 +481,21 @@ sub DoAuth {
     # get a full, valid user from an authoritative external source.
     unless ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id) {
         $session->{'CurrentUser'} = RT::CurrentUser->new;
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'CurrentUser',
+            SessionValue => $session->{'CurrentUser'},
+        );
         return (0, "No User");
     }
 
     unless($success) {
         $session->{'CurrentUser'} = RT::CurrentUser->new;
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'CurrentUser',
+            SessionValue => $session->{'CurrentUser'},
+        );
         return (0, "Password Invalid");
     }
 
@@ -515,6 +531,11 @@ sub DoAuth {
         # if the user is disabled, kick them out. Now!
         if ($session->{'CurrentUser'}->UserObj->Disabled) {
             $session->{'CurrentUser'} = RT::CurrentUser->new;
+            RT::Interface::Web::Session::Set(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'CurrentUser',
+                SessionValue => $session->{'CurrentUser'},
+            );
             return (0, "User account disabled, login denied");
         }
     }
@@ -534,9 +555,19 @@ sub DoAuth {
             my $cu = $session->{CurrentUser};
             RT::Interface::Web::InstantiateNewSession();
             $session->{CurrentUser} = $cu;
+            RT::Interface::Web::Session::Set(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'CurrentUser',
+                SessionValue => $session->{'CurrentUser'},
+            );
     } else {
             # Make SURE the session is purged to an empty user.
             $session->{'CurrentUser'} = RT::CurrentUser->new;
+            RT::Interface::Web::Session::Set(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'CurrentUser',
+                SessionValue => $session->{'CurrentUser'},
+            );
             return (0, "Failed to authenticate externally");
             # This will cause autohandler to request IsPassword
             # which will in turn call IsExternalPassword
diff --git a/lib/RT/Dashboard/Mailer.pm b/lib/RT/Dashboard/Mailer.pm
index 5eca55a391..c68a936c57 100644
--- a/lib/RT/Dashboard/Mailer.pm
+++ b/lib/RT/Dashboard/Mailer.pm
@@ -334,7 +334,17 @@ SUMMARY
     }
 
     local $HTML::Mason::Commands::session{CurrentUser} = $currentuser;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CurrentUser',
+        SessionValue => $currentuser,
+    );
     local $HTML::Mason::Commands::session{ContextUser} = $context_user;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'ContextUser',
+        SessionValue => $context_user,
+    );
     local $HTML::Mason::Commands::r = RT::Dashboard::FakeRequest->new;
 
     my $HasResults = undef;
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 6e827ffe28..f83194776f 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -333,7 +333,6 @@ sub HandleRequest {
 
     MaybeRebuildCustomRolesCache();
     RT->System->MaybeRebuildLifecycleCache();
-
     $HTML::Mason::Commands::m->comp( '/Elements/SetupSessionCookie', %$ARGS );
     SendSessionCookie();
 
@@ -347,6 +346,13 @@ sub HandleRequest {
         $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new();
     }
 
+    # Write changes back to persistent session
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CurrentUser',
+        SessionValue => $HTML::Mason::Commands::session{'CurrentUser'},
+    );
+
     # attempt external auth
     $HTML::Mason::Commands::m->comp( '/Elements/DoAuth', %$ARGS )
         if @{ RT->Config->Get( 'ExternalAuthPriority' ) || [] };
@@ -372,7 +378,11 @@ sub HandleRequest {
     $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Auth', CallbackPage => '/autohandler' );
 
     if ( $ARGS->{'NotMobile'} ) {
-        $HTML::Mason::Commands::session{'NotMobile'} = 1;
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'NotMobile',
+            SessionValue => 1,
+        );
     }
 
     unless ( _UserLoggedIn() ) {
@@ -411,8 +421,13 @@ sub HandleRequest {
     MaybeShowInterstitialCSRFPage($ARGS);
 
     # now it applies not only to home page, but any dashboard that can be used as a workspace
-    $HTML::Mason::Commands::session{'home_refresh_interval'} = $ARGS->{'HomeRefreshInterval'}
-        if ( $ARGS->{'HomeRefreshInterval'} );
+    if ( $ARGS->{'HomeRefreshInterval'} ) {
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'home_refresh_interval',
+            SessionValue => $ARGS->{'HomeRefreshInterval'},
+        );
+    }
 
     # Process per-page global callbacks
     $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Default', CallbackPage => '/autohandler' );
@@ -430,7 +445,10 @@ sub HandleRequest {
 
 sub _ForceLogout {
 
-    delete $HTML::Mason::Commands::session{'CurrentUser'};
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CurrentUser',
+    );
 }
 
 sub _UserLoggedIn {
@@ -451,8 +469,15 @@ Pushes a login error into the Actions session store and returns the hash key.
 sub LoginError {
     my $new = shift;
     my $key = Digest::MD5::md5_hex( rand(1024) );
-    push @{ $HTML::Mason::Commands::session{"Actions"}->{$key} ||= [] }, $new;
-    $HTML::Mason::Commands::session{'i'}++;
+
+    my @actions = @{ $HTML::Mason::Commands::session{"Actions"}->{$key} ||= [] };
+    push @actions, $new;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'Actions',
+        SessionValue => \@actions,
+    );
+
     return $key;
 }
 
@@ -488,8 +513,13 @@ sub SetNextPage {
         }
     }
 
-    $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $page;
-    $HTML::Mason::Commands::session{'i'}++;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'NextPage',
+        SessionSubKey => $hash,
+        SessionValue => $page,
+    );
+
     return $hash;
 }
 
@@ -501,6 +531,11 @@ Returns the stashed next page hashref for the given hash.
 
 sub FetchNextPage {
     my $hash = shift || "";
+    RT::Interface::Web::Session::Load(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionId => $HTML::Mason::Commands::session{'_session_id'},
+    );
+
     return $HTML::Mason::Commands::session{'NextPage'}->{$hash};
 }
 
@@ -512,7 +547,13 @@ Removes the stashed next page for the given hash and returns it.
 
 sub RemoveNextPage {
     my $hash = shift || "";
-    return delete $HTML::Mason::Commands::session{'NextPage'}->{$hash};
+    my $return_hash = $HTML::Mason::Commands::session{'NextPage'}->{$hash};
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'NextPage',
+        SessionSubKey => $hash,
+    );
+    return $return_hash;
 }
 
 =head2 TangentForLogin ARGSRef [HASH]
@@ -767,10 +808,17 @@ sub AttemptExternalAuth {
 
         my $next = RemoveNextPage($ARGS->{'next'});
            $next = $next->{'url'} if ref $next;
+
         InstantiateNewSession() unless _UserLoggedIn;
         $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new();
         $HTML::Mason::Commands::session{'CurrentUser'}->$load_method($user);
 
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'CurrentUser',
+            SessionValue => $HTML::Mason::Commands::session{'CurrentUser'},
+        );
+
         if ( RT->Config->Get('WebRemoteUserAutocreate') and not _UserLoggedIn() ) {
 
             # Create users on-the-fly
@@ -799,6 +847,11 @@ sub AttemptExternalAuth {
                     $UserObj->$method( $new_user_info->{$attribute} ) if defined $new_user_info->{$attribute};
                 }
                 $HTML::Mason::Commands::session{'CurrentUser'}->Load($user);
+                RT::Interface::Web::Session::Set(
+                    SessionRef => \%HTML::Mason::Commands::session,
+                    SessionKey => 'CurrentUser',
+                    SessionValue => $HTML::Mason::Commands::session{'CurrentUser'},
+                );
             } else {
                 RT->Logger->error("Couldn't auto-create user '$user' when attempting WebRemoteUser: $msg");
                 AbortExternalAuth( Error => "UserAutocreateDefaultsOnLogin" );
@@ -806,7 +859,11 @@ sub AttemptExternalAuth {
         }
 
         if ( _UserLoggedIn() ) {
-            $HTML::Mason::Commands::session{'WebExternallyAuthed'} = 1;
+            RT::Interface::Web::Session::Set(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'WebExternallyAuthed',
+                SessionValue => 1,
+            );
             $m->callback( %$ARGS, CallbackName => 'ExternalAuthSuccessfulLogin', CallbackPage => '/autohandler' );
             # It is possible that we did a redirect to the login page,
             # if the external auth allows lack of auth through with no
@@ -889,7 +946,12 @@ sub AttemptPasswordAuthentication {
            $next = $next->{'url'} if ref $next;
 
         InstantiateNewSession();
-        $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
+
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'CurrentUser',
+            SessionValue => $user_obj,
+        );
 
         $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler', RedirectTo => \$next );
 
@@ -924,7 +986,11 @@ sub AttemptTokenAuthentication {
             $next = $next->{'url'} if ref $next;
 
             RT::Interface::Web::InstantiateNewSession();
-            $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
+            RT::Interface::Web::Session::Set(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'CurrentUser',
+                SessionValue => $user_obj,
+            );
 
             # Really the only time we don't want to redirect here is if we were
             # passed user and pass as query params in the URL.
@@ -957,7 +1023,12 @@ sub LoadSessionFromCookie {
     my %cookies       = CGI::Cookie->parse(RequestENV('HTTP_COOKIE'));
     my $cookiename    = _SessionCookieName();
     my $SessionCookie = ( $cookies{$cookiename} ? $cookies{$cookiename}->value : undef );
-    tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', $SessionCookie;
+
+    RT::Interface::Web::Session::Load(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionId => $SessionCookie,
+    );
+
     unless ( $SessionCookie && $HTML::Mason::Commands::session{'_session_id'} eq $SessionCookie ) {
         InstantiateNewSession();
     }
@@ -970,13 +1041,25 @@ sub LoadSessionFromCookie {
         }
 
         # save session on each request when AutoLogoff is turned on
-        $HTML::Mason::Commands::session{'_session_last_update'} = $now if $now != $last_update;
+        if ( $now != $last_update ) {
+            RT::Interface::Web::Session::Set(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => '_session_last_update',
+                SessionValue => $now,
+            );
+        }
     }
 }
 
 sub InstantiateNewSession {
-    tied(%HTML::Mason::Commands::session)->delete if tied(%HTML::Mason::Commands::session);
-    tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', undef;
+    # Starting a new session, so clear out any existing one
+    RT::Interface::Web::Session::Delete( SessionRef => \%HTML::Mason::Commands::session );
+
+    RT::Interface::Web::Session::Load(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionId => undef,
+    );
+
     SendSessionCookie();
 }
 
@@ -1023,10 +1106,9 @@ a cached DBI statement handle twice at the same time.
 
 sub Redirect {
     my $redir_to = shift;
-    untie $HTML::Mason::Commands::session;
     my $uri        = URI->new($redir_to);
     my $server_uri = URI->new( RT->Config->Get('WebURL') );
-    
+
     # Make relative URIs absolute from the server host and scheme
     $uri->scheme($server_uri->scheme) if not defined $uri->scheme;
     if (not defined $uri->host) {
@@ -1662,8 +1744,15 @@ sub IsPossibleCSRF {
     # endpoints.  We do this because using a REST cookie in a browser
     # would open the user to CSRF attacks to the REST endpoints.
     my $path = $HTML::Mason::Commands::r->path_info;
-    $HTML::Mason::Commands::session{'REST'} = $path =~ m{^/+REST/\d+\.\d+(/|$)}
-        unless defined $HTML::Mason::Commands::session{'REST'};
+
+    unless ( defined $HTML::Mason::Commands::session{'REST'} ) {
+        my $rest_path = $path =~ m{^/+REST/\d+\.\d+(/|$)};
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'REST',
+            SessionValue => $rest_path,
+        );
+    }
 
     if ($HTML::Mason::Commands::session{'REST'}) {
         return 0 if $path =~ m{^/+REST/\d+\.\d+(/|$)};
@@ -1729,8 +1818,14 @@ sub ExpandCSRFToken {
     if ($data->{attach}) {
         my $filename = $data->{attach}{filename};
         my $mime     = $data->{attach}{mime};
-        $HTML::Mason::Commands::session{'Attachments'}{$ARGS->{'Token'}||''}{$filename}
-            = $mime;
+
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'Attachments',
+            SessionSubKey => $ARGS->{'Token'}||'',
+            SessionSubSubKey => $filename,
+            SessionValue => $mime,
+        );
     }
 
     return 1;
@@ -1759,8 +1854,13 @@ sub StoreRequestToken {
         };
     }
 
-    $HTML::Mason::Commands::session{'CSRF'}->{$token} = $data;
-    $HTML::Mason::Commands::session{'i'}++;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CSRF',
+        SessionSubKey => $token,
+        SessionValue => $data,
+    );
+
     return $token;
 }
 
@@ -2198,8 +2298,19 @@ sub MaybeRedirectForResults {
 
     if ( $has_actions ) {
         my $key = Digest::MD5::md5_hex( rand(1024) );
-        push @{ $session{"Actions"}{ $key } ||= [] }, @{ $args{'Actions'} };
-        $session{'i'}++;
+        my $actions_ref = [];
+        if ( $session{"Actions"}{ $key } ) {
+            $actions_ref = $session{"Actions"}{ $key };
+        }
+        push @{$actions_ref}, @{ $args{'Actions'} };
+
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'Actions',
+            SessionSubKey => $key,
+            SessionValue => $actions_ref,
+        );
+
         $arguments{'results'} = $key;
     }
 
@@ -2316,10 +2427,13 @@ sub CreateTicket {
     if ( my $tmp = $session{'Attachments'}{ $ARGS{'Token'} || '' } ) {
         push @attachments, grep $_, map $tmp->{$_}, sort keys %$tmp;
 
-        delete $session{'Attachments'}{ $ARGS{'Token'} || '' }
-            unless $ARGS{'KeepAttachments'} or $Ticket->{DryRun};
-        $session{'Attachments'} = $session{'Attachments'}
-            if @attachments;
+        unless ( $ARGS{'KeepAttachments'} or $Ticket->{DryRun} ) {
+            RT::Interface::Web::Session::Delete(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'Attachments',
+                SessionSubKey => $ARGS{'Token'} || '',
+            );
+        }
     }
     if ( $ARGS{'Attachments'} ) {
         push @attachments, grep $_, map $ARGS{Attachments}->{$_}, sort keys %{ $ARGS{'Attachments'} };
@@ -2452,11 +2566,14 @@ sub ProcessUpdateMessage {
     if ( my $tmp = $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' } ) {
         push @attachments, grep $_, map $tmp->{$_}, sort keys %$tmp;
 
-        delete $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' }
-            unless $args{'KeepAttachments'}
-            or ($args{TicketObj} and $args{TicketObj}{DryRun});
-        $session{'Attachments'} = $session{'Attachments'}
-            if @attachments;
+        unless ( $args{'KeepAttachments'}
+                 or ( $args{TicketObj} and $args{TicketObj}{DryRun} )) {
+            RT::Interface::Web::Session::Delete(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'Attachments',
+                SessionSubKey => $args{'ARGSRef'}{'Token'} || '',
+            );
+        }
     }
     if ( $args{ARGSRef}{'UpdateAttachments'} ) {
         push @attachments, grep $_, map $args{ARGSRef}->{UpdateAttachments}{$_},
@@ -2624,14 +2741,16 @@ sub ProcessAttachments {
     my $token = $args{'ARGSRef'}{'Token'}
         ||= $args{'Token'} ||= Digest::MD5::md5_hex( rand(1024) );
 
-    my $update_session = 0;
-
     # deal with deleting uploaded attachments
     if ( my $del = $args{'ARGSRef'}{'DeleteAttach'} ) {
-        delete $session{'Attachments'}{ $token }{ $_ }
-            foreach ref $del? @$del : ($del);
-
-        $update_session = 1;
+        foreach my $delete ( ref $del ? @$del : ($del) ) {
+            RT::Interface::Web::Session::Delete(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'Attachments',
+                SessionSubKey => $token,
+                SessionSubSubKey => $delete,
+            );
+        }
     }
 
     # store the uploaded attachment in session
@@ -2663,11 +2782,15 @@ sub ProcessAttachments {
             }
         }
 
-        $session{'Attachments'}{ $token }{ $file_path } = $attachment;
-
-        $update_session = 1;
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'Attachments',
+            SessionSubKey => $token,
+            SessionSubSubKey => $file_path,
+            SessionValue => $attachment,
+        );
     }
-    $session{'Attachments'} = $session{'Attachments'} if $update_session;
+
     return 1;
 }
 
@@ -4192,7 +4315,13 @@ sub ProcessQuickCreate {
             );
         }
 
-        $session{QuickCreate} = \%ARGS unless $created;
+        unless ( $created ) {
+            RT::Interface::Web::Session::Set(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'QuickCreate',
+                SessionValue => \%ARGS,
+            );
+        }
 
         MaybeRedirectForResults(
             Actions   => \@results,
@@ -4608,16 +4737,25 @@ sub SetObjectSessionCache {
         CheckRight => $CheckRight, ShowAll => $ShowAll );
 
     if ( defined $session{$cache_key} && !$session{$cache_key}{id} ) {
-        delete $session{$cache_key};
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => $cache_key,
+        );
     }
 
     if ( defined $session{$cache_key}
          && ref $session{$cache_key} eq 'ARRAY') {
-        delete $session{$cache_key};
+         RT::Interface::Web::Session::Delete(
+             SessionRef => \%HTML::Mason::Commands::session,
+             SessionKey => $cache_key,
+         );
     }
     if ( defined $session{$cache_key} && defined $CacheNeedsUpdate &&
         $session{$cache_key}{lastupdated} <= $CacheNeedsUpdate ) {
-        delete $session{$cache_key};
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => $cache_key,
+        );
     }
 
     if ( not defined $session{$cache_key} ) {
@@ -4628,24 +4766,36 @@ sub SetObjectSessionCache {
             CallbackPage => '/Elements/Quicksearch',
             ARGSRef => \%args, Collection => $collection, ObjectType => $ObjectType );
 
-        $session{$cache_key}{id} = {};
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => $cache_key,
+        );
 
+        my %ids;
         while (my $object = $collection->Next) {
             if ($ShowAll
                 or not $CheckRight
                 or $session{CurrentUser}->HasRight( Object => $object, Right => $CheckRight ))
             {
                 next if $args{'Exclude'} and exists $args{'Exclude'}->{$object->Name};
-                push @{$session{$cache_key}{objects}}, {
+                push @{$ids{objects}}, {
                     Id          => $object->Id,
                     Name        => $object->Name,
                     Description => $object->_Accessible("Description" => "read") ? $object->Description : undef,
                     Lifecycle   => $object->_Accessible("Lifecycle" => "read") ? $object->Lifecycle : undef,
                 };
-                $session{$cache_key}{id}{ $object->id } = 1;
+                $ids{id}{ $object->id } = 1;
             }
         }
-        $session{$cache_key}{lastupdated} = time();
+
+        $ids{'lastupdated'} = time();
+
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => $cache_key,
+            SessionValue => \%ids,
+        );
+
     }
 
     return $cache_key;
diff --git a/lib/RT/Interface/Web/Session.pm b/lib/RT/Interface/Web/Session.pm
index a6ddccad8e..ab6297ad88 100644
--- a/lib/RT/Interface/Web/Session.pm
+++ b/lib/RT/Interface/Web/Session.pm
@@ -51,6 +51,7 @@ use warnings;
 use strict;
 
 use RT::CurrentUser;
+use Clone;
 
 =head1 NAME
 
@@ -316,6 +317,166 @@ sub ClearByUser {
     $self->ClearOrphanLockFiles if $deleted;
 }
 
+=head3 Load
+
+Load a session or create a new one.
+
+Accepts: SesseionRef => \%session, SessionId
+
+SessionRef is a reference to a hash which will be loaded with
+session data.
+
+SessionId is the id of an existing session. If set to undef or
+omitted, an empty new session will be created with a session id
+set with the key '_session_id'.
+
+=cut
+
+sub Load {
+    my %args = (
+        SessionRef => undef,
+        SessionId  => undef,
+        @_
+    );
+
+    my %local_session;
+    tie %local_session, 'RT::Interface::Web::Session', $args{'SessionId'};
+
+    %{$args{'SessionRef'}} = %{Clone::clone( \%local_session )};
+
+    untie %local_session;
+
+    return 1;
+}
+
+=head3 Set
+
+Set a value in the session.
+
+Accepts: SesseionRef => \%session, SessionKey, SessionSubKey,
+SessionSubSubKey, SessionValue
+
+SessionRef is a reference to a hash for an existing session.
+It is expected to have a key '_session_id' with the id of the
+current session. The referenced hash will be also be updated
+with the new value.
+
+SessionKey and the SubKey parameters indicate where in the hash
+to set the value. The multiple subkey arguments handle multiple
+hash levels from the previous direct hash implementation.
+
+SessionValue is the value to set in the indicated key.
+
+=cut
+
+sub Set {
+    my %args = (
+        SessionRef   => undef,
+        SessionKey   => undef,
+        SessionSubKey => undef,
+        SessionSubSubKey => undef,
+        SessionValue => undef,
+        @_
+    );
+
+    my $session_id = $args{'SessionRef'}->{'_session_id'};
+    unless ( $session_id ) {
+        RT->Logger->error('No session id found. Cannot set a new value in session');
+        return 0;
+    }
+
+    my %local_session;
+    tie %local_session, 'RT::Interface::Web::Session', $session_id;
+
+    # Set the value, which will automagically set it in the back-end session storage
+    if ( $args{'SessionSubSubKey'} ) {
+        $local_session{$args{'SessionKey'}}{$args{'SessionSubKey'}}{$args{'SessionSubSubKey'}} = $args{'SessionValue'};
+    }
+    elsif ( $args{'SessionSubKey'} ) {
+        $local_session{$args{'SessionKey'}}{$args{'SessionSubKey'}} = $args{'SessionValue'};
+    }
+    else {
+        $local_session{$args{'SessionKey'}} = $args{'SessionValue'};
+    }
+
+    # Clone it back so we update the copy of the session with the latest values
+    %{$args{'SessionRef'}} = %{Clone::clone( \%local_session )};
+
+    # Apache::Session doesn't sync changes to subkeys, so force a sync
+    # with a change at the top level.
+    $local_session{i}++;
+
+    untie %local_session;
+
+    return 1;
+}
+
+=head3 Set
+
+Set a value in the session.
+
+Accepts: SesseionRef => \%session, SessionKey, SessionSubKey,
+SessionSubSubKey, SessionValue
+
+SessionRef is a reference to a hash for an existing session.
+It is expected to have a key '_session_id' with the id of the
+current session. The referenced hash will be also be updated
+with the new value.
+
+SessionKey and the SubKey parameters indicate where in the hash
+to set the value. The multiple subkey arguments handle multiple
+hash levels from the previous direct hash implementation.
+
+SessionValue is the value to set in the indicated key.
+
+=cut
+
+sub Delete {
+    my %args = (
+        SessionRef => undef,
+        SessionKey   => undef,
+        SessionSubKey => undef,
+        SessionSubSubKey => undef,
+        @_
+    );
+
+    my $session_id = $args{'SessionRef'}->{'_session_id'};
+    unless ( $session_id ) {
+        RT->Logger->error('No session id found. Cannot delete value from session');
+        return 0;
+    }
+
+    my %local_session;
+    tie %local_session, 'RT::Interface::Web::Session', $session_id;
+
+    if ( $args{'SessionKey'} ) {
+        # Delete requested item from the session
+        if ( defined $args{'SessionSubSubKey'} ) {
+            delete $local_session{$args{'SessionKey'}}{$args{'SessionSubKey'}}{$args{'SessionSubSubKey'}};
+        }
+        elsif ( defined $args{'SessionSubKey'} ) {
+            delete $local_session{$args{'SessionKey'}}{$args{'SessionSubKey'}};
+        }
+        else {
+            delete $local_session{$args{'SessionKey'}};
+        }
+
+        # Apache::Session doesn't sync changes to subkeys, so force a sync
+        # with a change at the top level.
+        $local_session{i}++;
+        %{$args{'SessionRef'}} = %{Clone::clone( \%local_session )};
+    }
+    else {
+        # No key provided, delete the whole session
+        tied(%local_session)->delete if tied(%local_session);
+        %{$args{'SessionRef'}} = ();
+    }
+
+    untie %local_session;
+
+    return 1;
+}
+
 sub TIEHASH {
     my $self = shift;
     my $id = shift;
@@ -337,4 +498,5 @@ sub TIEHASH {
     return tied %session;
 }
 
+
 1;
diff --git a/share/html/Admin/Global/DashboardsInMenu.html b/share/html/Admin/Global/DashboardsInMenu.html
index f72d8de206..407bd3024f 100644
--- a/share/html/Admin/Global/DashboardsInMenu.html
+++ b/share/html/Admin/Global/DashboardsInMenu.html
@@ -132,7 +132,10 @@ if ($ARGS{UpdateSearches}) {
                 );
             }
             push @actions, $ok ? loc('Global dashboards in menu saved.') : $msg;
-            delete $session{'dashboards_in_menu'};
+            RT::Interface::Web::Session::Delete(
+                SessionRef => \%HTML::Mason::Commands::session,
+                SessionKey => 'dashboards_in_menu',
+            );
         }
         else {
           my $report_names = ref $ARGS{'report'} eq 'ARRAY' ? $ARGS{'report'} : [$ARGS{'report'}];
@@ -155,7 +158,10 @@ if ($ARGS{UpdateSearches}) {
               );
           }
           push @actions, $ok ? loc('Preferences saved for reports in menu.') : $msg;
-          delete $session{'reports_in_menu'};
+          RT::Interface::Web::Session::Delete(
+              SessionRef => \%HTML::Mason::Commands::session,
+              SessionKey => 'reports_in_menu',
+          );
         }
     }
 }
diff --git a/share/html/Admin/Users/DashboardsInMenu.html b/share/html/Admin/Users/DashboardsInMenu.html
index fa4f0e248e..44617f60b7 100644
--- a/share/html/Admin/Users/DashboardsInMenu.html
+++ b/share/html/Admin/Users/DashboardsInMenu.html
@@ -150,7 +150,10 @@ if ($ARGS{UpdateSearches}) {
 
       my ( $ok, $msg ) = $UserObj->SetPreferences( $ARGS{'dashboard_id'}, { 'dashboards' => \@dashboard_ids } );
       push @actions, $ok ? loc('Preferences saved for dashboards in menu.') : $msg;
-      delete $session{'dashboards_in_menu'};
+      RT::Interface::Web::Session::Delete(
+          SessionRef => \%HTML::Mason::Commands::session,
+          SessionKey => 'dashboards_in_menu',
+      );
   }
   else {
     my $report_names = ref $ARGS{'report'} eq 'ARRAY' ? $ARGS{'report'} : [$ARGS{'report'}];
@@ -163,7 +166,10 @@ if ($ARGS{UpdateSearches}) {
 
     my ( $ok, $msg ) = $UserObj->SetPreferences( $ARGS{'dashboard_id'}, \@ret );
     push @actions, $ok ? loc('Preferences saved for reports in menu.') : $msg;
-    delete $session{'reports_in_menu'};
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'reports_in_menu',
+    );
   }
 }
 
diff --git a/share/html/Asset/Create.html b/share/html/Asset/Create.html
index f5e91da737..b48501dfe8 100644
--- a/share/html/Asset/Create.html
+++ b/share/html/Asset/Create.html
@@ -118,7 +118,11 @@ Abort(loc("You don't have permission to create assets in catalog [_1].",
     unless $catalog->CurrentUserHasRight("CreateAsset");
 
 # Update the current default with the latest selection
-$session{'DefaultCatalog'} = $catalog->Id;
+RT::Interface::Web::Session::Set(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => 'DefaultCatalog',
+    SessionValue => $catalog->Id,
+);
 
 my @results;
 
diff --git a/share/html/Asset/Elements/SelectCatalog b/share/html/Asset/Elements/SelectCatalog
index 0f003909c6..d836d1c077 100644
--- a/share/html/Asset/Elements/SelectCatalog
+++ b/share/html/Asset/Elements/SelectCatalog
@@ -65,7 +65,11 @@ $AutoSubmit     => 0
 <%init>
 my $catalog_obj = LoadDefaultCatalog($Default || '');
 if ( $UpdateSession && $catalog_obj->Id ){
-    $session{'DefaultCatalog'} = $catalog_obj->Id;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'DefaultCatalog',
+        SessionValue => $catalog_obj->Id,
+    );
     $Default = $catalog_obj->Id;
 }
 $ARGS{OnChange} = "jQuery(this).closest('form').find('input[name=CatalogChanged]').val(1);";
diff --git a/share/html/Dashboards/Elements/ShowPortlet/dashboard b/share/html/Dashboards/Elements/ShowPortlet/dashboard
index 30b37dc3d2..dabc241b4e 100644
--- a/share/html/Dashboards/Elements/ShowPortlet/dashboard
+++ b/share/html/Dashboards/Elements/ShowPortlet/dashboard
@@ -76,6 +76,7 @@ Abort("Possible recursive dashboard detected.") if $Depth > 8;
 
 <%perl>
 local $session{CurrentUser} = $session{ContextUser};
+
 for my $portlet (@panes) {
     $m->comp($portlet->{portlet_type},
         Portlet    => $portlet,
diff --git a/share/html/Dashboards/Render.html b/share/html/Dashboards/Render.html
index 187cfd0b80..838b313d8c 100644
--- a/share/html/Dashboards/Render.html
+++ b/share/html/Dashboards/Render.html
@@ -151,7 +151,14 @@ for my $sub ($session{'CurrentUser'}->UserObj->Attributes->Named('Subscription')
     last;
 }
 
-$session{ContextUser} ||= $session{CurrentUser};
+if ( !$session{ContextUser} ) {
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%session,
+        SessionKey => 'ContextUser',
+        SessionValue => $session{CurrentUser},
+    );
+}
+
 # otherwise honor their search preferences.. otherwise 50 rows
 # $rows == 0 means unlimited, which we don't want to ignore from above
 unless (defined($rows)) {
diff --git a/share/html/Elements/ListActions b/share/html/Elements/ListActions
index 0fd05fda49..b1279ae32c 100644
--- a/share/html/Elements/ListActions
+++ b/share/html/Elements/ListActions
@@ -61,20 +61,31 @@
 
 # backward compatibility, don't use array in new code, but use keyed hash
 if ( ref( $session{'Actions'} ) eq 'ARRAY' ) {
-    unshift @actions, @{ delete $session{'Actions'} };
-    $session{'i'}++;
+    unshift @actions, @{ $session{'Actions'} };
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'Actions',
+    );
 }
 
 if ( ref( $session{'Actions'}{''} ) eq 'ARRAY' ) {
-    unshift @actions, @{ delete $session{'Actions'}{''} };
-    $session{'i'}++;
+    unshift @actions, @{ $session{'Actions'}{''} };
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'Actions',
+        SessionSubKey => '',
+    );
 }
 
 my $actions_pointer = $DECODED_ARGS->{'results'};
 
 if ($actions_pointer &&  ref( $session{'Actions'}->{$actions_pointer} ) eq 'ARRAY' ) {
-    unshift @actions, @{ delete $session{'Actions'}->{$actions_pointer} };
-    $session{'i'}++;
+    unshift @actions, @{ $session{'Actions'}->{$actions_pointer} };
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'Actions',
+        SessionSubKey => $actions_pointer,
+    );
 }
 
 # XXX: run callbacks per row really crazy idea
diff --git a/share/html/Elements/QuickCreate b/share/html/Elements/QuickCreate
index 00ee3bd593..ce8f089c7f 100644
--- a/share/html/Elements/QuickCreate
+++ b/share/html/Elements/QuickCreate
@@ -94,5 +94,9 @@
 </div>
 
 <%INIT>
-my $args = delete $session{QuickCreate} || {};
+my $args = $session{QuickCreate} || {};
+RT::Interface::Web::Session::Delete(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => 'QuickCreate',
+);
 </%INIT>
diff --git a/share/html/Helpers/Upload/Delete b/share/html/Helpers/Upload/Delete
index 50fe73b23e..013c3c9d82 100644
--- a/share/html/Helpers/Upload/Delete
+++ b/share/html/Helpers/Upload/Delete
@@ -50,8 +50,12 @@ $Name => ''
 $Token => ''
 </%args>
 <%init>
-delete $session{'Attachments'}{ $Token }{ $Name };
-$session{'Attachments'} = $session{'Attachments'};
+RT::Interface::Web::Session::Delete(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => 'Attachments',
+    SessionSubKey => $Token,
+    SessionSubSubKey => $Name,
+);
 $r->content_type('application/json; charset=utf-8');
 $m->out( JSON({status => 'success'}) );
 $m->abort;
diff --git a/share/html/Install/index.html b/share/html/Install/index.html
index 9fb50a7da4..9ae2b873e8 100644
--- a/share/html/Install/index.html
+++ b/share/html/Install/index.html
@@ -135,6 +135,8 @@ elsif ( $Run ) {
 
     RT::Interface::Web::Redirect(RT->Config->Get('WebURL') . 'Install/DatabaseType.html');
 } elsif ( $ChangeLang && $Lang ) {
+    # Don't call RT::Interface::Web::Session::Set because if we're
+    # in the installer, we don't have a DB.
     # hackish, but works
     $session{'CurrentUser'} = RT::CurrentUser->new;
     $session{'CurrentUser'}->LanguageHandle( $Lang );
@@ -145,6 +147,7 @@ my $lang_handle = do { local $@;
             ->LanguageHandle
     }
 };
+
 </%init>
 
 <%args>
diff --git a/share/html/NoAuth/Logout.html b/share/html/NoAuth/Logout.html
index 4587533211..47540a8867 100644
--- a/share/html/NoAuth/Logout.html
+++ b/share/html/NoAuth/Logout.html
@@ -79,7 +79,11 @@ $m->callback( %ARGS, CallbackName => 'BeforeSessionDelete' );
 
 if (keys %session) {
     RT::Interface::Web::InstantiateNewSession();
-    $session{'CurrentUser'} = RT::CurrentUser->new;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CurrentUser',
+        SessionValue => RT::CurrentUser->new,
+    );
 }
 
 $m->callback( %ARGS, CallbackName => 'AfterSessionDelete' );
diff --git a/share/html/Prefs/DashboardsInMenu.html b/share/html/Prefs/DashboardsInMenu.html
index 26a9b72caf..cc94a338a1 100644
--- a/share/html/Prefs/DashboardsInMenu.html
+++ b/share/html/Prefs/DashboardsInMenu.html
@@ -107,7 +107,10 @@ if ( $ARGS{ResetDashboards} ) {
     # Empty DashboardsInMenu pref means to use system default.
     my ($ok, $msg) = $user->SetPreferences('DashboardsInMenu', {});
     push @results, $ok ? loc('Preferences saved.') : $msg;
-    delete $session{'dashboards_in_menu'};
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'dashboards_in_menu',
+    );
 }
 
 if ( $ARGS{ResetReports} ) {
@@ -116,7 +119,10 @@ if ( $ARGS{ResetReports} ) {
         # thus we need to delete preference instead.
         my ( $ok, $msg ) = $user->DeletePreferences('ReportsInMenu');
         push @results, $ok ? loc('Preferences saved.') : $msg;
-        delete $session{'reports_in_menu'};
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'reports_in_menu',
+        );
     }
 }
 
@@ -146,7 +152,10 @@ if ($ARGS{UpdateSearches}) {
 
         my ( $ok, $msg ) = $user->SetPreferences( $ARGS{'dashboard_id'}, { 'dashboards' => \@dashboard_ids } );
         push @results, $ok ? loc('Preferences saved for dashboards in menu.') : $msg;
-        delete $session{'dashboards_in_menu'};
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'dashboards_in_menu',
+        );
     }
     else {
       my $report_names = ref $ARGS{'report'} eq 'ARRAY' ? $ARGS{'report'} : [$ARGS{'report'}];
@@ -159,7 +168,10 @@ if ($ARGS{UpdateSearches}) {
 
       my ( $ok, $msg ) = $user->SetPreferences( $ARGS{'dashboard_id'}, \@ret );
       push @results, $ok ? loc('Preferences saved for reports in menu.') : $msg;
-      delete $session{'reports_in_menu'};
+      RT::Interface::Web::Session::Delete(
+          SessionRef => \%HTML::Mason::Commands::session,
+          SessionKey => 'reports_in_menu',
+      );
     }
 }
 
diff --git a/share/html/Prefs/QueueList.html b/share/html/Prefs/QueueList.html
index 5dca1630e7..63c20bd9cb 100644
--- a/share/html/Prefs/QueueList.html
+++ b/share/html/Prefs/QueueList.html
@@ -112,12 +112,18 @@ if ($ARGS{'Save'}) {
         # Clear for 'CreateTicket'
         my $cache_key = GetObjectSessionCacheKey( ObjectType => 'RT::Queue',
             CheckRight => 'CreateTicket', ShowAll => 0 );
-        delete $session{$cache_key};
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => $cache_key,
+        );
 
         # Clear for 'ShowTicket'
         $cache_key = GetObjectSessionCacheKey( ObjectType => 'RT::Queue',
             CheckRight => 'ShowTicket', ShowAll => 0 );
-        delete $session{$cache_key};
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => $cache_key,
+        );
     }
 }
 
diff --git a/share/html/REST/1.0/logout b/share/html/REST/1.0/logout
index fb2fe11b51..c310b060df 100644
--- a/share/html/REST/1.0/logout
+++ b/share/html/REST/1.0/logout
@@ -48,7 +48,11 @@
 <%PERL>
 if (keys %session) {
     RT::Interface::Web::InstantiateNewSession();
-    $session{CurrentUser} = RT::CurrentUser->new();
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CurrentUser',
+        SessionValue => RT::CurrentUser->new,
+    );
 }
 </%PERL>
 RT/<% $RT::VERSION %> 200 Ok
diff --git a/share/html/Search/Build.html b/share/html/Search/Build.html
index 2f3be5f914..7e7aaf2e24 100644
--- a/share/html/Search/Build.html
+++ b/share/html/Search/Build.html
@@ -172,7 +172,10 @@ if ( $NewQuery ) {
     %saved_search = ( Id => 'new' );
 
     # ..then wipe the session out..
-    delete $session{$hash_name};
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => $hash_name,
+    );
 
     # ..and the search results.
     $session{$session_name}->CleanSlate if defined $session{$session_name};
@@ -351,12 +354,16 @@ if ($ARGS{SavedSearchSave}) {
 
 # Push the updates into the session so we don't lose 'em
 
-$session{$hash_name} = {
-    %query,
-    SearchId    => $saved_search{'Id'},
-    Object      => $saved_search{'Object'},
-    Description => $saved_search{'Description'},
-};
+RT::Interface::Web::Session::Set(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => $hash_name,
+    SessionValue => {
+        %query,
+        SearchId    => $saved_search{'Id'},
+        Object      => $saved_search{'Object'},
+        Description => $saved_search{'Description'},
+    },
+);
 
 
 # Show the results, if we were asked.
diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index 68767c4f31..72fcd580f3 100644
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -557,7 +557,11 @@ unless ( $ARGS{'AddMoreAttach'} ) {
     }
     $RT::Handle->Commit;
 
-    delete $session{'Attachments'}{ $ARGS{'Token'} };
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'Attachments',
+        SessionSubKey => $ARGS{'Token'},
+    );
 
     $Tickets->RedoSearch();
 }
diff --git a/share/html/Search/Chart b/share/html/Search/Chart
index acc121d5c8..957177c600 100644
--- a/share/html/Search/Chart
+++ b/share/html/Search/Chart
@@ -105,10 +105,18 @@ use RT::Report::Tickets;
 my $report = RT::Report::Tickets->new( $session{'CurrentUser'} );
 
 my %columns;
-if ( $Cache and my $data = delete $session{'charts_cache'}{ $Cache } ) {
-    %columns = %{ $data->{'columns'} };
-    $report->Deserialize( $data->{'report'} );
-    $session{'i'}++;
+if ( $Cache ) {
+    my $data;
+    if ( $session{'charts_cache'}{ $Cache } ) {
+        $data = $session{'charts_cache'}{ $Cache };
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'charts_cache',
+            SessionSubKey => $Cache,
+        );
+        %columns = %{ $data->{'columns'} };
+        $report->Deserialize( $data->{'report'} );
+    }
 } else {
     %columns = $report->SetupGroupings(
         Query => $Query,
diff --git a/share/html/Search/Elements/Chart b/share/html/Search/Elements/Chart
index 021a07cfb7..c8c526ae5f 100644
--- a/share/html/Search/Elements/Chart
+++ b/share/html/Search/Elements/Chart
@@ -69,8 +69,13 @@ my $query_string = $m->comp('/Elements/QueryString', %ARGS, GroupBy => \@GroupBy
 my $key;
 if ( RT->Config->Get('EnableJSChart') || !RT->Config->Get('DisableGD') ) {
     $key = Digest::MD5::md5_hex( rand(1024) );
-    $session{'charts_cache'}{$key} = { columns => \%columns, report => $report->Serialize };
-    $session{'i'}++;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'charts_cache',
+        SessionSubKey => $key,
+        SessionValue => { columns => \%columns, report => $report->Serialize },
+    );
+
 }
 
 </%init>
diff --git a/share/html/Search/JSChart b/share/html/Search/JSChart
index bc3f9d3ac2..29a9d083d8 100644
--- a/share/html/Search/JSChart
+++ b/share/html/Search/JSChart
@@ -199,10 +199,18 @@ my $report = RT::Report::Tickets->new( $session{'CurrentUser'} );
 @GroupBy = 'Status' unless @GroupBy;
 
 my %columns;
-if ( $Cache and my $data = delete $session{'charts_cache'}{ $Cache } ) {
-    %columns = %{ $data->{'columns'} };
-    $report->Deserialize( $data->{'report'} );
-    $session{'i'}++;
+if ( $Cache ) {
+    my $data;
+    if ( $session{'charts_cache'}{ $Cache } ) {
+        $data = $session{'charts_cache'}{ $Cache };
+        RT::Interface::Web::Session::Delete(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'charts_cache',
+            SessionSubKey => $Cache,
+        );
+        %columns = %{ $data->{'columns'} };
+        $report->Deserialize( $data->{'report'} );
+    }
 } else {
     %columns = $report->SetupGroupings(
         Query => $Query,
diff --git a/share/html/Search/Results.html b/share/html/Search/Results.html
index e0de84d99f..9588717cca 100644
--- a/share/html/Search/Results.html
+++ b/share/html/Search/Results.html
@@ -59,7 +59,6 @@
 <&|/l_unsafe, "<i>".$m->interp->apply_escapes($msg, "h")."</i>" &>There was an error parsing your search query: [_1].  Your RT admin can find more information in the error logs.</&>
 </&>
 % } else {
-
 <& /Elements/CollectionList, 
     Query => $Query,
     TotalFound => $count,
@@ -164,8 +163,11 @@ $Page = 1 unless $Page && $Page > 0;
 my $hash_name = join '-', 'CurrentSearchHash', $Class, $ObjectType || ();
 my $session_name = join '-', 'collection', $Class, $ObjectType || ();
 
-$session{'i'}++;
-$session{$session_name} = $Class->new($session{'CurrentUser'}) ;
+RT::Interface::Web::Session::Set(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => $session_name,
+    SessionValue => $Class->new($session{'CurrentUser'}),
+);
 
 my ( $ok, $msg );
 if ( $Query ) {
@@ -197,16 +199,28 @@ if ($OrderBy =~ /\|/) {
 $session{$session_name}->RowsPerPage( $Rows ) if $Rows;
 $session{$session_name}->GotoPage( $Page - 1 );
 
-$session{$hash_name} = {
-    Format      => $Format,
-    Query       => $Query,
-    Page        => $Page,
-    Order       => $Order,
-    OrderBy     => $OrderBy,
-    RowsPerPage => $Rows,
-    ObjectType  => $ObjectType,
-};
+# Save the session again because we made changes
+# Otherwise the set below will restore it back to
+# the last set.
+RT::Interface::Web::Session::Set(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => $session_name,
+    SessionValue => $session{$session_name},
+);
 
+RT::Interface::Web::Session::Set(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => $hash_name,
+    SessionValue => {
+        Format      => $Format,
+        Query       => $Query,
+        Page        => $Page,
+        Order       => $Order,
+        OrderBy     => $OrderBy,
+        RowsPerPage => $Rows,
+        ObjectType  => $ObjectType,
+    },
+);
 
 my $count = $session{$session_name}->Query() ? $session{$session_name}->CountAll() : 0;
 
@@ -247,7 +261,11 @@ my $ShortQueryString = "?".$m->comp('/Elements/QueryString', Query => $Query);
 
 my $interval_name = join '_', $Class, $ObjectType || (), 'refresh_interval';
 if ($ARGS{'SearchResultsRefreshInterval'}) {
-    $session{$interval_name} = $ARGS{'SearchResultsRefreshInterval'};
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => $interval_name,
+        SessionValue => $ARGS{'SearchResultsRefreshInterval'},
+    );
 }
 my $refresh = $session{$interval_name} || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} );
 
diff --git a/share/html/SelfService/Elements/RequestUpdate b/share/html/SelfService/Elements/RequestUpdate
index fbb1972263..a0afc33eaa 100644
--- a/share/html/SelfService/Elements/RequestUpdate
+++ b/share/html/SelfService/Elements/RequestUpdate
@@ -82,7 +82,11 @@ action="<%RT->Config->Get('WebPath')%><% $r->path_info %>"
 </div>
 
 <%INIT>
-my $args = delete $session{QuickCreate} || {};
+my $args = $session{QuickCreate} || {};
+RT::Interface::Web::Session::Delete(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => 'QuickCreate',
+);
 </%INIT>
 
 <%ARGS>
diff --git a/share/html/SelfService/Helpers/Upload/Delete b/share/html/SelfService/Helpers/Upload/Delete
index 50fe73b23e..013c3c9d82 100644
--- a/share/html/SelfService/Helpers/Upload/Delete
+++ b/share/html/SelfService/Helpers/Upload/Delete
@@ -50,8 +50,12 @@ $Name => ''
 $Token => ''
 </%args>
 <%init>
-delete $session{'Attachments'}{ $Token }{ $Name };
-$session{'Attachments'} = $session{'Attachments'};
+RT::Interface::Web::Session::Delete(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => 'Attachments',
+    SessionSubKey => $Token,
+    SessionSubSubKey => $Name,
+);
 $r->content_type('application/json; charset=utf-8');
 $m->out( JSON({status => 'success'}) );
 $m->abort;
diff --git a/share/html/SelfService/Prefs.html b/share/html/SelfService/Prefs.html
index 128d534847..3ba496a04f 100644
--- a/share/html/SelfService/Prefs.html
+++ b/share/html/SelfService/Prefs.html
@@ -169,7 +169,11 @@ if ( $pref eq 'edit-prefs' || $pref eq 'edit-prefs-view-info' || $pref eq 'full-
 
     if ( $Lang ) {
         $session{'CurrentUser'}->LanguageHandle($Lang);
-        $session{'CurrentUser'} = $session{'CurrentUser'}; # force writeback
+        RT::Interface::Web::Session::Set(
+            SessionRef => \%HTML::Mason::Commands::session,
+            SessionKey => 'CurrentUser',
+            SessionValue => $session{'CurrentUser'},
+        );
     }
 }
 
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 6af9f0bee7..b9583a4090 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -368,7 +368,11 @@ unless ($Queue) {
 
 Abort( loc( "Permission Denied" ) ) unless $Queue;
 
-$session{DefaultQueue} = $Queue;
+RT::Interface::Web::Session::Set(
+    SessionRef => \%HTML::Mason::Commands::session,
+    SessionKey => 'DefaultQueue',
+    SessionValue => $Queue,
+);
 
 my $current_user = $session{'CurrentUser'};
 
diff --git a/share/html/Widgets/SavedSearch b/share/html/Widgets/SavedSearch
index 0359ae752d..c42452f302 100644
--- a/share/html/Widgets/SavedSearch
+++ b/share/html/Widgets/SavedSearch
@@ -82,7 +82,10 @@ if ( my ( $container_object, $search_id ) = _parse_saved_search(
 # need to delete $session{CurrentSearchHash} to let it not show the old one.
 # of course, the new one should not be shown there either because it's of
 # different type
-    delete $session{'CurrentSearchHash'};
+    RT::Interface::Web::Session::Delete(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CurrentSearchHash',
+    );
 }
 
 # look for the current one in the available saved searches
diff --git a/share/html/m/logout b/share/html/m/logout
index bd27b06614..4d1b840a39 100644
--- a/share/html/m/logout
+++ b/share/html/m/logout
@@ -48,7 +48,11 @@
 <%init>
 if (keys %session) {
     RT::Interface::Web::InstantiateNewSession();
-    $session{'CurrentUser'} = RT::CurrentUser->new;
+    RT::Interface::Web::Session::Set(
+        SessionRef => \%HTML::Mason::Commands::session,
+        SessionKey => 'CurrentUser',
+        SessionValue => RT::CurrentUser->new,
+    );
 }
 RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."m/");
 </%init>
diff --git a/t/web/attachments.t b/t/web/attachments.t
index b6c1b06db3..d0ebf85bbd 100644
--- a/t/web/attachments.t
+++ b/t/web/attachments.t
@@ -8,6 +8,7 @@ use constant FaviconFile => $RT::StaticPath .'/images/favicon.png';
 use constant TextFile => $RT::StaticPath .'/css/mobile.css';
 
 my ($url, $m) = RT::Test->started_ok;
+diag $url;
 ok $m->login, 'logged in';
 
 my $queue = RT::Test->load_or_create_queue( Name => 'General' );
diff --git a/t/web/dashboards-basics.t b/t/web/dashboards-basics.t
index aac674df06..669b1fdb8f 100644
--- a/t/web/dashboards-basics.t
+++ b/t/web/dashboards-basics.t
@@ -6,7 +6,7 @@ use RT::Test tests => undef;
 my ($baseurl, $m) = RT::Test->started_ok;
 
 my $url = $m->rt_base_url;
-
+diag "$url";
 my $user_obj = RT::User->new(RT->SystemUser);
 my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer at example.com');
 ok($ret, 'ACL test user creation');
diff --git a/t/web/session.t b/t/web/session.t
index b7b7d1dfdc..1feed8e73e 100644
--- a/t/web/session.t
+++ b/t/web/session.t
@@ -36,7 +36,11 @@ my ($session_id) = $agent->cookie_jar->as_string =~ /RT_SID_[^=]+=(\w+);/;
 
 diag 'Load session for root user';
 my %session;
-tie %session, 'RT::Interface::Web::Session', $session_id;
+RT::Interface::Web::Session::Load(
+    SessionRef => \%session,
+    SessionId => $session_id,
+);
+
 is ( $session{'_session_id'}, $session_id, 'Got session id ' . $session_id );
 is ( $session{'CurrentUser'}->Name, 'root', 'Session is for root user' );
 
@@ -49,16 +53,70 @@ is ( $session{'SelectObject---RT::Queue---' . $user_id . '---CreateTicket---0'}{
 my $last_updated = $session{'SelectObject---RT::Queue---' . $user_id . '---CreateTicket---0'}{'lastupdated'};
 ok( $last_updated, "Got a lastupdated timestamp of $last_updated");
 
-untie(%session);
 # Wait for 1 sec so we can confirm lastupdated doesn't change
 sleep 1;
 $agent->get($url);
 is ($agent->status, 200, "Loaded a page");
 
-tie %session, 'RT::Interface::Web::Session', $session_id;
+RT::Interface::Web::Session::Load(
+    SessionRef => \%session,
+    SessionId => $session_id,
+);
+
 is ( $session{'_session_id'}, $session_id, 'Got session id ' . $session_id );
 is ( $session{'CurrentUser'}->Name, 'root', 'Session is for root user' );
 is ($last_updated, $session{'SelectObject---RT::Queue---' . $user_id . '---CreateTicket---0'}{'lastupdated'},
     "lastupdated is still $last_updated");
 
+RT::Interface::Web::Session::Set(
+    SessionRef => \%session,
+    SessionKey => 'Testing',
+    SessionValue => 'TestValue',
+);
+
+is ( $session{'Testing'}, 'TestValue', 'Set a test value' );
+
+RT::Interface::Web::Session::Load(
+    SessionRef => \%session,
+    SessionId => $session_id,
+);
+
+is ( $session{'Testing'}, 'TestValue', 'Test value still set after Load' );
+
+RT::Interface::Web::Session::Delete(
+    SessionRef => \%session,
+    SessionKey => 'Testing',
+);
+
+ok ( !(exists $session{'Testing'}), 'Test value deleted' );
+
+RT::Interface::Web::Session::Load(
+    SessionRef => \%session,
+    SessionId => $session_id,
+);
+
+ok ( !(exists $session{'Testing'}), 'Test value still deleted after Load' );
+
+diag 'Test logging out';
+
+# Log in again first
+ok ( $agent->logout(), 'Logged out' );
+$agent->login('root' => 'password');
+# the field isn't named, so we have to click link 0
+is( $agent->status, 200, "Fetched the page ok");
+$agent->content_contains("Logout", "Found a logout link");
+
+my ($session_id2) = $agent->cookie_jar->as_string =~ /RT_SID_[^=]+=(\w+);/;
+
+ok ( $agent->logout(), 'Logged out' );
+
+RT::Interface::Web::Session::Load(
+    SessionRef => \%session,
+    SessionId => $session_id2,
+);
+
+isnt ( $session{'_session_id'}, $session_id, 'Got a new session id' );
+ok ( !( exists $session{'CurrentUser'} ), 'New session is empty' );
+
+
 done_testing;
diff --git a/t/web/simple_search.t b/t/web/simple_search.t
index 0a9c2c0c7f..7b74d80344 100644
--- a/t/web/simple_search.t
+++ b/t/web/simple_search.t
@@ -5,7 +5,7 @@ use RT::Test tests => undef,
     config => 'Set( %FullTextSearch, Enable => 1, Indexed => 0 );';
 my ($baseurl, $m) = RT::Test->started_ok;
 my $url = $m->rt_base_url;
-
+diag "running: $url";
 my $queue = RT::Queue->new($RT::SystemUser);
 $queue->Create( Name => 'other' );
 ok( $queue->id, 'created queue other');
diff --git a/t/web/unlimited_search.t b/t/web/unlimited_search.t
index 6a5f69724d..cbb3f64587 100644
--- a/t/web/unlimited_search.t
+++ b/t/web/unlimited_search.t
@@ -4,6 +4,10 @@ use warnings;
 use RT::Test tests => 85;
 my ($baseurl, $agent) = RT::Test->started_ok;
 
+# Call login first to avoid losing mysql DB connection
+# Todo: figure out why
+ok $agent->login('root', 'password'), 'logged in as root';
+
 my $ticket = RT::Ticket->new(RT->SystemUser);
 for ( 1 .. 75 ) {
     ok $ticket->Create(
@@ -14,8 +18,6 @@ for ( 1 .. 75 ) {
     );
 }
 
-ok $agent->login('root', 'password'), 'logged in as root';
-
 $agent->get_ok('/Search/Build.html');
 $agent->form_name('BuildQuery');
 $agent->field('idOp', '>');

-----------------------------------------------------------------------


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list