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 66a8f1345..f835b4352 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm @@ -18,11 +18,11 @@ use URI::Escape; use JSON; # 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 { @@ -57,9 +57,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 => '', + : ( + value => '', expires => 'Wed, 21 Oct 2015 00:00:00 GMT' ) ) @@ -93,7 +95,8 @@ 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 }, @@ -105,7 +108,8 @@ 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,7 +122,8 @@ sub authenticatedRequest { my ( $self, $req ) = @_; return $self->do( $req, - [ 'importHandlerData', 'controlUrl', + [ + 'importHandlerData', 'controlUrl', 'checkLogout', @{ $self->forAuthUser } ] ); @@ -128,7 +133,8 @@ sub postAuthenticatedRequest { my ( $self, $req ) = @_; return $self->do( $req, - [ 'importHandlerData', 'restoreArgs', + [ + 'importHandlerData', 'restoreArgs', 'controlUrl', 'checkLogout', @{ $self->forAuthUser } ] @@ -145,8 +151,8 @@ sub refresh { foreach ( keys %data ) { delete $data{$_} unless ( /^_/ or /^(?:startTime)$/ ); } - $req->steps( - [ 'getUser', + $req->steps( [ + 'getUser', @{ $self->betweenAuthAndData }, 'setAuthSessionInfo', 'setSessionInfo', @@ -164,21 +170,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' ] ); @@ -195,9 +201,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 @@ -223,14 +229,16 @@ 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 @@ -239,7 +247,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"); @@ -257,20 +265,21 @@ 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; } } @@ -287,7 +296,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} ) { @@ -297,9 +306,8 @@ 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); @@ -319,8 +327,8 @@ sub getApacheSession { $self->logger->debug("Try to get a new $args{kind} session"); } - my $as = Lemonldap::NG::Common::Session->new( - { storageModule => $self->conf->{globalStorage}, + my $as = Lemonldap::NG::Common::Session->new( { + storageModule => $self->conf->{globalStorage}, storageModuleOptions => $self->conf->{globalStorageOptions}, cacheModule => $self->conf->{localSessionStorage}, cacheModuleOptions => $self->conf->{localSessionStorageOptions}, @@ -334,7 +342,8 @@ 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' ) @@ -349,19 +358,21 @@ sub getApacheSession { $self->logger->debug("Get session $id from Portal::Main::Run") if ($id); $self->logger->debug( "Check session validity -> " . $self->conf->{timeoutActivity} . "s" ) - if ( $self->conf->{timeoutActivity} ); + if ( $self->conf->{timeoutActivity} ); my $now = time; - if ( $id + if ( + $id and defined $as->data->{_utime} and ( ( ( $now - $as->data->{_utime} ) > $self->conf->{timeout} ) - or ( $self->conf->{timeoutActivity} + 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; @@ -383,8 +394,8 @@ sub getPersistentSession { $info->{_session_uid} = $uid; - my $ps = Lemonldap::NG::Common::Session->new( - { storageModule => $self->conf->{persistentStorage}, + my $ps = Lemonldap::NG::Common::Session->new( { + storageModule => $self->conf->{persistentStorage}, storageModuleOptions => $self->conf->{persistentStorageOptions}, id => $pid, force => 1, @@ -425,11 +436,10 @@ 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"); @@ -471,14 +481,14 @@ sub updateSession { foreach ( keys %$infos ) { $self->logger->debug("Update sessionInfo $_"); $self->_dump( $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"); @@ -561,10 +571,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; } @@ -696,7 +706,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; @@ -719,8 +729,8 @@ sub sendHtml { my ( $self, $req, $template, %args ) = @_; $args{params}->{TROVER} = $self->trOver; - $args{templateDir} - = $self->conf->{templateDir} . '/' . $self->getSkin($req); + $args{templateDir} = + $self->conf->{templateDir} . '/' . $self->getSkin($req); my $tmpl = $args{templateDir} . "/$template.tpl"; my $troverJson = $args{templateDir} . "/$template.json"; unless ( -f $tmpl ) { @@ -733,7 +743,7 @@ sub sendHtml { if ( -r $troverJson ) { open my $tr_file, '<', $troverJson - or die "Can't open" . $troverJson . " : $!"; + or die "Can't open" . $troverJson . " : $!"; while (<$tr_file>) { chomp; $args{params}->{TROVERbyJSON} .= $_; @@ -752,11 +762,11 @@ sub sendHtml { } my $res = $self->SUPER::sendHtml( $req, $template, %args ); push @{ $res->[1] }, - 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff', - 'Cache-Control' => 'no-cache, no-store, must-revalidate', # HTTP 1.1 - 'Pragma' => 'no-cache', # HTTP 1.0 - 'Expires' => '0'; # Proxies + 'X-XSS-Protection' => '1; mode=block', + 'X-Content-Type-Options' => 'nosniff', + 'Cache-Control' => 'no-cache, no-store, must-revalidate', # HTTP 1.1 + 'Pragma' => 'no-cache', # HTTP 1.0 + 'Expires' => '0'; # Proxies # Set authorized URL for POST my $csp = $self->csp . "form-action " . $self->conf->{cspFormAction}; @@ -770,14 +780,13 @@ 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}; } @@ -803,7 +812,7 @@ sub sendHtml { my @url; if ( $req->info ) { @url = map { s#https?://([^/]+).*#$1#; $_ } - ( $req->info =~ /info =~ /conf->{portalSkinBackground} ) { - $s - .= 'html,body{background:url("' - . $self->staticPrefix - . '/common/backgrounds/' - . $self->conf->{portalSkinBackground} - . '") no-repeat center fixed;' - . 'background-size:cover;}'; + $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', ], @@ -853,16 +863,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); @@ -877,8 +887,8 @@ sub tplParams { $portalPath =~ s#[^/]+\.fcgi$##; for my $session_key ( keys %{ $req->{sessionInfo} } ) { - $templateParams{ "session_" . $session_key } - = $req->{sessionInfo}->{$session_key}; + $templateParams{ "session_" . $session_key } = + $req->{sessionInfo}->{$session_key}; } for my $env_key ( keys %{ $req->env } ) { @@ -899,8 +909,38 @@ sub tplParams { sub registerLogin { my ( $self, $req ) = @_; return - unless ( $self->conf->{loginHistoryEnabled} + unless ( $self->conf->{loginHistoryEnabled} and defined $req->authResult ); + + # Check old login history + if ( $req->sessionInfo->{loginHistory} ) { + + if ( !$req->sessionInfo->{_loginHistory} ) { + $self->logger->debug("Restore old login history"); + + # Restore success login + $req->sessionInfo->{_loginHistory}->{successLogin} = + $req->sessionInfo->{loginHistory}->{successLogin}; + + # Restore failed login, with generic error + if ( $req->sessionInfo->{loginHistory}->{failedLogin} ) { + $self->logger->debug("Restore old failed logins"); + $req->sessionInfo->{_loginHistory}->{failedLogin} = []; + foreach ( + @{ $req->sessionInfo->{loginHistory}->{failedLogin} } ) + { + $self->logger->debug( + "Replace old failed login error " . $_->{error} ); + $_->{error} = 5; + push @{ $req->sessionInfo->{_loginHistory}->{failedLogin} }, + $_; + } + } + } + $self->updatePersistentSession( $req, { 'loginHistory' => undef } ); + delete $req->sessionInfo->{loginHistory}; + } + my $history = $req->sessionInfo->{_loginHistory} ||= {}; my $type = ( $req->authResult > 0 ? 'failed' : 'success' ) . 'Login'; $history->{$type} ||= []; @@ -909,18 +949,17 @@ 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 ); $self->logger->debug( " Current login -> " . $login->{error} ) - if ( $login->{error} ); + if ( $login->{error} ); # 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, } ); @@ -933,12 +972,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; } @@ -947,12 +986,12 @@ sub _sumUpSession { sub loadTemplate { my ( $self, $name, %prm ) = @_; $name .= '.tpl'; - my $file - = $self->conf->{templateDir} . '/' - . $self->conf->{portalSkin} . '/' - . $name; + my $file = + $self->conf->{templateDir} . '/' + . $self->conf->{portalSkin} . '/' + . $name; $file = $self->conf->{templateDir} . '/common/' . $name - unless ( -e $file ); + unless ( -e $file ); unless ( -e $file ) { die "Unable to find $name in $self->conf->{templateDir}"; }