2019-06-21 14:49:38 +02:00
|
|
|
package Lemonldap::NG::Portal::Plugins::ContextSwitching;
|
2019-06-19 18:13:17 +02:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
2019-07-03 06:47:33 +02:00
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_OK
|
|
|
|
PE_ERROR
|
2019-11-11 21:51:00 +01:00
|
|
|
PE_NOTOKEN
|
2019-07-03 06:47:33 +02:00
|
|
|
PE_REDIRECT
|
2019-11-11 21:51:00 +01:00
|
|
|
PE_TOKENEXPIRED
|
2019-07-03 06:47:33 +02:00
|
|
|
PE_MALFORMEDUSER
|
2019-07-04 22:50:46 +02:00
|
|
|
PE_BADCREDENTIALS
|
2019-07-03 23:08:40 +02:00
|
|
|
PE_SESSIONEXPIRED
|
2019-07-04 22:50:46 +02:00
|
|
|
PE_IMPERSONATION_SERVICE_NOT_ALLOWED
|
2019-07-03 06:47:33 +02:00
|
|
|
);
|
2019-06-19 18:13:17 +02:00
|
|
|
|
2020-11-17 23:18:05 +01:00
|
|
|
our $VERSION = '2.0.10';
|
2019-06-19 18:13:17 +02:00
|
|
|
|
2019-08-03 11:05:12 +02:00
|
|
|
extends qw(
|
|
|
|
Lemonldap::NG::Portal::Main::Plugin
|
|
|
|
Lemonldap::NG::Portal::Lib::_tokenRule
|
|
|
|
Lemonldap::NG::Portal::Lib::OtherSessions
|
|
|
|
);
|
2019-06-19 18:13:17 +02:00
|
|
|
|
|
|
|
# INITIALIZATION
|
|
|
|
|
2019-06-25 19:28:05 +02:00
|
|
|
has ott => (
|
2019-06-19 18:13:17 +02:00
|
|
|
is => 'rw',
|
|
|
|
lazy => 1,
|
|
|
|
default => sub {
|
|
|
|
my $ott =
|
|
|
|
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
|
|
|
|
$ott->timeout( $_[0]->{conf}->{formTimeout} );
|
|
|
|
return $ott;
|
|
|
|
}
|
|
|
|
);
|
2020-05-24 00:04:33 +02:00
|
|
|
has rule => ( is => 'rw', default => sub { 0 } );
|
|
|
|
has idRule => ( is => 'rw', default => sub { 1 } );
|
|
|
|
has unrestrictedUsersRule => ( is => 'rw', default => sub { 0 } );
|
2019-06-19 18:13:17 +02:00
|
|
|
|
|
|
|
sub init {
|
|
|
|
my ($self) = @_;
|
2020-08-28 23:50:57 +02:00
|
|
|
$self->addAuthRoute( switchcontext => 'run', ['POST'] )
|
2019-07-31 23:38:48 +02:00
|
|
|
->addAuthRoute( switchcontext => 'display', ['GET'] );
|
2019-06-19 18:13:17 +02:00
|
|
|
|
2020-04-25 14:41:23 +02:00
|
|
|
# Parse ContextSwitching rules
|
|
|
|
$self->rule(
|
2020-04-25 14:48:27 +02:00
|
|
|
$self->p->buildRule(
|
|
|
|
$self->conf->{contextSwitchingRule},
|
|
|
|
'contextSwitching'
|
|
|
|
)
|
|
|
|
);
|
2020-04-25 14:41:23 +02:00
|
|
|
return 0 unless $self->rule;
|
2019-06-23 22:07:05 +02:00
|
|
|
|
2020-04-25 14:41:23 +02:00
|
|
|
$self->idRule(
|
2020-04-25 14:48:27 +02:00
|
|
|
$self->p->buildRule(
|
|
|
|
$self->conf->{contextSwitchingIdRule},
|
|
|
|
'contextSwitchingId'
|
|
|
|
)
|
|
|
|
);
|
2020-04-25 14:41:23 +02:00
|
|
|
return 0 unless $self->idRule;
|
2020-04-25 14:48:27 +02:00
|
|
|
|
2020-05-20 21:43:37 +02:00
|
|
|
$self->unrestrictedUsersRule(
|
|
|
|
$self->p->buildRule(
|
|
|
|
$self->conf->{contextSwitchingUnrestrictedUsersRule},
|
|
|
|
'contextSwitchingUnrestrictedUsers'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return 0 unless $self->unrestrictedUsersRule;
|
|
|
|
|
2019-06-19 18:13:17 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
# RUNNING METHOD
|
|
|
|
|
|
|
|
sub display {
|
|
|
|
my ( $self, $req ) = @_;
|
2020-04-16 22:42:01 +02:00
|
|
|
my ( $realSession, $realSessionId );
|
|
|
|
if ( $realSessionId =
|
|
|
|
$req->userData->{"$self->{conf}->{contextSwitchingPrefix}_session_id"} )
|
|
|
|
{
|
|
|
|
unless ( $realSession = $self->p->getApacheSession($realSessionId) ) {
|
|
|
|
$self->userLogger->info(
|
|
|
|
"ContextSwitching: session $realSessionId expired");
|
|
|
|
return $self->p->do( $req, [ sub { PE_SESSIONEXPIRED } ] );
|
|
|
|
}
|
2019-07-03 06:47:33 +02:00
|
|
|
}
|
2019-06-19 18:13:17 +02:00
|
|
|
|
2019-06-22 23:46:02 +02:00
|
|
|
# Check access rules
|
|
|
|
unless ( $self->rule->( $req, $req->userData )
|
2020-04-04 00:08:20 +02:00
|
|
|
|| $req->userData->{
|
|
|
|
"$self->{conf}->{contextSwitchingPrefix}_session_id"} )
|
2019-06-22 23:46:02 +02:00
|
|
|
{
|
2019-07-04 22:50:46 +02:00
|
|
|
$self->userLogger->warn('ContextSwitching service NOT authorized');
|
2019-06-22 23:46:02 +02:00
|
|
|
return $self->p->do( $req,
|
|
|
|
[ sub { PE_IMPERSONATION_SERVICE_NOT_ALLOWED } ] );
|
|
|
|
}
|
|
|
|
|
2020-04-04 00:08:20 +02:00
|
|
|
if (
|
|
|
|
$req->userData->{"$self->{conf}->{contextSwitchingPrefix}_session_id"} )
|
|
|
|
{
|
2019-06-27 21:54:14 +02:00
|
|
|
$self->logger->debug('Request to stop ContextSwitching');
|
2019-06-23 22:07:05 +02:00
|
|
|
if ( $self->conf->{contextSwitchingStopWithLogout} ) {
|
2019-07-04 07:24:50 +02:00
|
|
|
$self->userLogger->notice("Stop ContextSwitching for $req->{user}");
|
2019-11-11 21:51:00 +01:00
|
|
|
$self->userLogger->info("Remove real session $realSessionId");
|
2019-07-03 23:08:40 +02:00
|
|
|
$realSession->remove;
|
2019-06-23 22:07:05 +02:00
|
|
|
return $self->p->do( $req,
|
|
|
|
[ @{ $self->p->beforeLogout }, 'authLogout', 'deleteSession' ]
|
|
|
|
);
|
2019-07-03 23:08:40 +02:00
|
|
|
|
2019-06-23 22:07:05 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-07-03 06:47:33 +02:00
|
|
|
$req = $self->_abortImpersonation( $req, $req->{user},
|
2019-07-03 23:08:40 +02:00
|
|
|
$realSession->data->{ $self->conf->{whatToTrace} }, 0 );
|
2019-06-24 23:52:39 +02:00
|
|
|
$self->p->updateSession( $req, $req->userData );
|
2019-06-23 22:07:05 +02:00
|
|
|
return $self->p->do( $req, [ sub { PE_REDIRECT } ] );
|
|
|
|
}
|
2019-06-22 23:46:02 +02:00
|
|
|
}
|
|
|
|
|
2019-06-19 18:13:17 +02:00
|
|
|
# Display form
|
|
|
|
my $params = {
|
2020-12-21 21:55:51 +01:00
|
|
|
PORTAL => $self->conf->{portal},
|
|
|
|
MAIN_LOGO => $self->conf->{portalMainLogo},
|
|
|
|
SKIN => $self->p->getSkin($req),
|
|
|
|
LANGS => $self->conf->{showLanguages},
|
|
|
|
MSG => 'contextSwitching_ON',
|
|
|
|
ALERTE => 'alert-danger',
|
|
|
|
IMPERSONATION => $self->conf->{contextSwitchingRule},
|
|
|
|
TOKEN => (
|
2019-06-19 18:13:17 +02:00
|
|
|
$self->ottRule->( $req, {} )
|
|
|
|
? $self->ott->createToken()
|
|
|
|
: ''
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2019-06-21 14:49:38 +02:00
|
|
|
return $self->p->sendHtml( $req, 'contextSwitching', params => $params, );
|
2019-06-19 18:13:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub run {
|
|
|
|
my ( $self, $req ) = @_;
|
2020-12-21 21:55:51 +01:00
|
|
|
my $statut = PE_OK;
|
|
|
|
my $realId = $req->userData->{ $self->conf->{whatToTrace} };
|
2019-06-24 23:36:23 +02:00
|
|
|
my $spoofId = $req->param('spoofId') || ''; # ContextSwitching required ?
|
2020-05-20 21:43:37 +02:00
|
|
|
my $unUser = $self->unrestrictedUsersRule->( $req, $req->userData ) || 0;
|
2019-06-19 18:13:17 +02:00
|
|
|
|
2019-11-11 21:51:00 +01:00
|
|
|
# Check token
|
|
|
|
if ( $self->ottRule->( $req, {} ) ) {
|
|
|
|
my $token = $req->param('token');
|
|
|
|
unless ($token) {
|
|
|
|
$self->userLogger->warn('ContextSwitching called without token');
|
|
|
|
return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
|
|
|
|
}
|
|
|
|
unless ( $self->ott->getToken($token) ) {
|
2019-12-16 23:21:43 +01:00
|
|
|
$self->userLogger->warn(
|
|
|
|
'ContextSwitching called with an expired/bad token');
|
2019-11-11 21:51:00 +01:00
|
|
|
return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-21 16:24:50 +02:00
|
|
|
# Check activation rule
|
|
|
|
unless ( $self->rule->( $req, $req->userData ) ) {
|
2019-07-03 23:08:40 +02:00
|
|
|
$self->userLogger->warn('ContextSwitching service NOT authorized');
|
2019-06-21 16:24:50 +02:00
|
|
|
$spoofId = '';
|
2019-06-22 23:46:02 +02:00
|
|
|
return $self->p->do( $req,
|
|
|
|
[ sub { PE_IMPERSONATION_SERVICE_NOT_ALLOWED } ] );
|
2019-06-19 18:13:17 +02:00
|
|
|
}
|
|
|
|
|
2019-06-24 23:36:23 +02:00
|
|
|
# ContextSwitching required -> Check user Id
|
2019-06-22 23:46:02 +02:00
|
|
|
if ( $spoofId && $spoofId ne $req->{user} ) {
|
2020-05-23 23:02:37 +02:00
|
|
|
$self->logger->debug("Spoofed Id: $spoofId");
|
2019-06-21 16:24:50 +02:00
|
|
|
unless ( $spoofId =~ /$self->{conf}->{userControl}/o ) {
|
2019-06-24 23:36:23 +02:00
|
|
|
$self->userLogger->warn('Malformed spoofed Id');
|
2019-06-21 16:24:50 +02:00
|
|
|
$self->logger->debug(
|
2019-07-03 23:08:40 +02:00
|
|
|
"ContextSwitching tried with spoofed Id: $spoofId");
|
2019-06-22 23:46:02 +02:00
|
|
|
return $self->p->do( $req, [ sub { PE_MALFORMEDUSER } ] );
|
2019-06-19 18:13:17 +02:00
|
|
|
}
|
|
|
|
}
|
2019-06-21 16:24:50 +02:00
|
|
|
else {
|
2019-07-03 23:08:40 +02:00
|
|
|
$self->logger->debug("contextSwitching NOT required");
|
2019-06-22 23:46:02 +02:00
|
|
|
$req->urldc( $self->conf->{portal} );
|
|
|
|
return $self->p->do( $req, [ sub { PE_OK } ] );
|
2019-06-21 16:24:50 +02:00
|
|
|
}
|
|
|
|
|
2019-06-24 23:36:23 +02:00
|
|
|
# Create spoofed session
|
2020-05-20 21:43:37 +02:00
|
|
|
$req = $self->_switchContext( $req, $spoofId, $unUser );
|
2020-04-04 00:08:20 +02:00
|
|
|
$statut =
|
|
|
|
( $req->error == PE_BADCREDENTIALS ? PE_MALFORMEDUSER : $req->error )
|
|
|
|
if $req->error;
|
2019-06-19 18:13:17 +02:00
|
|
|
|
|
|
|
# Main session
|
2019-06-24 23:36:23 +02:00
|
|
|
$self->p->updateSession( $req, $req->sessionInfo );
|
2019-07-03 00:09:14 +02:00
|
|
|
$self->userLogger->notice(
|
2020-05-24 00:04:33 +02:00
|
|
|
"ContextSwitching: Update \"$realId\" session with \"$spoofId\" session data"
|
|
|
|
);
|
2020-08-28 23:50:57 +02:00
|
|
|
|
2020-08-08 20:00:41 +02:00
|
|
|
$req->mustRedirect(1);
|
2019-06-19 18:13:17 +02:00
|
|
|
return $self->p->do( $req, [ sub { $statut } ] );
|
|
|
|
}
|
|
|
|
|
2019-06-24 23:36:23 +02:00
|
|
|
sub _switchContext {
|
2020-05-20 21:43:37 +02:00
|
|
|
my ( $self, $req, $spoofId, $unUser ) = @_;
|
2019-06-24 23:36:23 +02:00
|
|
|
my $realSessionId = $req->userData->{_session_id};
|
2020-04-01 00:35:09 +02:00
|
|
|
my $realAuthLevel = $req->userData->{authenticationLevel};
|
2020-11-17 23:18:05 +01:00
|
|
|
my $realId = $req->userData->{ $self->conf->{whatToTrace} };
|
2019-06-25 19:28:05 +02:00
|
|
|
my $raz = 0;
|
2019-06-24 23:52:39 +02:00
|
|
|
$req->{user} = $spoofId;
|
2019-06-19 18:13:17 +02:00
|
|
|
|
2019-06-24 23:36:23 +02:00
|
|
|
# Search user in database & create session
|
2019-07-03 23:21:19 +02:00
|
|
|
$req->steps( [
|
2020-10-03 13:05:51 +02:00
|
|
|
'getUser', 'setAuthSessionInfo',
|
|
|
|
'setSessionInfo', $self->p->groupsAndMacros,
|
|
|
|
'setPersistentSessionInfo', 'setLocalGroups',
|
|
|
|
'store', 'buildCookie'
|
2019-07-03 23:21:19 +02:00
|
|
|
]
|
|
|
|
);
|
2019-06-19 18:13:17 +02:00
|
|
|
if ( my $error = $self->p->process($req) ) {
|
2020-04-04 00:08:20 +02:00
|
|
|
$self->userLogger->warn(
|
2020-08-28 21:53:19 +02:00
|
|
|
'ContextSwitching requested for an invalid user ('
|
2020-04-04 00:08:20 +02:00
|
|
|
. $req->{user}
|
|
|
|
. ")" )
|
|
|
|
if ( $error == PE_BADCREDENTIALS );
|
2019-06-19 18:13:17 +02:00
|
|
|
$self->logger->debug("Process returned error: $error");
|
|
|
|
$req->error($error);
|
|
|
|
$raz = 1;
|
|
|
|
}
|
|
|
|
|
2020-05-20 21:43:37 +02:00
|
|
|
# Check identities rule if ContextSwitching required
|
|
|
|
$self->logger->info("\"$realId\" is an unrestricted user!") if $unUser;
|
|
|
|
unless ( $unUser || $self->idRule->( $req, $req->sessionInfo ) ) {
|
2019-06-22 23:46:02 +02:00
|
|
|
$self->userLogger->warn(
|
2020-08-28 21:53:19 +02:00
|
|
|
'ContextSwitching requested for an invalid user ('
|
2019-06-22 23:46:02 +02:00
|
|
|
. $req->{user}
|
|
|
|
. ")" );
|
2019-06-24 23:36:23 +02:00
|
|
|
$self->logger->debug('Identity NOT authorized');
|
2019-07-03 23:08:40 +02:00
|
|
|
$req->error(PE_MALFORMEDUSER); # Catch error to preserve protected Id
|
2019-06-22 23:46:02 +02:00
|
|
|
$raz = 1;
|
2019-06-19 18:13:17 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 23:09:55 +02:00
|
|
|
$req->sessionInfo->{"$self->{conf}->{contextSwitchingPrefix}_session_id"} =
|
2019-06-24 23:36:23 +02:00
|
|
|
$realSessionId;
|
2019-06-19 18:13:17 +02:00
|
|
|
|
2020-04-04 00:08:20 +02:00
|
|
|
return $self->_abortImpersonation( $req, $spoofId, $realId, 1 ) if $raz;
|
2020-04-01 00:35:09 +02:00
|
|
|
|
2020-04-04 00:08:20 +02:00
|
|
|
$self->logger->debug(
|
|
|
|
"Update sessionInfo with real authenticationLevel: $realAuthLevel");
|
|
|
|
$req->sessionInfo->{authenticationLevel} = $realAuthLevel;
|
|
|
|
delete $req->sessionInfo->{groups};
|
2020-04-01 00:35:09 +02:00
|
|
|
|
2020-04-04 00:08:20 +02:00
|
|
|
# Compute groups & macros again with real authenticationLevel
|
2020-04-23 14:16:35 +02:00
|
|
|
$req->steps(
|
|
|
|
[ 'setSessionInfo', $self->p->groupsAndMacros, 'setLocalGroups' ] );
|
2020-04-04 00:08:20 +02:00
|
|
|
if ( my $error = $self->p->process($req) ) {
|
|
|
|
$self->logger->debug(
|
|
|
|
"ContextSwitching: Process returned error: $error");
|
|
|
|
$req->error($error);
|
2020-04-01 00:35:09 +02:00
|
|
|
}
|
2020-04-04 00:08:20 +02:00
|
|
|
|
|
|
|
$self->userLogger->notice(
|
2020-05-23 23:02:37 +02:00
|
|
|
"Start ContextSwitching: \"$realId\" becomes \"$spoofId\"");
|
2020-04-04 00:08:20 +02:00
|
|
|
return $req;
|
2019-06-19 18:13:17 +02:00
|
|
|
}
|
|
|
|
|
2019-06-25 19:28:05 +02:00
|
|
|
sub _abortImpersonation {
|
2019-07-03 00:09:14 +02:00
|
|
|
my ( $self, $req, $spoofId, $realId, $abort ) = @_;
|
2019-06-25 19:28:05 +02:00
|
|
|
my $type = $abort ? 'sessionInfo' : 'userData';
|
2019-06-24 23:36:23 +02:00
|
|
|
my $realSessionId =
|
2020-04-03 23:09:55 +02:00
|
|
|
$req->{$type}->{"$self->{conf}->{contextSwitchingPrefix}_session_id"};
|
2019-07-03 06:47:33 +02:00
|
|
|
my $session;
|
|
|
|
unless ( $session = $self->p->getApacheSession($realSessionId) ) {
|
2019-07-04 07:24:50 +02:00
|
|
|
$self->userLogger->info("Session $session expired");
|
2019-07-03 23:08:40 +02:00
|
|
|
return $req->error(PE_SESSIONEXPIRED);
|
2019-07-03 06:47:33 +02:00
|
|
|
}
|
2019-06-28 23:53:20 +02:00
|
|
|
|
2019-06-25 19:28:05 +02:00
|
|
|
if ($abort) {
|
2019-07-03 00:09:14 +02:00
|
|
|
$self->userLogger->notice(
|
2020-05-23 23:02:37 +02:00
|
|
|
"Abort ContextSwitching: \"$spoofId\" by \"$realId\"");
|
2019-07-03 06:47:33 +02:00
|
|
|
if ( my $abortSession = $self->p->getApacheSession( $req->id ) ) {
|
|
|
|
$abortSession->remove;
|
|
|
|
}
|
|
|
|
else {
|
2019-07-04 07:24:50 +02:00
|
|
|
$self->userLogger->info(
|
2019-07-03 23:08:40 +02:00
|
|
|
"ContextSwitching: session " . $req->id . " expired" );
|
2019-07-03 06:47:33 +02:00
|
|
|
}
|
2019-06-25 19:28:05 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-07-03 23:12:15 +02:00
|
|
|
$self->userLogger->notice(
|
2020-05-23 23:02:37 +02:00
|
|
|
"Stop ContextSwitching for \"$realId\" with uid \"$spoofId\"");
|
2019-06-25 22:01:20 +02:00
|
|
|
$self->p->deleteSession($req);
|
2019-06-25 19:28:05 +02:00
|
|
|
}
|
2019-06-24 23:36:23 +02:00
|
|
|
|
|
|
|
# Restore real session
|
2019-07-03 23:12:15 +02:00
|
|
|
$req->{$type} = { %{ $session->data } };
|
2019-07-03 23:08:40 +02:00
|
|
|
$req->{user} = $session->data->{_user};
|
2019-06-22 23:46:02 +02:00
|
|
|
$req->urldc( $self->conf->{portal} );
|
2019-06-24 23:36:23 +02:00
|
|
|
$req->id($realSessionId);
|
|
|
|
$self->p->buildCookie($req);
|
2020-04-04 00:08:20 +02:00
|
|
|
delete $req->{$type}
|
|
|
|
->{"$self->{conf}->{contextSwitchingPrefix}_session_id"};
|
2019-06-23 22:07:05 +02:00
|
|
|
|
2019-06-24 23:36:23 +02:00
|
|
|
return $req;
|
2019-06-22 23:46:02 +02:00
|
|
|
}
|
|
|
|
|
2020-02-13 23:27:13 +01:00
|
|
|
sub displayLink {
|
2019-06-24 23:36:23 +02:00
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
return 'OFF'
|
2020-04-03 23:09:55 +02:00
|
|
|
if $req->userData->{"$self->{conf}->{contextSwitchingPrefix}_session_id"};
|
2019-06-24 23:36:23 +02:00
|
|
|
return 'ON' if $self->rule->( $req, $req->userData );
|
2019-06-22 23:46:02 +02:00
|
|
|
}
|
|
|
|
|
2019-06-19 18:13:17 +02:00
|
|
|
1;
|