WIP - AdminImpersonation skeleton (#1783)

This commit is contained in:
Christophe Maudoux 2019-06-19 18:13:17 +02:00
parent 2d5b38fb95
commit a2ebaf31b1
8 changed files with 341 additions and 5 deletions

View File

@ -5,11 +5,12 @@ our $VERSION = '2.0.5';
sub defaultValues {
return {
'activeTimer' => 1,
'ADPwdExpireWarning' => 0,
'ADPwdMaxAge' => 0,
'apacheAuthnLevel' => 4,
'applicationList' => {
'activeTimer' => 1,
'adminImpersonationRule' => 1,
'ADPwdExpireWarning' => 0,
'ADPwdMaxAge' => 0,
'apacheAuthnLevel' => 4,
'applicationList' => {
'default' => {
'catname' => 'Default category',
'type' => 'category'

View File

@ -257,6 +257,10 @@ sub attributes {
'default' => 1,
'type' => 'bool'
},
'adminImpersonationRule' => {
'default' => 1,
'type' => 'boolOrExpr'
},
'ADPwdExpireWarning' => {
'default' => 0,
'type' => 'int'

View File

@ -486,6 +486,11 @@ sub attributes {
documentation => 'Skip session empty values',
flags => 'p',
},
adminImpersonationRule => {
type => 'boolOrExpr',
default => 1,
documentation => 'adminImpersonation activation rule',
},
skipRenewConfirmation => {
type => 'bool',
default => 0,

View File

@ -114,6 +114,16 @@ sub params {
$self->p->_sfEngine->display2fRegisters( $req, $req->userData );
$self->logger->debug("Display 2fRegisters link") if $res{sfaManager};
# Display adminImpersonation link only if allowed
my $impPlugin = $self->p->loadedModules->{
'Lemonldap::NG::Portal::Plugins::AdminImpersonation'};
$res{adminImpersonation} =
$impPlugin
? $impPlugin->displayAdminImpersonation( $req, $req->userData )
: '';
$self->logger->debug("Display AdminImpersonation link")
if $res{adminImpersonation};
return %res;
}

View File

@ -27,6 +27,7 @@ our @pList = (
portalForceAuthn => '::Plugins::ForceAuthn',
checkUser => '::Plugins::CheckUser',
impersonationRule => '::Plugins::Impersonation',
adminImpersonationRule => '::Plugins::AdminImpersonation',
);
##@method list enabledPlugins

View File

@ -0,0 +1,271 @@
package Lemonldap::NG::Portal::Plugins::AdminImpersonation;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants
qw( PE_OK PE_BADCREDENTIALS PE_IMPERSONATION_SERVICE_NOT_ALLOWED PE_MALFORMEDUSER );
our $VERSION = '2.0.6';
extends
qw(Lemonldap::NG::Portal::Main::Plugin Lemonldap::NG::Portal::Lib::_tokenRule);
# INITIALIZATION
has rule => ( is => 'rw', default => sub { 1 } );
has idRule => ( is => 'rw', default => sub { 1 } );
sub hAttr {
$_[0]->{conf}->{impersonationHiddenAttributes} . ' '
. $_[0]->{conf}->{hiddenAttributes};
}
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) = @_;
my $hd = $self->p->HANDLER;
$self->addAuthRoute( impersonate => 'run', ['POST'] );
$self->addAuthRoute( impersonate => 'display', ['GET'] );
# Parse activation rule
$self->logger->debug(
'AdminImpersonation rule -> ' . $self->conf->{adminImpersonationRule} );
my $rule =
$hd->buildSub( $hd->substitute( $self->conf->{adminImpersonationRule} ) );
unless ($rule) {
$self->error(
'Bad adminImpersonation rule -> ' . $hd->tsv->{jail}->error );
return 0;
}
$self->rule($rule);
# Parse identity rule
$self->logger->debug( "Impersonation identity rule -> "
. $self->conf->{impersonationIdRule} );
$rule =
$hd->buildSub( $hd->substitute( $self->conf->{impersonationIdRule} ) );
unless ($rule) {
$self->error(
"Bad impersonation identity rule -> " . $hd->tsv->{jail}->error );
return 0;
}
$self->idRule($rule);
return 1;
}
# RUNNING METHOD
sub display {
my ( $self, $req ) = @_;
# Display form
my $params = {
PORTAL => $self->conf->{portal},
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
MSG => 'impersonate',
ALERTE => 'alert-danger',
LOGIN => '',
SPOOFID => $self->conf->{adminImpersonationRule},
TOKEN => (
$self->ottRule->( $req, {} )
? $self->ott->createToken()
: ''
)
};
return $self->p->sendHtml( $req, 'adminImpersonation', params => $params, );
}
sub run {
my ( $self, $req ) = @_;
my $statut = PE_OK;
my $spoofId = $req->param('spoofId') || ''; # Impersonation required ?
unless ($spoofId) {
$self->logger->debug("No impersonation required");
$req->mustRedirect(1);
return $self->p->do( $req, [ sub { PE_OK } ] );
}
unless ( $spoofId =~ /$self->{conf}->{userControl}/o ) {
$self->userLogger->error('Malformed spoofed Id');
$self->logger->debug(
"AdminImpersonation tried with spoofed Id: $spoofId");
$spoofId = $req->{user};
$statut = PE_MALFORMEDUSER;
}
# Check activation rule
if ($spoofId) {
$self->logger->debug("Spoof Id: $spoofId");
unless ( $self->rule->( $req, $req->sessionInfo ) ) {
$self->userLogger->error(
'adminImpersonation service not authorized');
$spoofId = '';
$statut = PE_IMPERSONATION_SERVICE_NOT_ALLOWED;
}
}
# Fill spoof session
my ( $realSession, $spoofSession ) = ( {}, {} );
$self->logger->debug("Rename real attributes...");
my $spk = '';
foreach my $k ( keys %{ $req->{userData} } ) {
if ( $self->{conf}->{impersonationSkipEmptyValues} ) {
next unless defined $req->{userData}->{$k};
}
$spk = "$self->{conf}->{impersonationPrefix}$k";
unless ( $self->hAttr =~ /\b$k\b/
|| $k =~ /^(?:_imp|token|_type)\w*\b/ )
{
$realSession->{$spk} = $req->{userData}->{$k};
$self->logger->debug("-> Store $k in realSession key: $spk");
}
$self->logger->debug("Delete $k");
delete $req->{userData}->{$k};
}
$spoofSession = $self->_userData( $req, $spoofId, $realSession );
if ( $req->error ) {
if ( $req->error == PE_BADCREDENTIALS ) {
$statut = PE_BADCREDENTIALS;
}
else {
return $req->error;
}
}
# Update spoof session
$self->logger->debug("Populating spoof session...");
foreach (qw (_auth _userDB _session_id)) {
$self->logger->debug("Processing $_...");
$spk = "$self->{conf}->{impersonationPrefix}$_";
$spoofSession->{$_} = $realSession->{$spk};
}
# Merging SSO Groups and hGroups & dedup
$spoofSession->{groups} ||= '';
$spoofSession->{hGroups} ||= {};
if ( $self->{conf}->{impersonationMergeSSOgroups} ) {
$self->userLogger->warn("MERGING SSO groups and hGroups...");
my $spg = "$self->{conf}->{impersonationPrefix}groups";
my $sphg = "$self->{conf}->{impersonationPrefix}hGroups";
my $separator = $self->{conf}->{multiValuesSeparator};
## GROUPS
my @spoofGrps = split /\Q$separator/, $spoofSession->{groups};
my @realGrps = split /\Q$separator/, $realSession->{$spg};
## hGROUPS
$realSession->{$sphg} ||= {};
# Merge specified groups/hGroups only
unless ( $self->{conf}->{impersonationMergeSSOgroups} eq 1 ) {
my %SSOgroups = map { $_, 1 } split /\Q$separator/,
$self->{conf}->{impersonationMergeSSOgroups};
$self->logger->debug("Filtering specified groups/hGroups...");
@realGrps = grep { exists $SSOgroups{$_} } @realGrps;
my %intersct =
map {
$realSession->{$sphg}->{$_}
? ( $_, $realSession->{$sphg}->{$_} )
: ()
} keys %SSOgroups;
$realSession->{$sphg} = \%intersct;
}
$self->logger->debug("Processing groups...");
@spoofGrps = ( @spoofGrps, @realGrps );
my %hash = map { $_, 1 } @spoofGrps;
$spoofSession->{groups} = join $separator, sort keys %hash;
$self->logger->debug("Processing hGroups...");
$spoofSession->{hGroups} =
{ %{ $spoofSession->{hGroups} }, %{ $realSession->{$sphg} } };
}
# Main session
$self->p->updateSession( $req, $spoofSession );
return $self->p->do( $req, [ sub { $statut } ] );
}
sub _userData {
my ( $self, $req, $spoofId, $realSession ) = @_;
my $realId = $req->{user};
$req->{user} = $spoofId;
my $raz = 0;
# Compute Macros and Groups with real and spoof sessions
$req->{sessionInfo} = {%$realSession};
# Search user in database
$req->steps( [
'getUser', 'setSessionInfo',
'setMacros', 'setGroups',
'setLocalGroups'
]
);
if ( my $error = $self->p->process($req) ) {
if ( $error == PE_BADCREDENTIALS ) {
$self->userLogger->warn(
'Impersonation requested for an unvalid user ('
. $req->{user}
. ")" );
}
$self->logger->debug("Process returned error: $error");
$req->error($error);
$raz = 1;
}
# Check identity rule if Impersonation required
if ( $realId ne $spoofId ) {
unless ( $self->idRule->( $req, $req->sessionInfo ) ) {
$self->userLogger->warn(
'Impersonation requested for an unvalid user ('
. $req->{user}
. ")" );
$self->logger->debug('Identity not authorized');
$raz = 1;
}
}
# Same real and spoof session - Compute Macros and Groups
if ($raz) {
$req->{sessionInfo} = {};
$req->{sessionInfo} = {%$realSession};
$req->{user} = $realId;
$req->steps( [
'getUser', 'setSessionInfo',
'setMacros', 'setGroups',
'setLocalGroups'
]
);
$self->logger->debug('Spoof session equal real session');
$req->error(PE_BADCREDENTIALS);
if ( my $error = $self->p->process($req) ) {
$self->logger->debug("Process returned error: $error");
$req->error($error);
}
}
return $req->{sessionInfo};
}
sub displayAdminImpersonation {
my ( $self, $req ) = @_;
return $self->rule->( $req, $req->userData )
|| $req->userData->{"$self->{conf}->{impersonationPrefix}_session_id"};
}
1;

View File

@ -0,0 +1,38 @@
<TMPL_INCLUDE NAME="header.tpl">
<div id="errorcontent" class="container">
<!--
<div class="message message-positive alert"><span trspan="<TMPL_VAR NAME="MSG">"></span></div>
-->
<div class="alert <TMPL_VAR NAME="ALERTE"> alert"><div class="text-center"><span trspan="<TMPL_VAR NAME="MSG">"></span></div></div>
<form id="adminImpersonation" action="/impersonate" method="post" class="password" role="form">
<div class="buttons">
<TMPL_IF NAME="TOKEN">
<input type="hidden" name="token" value="<TMPL_VAR NAME="TOKEN">" />
</TMPL_IF>
<TMPL_INCLUDE NAME="impersonation.tpl">
<button type="submit" class="btn btn-success">
<span class="fa fa-random"></span>
<span trspan="impersonate">Impersonate</span>
</button>
</div>
</form>
<div class="buttons">
<!--
<button type="submit" class="btn btn-success">
<span class="fa fa-sign-in"></span>
<span trspan="search">Search</span>
</button>
-->
<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>
</div>
</div>
<TMPL_INCLUDE NAME="footer.tpl">

View File

@ -67,6 +67,12 @@
<span trspan="sfaManager">sfaManager</span>
</a></li>
</TMPL_IF>
<TMPL_IF NAME="adminImpersonation">
<li class="dropdown-item"><a href="/impersonate" class="nav-link">
<img src="<TMPL_VAR NAME="STATIC_PREFIX">common/icons/sfa_manager.png" width="16" height="16" alt="refresh" />
<span trspan="adminImpersonate">adminImpersonate</span>
</a></li>
</TMPL_IF>
<li class="dropdown-item"><a href="/refresh" class="nav-link">
<img src="<TMPL_VAR NAME="STATIC_PREFIX">common/icons/arrow_refresh.png" width="16" height="16" alt="refresh" />
<span trspan="refreshrights">Refresh</span>