WIP - Append GlobalLogout plugin (#1999)

This commit is contained in:
Christophe Maudoux 2019-11-10 23:16:24 +01:00
parent e225516105
commit 80a1e4bf57
9 changed files with 292 additions and 20 deletions

View File

@ -69,6 +69,8 @@ sub defaultValues {
'facebookUserField' => 'id',
'failedLoginNumber' => 5,
'formTimeout' => 120,
'globalLogoutRule' => 0,
'globalLogoutTimeout' => 30,
'globalStorage' => 'Apache::Session::File',
'globalStorageOptions' => {
'Directory' => '/var/lib/lemonldap-ng/sessions/',

View File

@ -1221,6 +1221,14 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
'default' => 120,
'type' => 'int'
},
'globalLogoutRule' => {
'default' => 0,
'type' => 'boolOrExpr'
},
'globalLogoutTimeout' => {
'default' => 30,
'type' => 'int'
},
'globalStorage' => {
'default' => 'Apache::Session::File',
'type' => 'PerlModule'

View File

@ -450,9 +450,10 @@ sub attributes {
flags => 'p',
},
checkUserSearchAttributes => {
type => 'text',
documentation => 'Attributes used for retrieving sessions in user DataBase',
flags => 'p',
type => 'text',
documentation =>
'Attributes used for retrieving sessions in user DataBase',
flags => 'p',
},
checkUserDisplayPersistentInfo => {
default => 0,
@ -466,6 +467,17 @@ sub attributes {
documentation => 'Display session empty values',
flags => 'p',
},
globalLogoutRule => {
type => 'boolOrExpr',
default => 0,
documentation => 'Global logout activation rule',
flags => 'p',
},
globalLogoutTimeout => {
default => 30,
type => 'int',
documentation => 'Global logout timeout',
},
impersonationMergeSSOgroups => {
default => 0,
type => 'boolOrExpr',
@ -544,7 +556,7 @@ sub attributes {
forceGlobalStorageIssuerOTT => {
type => 'bool',
documentation =>
'Force Issuer tokens be stored into Global Storage',
'Force Issuer tokens to be stored into Global Storage',
},
handlerInternalCache => {
type => 'int',
@ -1978,15 +1990,15 @@ sub attributes {
vhostType => {
type => 'select',
select => [
{ k => 'AuthBasic', v => 'AuthBasic' },
{ k => 'CDA', v => 'CDA' },
{ k => 'DevOps', v => 'DevOps' },
{ k => 'DevOpsST', v => 'DevOpsST' },
{ k => 'Main', v => 'Main' },
{ k => 'OAuth2', v => 'OAuth2' },
{ k => 'SecureToken', v => 'SecureToken' },
{ k => 'ServiceToken', v => 'ServiceToken' },
{ k => 'ZimbraPreAuth',v => 'ZimbraPreAuth' },
{ k => 'AuthBasic', v => 'AuthBasic' },
{ k => 'CDA', v => 'CDA' },
{ k => 'DevOps', v => 'DevOps' },
{ k => 'DevOpsST', v => 'DevOpsST' },
{ k => 'Main', v => 'Main' },
{ k => 'OAuth2', v => 'OAuth2' },
{ k => 'SecureToken', v => 'SecureToken' },
{ k => 'ServiceToken', v => 'ServiceToken' },
{ k => 'ZimbraPreAuth', v => 'ZimbraPreAuth' },
],
default => 'Main',
documentation => 'Handler type',

View File

@ -671,6 +671,13 @@ sub tree {
help => 'autosignin.html',
nodes => ['autoSigninRules'],
},
{
title => 'globalLogout',
help => 'globallogout.html',
form => 'simpleInputContainer',
nodes =>
[ 'globalLogoutRule', 'globalLogoutTimeout', ],
},
{
title => 'stateCheck',
help => 'checkstate.html',
@ -716,10 +723,8 @@ sub tree {
title => 'decryptValue',
help => 'decryptvalue.html',
form => 'simpleInputContainer',
nodes => [
'decryptValueRule',
'decryptValueFunctions',
]
nodes =>
[ 'decryptValueRule', 'decryptValueFunctions', ]
},
]
},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,6 +12,7 @@ use Mouse;
# Plugins enabled by a simple boolean value (ordered list)
#
# Developers: 2FA must be loaded before Notifications
# Developers: GlobalLogout must be the last loaded plugin
our @pList = (
portalDisplayResetPassword => '::Plugins::MailPasswordReset',
portalStatus => '::Plugins::Status',
@ -28,7 +29,8 @@ our @pList = (
checkUser => '::Plugins::CheckUser',
impersonationRule => '::Plugins::Impersonation',
contextSwitchingRule => '::Plugins::ContextSwitching',
decryptValueRule => '::Plugins::DecryptValue'
decryptValueRule => '::Plugins::DecryptValue',
globalLogoutRule => '::Plugins::GlobalLogout'
);
##@method list enabledPlugins

View File

@ -0,0 +1,193 @@
package Lemonldap::NG::Portal::Plugins::GlobalLogout;
use strict;
use Mouse;
use JSON qw(from_json to_json);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_ERROR
PE_NOTOKEN
PE_TOKENEXPIRED
PE_SENDRESPONSE
PE_SESSIONEXPIRED
);
use Data::Dumper;
our $VERSION = '2.0.8';
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;
}
);
# Default timeout: 30 seconds
has timeout => (
is => 'rw',
lazy => 1,
default => sub {
$_[0]->{conf}->{globalLogoutTimeout} // 30;
}
);
sub init {
my ($self) = @_;
$self->addAuthRoute( globallogout => 'globalLogout', [ 'POST', 'GET' ] );
# Parse activation rule
my $hd = $self->p->HANDLER;
$self->logger->debug(
"GlobalLogout rule -> " . $self->conf->{globalLogoutRule} );
my $rule =
$hd->buildSub( $hd->substitute( $self->conf->{globalLogoutRule} ) );
unless ($rule) {
$self->error( "Bad globalLogout rule -> " . $hd->tsv->{jail}->error );
return 0;
}
$self->rule($rule);
return 1;
}
# RUNNING METHODS
# Look for user active SSO sessions and propose to close them
sub run {
my ( $self, $req ) = @_;
# Check activation rules
return PE_OK unless ( $self->rule->( $req, $req->userData ) );
# Looking for active sessions and store them into OTT
my $sessions = $self->_activeSessions($req);
return PE_OK unless ( scalar @{$sessions} > 1 );
my $token = $self->ott->createToken( {
user => $req->{userData}->{ $self->conf->{whatToTrace} },
sessions => to_json($sessions)
}
);
# Prepare form
$self->logger->debug("Prepare global logout confirmation");
my $tmp = $self->p->sendHtml(
$req,
'globallogout',
params => {
PORTAL => $self->conf->{portal},
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
LANGS => $self->conf->{showLanguages},
LOGIN => $req->{userData}->{ $self->conf->{whatToTrace} },
SESSIONS => $sessions,
TOKEN => $token
}
);
$req->response($tmp);
return PE_SENDRESPONSE;
}
sub globalLogout {
my ( $self, $req ) = @_;
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 ($@) {
$self->logger->error("Bad encoding in OTT: $@");
return PE_ERROR;
}
my $as;
foreach (@$sessions) {
unless ( $as = $self->p->getApacheSession( $_->{id} ) ) {
$self->userLogger->info(
"GolbalLogout: session $_->{id} expired");
next;
}
my $user = $token->{user};
if ( $req->{userData}->{ $self->{conf}->{whatToTrace} } eq
$user )
{
unless ( $req->{userData}->{_session_id} eq $_->{id} ) {
$self->userLogger->info(
"Remove \"$user\" session: $_->{id}");
$as->remove;
}
}
else {
$self->userLogger->warn(
"GolbalLogout called with an unvalid token");
return PE_TOKENEXPIRED;
}
}
}
else {
$self->userLogger->error(
"GlobalLogout called with an expired token");
return PE_TOKENEXPIRED;
}
}
else {
$self->userLogger->error('GlobalLogout called without token');
return PE_NOTOKEN;
}
}
return $self->p->do( $req, [ 'authLogout', 'deleteSession' ] );
}
sub _activeSessions {
my ( $self, $req ) = @_;
my $activeSessions = [];
my $sessions = {};
# Try to retrieve session from sessions DB
$self->logger->debug('Try to retrieve session from DB');
my $moduleOptions = $self->conf->{globalStorageOptions} || {};
$moduleOptions->{backend} = $self->conf->{globalStorage};
$self->logger->debug( 'Looking for "'
. $req->userData->{ $self->conf->{whatToTrace} }
. '" sessions...' );
$sessions = $self->module->searchOn(
$moduleOptions,
$self->conf->{whatToTrace},
$req->userData->{ $self->conf->{whatToTrace} }
);
$self->logger->debug("Building array ref for template loop...");
@$activeSessions = map { {
id => $_,
UA => $sessions->{$_}->{'UA'},
ipAddr => $sessions->{$_}->{'ipAddr'},
authLevel => $sessions->{$_}->{'authenticationLevel'},
startTime => $sessions->{$_}->{'_startTime'},
updateTime => $sessions->{$_}->{'_updateTime'},
};
} keys %$sessions;
$self->logger->debug(
"**************" . Data::Dumper::Dumper $activeSessions );
return $activeSessions;
}
1;

View File

@ -0,0 +1,50 @@
<TMPL_INCLUDE NAME="header.tpl">
<div id="errorcontent" class="container">
<div class="alert alert-warning alert"><div class="text-center"><span trspan="globalLogout">globalLogout</span></div></div>
<div class="row">
<TMPL_IF NAME="SESSIONS">
<div class="card col border-secondary">
<div class="text-center bg-light text-dark"><b><span trspan="activeSessions">ACTIVE SESSIONS</span>: <u><TMPL_VAR NAME="LOGIN"></u></b></div>
<table class="table table-sm table-hover">
<thead>
<tr>
<th scope="col"><span trspan="authLevel">authLevel</span></th>
<th scope="col"><span trspan="ipAddr">ipAddr</span></th>
</tr>
</thead>
<tbody>
<TMPL_LOOP NAME="SESSIONS">
<tr>
<td scope="row"><TMPL_VAR NAME="authLevel"></td>
<td scope="row"><TMPL_VAR NAME="ipAddr"></td>
</tr>
</TMPL_LOOP>
</tbody>
</table>
</div>
</TMPL_IF>
</div>
<form id="globallogout" action="/globallogout?all=1" method="post" class="password" role="form">
<input type="hidden" name="token" value="<TMPL_VAR NAME="TOKEN">" />
<div class="buttons">
<button type="submit" class="btn btn-danger">
<span class="fa fa-search"></span>
<span trspan="all">All</span>
</button>
<a href="<TMPL_VAR NAME="PORTAL_URL">globallogout" class="btn btn-success" role="button">
<span class="fa fa-home"></span>
<span trspan="current">Current</span>
</a>
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="goToPortal">Go to portal</span>
</a>
</div>
</form>
</div>
<TMPL_INCLUDE NAME="footer.tpl">