diff --git a/TODO-2.0.md b/TODO-2.0.md index dbb39db9e..d70b4feea 100644 --- a/TODO-2.0.md +++ b/TODO-2.0.md @@ -2,16 +2,13 @@ * write REST method to create session with an id * Test ForceAuth * Calendar in notifications explorer -* login history * Test for Zero * replace SOAP by REST for notification creation * "mail" in UserDB/* * checkLogins in SAML * verify activeTimer on/off on screen -* Don't display "login" when connected * Add test for #173 -* lwpSslOpt # Combination diff --git a/lemonldap-ng-portal/MANIFEST b/lemonldap-ng-portal/MANIFEST index 5229ba0c4..c3d7c8d75 100644 --- a/lemonldap-ng-portal/MANIFEST +++ b/lemonldap-ng-portal/MANIFEST @@ -406,6 +406,7 @@ t/50-IssuerGet.t t/60-status.t t/61-grantSession.t t/62-singleSession.t +t/63-History.t t/90-translations.t t/99-pod.t t/lmConf-1.js diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Plugins.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Plugins.pm index f44f8f427..42fc3ccc5 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Plugins.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Plugins.pm @@ -21,7 +21,7 @@ our @pList = ( u2fActivation => '::Plugins::U2F', u2fSelfRegistration => '::Register::U2F', notification => '::Plugins::Notifications', - checkLogins => '::Plugins::History', + portalCheckLogins => '::Plugins::History', ); ##@method list enabledPlugins 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 1b30e8f5a..9cea41c55 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Process.pm @@ -141,6 +141,7 @@ sub authLogout { sub deleteSession { my ( $self, $req ) = @_; + $req->userData( {} ); my $apacheSession = $self->getApacheSession( $req->id ); my $id = $req->id; unless ($apacheSession) { @@ -267,7 +268,7 @@ sub getUser { sub authenticate { my ( $self, $req ) = @_; - return $self->_authentication->authenticate($req); + return $req->authResult( $self->_authentication->authenticate($req) ); } # Third block: Session data providing diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Request.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Request.pm index 038c697f5..3798a6ea3 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Request.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Request.pm @@ -12,6 +12,9 @@ extends 'Lemonldap::NG::Common::PSGI::Request'; # List of methods to call has steps => ( is => 'rw' ); +# Authentication result +has authResult => ( is => 'rw' ); + # Datas shared between methods has datas => ( is => 'rw' ); 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 9067abcfe..2eda9355c 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm @@ -152,12 +152,17 @@ sub do { $req->steps($steps); my $err = $req->error( $self->process($req) ); - # TODO: updateStatus + # Update status if ( my $p = $self->HANDLER->tsv->{statusPipe} ) { print $p ( $req->user ? $req->user : $req->address ) . ' => ' . $req->uri . " $err\n"; } + + # Update history + if ( $self->conf->{loginHistoryEnabled} ) { + $self->registerLogin($req); + } if ( $err == PE_SENDRESPONSE ) { return $req->response; } @@ -374,9 +379,6 @@ sub updatePersistentSession { # Return if no infos to update return () unless ( ref $infos eq 'HASH' and %$infos ); - # Update current session - $self->updateSession( $req, $infos, $id ); - $uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} } || $req->userData->{ $self->conf->{whatToTrace} }; unless ($uid) { @@ -385,6 +387,9 @@ sub updatePersistentSession { } $self->logger->debug("Update $uid persistent session"); + # Update current session + $self->updateSession( $req, $infos, $id ); + my $persistentSession = $self->getPersistentSession($uid); $persistentSession->update($infos); @@ -719,4 +724,43 @@ sub tplParams { ); } +sub registerLogin { + my ( $self, $req ) = @_; + return unless ( defined $req->authResult ); + my $history = $req->sessionInfo->{loginHistory} ||= {}; + my $type = ( $req->authResult > 0 ? 'failed' : 'success' ) . 'Login'; + $history->{$type} ||= []; + $self->logger->debug("Current login saved into $type"); + + # Gather current login's parameters + my $login = $self->_sumUpSession( $req->{sessionInfo}, 1 ); + $login->{error} = $self->error( $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" } ); + + # Save into persistent session + $self->updatePersistentSession( $req, { loginHistory => $history, } ); +} + +# put main session data into a hash ref +# @param hashref $session The session to sum up +# @return hashref +sub _sumUpSession { + my ( $self, $session, $withoutUser ) = @_; + my $res = + $withoutUser + ? {} + : { user => $session->{ $self->conf->{whatToTrace} } }; + $res->{$_} = $session->{$_} + foreach ( "_utime", "ipAddr", + keys %{ $self->conf->{sessionDataToRemember} } ); + return $res; +} + 1; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/GrantSession.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/GrantSession.pm index 1685c43f9..a444fdd66 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/GrantSession.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/GrantSession.pm @@ -40,11 +40,11 @@ sub grantSession { foreach ( sort sortByComment keys %{ $self->rules } ) { $self->logger->debug("Grant session condition \"$_\""); unless ( $self->rules->{$_}->( $req->sessionInfo ) ) { - $req->userData({}); + $req->userData( {} ); $self->userLogger->error( 'User ' . $req->user . " was not granted to open session (rule $_)" ); - return PE_SESSIONNOTGRANTED; + return $req->authResult(PE_SESSIONNOTGRANTED); } } return PE_OK; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/History.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/History.pm index d64ab86dd..627eb9781 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/History.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/History.pm @@ -2,32 +2,41 @@ package Lemonldap::NG::Portal::Plugins::History; use strict; use Mouse; -use Lemonldap::NG::Portal::Main::Constants qw(PE_OK); +use Lemonldap::NG::Portal::Main::Constants qw(PE_INFO PE_OK); extends 'Lemonldap::NG::Portal::Main::Plugin', 'Lemonldap::NG::Portal::Lib::OtherSessions'; sub afterDatas { 'run' } +sub init { 1 } + sub run { my ( $self, $req ) = @_; - $req->info( - ( - $req->sessionInfo->{loginHistory}->{successLogin} - ? $self->mkSessionArray( - $req->sessionInfo->{loginHistory}->{successLogin}, - 'lastLogins', 0, 0 ) - : "" - ) - . ( - $req->sessionInfo->{loginHistory}->{failedLogin} - ? $self->mkSessionArray( - $req->sessionInfo->{loginHistory}->{failedLogin}, - 'lastFailedLogins', 0, 1 ) - : "" - ) - ); - PE_OK; + if ( $req->param('checkLogins') ) { + $self->logger->debug('History asked'); + $req->info( + ( + $req->sessionInfo->{loginHistory}->{successLogin} + ? $self->mkSessionArray( + $req->sessionInfo->{loginHistory}->{successLogin}, + 'lastLogins', 0, 0 ) + : "" + ) + . ( + $req->sessionInfo->{loginHistory}->{failedLogin} + ? $self->mkSessionArray( + $req->sessionInfo->{loginHistory}->{failedLogin}, + 'lastFailedLogins', 0, 1 ) + : "" + ) + ); + unless($req->info) { + $req->info('

'); + } + return PE_INFO; + } + return PE_OK; } 1; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SingleSession.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SingleSession.pm index 13805a7f1..dbc56dbe4 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SingleSession.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SingleSession.pm @@ -35,11 +35,11 @@ sub run { and $req->{sessionInfo}->{ipAddr} ne $session->data->{ipAddr} ) ) { - push @$deleted, $self->_sumUpSession( $session->data ); + push @$deleted, $self->p->_sumUpSession( $session->data ); $self->p->_deleteSession( $req, $session, 1 ); } else { - push @$otherSessions, $self->_sumUpSession( $session->data ); + push @$otherSessions, $self->p->_sumUpSession( $session->data ); } } if ( $self->conf->{singleUserByIP} ) { @@ -52,7 +52,7 @@ sub run { unless ( $req->{sessionInfo}->{ $self->conf->{whatToTrace} } eq $session->data->{ $self->conf->{whatToTrace} } ) { - push @$deleted, $self->_sumUpSession( $session->data ); + push @$deleted, $self->p->_sumUpSession( $session->data ); $self->p->_deleteSession( $req, $session, 1 ); } } @@ -66,21 +66,6 @@ sub run { PE_OK; } -# put main session data into a hash ref -# @param hashref $session The session to sum up -# @return hashref -sub _sumUpSession { - my ( $self, $session, $withoutUser ) = @_; - my $res = - $withoutUser - ? {} - : { user => $session->{ $self->conf->{whatToTrace} } }; - $res->{$_} = $session->{$_} - foreach ( "_utime", "ipAddr", - keys %{ $self->conf->{sessionDataToRemember} } ); - return $res; -} - # Build the removeOther link # Last part of URL is built trough javascript # @return removeOther link in HTML code diff --git a/lemonldap-ng-portal/t/63-History.t b/lemonldap-ng-portal/t/63-History.t new file mode 100644 index 000000000..a515edda2 --- /dev/null +++ b/lemonldap-ng-portal/t/63-History.t @@ -0,0 +1,64 @@ +use Test::More; +use strict; +use IO::String; + +BEGIN { + require 't/test-lib.pm'; +} + +my $res; + +my $client = LLNG::Manager::Test->new( + { + ini => { + logLevel => 'error', + authentication => 'Demo', + userDB => 'Same', + loginHistoryEnabled => 1, + } + } +); + +# Case "no history" +ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho&checkLogins=1'), + length => 37, + accept => 'text/html', + ), + 'Auth query' +); +count(1); +expectOK($res); +my $id1 = expectCookie($res); +ok( $res->[2]->[0] =~ /trspan="noHistory"/, 'No history found' ); +count(1); + +ok( $res = $client->_get( '/', cookie => "lemonldap=$id1" ), + 'Verify connection' ); +count(1); +expectOK($res); + +$client->logout($id1); + +# History with 1 success +ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho&checkLogins=1'), + length => 37, + accept => 'text/html', + ), + 'Auth query' +); +count(1); +expectOK($res); +$id1 = expectCookie($res); + +ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' ); +count(1); + +clean_sessions(); + +done_testing( count() );