Fix logins history update - Failed and Granted Access with and without SFA (#1501)

This commit is contained in:
Christophe Maudoux 2018-09-12 23:14:35 +02:00
parent 1791747281
commit 0ed8dbdde2
5 changed files with 218 additions and 211 deletions

View File

@ -35,8 +35,8 @@ has sfReq => ( is => 'rw' );
has ott => (
is => 'rw',
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
my $ott = $_[0]->{p}
->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} );
return $ott;
}
@ -49,7 +49,9 @@ sub init {
for my $i ( 0 .. 1 ) {
foreach (
split /,\s*/,
$self->conf->{ $i ? 'available2FSelfRegistration' : 'available2F' }
$self->conf->{ $i
? 'available2FSelfRegistration'
: 'available2F' }
)
{
my $prefix = lc($_);
@ -62,8 +64,9 @@ sub init {
# Unless $rule, skip loading
if ( $self->conf->{$ap} ) {
$self->logger->debug("Trying to load $_ 2F");
my $m =
$self->p->loadPlugin( $i ? "::2F::Register::$_" : "::2F::$_" )
my $m
= $self->p->loadPlugin(
$i ? "::2F::Register::$_" : "::2F::$_" )
or return 0;
# Rule and prefix may be modified by 2F module, reread them
@ -158,14 +161,14 @@ sub run {
if ( $self->sfReq->( $req, $req->sessionInfo ) ) {
$self->logger->debug("2F is required...");
$self->logger->debug(" -> Register 2F");
$req->pdata->{sfRegToken} =
$self->ott->createToken( $req->sessionInfo );
$req->pdata->{sfRegToken}
= $self->ott->createToken( $req->sessionInfo );
$self->logger->debug("Just one 2F is enabled");
$self->logger->debug(" -> Redirect to /2fregisters/");
$req->response(
[
302,
[ Location => $self->conf->{portal} . '/2fregisters/' ], []
[ 302,
[ Location => $self->conf->{portal} . '/2fregisters/' ],
[]
]
);
return PE_SENDRESPONSE;
@ -181,6 +184,7 @@ sub run {
# Store user data in a token
$req->sessionInfo->{_2fRealSession} = $req->id;
$req->sessionInfo->{_2fUrldc} = $req->urldc;
$req->sessionInfo->{_2fUtime} = $req->{sessionInfo}->{_utime};
my $token = $self->ott->createToken( $req->sessionInfo );
delete $req->{authResult};
@ -199,7 +203,8 @@ sub run {
params => {
SKIN => $self->conf->{portalSkin},
TOKEN => $token,
MODULES => [ map { { CODE => $_->prefix, LOGO => $_->logo } } @am ],
MODULES =>
[ map { { CODE => $_->prefix, LOGO => $_->logo } } @am ],
CHECKLOGINS => $checkLogins
}
);
@ -225,7 +230,8 @@ sub _choice {
# Restore session
unless ( $token = $req->param('token') ) {
$self->userLogger->error( $self->prefix . ' 2F access without token' );
$self->userLogger->error(
$self->prefix . ' 2F access without token' );
$req->mustRedirect(1);
return $self->p->do( $req, [ sub {PE_NOTOKEN} ] );
}
@ -248,8 +254,7 @@ sub _choice {
$req->authResult($res);
return $self->p->do(
$req,
[
sub { $res }, 'controlUrl',
[ sub {$res}, 'controlUrl',
'buildCookie', @{ $self->p->endAuth },
]
);
@ -264,7 +269,8 @@ sub _redirect {
my $arg = $req->env->{QUERY_STRING};
$self->logger->debug('Call sfEngine _redirect method');
return [
302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], []
302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ],
[]
];
}
@ -302,16 +308,17 @@ sub _displayRegister {
}
}
if ( @am == 1
and not( $req->userData->{_2fDevices} or $req->data->{sfRegRequired} ) )
and
not( $req->userData->{_2fDevices} or $req->data->{sfRegRequired} ) )
{
return [ 302, [ Location => $self->conf->{portal} . $am[0]->{URL} ],
[] ];
}
my $_2fDevices =
$req->userData->{_2fDevices}
? eval { from_json( $req->userData->{_2fDevices},
{ allow_nonref => 1 } ); }
my $_2fDevices = $req->userData->{_2fDevices}
? eval {
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
}
: undef;
unless ($_2fDevices) {

View File

@ -84,8 +84,7 @@ sub init {
my ( $self, $args ) = @_;
$args ||= {};
$self->localConfig(
{
%{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} )
{ %{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} )
->getLocalConf('portal')
},
%$args
@ -170,8 +169,8 @@ sub reloadConf {
$self->csp($csp);
# Initialize templateDir
$self->{templateDir} =
$self->conf->{templateDir} . '/' . $self->conf->{portalSkin};
$self->{templateDir}
= $self->conf->{templateDir} . '/' . $self->conf->{portalSkin};
unless ( -d $self->{templateDir} ) {
$self->error("Template dir $self->{templateDir} doesn't exist");
return $self->fail;
@ -191,8 +190,8 @@ sub reloadConf {
# Initialize persistent session DB
unless ( $self->conf->{persistentStorage} ) {
$self->conf->{persistentStorage} = $self->conf->{globalStorage};
$self->conf->{persistentStorageOptions} =
$self->conf->{globalStorageOptions};
$self->conf->{persistentStorageOptions}
= $self->conf->{globalStorageOptions};
}
# Initialize cookie domain
@ -227,8 +226,8 @@ sub reloadConf {
# Load second-factor engine
return $self->fail
unless $self->{_sfEngine} =
$self->loadPlugin( $self->conf->{'sfEngine'} );
unless $self->{_sfEngine}
= $self->loadPlugin( $self->conf->{'sfEngine'} );
# Initialize trusted domain regexp
if ( $self->conf->{trustedDomains}
@ -251,8 +250,8 @@ sub reloadConf {
# - $domainlabel.$td
# $domainlabel is build looking RFC2396
# (see Regexp::Common::URI::RFC2396)
$_ =~
s/\*\\\./(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9]\\.)*/g;
$_
=~ s/\*\\\./(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9]\\.)*/g;
$re->add("$_");
}
}
@ -263,8 +262,8 @@ sub reloadConf {
$self->logger->debug("Vhost $vhost added in trusted domains");
$re->add( quotemeta($vhost) );
$self->conf->{vhostOptions} ||= {};
if ( my $tmp =
$self->conf->{vhostOptions}->{$vhost}->{vhostAliases} )
if ( my $tmp
= $self->conf->{vhostOptions}->{$vhost}->{vhostAliases} )
{
foreach my $alias ( split /\s+/, $tmp ) {
$self->logger->debug(
@ -282,8 +281,7 @@ sub reloadConf {
$self->{"_$type"} = {};
if ( $self->conf->{$type} ) {
for my $name ( sort keys %{ $self->conf->{$type} } ) {
my $sub =
HANDLER->buildSub(
my $sub = HANDLER->buildSub(
HANDLER->substitute( $self->conf->{$type}->{$name} ) );
if ($sub) {
$self->{"_$type"}->{$name} = $sub;
@ -295,8 +293,9 @@ sub reloadConf {
}
}
}
$self->{_jsRedirect} =
HANDLER->buildSub( HANDLER->substitute( $self->conf->{jsRedirect} ) )
$self->{_jsRedirect}
= HANDLER->buildSub(
HANDLER->substitute( $self->conf->{jsRedirect} ) )
or $self->logger->error(
'jsRedirect returns an error: ' . HANDLER->tsv->{jail}->error );

View File

@ -99,8 +99,7 @@ sub controlUrl {
);
# XSS attack
if (
$self->checkXSSAttack(
if ($self->checkXSSAttack(
$req->param('logout') ? 'HTTP Referer' : 'urldc',
$req->{urldc}
)
@ -166,7 +165,8 @@ sub deleteSession {
# TODO
# Collect logout services and build hidden iFrames
if ( $req->data->{logoutServices} and %{ $req->data->{logoutServices} } ) {
if ( $req->data->{logoutServices} and %{ $req->data->{logoutServices} } )
{
$self->logger->debug("Create iFrames to forward logout to services");
@ -178,14 +178,14 @@ sub deleteSession {
foreach ( keys %{ $req->data->{logoutServices} } ) {
my $logoutServiceName = $_;
my $logoutServiceUrl =
$req->data->{logoutServices}->{$logoutServiceName};
my $logoutServiceUrl
= $req->data->{logoutServices}->{$logoutServiceName};
$self->logger->debug(
"Find logout service $logoutServiceName ($logoutServiceUrl)");
my $iframe =
qq'<iframe src="$logoutServiceUrl" alt="$logoutServiceName"'
my $iframe
= qq'<iframe src="$logoutServiceUrl" alt="$logoutServiceName"'
. ' marginwidth="0" marginheight="0" scrolling="no"'
. ' class="hiddenFrame" width="0" height="0"'
. ' frameborder="0"></iframe>';
@ -195,7 +195,8 @@ sub deleteSession {
# Redirect on logout page if no other target defined
if ( !$req->urldc and !$req->postUrl ) {
$self->logger->debug('No other target defined, redirect on logout');
$self->logger->debug(
'No other target defined, redirect on logout');
$req->urldc( $req->script_name . "?logout=1" );
}
}
@ -283,10 +284,15 @@ sub getUser {
sub authenticate {
my ( $self, $req ) = @_;
my $ret = $req->authResult( $self->_authentication->authenticate($req) );
$self->logger->debug(" -> authResult = $ret");
if ( $ret == PE_OK ) {
$req->{sessionInfo}->{_lastAuthnUTime} = time();
return $ret;
}
$self->setSessionInfo($req);
$self->setPersistentSessionInfo($req);
$self->setMacros($req);
$self->storeHistory($req);
return $ret;
}
@ -300,7 +306,8 @@ sub setAuthSessionInfo {
if ( $ret == PE_OK
and not( defined $req->sessionInfo->{authenticationLevel} ) )
{
$self->logger->error('Authentication level is not set by auth module');
$self->logger->error(
'Authentication level is not set by auth module');
}
return $ret;
}
@ -320,13 +327,13 @@ sub setSessionInfo {
# Date and time
if ( $self->conf->{updateSession} ) {
$req->{sessionInfo}->{_updateTime} =
strftime( "%Y%m%d%H%M%S", localtime() );
$req->{sessionInfo}->{_updateTime}
= strftime( "%Y%m%d%H%M%S", localtime() );
}
else {
$req->{sessionInfo}->{_utime} ||= time();
$req->{sessionInfo}->{_startTime} =
strftime( "%Y%m%d%H%M%S", localtime() );
$req->{sessionInfo}->{_startTime}
= strftime( "%Y%m%d%H%M%S", localtime() );
$req->{sessionInfo}->{_lastSeen} = time()
if $self->conf->{timeoutActivity};
}
@ -346,8 +353,8 @@ sub setSessionInfo {
sub setMacros {
my ( $self, $req ) = @_;
foreach ( sort keys %{ $self->_macros } ) {
$req->{sessionInfo}->{$_} =
$self->_macros->{$_}->( $req, $req->sessionInfo );
$req->{sessionInfo}->{$_}
= $self->_macros->{$_}->( $req, $req->sessionInfo );
}
PE_OK;
}
@ -387,16 +394,16 @@ sub setLocalGroups {
my ( $self, $req ) = @_;
foreach ( sort keys %{ $self->_groups } ) {
if ( $self->_groups->{$_}->( $req, $req->sessionInfo ) ) {
$req->{sessionInfo}->{groups} .=
$self->conf->{multiValuesSeparator} . $_;
$req->{sessionInfo}->{groups}
.= $self->conf->{multiValuesSeparator} . $_;
$req->{sessionInfo}->{hGroups}->{$_}->{name} = $_;
}
}
# Clear values separator at the beginning
if ( $req->{sessionInfo}->{groups} ) {
$req->{sessionInfo}->{groups} =~
s/^$self->{conf}->{multiValuesSeparator}//o;
$req->{sessionInfo}->{groups}
=~ s/^$self->{conf}->{multiValuesSeparator}//o;
}
PE_OK;
}
@ -420,8 +427,8 @@ sub store {
# Compute unsecure cookie value if needed
if ( $self->conf->{securedCookie} == 3 ) {
$req->{sessionInfo}->{_httpSession} =
$self->conf->{cipher}->encryptHex( $req->{id}, "http" );
$req->{sessionInfo}->{_httpSession}
= $self->conf->{cipher}->encryptHex( $req->{id}, "http" );
}
# Fill session

View File

@ -56,10 +56,11 @@ sub handler {
if ( $sp or %{ $req->pdata } ) {
my %v = (
name => $self->conf->{cookieName} . 'pdata',
(
%{ $req->pdata }
( %{ $req->pdata }
? ( value => uri_escape( JSON::to_json( $req->pdata ) ) )
: ( value => '', expires => 'Wed, 21 Oct 2015 00:00:00 GMT' )
: ( value => '',
expires => 'Wed, 21 Oct 2015 00:00:00 GMT'
)
)
);
push @{ $res->[1] }, 'Set-Cookie', $self->cookie(%v);
@ -91,8 +92,7 @@ sub login {
my ( $self, $req ) = @_;
return $self->do(
$req,
[
'controlUrl', @{ $self->beforeAuth },
[ 'controlUrl', @{ $self->beforeAuth },
$self->authProcess, @{ $self->betweenAuthAndData },
$self->sessionData, @{ $self->afterData },
$self->validSession, @{ $self->endAuth },
@ -104,8 +104,7 @@ sub postLogin {
my ( $self, $req ) = @_;
return $self->do(
$req,
[
'restoreArgs', 'controlUrl',
[ 'restoreArgs', 'controlUrl',
@{ $self->beforeAuth }, $self->authProcess,
@{ $self->betweenAuthAndData }, $self->sessionData,
@{ $self->afterData }, $self->validSession,
@ -118,8 +117,7 @@ sub authenticatedRequest {
my ( $self, $req ) = @_;
return $self->do(
$req,
[
'importHandlerData', 'controlUrl',
[ 'importHandlerData', 'controlUrl',
'checkLogout', @{ $self->forAuthUser }
]
);
@ -129,8 +127,7 @@ sub postAuthenticatedRequest {
my ( $self, $req ) = @_;
return $self->do(
$req,
[
'importHandlerData', 'restoreArgs',
[ 'importHandlerData', 'restoreArgs',
'controlUrl', 'checkLogout',
@{ $self->forAuthUser }
]
@ -148,8 +145,7 @@ sub refresh {
delete $data{$_} unless ( /^_/ or /^(?:startTime)$/ );
}
$req->steps(
[
'getUser',
[ 'getUser',
@{ $self->betweenAuthAndData },
'setAuthSessionInfo',
'setSessionInfo',
@ -167,7 +163,8 @@ sub refresh {
if ($res) {
$req->info(
$self->loadTemplate(
'simpleInfo', params => { trspan => 'rightsReloadNeedsLogout' }
'simpleInfo',
params => { trspan => 'rightsReloadNeedsLogout' }
)
);
$req->urldc( $self->conf->{portal} );
@ -180,8 +177,7 @@ sub logout {
my ( $self, $req ) = @_;
return $self->do(
$req,
[
'controlUrl', @{ $self->beforeLogout },
[ 'controlUrl', @{ $self->beforeLogout },
'authLogout', 'deleteSession'
]
);
@ -212,8 +208,7 @@ sub do {
if ( ( $err > 0 and !$req->id ) or $err eq PE_SESSIONNOTGRANTED ) {
return [
401,
[
'WWW-Authenticate' => "SSO " . $self->conf->{portal},
[ 'WWW-Authenticate' => "SSO " . $self->conf->{portal},
'Access-Control-Allow-Origin' => '*'
],
[qq'{"result":0,"error":$err}']
@ -229,16 +224,14 @@ sub do {
else {
return $self->sendJSONresponse(
$req,
{
result => 1,
{ result => 1,
code => $err
}
);
}
}
else {
if (
$err
if ( $err
and $err != PE_LOGOUT_OK
and (
$err != PE_REDIRECT
@ -265,8 +258,7 @@ sub do {
sub getModule {
my ( $self, $req, $type ) = @_;
if (
my $mod = {
if (my $mod = {
auth => '_authentication',
user => '_userDB',
password => '_passwordDB'
@ -278,8 +270,8 @@ sub getModule {
}
else {
my $s = ref( $self->$mod );
$s =~
s/^Lemonldap::NG::Portal::(?:(?:Issuer|UserDB|Auth|Password)::)?//;
$s
=~ s/^Lemonldap::NG::Portal::(?:(?:Issuer|UserDB|Auth|Password)::)?//;
return $s;
}
}
@ -306,8 +298,9 @@ sub autoRedirect {
$req->data->{redirectFormMethod} = "get";
}
else {
return [ 302,
[ Location => $req->{urldc}, @{ $req->respHeaders } ], [] ];
return [
302, [ Location => $req->{urldc}, @{ $req->respHeaders } ], []
];
}
}
my ( $tpl, $prms ) = $self->display($req);
@ -329,8 +322,7 @@ sub getApacheSession {
}
my $as = Lemonldap::NG::Common::Session->new(
{
storageModule => $self->conf->{globalStorage},
{ storageModule => $self->conf->{globalStorage},
storageModuleOptions => $self->conf->{globalStorageOptions},
cacheModule => $self->conf->{localSessionStorage},
cacheModuleOptions => $self->conf->{localSessionStorageOptions},
@ -344,8 +336,7 @@ sub getApacheSession {
if ( my $err = $as->error ) {
$self->lmLog(
$err,
(
$err =~ /(?:Object does not exist|Invalid session ID)/
( $err =~ /(?:Object does not exist|Invalid session ID)/
? 'notice'
: 'error'
)
@ -359,15 +350,14 @@ sub getApacheSession {
}
my $now = time;
if (
$id
if ( $id
and defined $as->data->{_utime}
and (
$now - $as->data->{_utime} > $self->conf->{timeout}
or ( $self->conf->{timeoutActivity}
and $as->data->{_lastSeen}
and $now - $as->data->{_lastSeen} >
$self->conf->{timeoutActivity} )
and $now - $as->data->{_lastSeen}
> $self->conf->{timeoutActivity} )
)
)
{
@ -392,8 +382,7 @@ sub getPersistentSession {
$info->{_session_uid} = $uid;
my $ps = Lemonldap::NG::Common::Session->new(
{
storageModule => $self->conf->{persistentStorage},
{ storageModule => $self->conf->{persistentStorage},
storageModuleOptions => $self->conf->{persistentStorageOptions},
id => $pid,
force => 1,
@ -433,11 +422,12 @@ sub updatePersistentSession {
# Return if no infos to update
return () unless ( ref $infos eq 'HASH' and %$infos );
$uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} }
|| $req->userData->{ $self->conf->{whatToTrace} };
$self->logger->debug("Found 'whatToTrace' -> $uid");
unless ($uid) {
$self->logger->debug('No uid found, skipping updatePersistentSession');
$self->logger->debug(
'No uid found, skipping updatePersistentSession');
return ();
}
$self->logger->debug("Update $uid persistent session");
@ -479,14 +469,14 @@ sub updateSession {
foreach ( keys %$infos ) {
$self->logger->debug(
"Update sessionInfo $_ with " . $infos->{$_} );
$req->{sessionInfo}->{$_} = $self->HANDLER->data->{$_} =
$infos->{$_};
$req->{sessionInfo}->{$_} = $self->HANDLER->data->{$_}
= $infos->{$_};
}
# Update session in global storage with _updateTime
$infos->{_updateTime} = strftime( "%Y%m%d%H%M%S", localtime() );
if ( my $apacheSession =
$self->getApacheSession( $id, info => $infos ) )
if ( my $apacheSession
= $self->getApacheSession( $id, info => $infos ) )
{
if ( $apacheSession->error ) {
$self->logger->error("Cannot update session $id");
@ -569,8 +559,10 @@ sub isTrustedUrl {
sub stamp {
my $self = shift;
my $res =
$self->conf->{cipher} ? $self->conf->{cipher}->encrypt( time() ) : 1;
my $res
= $self->conf->{cipher}
? $self->conf->{cipher}->encrypt( time() )
: 1;
$res =~ s/\+/%2B/g;
return $res;
}
@ -740,13 +732,14 @@ sub sendHtml {
if ( defined $url ) {
$self->logger->debug("Required Params URL : $url");
if ( $url =~ s#(https?://[^/]+).*#$1# ) {
$self->logger->debug("Set CSP form-action with Params URL : $url");
$self->logger->debug(
"Set CSP form-action with Params URL : $url");
$csp .= " $url";
}
}
if ( defined $req->{cspFormAction} ) {
$self->logger->debug(
"Set CSP form-action with request URL: " . $req->{cspFormAction} );
$self->logger->debug( "Set CSP form-action with request URL: "
. $req->{cspFormAction} );
$csp .= " " . $req->{cspFormAction};
}
$csp .= ';';
@ -775,8 +768,8 @@ sub sendHtml {
sub sendCss {
my ( $self, $req ) = @_;
my $s =
'html,body{background:url("'
my $s
= 'html,body{background:url("'
. $self->staticPrefix
. '/common/backgrounds/'
. $self->conf->{portalSkinBackground}
@ -784,8 +777,7 @@ sub sendCss {
. 'background-size:cover;}';
return [
200,
[
'Content-Type' => 'text/css',
[ 'Content-Type' => 'text/css',
'Content-Length' => length($s),
'Cache-Control' => 'public,max-age=3600',
],
@ -856,7 +848,8 @@ sub registerLogin {
# Forget oldest logins
splice @{ $history->{$type} }, $self->conf->{ $type . "Number" }
if ( scalar @{ $history->{$type} } > $self->conf->{ $type . "Number" } );
if (
scalar @{ $history->{$type} } > $self->conf->{ $type . "Number" } );
# Save into persistent session
$self->updatePersistentSession( $req, { _loginHistory => $history, } );
@ -867,8 +860,8 @@ sub registerLogin {
# @return hashref
sub _sumUpSession {
my ( $self, $session, $withoutUser ) = @_;
my $res =
$withoutUser
my $res
= $withoutUser
? {}
: { user => $session->{ $self->conf->{whatToTrace} } };
$res->{$_} = $session->{$_}
@ -881,11 +874,12 @@ sub _sumUpSession {
sub loadTemplate {
my ( $self, $name, %prm ) = @_;
$name .= '.tpl';
my $file =
$self->conf->{templateDir} . '/'
my $file
= $self->conf->{templateDir} . '/'
. $self->conf->{portalSkin} . '/'
. $name;
$file = $self->conf->{templateDir} . '/common/' . $name unless ( -e $file );
$file = $self->conf->{templateDir} . '/common/' . $name
unless ( -e $file );
unless ( -e $file ) {
die "Unable to find $name in $self->conf->{templateDir}";
}

View File

@ -1,6 +1,5 @@
package Lemonldap::NG::Portal::Main::SecondFactor;
use Data::Dumper;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
@ -88,7 +87,6 @@ sub _verify {
$req->sessionInfo($session);
$req->id( delete $req->sessionInfo->{_2fRealSession} );
$req->urldc( delete $req->sessionInfo->{_2fUrldc} );
$self->logger->debug("req badcredentials -> " . Dumper($req));
$req->authResult(PE_BADCREDENTIALS);
return $self->p->do( $req,
[ $self->p->storeHistory($req), sub {$res} ] );
@ -98,11 +96,13 @@ sub _verify {
$req->sessionInfo($session);
$req->id( delete $req->sessionInfo->{_2fRealSession} );
$req->urldc( delete $req->sessionInfo->{_2fUrldc} );
$req->{sessionInfo}->{_utime} = delete $req->{sessionInfo}->{_2fUtime};
$self->p->rebuildCookies($req);
$req->mustRedirect(1);
$self->userLogger->notice( $self->prefix
. '2F verification for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
if ( my $l = $self->conf->{ $self->prefix . '2fAuthnLevel' } ) {
$self->p->updateSession( $req, { authenticationLevel => $l } );
}