From 0ed8dbdde26f54fd0c93b0440dd99736b4eb3ca9 Mon Sep 17 00:00:00 2001 From: Christophe Maudoux Date: Wed, 12 Sep 2018 23:14:35 +0200 Subject: [PATCH] Fix logins history update - Failed and Granted Access with and without SFA (#1501) --- .../Lemonldap/NG/Portal/2F/Engines/Default.pm | 95 +++++----- .../lib/Lemonldap/NG/Portal/Main/Init.pm | 89 +++++---- .../lib/Lemonldap/NG/Portal/Main/Process.pm | 67 ++++--- .../lib/Lemonldap/NG/Portal/Main/Run.pm | 174 +++++++++--------- .../Lemonldap/NG/Portal/Main/SecondFactor.pm | 4 +- 5 files changed, 218 insertions(+), 211 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm index ef56382ea..0a3bf71e5 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm @@ -13,11 +13,11 @@ use strict; use Mouse; use JSON qw(from_json to_json); use Lemonldap::NG::Portal::Main::Constants qw( - PE_ERROR - PE_NOTOKEN - PE_OK - PE_SENDRESPONSE - PE_TOKENEXPIRED + PE_ERROR + PE_NOTOKEN + PE_OK + PE_SENDRESPONSE + PE_TOKENEXPIRED ); our $VERSION = '2.0.0'; @@ -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,8 +49,10 @@ sub init { for my $i ( 0 .. 1 ) { foreach ( split /,\s*/, - $self->conf->{ $i ? 'available2FSelfRegistration' : 'available2F' } - ) + $self->conf->{ $i + ? 'available2FSelfRegistration' + : 'available2F' } + ) { my $prefix = lc($_); $prefix =~ s/2f$//i; @@ -62,9 +64,10 @@ 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::$_" ) - or return 0; + my $m + = $self->p->loadPlugin( + $i ? "::2F::Register::$_" : "::2F::$_" ) + or return 0; # Rule and prefix may be modified by 2F module, reread them my $rule = $self->conf->{$ap}; @@ -74,13 +77,13 @@ sub init { $rule = $self->p->HANDLER->substitute($rule); unless ( $rule = $self->p->HANDLER->buildSub($rule) ) { $self->error( 'External 2F rule error: ' - . $self->p->HANDLER->tsv->{jail}->error ); + . $self->p->HANDLER->tsv->{jail}->error ); return 0; } # Store module push @{ $self->{ $i ? 'sfRModules' : 'sfModules' } }, - { p => $prefix, m => $m, r => $rule }; + { p => $prefix, m => $m, r => $rule }; } else { $self->logger->debug(' -> not enabled'); @@ -94,10 +97,10 @@ sub init { $self->p->HANDLER->substitute( $self->conf->{sfRequired} ) ) ) - ) + ) { $self->error( 'Error in sfRequired rule' - . $self->p->HANDLER->tsv->{jail}->error ); + . $self->p->HANDLER->tsv->{jail}->error ); return 0; } @@ -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; @@ -176,11 +179,12 @@ sub run { } $self->userLogger->info( 'Second factor required for ' - . $req->sessionInfo->{ $self->conf->{whatToTrace} } ); + . $req->sessionInfo->{ $self->conf->{whatToTrace} } ); # 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}; @@ -197,9 +201,10 @@ sub run { $req, '2fchoice', params => { - SKIN => $self->conf->{portalSkin}, - TOKEN => $token, - MODULES => [ map { { CODE => $_->prefix, LOGO => $_->logo } } @am ], + SKIN => $self->conf->{portalSkin}, + TOKEN => $token, + MODULES => + [ map { { CODE => $_->prefix, LOGO => $_->logo } } @am ], CHECKLOGINS => $checkLogins } ); @@ -225,15 +230,16 @@ 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 } ] ); + return $self->p->do( $req, [ sub {PE_NOTOKEN} ] ); } my $session; unless ( $session = $self->ott->getToken($token) ) { $self->userLogger->info('Token expired'); - return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] ); + return $self->p->do( $req, [ sub {PE_TOKENEXPIRED} ] ); } $req->sessionInfo($session); @@ -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" : '' ) ], + [] ]; } @@ -294,25 +300,26 @@ sub _displayRegister { 'Looking if ' . $m->{m}->prefix . '2F register is available' ); if ( $m->{r}->( $req, $req->userData ) ) { push @am, - { + { CODE => $m->{m}->prefix, URL => '/2fregisters/' . $m->{m}->prefix, LOGO => $m->{m}->logo, - }; + }; } } 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 } ); } - : undef; + my $_2fDevices = $req->userData->{_2fDevices} + ? eval { + from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } ); + } + : undef; unless ($_2fDevices) { $self->logger->debug("No 2F Device found"); @@ -358,11 +365,11 @@ sub register { $self->logger->debug(' -> OK'); my $name = $m->{m}->prefix; push @am, - { + { name => $name, logo => $m->{m}->logo, url => "/2fregisters/$name" - }; + }; } } return $self->p->sendJSONresponse( $req, \@am ); @@ -371,12 +378,12 @@ sub register { sub restoreSession { my ( $self, $req, @path ) = @_; my $token = $req->pdata->{sfRegToken} - or return [ 302, [ Location => $self->conf->{portal} ], [] ]; + or return [ 302, [ Location => $self->conf->{portal} ], [] ]; $req->userData( $self->ott->getToken( $token, 1 ) ); $req->data->{sfRegRequired} = 1; return $req->method eq 'POST' - ? $self->register( $req, @path ) - : $self->_displayRegister( $req, @path ); + ? $self->register( $req, @path ) + : $self->_displayRegister( $req, @path ); } 1; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm index eeba5ccfd..68db56789 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm @@ -84,9 +84,8 @@ sub init { my ( $self, $args ) = @_; $args ||= {}; $self->localConfig( - { - %{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} ) - ->getLocalConf('portal') + { %{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} ) + ->getLocalConf('portal') }, %$args } @@ -110,33 +109,33 @@ sub init { # Handle requests (other path may be declared in enabled plugins) $self - # "/" or undeclared paths - ->addUnauthRoute( '*' => 'login', ['GET'] ) - ->addUnauthRoute( '*' => 'postLogin', ['POST'] ) - ->addAuthRoute( '*' => 'authenticatedRequest', ['GET'] ) - ->addAuthRoute( '*' => 'postAuthenticatedRequest', ['POST'] ) + # "/" or undeclared paths + ->addUnauthRoute( '*' => 'login', ['GET'] ) + ->addUnauthRoute( '*' => 'postLogin', ['POST'] ) + ->addAuthRoute( '*' => 'authenticatedRequest', ['GET'] ) + ->addAuthRoute( '*' => 'postAuthenticatedRequest', ['POST'] ) - # psgi.js - ->addUnauthRoute( 'psgi.js' => 'sendJs', ['GET'] ) - ->addAuthRoute( 'psgi.js' => 'sendJs', ['GET'] ) + # psgi.js + ->addUnauthRoute( 'psgi.js' => 'sendJs', ['GET'] ) + ->addAuthRoute( 'psgi.js' => 'sendJs', ['GET'] ) - # portal.css - ->addUnauthRoute( 'portal.css' => 'sendCss', ['GET'] ) - ->addAuthRoute( 'portal.css' => 'sendCss', ['GET'] ) + # portal.css + ->addUnauthRoute( 'portal.css' => 'sendCss', ['GET'] ) + ->addAuthRoute( 'portal.css' => 'sendCss', ['GET'] ) - # lmerror - ->addUnauthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] ) - ->addAuthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] ) + # lmerror + ->addUnauthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] ) + ->addAuthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] ) - # Core REST API - ->addUnauthRoute( ping => 'pleaseAuth', ['GET'] ) - ->addAuthRoute( ping => 'authenticated', ['GET'] ) + # Core REST API + ->addUnauthRoute( ping => 'pleaseAuth', ['GET'] ) + ->addAuthRoute( ping => 'authenticated', ['GET'] ) - # Refresh session - ->addAuthRoute( refresh => 'refresh', ['GET'] ) + # Refresh session + ->addAuthRoute( refresh => 'refresh', ['GET'] ) - # Logout - ->addAuthRoute( logout => 'logout', ['GET'] ); + # Logout + ->addAuthRoute( logout => 'logout', ['GET'] ); # Default routes must point to routines declared above $self->defaultAuthRoute(''); @@ -151,7 +150,7 @@ sub reloadConf { %{ $self->{conf} } = %{ $self->localConfig }; # Reinitialize arrays - foreach ( qw(_macros _groups), @entryPoints) { + foreach ( qw(_macros _groups), @entryPoints ) { $self->{$_} = []; } $self->spRules( {} ); @@ -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 @@ -216,19 +215,19 @@ sub reloadConf { return $self->fail; } $mod = $self->conf->{$type} - unless ( $self->conf->{$type} eq 'Same' ); + unless ( $self->conf->{$type} eq 'Same' ); my $module = '::' . ucfirst($type) . '::' . $mod; $module =~ s/Authentication/Auth/; # Launch and initialize module return $self->fail - unless ( $self->{"_$type"} = $self->loadPlugin($module) ); + unless ( $self->{"_$type"} = $self->loadPlugin($module) ); } # 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,22 +281,22 @@ 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; } else { $self->logger->error( "$type $name returns an error: " - . HANDLER->tsv->{jail}->error ); + . HANDLER->tsv->{jail}->error ); } } } } - $self->{_jsRedirect} = - HANDLER->buildSub( HANDLER->substitute( $self->conf->{jsRedirect} ) ) - or $self->logger->error( + $self->{_jsRedirect} + = HANDLER->buildSub( + HANDLER->substitute( $self->conf->{jsRedirect} ) ) + or $self->logger->error( 'jsRedirect returns an error: ' . HANDLER->tsv->{jail}->error ); # Load plugins @@ -333,7 +332,7 @@ sub loadPlugin { } my $obj; return 0 - unless ( $obj = $self->loadModule("$plugin") ); + unless ( $obj = $self->loadModule("$plugin") ); return $self->findEP( $plugin, $obj ); } @@ -363,7 +362,7 @@ sub findEP { if ( $obj->can('spRules') ) { foreach my $k ( keys %{ $obj->{spRules} } ) { $self->logger->info( -"$k is defined more than one time, it can have some bad effect on Menu display" + "$k is defined more than one time, it can have some bad effect on Menu display" ) if ( $self->spRules->{$k} ); $self->spRules->{$k} = $obj->{spRules}->{$k}; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm index 33f720922..ea9f54e97 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm @@ -99,12 +99,11 @@ sub controlUrl { ); # XSS attack - if ( - $self->checkXSSAttack( + if ($self->checkXSSAttack( $req->param('logout') ? 'HTTP Referer' : 'urldc', $req->{urldc} ) - ) + ) { delete $req->{urldc}; return PE_BADURL; @@ -113,9 +112,9 @@ sub controlUrl { # Unprotected hosts if ( $tmp and !$self->isTrustedUrl($tmp) ) { $self->userLogger->error( - "URL contains a non protected host (param: " - . ( $req->param('logout') ? 'HTTP Referer' : 'urldc' ) - . " | value: $tmp)" ); + "URL contains a non protected host (param: " + . ( $req->param('logout') ? 'HTTP Referer' : 'urldc' ) + . " | value: $tmp)" ); delete $req->{urldc}; return PE_BADURL; } @@ -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,24 +178,25 @@ 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''; + my $iframe + = qq''; $req->info($iframe); } # 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,15 +327,15 @@ 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}; + if $self->conf->{timeoutActivity}; } # Store URL origin in session @@ -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 diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm index dca072079..15d022aa6 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm @@ -17,11 +17,11 @@ use strict; use URI::Escape; # List constants -sub authProcess { qw(extractFormInfo getUser authenticate) } +sub authProcess {qw(extractFormInfo getUser authenticate)} sub sessionData { qw(setAuthSessionInfo setSessionInfo setMacros setGroups setPersistentSessionInfo - setLocalGroups store secondFactor); + setLocalGroups store secondFactor); } sub validSession { @@ -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,21 +163,21 @@ sub refresh { if ($res) { $req->info( $self->loadTemplate( - 'simpleInfo', params => { trspan => 'rightsReloadNeedsLogout' } + 'simpleInfo', + params => { trspan => 'rightsReloadNeedsLogout' } ) ); $req->urldc( $self->conf->{portal} ); - return $self->do( $req, [ sub { PE_INFO } ] ); + return $self->do( $req, [ sub {PE_INFO} ] ); } - return $self->do( $req, [ sub { PE_OK } ] ); + return $self->do( $req, [ sub {PE_OK} ] ); } sub logout { my ( $self, $req ) = @_; return $self->do( $req, - [ - 'controlUrl', @{ $self->beforeLogout }, + [ 'controlUrl', @{ $self->beforeLogout }, 'authLogout', 'deleteSession' ] ); @@ -198,9 +194,9 @@ sub do { # Update status if ( my $p = $self->HANDLER->tsv->{statusPipe} ) { - $p->print( ( $req->user ? $req->user : $req->address ) . ' => ' - . $req->uri - . " $err\n" ); + $p->print(( $req->user ? $req->user : $req->address ) . ' => ' + . $req->uri + . " $err\n" ); } # Update history @@ -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 @@ -247,7 +240,7 @@ sub do { and $req->data->{redirectFormMethod} eq 'post' ) or $req->info ) - ) + ) { my ( $tpl, $prms ) = $self->display($req); $self->logger->debug("Calling sendHtml with template $tpl"); @@ -265,21 +258,20 @@ sub do { sub getModule { my ( $self, $req, $type ) = @_; - if ( - my $mod = { + if (my $mod = { auth => '_authentication', user => '_userDB', password => '_passwordDB' }->{$type} - ) + ) { if ( my $sub = $self->$mod->can('name') ) { return $sub->( $self->$mod, $req, $type ); } 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; } } @@ -296,7 +288,7 @@ sub autoRedirect { # Set redirection URL if needed $req->{urldc} ||= $self->conf->{portal} - if ( $req->mustRedirect and not( $req->info ) ); + if ( $req->mustRedirect and not( $req->info ) ); # Redirection should be made if urldc defined if ( $req->{urldc} ) { @@ -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,17 +350,16 @@ 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} ) + ) ) - ) { $self->logger->debug("Session $args{kind} $id expired"); return; @@ -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} }; + || $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; } @@ -702,7 +694,7 @@ sub cookie { $h{path} ||= '/'; $h{HttpOnly} //= $self->conf->{httpOnly}; $h{max_age} //= $self->conf->{cookieExpiration} - if ( $self->conf->{cookieExpiration} ); + if ( $self->conf->{cookieExpiration} ); foreach (qw(domain path expires max_age HttpOnly)) { my $f = $_; $f =~ s/_/-/g; @@ -725,8 +717,8 @@ sub sendHtml { my ( $self, $req, $template, %args ) = @_; my $res = $self->SUPER::sendHtml( $req, $template, %args ); push @{ $res->[1] }, - 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff'; + 'X-XSS-Protection' => '1; mode=block', + 'X-Content-Type-Options' => 'nosniff'; # Set authorized URL for POST my $csp = $self->csp . "form-action 'self'"; @@ -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 .= ';'; @@ -761,7 +754,7 @@ sub sendHtml { my @url; if ( $req->info ) { @url = map { s#https?://([^/]+).*#$1#; $_ } - ( $req->info =~ /info =~ /staticPrefix - . '/common/backgrounds/' - . $self->conf->{portalSkinBackground} - . '") no-repeat center fixed;' - . 'background-size:cover;}'; + my $s + = 'html,body{background:url("' + . $self->staticPrefix + . '/common/backgrounds/' + . $self->conf->{portalSkinBackground} + . '") no-repeat center fixed;' + . 'background-size:cover;}'; return [ 200, - [ - 'Content-Type' => 'text/css', + [ 'Content-Type' => 'text/css', 'Content-Length' => length($s), 'Cache-Control' => 'public,max-age=3600', ], @@ -807,16 +799,16 @@ sub lmError { # Error code $templateParams{"ERROR$_"} = ( $httpError == $_ ? 1 : 0 ) - foreach ( 403, 404, 500, 502, 503 ); + foreach ( 403, 404, 500, 502, 503 ); return $self->sendHtml( $req, 'error', params => \%templateParams ); } sub rebuildCookies { my ( $self, $req ) = @_; my @tmp; - for ( my $i = 0 ; $i < @{ $req->{respHeaders} } ; $i += 2 ) { + for ( my $i = 0; $i < @{ $req->{respHeaders} }; $i += 2 ) { push @tmp, $req->respHeaders->[0], $req->respHeaders->[1] - unless ( $req->respHeaders->[0] eq 'Set-Cookie' ); + unless ( $req->respHeaders->[0] eq 'Set-Cookie' ); } $req->{respHeaders} = \@tmp; $self->buildCookie($req); @@ -839,7 +831,7 @@ sub tplParams { sub registerLogin { my ( $self, $req ) = @_; return - unless ( $self->conf->{loginHistoryEnabled} + unless ( $self->conf->{loginHistoryEnabled} and defined $req->authResult ); my $history = $req->sessionInfo->{_loginHistory} ||= {}; my $type = ( $req->authResult > 0 ? 'failed' : 'success' ) . 'Login'; @@ -849,14 +841,15 @@ sub registerLogin { # Gather current login's parameters my $login = $self->_sumUpSession( $req->{sessionInfo}, 1 ); $login->{error} = $self->error( $req->authResult ) - if ( $req->authResult ); + if ( $req->authResult ); # Add current login into history unshift @{ $history->{$type} }, $login; # 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,12 +860,12 @@ sub registerLogin { # @return hashref sub _sumUpSession { my ( $self, $session, $withoutUser ) = @_; - my $res = - $withoutUser - ? {} - : { user => $session->{ $self->conf->{whatToTrace} } }; + my $res + = $withoutUser + ? {} + : { user => $session->{ $self->conf->{whatToTrace} } }; $res->{$_} = $session->{$_} - foreach ( "_utime", "ipAddr", + foreach ( "_utime", "ipAddr", keys %{ $self->conf->{sessionDataToRemember} } ); return $res; } @@ -881,11 +874,12 @@ sub _sumUpSession { sub loadTemplate { my ( $self, $name, %prm ) = @_; $name .= '.tpl'; - my $file = - $self->conf->{templateDir} . '/' - . $self->conf->{portalSkin} . '/' - . $name; - $file = $self->conf->{templateDir} . '/common/' . $name unless ( -e $file ); + my $file + = $self->conf->{templateDir} . '/' + . $self->conf->{portalSkin} . '/' + . $name; + $file = $self->conf->{templateDir} . '/common/' . $name + unless ( -e $file ); unless ( -e $file ) { die "Unable to find $name in $self->conf->{templateDir}"; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm index c7fe022a0..f70c36ae0 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm @@ -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 } ); }