diff --git a/Kernel/System/AuthSession.pm b/Kernel/System/AuthSession.pm
index 464b900d6fc..f54f8b6b7f5 100644
--- a/Kernel/System/AuthSession.pm
+++ b/Kernel/System/AuthSession.pm
@@ -265,7 +265,13 @@ sub RemoveSessionByUser {
SessionID => $SessionID,
);
- next SESSIONID if $SessionData{UserLogin} ne $Param{UserLogin};
+ if (
+ defined $SessionData{UserLogin}
+ && ( $SessionData{UserLogin} ne $Param{UserLogin} )
+ )
+ {
+ next SESSIONID;
+ }
$Self->{Backend}->RemoveSessionID(
SessionID => $SessionID,
@@ -376,6 +382,34 @@ sub GetActiveSessions {
return $Self->{Backend}->GetActiveSessions(%Param);
}
+=head2 GetOrphanedSessionIDs()
+
+returns an array with orphaned session ids,
+missing user-login defines an orphaned session for now
+
+ my @Sessions = $SessionObject->GetOrphanedSessionIDs();
+
+=cut
+
+sub GetOrphanedSessionIDs {
+ my ( $Self, %Param ) = @_;
+
+ my @OrphanedSessionIDs;
+ my @SessionIDs = $Self->GetAllSessionIDs();
+ for my $SessionID (@SessionIDs) {
+
+ my %SessionData = $Self->{Backend}->GetSessionIDData(
+ SessionID => $SessionID,
+ );
+
+ if ( !defined $SessionData{UserLogin} ) {
+ push @OrphanedSessionIDs, $SessionID;
+ }
+ }
+
+ return @OrphanedSessionIDs;
+}
+
=head2 CleanUp()
clean-up of sessions in your system
diff --git a/Kernel/System/Console/Command/Maint/Session/DeleteOrphaned.pm b/Kernel/System/Console/Command/Maint/Session/DeleteOrphaned.pm
new file mode 100644
index 00000000000..3fc48c35f7c
--- /dev/null
+++ b/Kernel/System/Console/Command/Maint/Session/DeleteOrphaned.pm
@@ -0,0 +1,56 @@
+# --
+# Copyright (C) 2021 Znuny GmbH, https://znuny.org/
+# --
+# This software comes with ABSOLUTELY NO WARRANTY. For details, see
+# the enclosed file COPYING for license information (AGPL). If you
+# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
+# --
+
+package Kernel::System::Console::Command::Maint::Session::DeleteOrphaned;
+
+use strict;
+use warnings;
+use utf8;
+
+use parent qw(Kernel::System::Console::BaseCommand);
+
+our @ObjectDependencies = (
+ 'Kernel::System::AuthSession',
+);
+
+sub Configure {
+ my ( $Self, %Param ) = @_;
+
+ $Self->Description('Delete orphaned sessions.');
+
+ return;
+}
+
+sub Run {
+ my ( $Self, %Param ) = @_;
+
+ my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession');
+
+ $Self->Print("Deleting orphaned sessions...\n");
+
+ my @OrphanedSessions = $SessionObject->GetOrphanedSessionIDs();
+
+ for my $SessionID ( @OrphanedSessions ) {
+ my $Success = $SessionObject->RemoveSessionID(
+ SessionID => $SessionID,
+ );
+
+ if ( !$Success ) {
+ $Self->PrintError("Session $SessionID could not be deleted.");
+ return $Self->ExitCodeError();
+ }
+
+ $Self->Print(" $SessionID\n");
+ }
+
+ $Self->Print("Done.\n");
+
+ return $Self->ExitCodeOk();
+}
+
+1;
diff --git a/Kernel/System/Console/Command/Maint/Session/ListOrphaned.pm b/Kernel/System/Console/Command/Maint/Session/ListOrphaned.pm
new file mode 100644
index 00000000000..7f471221051
--- /dev/null
+++ b/Kernel/System/Console/Command/Maint/Session/ListOrphaned.pm
@@ -0,0 +1,47 @@
+# --
+# Copyright (C) 2021 Znuny GmbH, https://znuny.org/
+# --
+# This software comes with ABSOLUTELY NO WARRANTY. For details, see
+# the enclosed file COPYING for license information (AGPL). If you
+# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
+# --
+
+package Kernel::System::Console::Command::Maint::Session::ListOrphaned;
+
+use strict;
+use warnings;
+use utf8;
+
+use parent qw(Kernel::System::Console::BaseCommand);
+
+our @ObjectDependencies = (
+ 'Kernel::System::AuthSession',
+);
+
+sub Configure {
+ my ( $Self, %Param ) = @_;
+
+ $Self->Description('List orphaned sessions.');
+
+ return;
+}
+
+sub Run {
+ my ( $Self, %Param ) = @_;
+
+ my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession');
+
+ $Self->Print("Listing orphaned sessions...\n");
+
+ my @OrphanedSessions = $SessionObject->GetOrphanedSessionIDs();
+
+ for my $SessionID (@OrphanedSessions) {
+ $Self->Print(" $SessionID\n");
+ }
+
+ $Self->Print("Done.\n");
+
+ return $Self->ExitCodeOk();
+}
+
+1;
diff --git a/scripts/test/RemoveOrphanedSessions.t b/scripts/test/RemoveOrphanedSessions.t
new file mode 100644
index 00000000000..e944ca935b3
--- /dev/null
+++ b/scripts/test/RemoveOrphanedSessions.t
@@ -0,0 +1,100 @@
+# --
+# Copyright (C) 2021 Znuny GmbH, https://znuny.org/
+# --
+# This software comes with ABSOLUTELY NO WARRANTY. For details, see
+# the enclosed file COPYING for license information (AGPL). If you
+# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
+# --
+
+use strict;
+use warnings;
+use utf8;
+
+use vars (qw($Self));
+
+my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession');
+my $UserObject = $Kernel::OM->Get('Kernel::System::User');
+
+$Kernel::OM->ObjectParamAdd(
+ 'Kernel::System::UnitTest::Helper' => {
+ RestoreDatabase => 1,
+ },
+);
+my $HelperObject = $Kernel::OM->Get('Kernel::System::UnitTest::Helper');
+
+# Create test users and a session for every one.
+my @TestUserLogins;
+for my $Count ( 1 .. 3 ) {
+ my ( $TestUserLogin, $TestUserID ) = $HelperObject->TestUserCreate();
+ push @TestUserLogins, $TestUserLogin;
+
+ my %UserData = $UserObject->GetUserData(
+ UserID => $TestUserID,
+ NoOutOfOffice => 1,
+ );
+
+ my $NewSessionID = $SessionObject->CreateSessionID(
+ %UserData,
+ UserLastRequest => $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch(),
+ UserType => 'User',
+ SessionSource => 'AgentInterface',
+ );
+ $Self->True(
+ $NewSessionID,
+ "SessionID '$NewSessionID' is created for user '$TestUserLogin'",
+ );
+}
+
+# orphan first session
+my @SessionIDs = $SessionObject->GetAllSessionIDs();
+my $OrphanedTestUserSessionID = $SessionIDs[0];
+
+$SessionObject->UpdateSessionID(
+ SessionID => $OrphanedTestUserSessionID,
+ Key => 'UserLogin',
+ Value => undef,
+);
+$Self->True(
+ $OrphanedTestUserSessionID,
+ "SessionID '$OrphanedTestUserSessionID' has been orphaned",
+);
+
+# delete orphaned session via console command method
+# ( i.e. bin/znuny.Console.pl Maint::Session::DeleteOrphaned for maintenance )
+my $CommandObject = $Kernel::OM->Get('Kernel::System::Console::Command::Maint::Session::DeleteOrphaned');
+my $ExitCode = $CommandObject->Execute();
+
+$Self->Is(
+ $ExitCode,
+ $CommandObject->ExitCodeOk(),
+ "Orphaned session '$OrphanedTestUserSessionID' was deleted",
+);
+
+# Check for remaining sessions.
+my @RemainingSessionIDs = $SessionObject->GetAllSessionIDs();
+
+$Self->Is(
+ scalar @RemainingSessionIDs,
+ 2,
+ "Ok, only one session was deleted, two remaining.",
+);
+
+# Check if orphaned session is removed.
+for my $SessionID (@RemainingSessionIDs) {
+ if ( $SessionID eq $OrphanedTestUserSessionID ) {
+ $Self->False(
+ $OrphanedTestUserSessionID,
+ "Orphaned session: '$OrphanedTestUserSessionID' was not deleted",
+ );
+ }
+ else {
+ $Self->True(
+ $SessionID,
+ "Session '$SessionID' is found",
+ );
+ }
+}
+
+# Restore to the previous state is done by RestoreDatabase.
+
+1;