lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/GlobalLogout.pm

248 lines
7.6 KiB
Perl
Raw Normal View History

package Lemonldap::NG::Portal::Plugins::GlobalLogout;
use strict;
use Mouse;
use JSON qw(from_json to_json);
use Time::Local;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_ERROR
PE_NOTOKEN
PE_TOKENEXPIRED
PE_SENDRESPONSE
);
2022-02-01 16:33:08 +01:00
our $VERSION = '2.0.14';
extends qw(
Lemonldap::NG::Portal::Main::Plugin
Lemonldap::NG::Portal::Lib::OtherSessions
);
# INTERFACE
use constant beforeLogout => 'run';
# INITIALIZATION
has rule => ( is => 'rw', default => sub { 0 } );
has ott => (
is => 'rw',
lazy => 1,
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->conf->{formTimeout} );
return $ott;
}
);
sub init {
my ($self) = @_;
$self->addAuthRoute( globallogout => 'globalLogout', [ 'POST', 'GET' ] );
# Parse activation rule
2020-04-25 14:42:02 +02:00
$self->rule(
2020-04-25 14:48:27 +02:00
$self->p->buildRule( $self->conf->{globalLogoutRule}, 'globalLogout' )
);
2020-04-25 14:42:02 +02:00
return 0 unless $self->rule;
return 1;
}
# RUNNING METHODS
# Look for user active SSO sessions and suggest to close them
sub run {
my ( $self, $req ) = @_;
2019-11-13 16:10:20 +01:00
my $user = $req->{userData}->{ $self->conf->{whatToTrace} };
2020-11-25 21:59:49 +01:00
# Check activation rule
2019-11-13 16:10:20 +01:00
unless ( $self->rule->( $req, $req->userData ) ) {
2020-11-25 21:59:49 +01:00
$self->userLogger->info("GlobaLogout not allowed for $user");
2019-11-13 16:10:20 +01:00
return PE_OK;
}
2019-11-13 16:10:20 +01:00
# Looking for active sessions
2019-11-14 22:02:18 +01:00
my $sessions = $self->activeSessions($req);
2019-11-13 16:10:20 +01:00
my $nbr = @{$sessions};
2019-11-14 22:02:18 +01:00
$self->logger->debug("GlobalLogout: $nbr session(s) found") if $nbr;
2019-11-13 16:10:20 +01:00
return PE_OK unless ( $nbr > 1 );
2019-11-13 21:27:48 +01:00
# Force GlobalLogout if timer is disabled
2019-11-13 20:56:56 +01:00
unless ( $self->conf->{globalLogoutTimer} ) {
$self->logger->debug("GlobalLogout: timer disabled");
2019-11-13 16:10:20 +01:00
$self->userLogger->info("GlobalLogout: force global logout for $user");
2019-11-14 22:02:18 +01:00
$nbr = $self->removeOtherActiveSessions( $req, $sessions );
$self->userLogger->info("$nbr remaining session(s) removed");
2019-11-13 16:10:20 +01:00
return PE_OK;
}
2019-11-13 16:10:20 +01:00
# Prepare token
my $token = $self->ott->createToken( {
2019-11-13 16:10:20 +01:00
user => $user,
sessions => to_json($sessions)
}
);
# Prepare form
$self->logger->debug("Prepare global logout confirmation");
my $tmp = $self->p->sendHtml(
$req,
'globallogout',
params => {
2019-11-14 22:02:18 +01:00
PORTAL => $self->conf->{portal},
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
LANGS => $self->conf->{showLanguages},
SESSIONS => $sessions,
TOKEN => $token,
LOGIN => $user,
CUSTOMPRM => $self->conf->{globalLogoutCustomParam}
}
);
$req->response($tmp);
2019-11-14 22:02:18 +01:00
return PE_SENDRESPONSE;
}
sub globalLogout {
my ( $self, $req ) = @_;
2020-04-25 14:48:27 +02:00
my $res = PE_OK;
2019-11-14 22:02:18 +01:00
my $count = 0;
if ( $req->param('all') ) {
if ( my $token = $req->param('token') ) {
if ( $token = $self->ott->getToken($token) ) {
# Read active sessions from token
my $sessions = eval { from_json( $token->{sessions} ) };
if ($@) {
2020-11-25 21:59:49 +01:00
$self->logger->error(
"GlobalLogout: bad encoding in OTT ($@)");
2020-04-25 01:01:57 +02:00
$res = PE_ERROR;
}
my $as;
2020-11-25 21:59:49 +01:00
my $user = $token->{user};
my $req_user =
$req->{userData}->{ $self->{conf}->{whatToTrace} };
if ( $req_user eq $user ) {
foreach (@$sessions) {
unless ( $as = $self->p->getApacheSession( $_->{id} ) )
{
$self->userLogger->info(
"GlobalLogout: session $_->{id} expired");
next;
}
unless ( $req->{userData}->{_session_id} eq $_->{id} ) {
$self->userLogger->info(
"Remove \"$user\" session: $_->{id}");
2020-04-26 12:13:13 +02:00
$as->remove;
2019-11-14 22:02:18 +01:00
$count++;
}
}
2020-11-25 21:59:49 +01:00
}
else {
$self->userLogger->warn(
"GlobalLogout called with an invalid token: $req_user is NOT $user"
);
$res = PE_TOKENEXPIRED;
}
}
else {
$self->userLogger->error(
"GlobalLogout called with an expired token");
2020-04-25 01:01:57 +02:00
$res = PE_TOKENEXPIRED;
}
}
else {
$self->userLogger->error('GlobalLogout called without token');
2020-04-25 01:01:57 +02:00
$res = PE_NOTOKEN;
}
}
2020-04-25 01:01:57 +02:00
return $self->p->do( $req, [ sub { $res } ] ) if $res;
2020-11-25 21:59:49 +01:00
$self->userLogger->info("$count remaining session(s) removed");
return $self->p->do( $req, [ 'authLogout', 'deleteSession' ] );
}
2019-11-14 22:02:18 +01:00
sub activeSessions {
my ( $self, $req ) = @_;
my $activeSessions = [];
my $sessions = {};
2019-11-14 22:02:18 +01:00
my $user = $req->{userData}->{ $self->conf->{whatToTrace} };
my $customParam = $self->conf->{globalLogoutCustomParam} || '';
# Try to retrieve sessions from sessions DB
2021-06-08 22:55:09 +02:00
if ($user) {
$self->logger->debug('Try to retrieve sessions from DB');
my $moduleOptions = $self->conf->{globalStorageOptions} || {};
$moduleOptions->{backend} = $self->conf->{globalStorage};
$self->logger->debug("Looking for \"$user\" sessions...");
$sessions =
$self->module->searchOn( $moduleOptions, $self->conf->{whatToTrace},
$user );
$self->logger->debug('Skip non-SSO session(s)...');
my $other = 0;
foreach ( keys %$sessions ) {
unless ( $sessions->{$_}->{_session_kind} eq 'SSO' ) {
delete $sessions->{$_};
$other++;
}
2020-09-30 21:39:09 +02:00
}
2021-06-08 22:55:09 +02:00
$self->logger->info("$other non-SSO session(s) skipped")
if $other;
$self->logger->debug('Build an array ref with sessions info...');
@$activeSessions =
map {
my $epoch;
my $regex = '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$';
$_->{startTime} =~ /$regex/;
$epoch = timelocal( $6, $5, $4, $3, $2 - 1, $1 );
2021-06-08 22:55:09 +02:00
$_->{startTime} = $epoch;
if ( $_->{updateTime} ) {
$_->{updateTime} =~ /$regex/;
$epoch = timelocal( $6, $5, $4, $3, $2 - 1, $1 );
$_->{updateTime} = $epoch;
}
$_;
}
2022-02-16 17:43:29 +01:00
sort { $b->{startTime} cmp $a->{startTime} } map {
{
2021-06-08 22:55:09 +02:00
id => $_,
customParam => $sessions->{$_}->{$customParam},
ipAddr => $sessions->{$_}->{ipAddr},
authLevel => $sessions->{$_}->{authenticationLevel},
startTime => $sessions->{$_}->{_startTime},
updateTime => $sessions->{$_}->{_updateTime}
};
} keys %$sessions;
}
return $activeSessions;
}
2019-11-14 22:02:18 +01:00
sub removeOtherActiveSessions {
my ( $self, $req, $sessions ) = @_;
my $count = 0;
my $as;
foreach (@$sessions) {
unless ( $as = $self->p->getApacheSession( $_->{id} ) ) {
$self->userLogger->info("GlobalLogout: session $_->{id} expired");
next;
}
unless ( $req->{userData}->{_session_id} eq $_->{id} ) {
$self->userLogger->info(
"Remove \"$req->{userData}->{ $self->conf->{whatToTrace} }\" session: $_->{id}"
);
$as->remove;
$count++;
}
}
return $count;
}
1;