Other session plugins (#595)

This commit is contained in:
Xavier Guimard 2017-02-17 07:40:18 +00:00
parent 97b8b40cc5
commit 87bb55cb00
7 changed files with 270 additions and 20 deletions

View File

@ -0,0 +1,60 @@
package Lemonldap::NG::Portal::Lib::OtherSessions;
use strict;
use Mouse;
our $VERSION = '2.0.0';
has module =>
( is => 'rw', default => 'Lemonldap::NG::Common::Apache::Session' );
has moduleOpts => (
is => 'rw',
default => sub {
my %opts = %{ $_[0]->{conf}->{globalStorageOptions} || {} };
$opts{backend} = $_[0]->{conf}->{globalStorage};
return \%opts;
}
);
# Build an HTML array to display sessions
# @param $sessions Array ref of hash ref containing sessions datas
# @param $title Title of the array
# @param $displayUser To display "User" column
# @param $displaError To display "Error" column
# @return HTML string
sub mkSessionArray {
my ( $self, $sessions, $title, $displayUser, $displayError ) = @_;
return "" unless ( ref $sessions eq "ARRAY" and @$sessions );
my $tmp = $title ? qq'<h3 trspan="$title"></h3>' : "";
$tmp .= "<table class=\"info\"><tbody>";
$tmp .= "<tr>";
$tmp .= '<th trspan="user"></th>'
if ($displayUser);
$tmp .= '<th trspan="date"></th>';
$tmp .= '<th trspan="ipAddr"></th>';
$tmp .= "<th>" . $self->{sessionDataToRemember}->{$_} . "</th>"
foreach ( keys %{ $self->{sessionDataToRemember} } );
$tmp .= '<th trspan="errorMsg"></th>'
if ($displayError);
$tmp .= '</tr>';
foreach my $session (@$sessions) {
$tmp .= "<tr>";
$tmp .= "<td>$session->{user}</td>" if ($displayUser);
$tmp .=
"<td><script type=\"text/javascript\">var _date=new Date($session->{_utime}*1000);document.write(_date.toLocaleString());</script></td>";
$tmp .= "<td>$session->{ipAddr}</td>";
$tmp .= "<td>" . ( $session->{$_} || "" ) . "</td>"
foreach ( keys %{ $self->{sessionDataToRemember} } );
$tmp .= "<td>$session->{error}</td>" if ($displayError);
$tmp .= "</tr>";
}
$tmp .= '</tbody></table>';
return $tmp;
}
1;

View File

@ -21,6 +21,7 @@ our @pList = (
u2fActivation => '::Plugins::U2F',
u2fSelfRegistration => '::Register::U2F',
notification => '::Plugins::Notifications',
checkLogins => '::Plugins::History',
);
##@method list enabledPlugins
@ -28,11 +29,12 @@ our @pList = (
#@return list of enabled plugins
sub enabledPlugins {
my ($self) = @_;
my $conf = $self->conf;
my @res;
# Search for Issuer* modules enabled
foreach my $key (qw(SAML OpenID CAS OpenIDConnect Get)) {
if ( $self->conf->{"issuerDB${key}Activation"} ) {
if ( $conf->{"issuerDB${key}Activation"} ) {
$self->logger->debug("Issuer${key} enabled");
push @res, "::Issuer::$key";
}
@ -40,31 +42,37 @@ sub enabledPlugins {
# Load static plugin list
for ( my $i = 0 ; $i < @pList ; $i += 2 ) {
push @res, $pList[ $i + 1 ] if ( $self->conf->{ $pList[$i] } );
push @res, $pList[ $i + 1 ] if ( $conf->{ $pList[$i] } );
}
# Load single session
push @res, '::Plugins::SingleSession'
if ( $conf->{singleSession}
or $conf->{singleIP}
or $conf->{singleUserByIP}
or $conf->{notifyOther} );
# Check if SOAP is enabled
push @res, '::Plugins::SOAPServer'
if ( $self->conf->{soapSessionServer}
or $self->conf->{soapConfigServer} );
if ( $conf->{soapSessionServer}
or $conf->{soapConfigServer} );
# Add REST (check is done by it)
push @res, '::Plugins::RESTServer';
if ( my $p = $self->conf->{passwordDB} ) {
if ( my $p = $conf->{passwordDB} ) {
push @res, "::Password::$p" if ( $p ne 'Null' );
}
# Check if register is enabled
push @res, '::Plugins::Register'
if ( $self->conf->{registerDB} and $self->conf->{registerDB} ne 'Null' );
if ( $conf->{registerDB} and $conf->{registerDB} ne 'Null' );
# Check if custom plugins are required
# TODO: change this name
if ( $self->conf->{customPlugins} ) {
$self->logger->debug(
'Custom plugins: ' . $self->conf->{customPlugins} );
push @res, grep ( /\w/, split( /,\s*/, $self->conf->{customPlugins} ) );
if ( $conf->{customPlugins} ) {
$self->logger->debug( 'Custom plugins: ' . $conf->{customPlugins} );
push @res, grep ( /\w/, split( /,\s*/, $conf->{customPlugins} ) );
}
return @res;
}

View File

@ -0,0 +1,33 @@
package Lemonldap::NG::Portal::Plugins::History;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK);
extends 'Lemonldap::NG::Portal::Main::Plugin',
'Lemonldap::NG::Portal::Lib::OtherSessions';
sub afterDatas { 'run' }
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;
}
1;

View File

@ -0,0 +1,97 @@
package Lemonldap::NG::Portal::Plugins::SingleSession;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK);
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Plugin',
'Lemonldap::NG::Portal::Lib::OtherSessions';
sub afterDatas { 'run' }
sub init { 1 }
sub run {
my ( $self, $req ) = @_;
my $deleted = [];
my $otherSessions = [];
my $moduleOptions = $self->conf->{globalStorageOptions} || {};
$moduleOptions->{backend} = $self->conf->{globalStorage};
my $sessions = $self->module->searchOn(
$moduleOptions,
$self->conf->{whatToTrace},
$req->{sessionInfo}->{ $self->conf->{whatToTrace} }
);
foreach my $id ( keys %$sessions ) {
next if ( $req->id eq $id );
my $session = $self->p->getApacheSession($id) or next;
if (
$self->conf->{singleSession}
or ( $self->conf->{singleIP}
and $req->{sessionInfo}->{ipAddr} ne $session->data->{ipAddr} )
)
{
push @$deleted, $self->_sumUpSession( $session->data );
$self->p->_deleteSession( $req, $session, 1 );
}
else {
push @$otherSessions, $self->_sumUpSession( $session->data );
}
}
if ( $self->conf->{singleUserByIP} ) {
my $sessions =
$self->module->searchOn( $moduleOptions, 'ipAddr',
$req->sessionInfo->ipAddr );
foreach my $id ( keys %$sessions ) {
next if ( $req->id eq $id );
my $session = $self->p->getApacheSession($id) or next;
unless ( $req->{sessionInfo}->{ $self->conf->{whatToTrace} } eq
$session->data->{ $self->conf->{whatToTrace} } )
{
push @$deleted, $self->_sumUpSession( $session->data );
$self->p->_deleteSession( $req, $session, 1 );
}
}
}
$req->info( $self->mkSessionArray( $deleted, 'sessionsDeleted', 1 ) )
if ( $self->conf->{notifyDeleted} and @$deleted );
$req->info( $self->mkSessionArray( $otherSessions, 'otherSessions', 1 )
. $self->_mkRemoveOtherLink() )
if ( $self->conf->{notifyOther} and @$otherSessions );
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
sub _mkRemoveOtherLink {
my $self = shift;
my $link = $self->conf->{portal} . "?removeOther=1";
# TODO: remove this
return
qq'<p class="removeOther"><a href="$link" onclick="_go=0" trspan="removeOtherSessions></a></p>';
}
1;

View File

@ -83,17 +83,12 @@
"PE81":"Invalid authentication attempt",
"PE82":"Exceeded authentication timeout",
"PE83":"U2F verification failed",
"PM3":"The following sessions have been closed",
"PM4":"Other active sessions",
"PM5":"Remove other sessions",
"PM8":"Select your Identity Provider",
"PM10":"Remember my choice",
"PM11":"Logout from service providers...",
"PM12":"Redirection in progress...",
"PM13":"Go back to service provider",
"PM17":"Update Common Domain Cookie",
"PM22":"Your last logins",
"PM23":"Your last failed logins",
"accept":"Accept",
"accessDenied":"You have no access authorization for this application",
"accountCreated":"Your account has been created, your temporary password has been sent to your mail address.",
@ -162,6 +157,7 @@
"openidRpns":"Parameter %s requested for federation isn't available",
"openSessionSpace":"This space allow you to open a SSO session. This will help you to securely access to all applications authorized by your profil.",
"openSSOSession":"Open your SSO session",
"otherSessions":"Other active sessions",
"password": "Password",
"ppGrace": "authentications remaining, change your password!",
"pwdChanged":"Your password was changed.",
@ -178,6 +174,7 @@
"register": "Register",
"registerRequestAlreadyIssued":"A register request for this account was already issued on ",
"rememberChoice":"Remember my choice",
"removeOtherSessions":"Remove other sessions",
"requestIssuedFromIP":"The request was issued from IP",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
@ -186,6 +183,7 @@
"sendPwd":"Send me a new password",
"serverError":"Error occurs on the server",
"serviceProvidedBy":"Service provided by",
"sessionsDeleted":"The following sessions have been closed",
"SSOSessionInactive":"SSO session inactive",
"submit":"Submit",
"touchU2fDevice": "Please touch the flashing U2F device now.",

View File

@ -83,17 +83,12 @@
"PE81":"Tentative d'authentification invalide",
"PE82":"Délai d'authentification dépassé",
"PE83":"La vérification U2F a échoué",
"PM3":"Les sessions suivantes ont été fermées",
"PM4":"Autres sessions ouvertes",
"PM5":"Fermer les autres sessions",
"PM8":"Choisissez votre fournisseur d'identité",
"PM10":"Se souvenir de mon choix",
"PM11":"Déconnexion des services...",
"PM12":"Redirection en cours...",
"PM13":"Retourner sur le fournisseur de service",
"PM17":"Mise à jour du cookie de domaine commun",
"PM22":"Vos dernières connexions",
"PM23":"Vos dernières connexions refusées",
"accept":"Accepter",
"accessDenied":"Vous n'avez pas les droits d'accès à cette application",
"accountCreated":"Votre compte a été créé, un mot de passe temporaire a été envoyé à votre adresse mail.",
@ -162,6 +157,7 @@
"openidRpns":"Le paramètre %s exigé pour la fédération n'est pas disponible",
"openSessionSpace":"Cet espace vous permet d'ouvrir une session SSO. Celle-ci vous aidera à accéder de manière totalement sécurisée à l'ensemble des applications autorisées par votre profil utilisateur.",
"openSSOSession":"Ouvrir une session SSO",
"otherSessions":"Autres sessions ouvertes",
"password": "Mot-de-passe",
"ppGrace": "authentifications restantes, changez votre mot de passe !",
"pwdChange":"Changement de mot de passe",
@ -178,6 +174,7 @@
"register": "Enregistrer",
"registerRequestAlreadyIssued":"Une demande de création pour ce compte a déjà été faite le ",
"rememberChoice":"Se souvenir de mon choix",
"removeOtherSessions":"Fermer les autres sessions",
"requestIssuedFromIP":"La demande provient de l'IP",
"resendConfirmMail":"Renvoyer le mail de confirmation ?",
"resentConfirm":"Voulez-vous que le message de confirmation soit renvoyé ?",
@ -186,6 +183,7 @@
"sendPwd":"Envoyez-moi un nouveau mot de passe",
"serverError":"Une erreur est survenue sur le serveur",
"serviceProvidedBy":"Ce service est fourni par",
"sessionsDeleted":"Les sessions suivantes ont été fermées",
"SSOSessionInactive":"Session SSO inactive",
"submit":"Envoyer",
"touchU2fDevice": "Poser votre doigt sur le périphérique U2F",

View File

@ -0,0 +1,56 @@
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',
singleSession => 1,
}
}
);
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
count(1);
expectOK($res);
my $id1 = expectCookie($res);
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
count(1);
expectOK($res);
my $id2 = expectCookie($res);
ok($res=$client->_get('/',cookie=>"lemonldap=$id2"),'Use id 2');
count(1);
expectOK($res);
ok($res=$client->_get('/',cookie=>"lemonldap=$id1"),'Use id 1');
count(1);
expectReject($res);
clean_sessions();
done_testing( count() );