Update Ext2F to support resend code (#2762)

This commit is contained in:
Maxime Besson 2022-06-20 15:09:36 +02:00
parent 86e572db52
commit 31db698df0
1 changed files with 142 additions and 47 deletions

View File

@ -4,6 +4,8 @@ use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_NOTOKEN
PE_TOKENEXPIRED
PE_ERROR
PE_BADOTP
PE_FORMEMPTY
@ -17,10 +19,30 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor';
# INITIALIZATION
has prefix => ( is => 'rw', default => 'ext' );
has resend_interval => (
is => 'rw',
lazy => 1,
default => sub {
$_[0]->{conf}->{ext2fResendInterval} || 0;
}
);
has random => ( is => 'rw' );
sub init {
my ($self) = @_;
$self->addUnauthRoute(
$self->prefix . '2fresend' => '_resend',
['POST']
);
$self->addAuthRoute(
$self->prefix . '2fresend' => '_resend',
['POST']
);
unless ( $self->conf->{ext2fCodeActivation} ) {
foreach (qw(ext2FSendCommand ext2FValidateCommand)) {
unless ( $self->conf->{$_} ) {
@ -50,12 +72,6 @@ sub init {
sub run {
my ( $self, $req, $token ) = @_;
my $checkLogins = $req->param('checkLogins');
$self->logger->debug("Ext2F: checkLogins set") if $checkLogins;
my $stayconnected = $req->param('stayconnected');
$self->logger->debug("Ext2F: stayconnected set") if $stayconnected;
# Generate Code to send
my $code;
if ( $self->conf->{ext2fCodeActivation} ) {
@ -64,18 +80,23 @@ sub run {
$self->ott->updateToken( $token, __ext2fcode => $code );
}
# Prepare command and launch it
$self->logger->debug( 'Launching "Send" external 2F command -> '
. $self->conf->{ext2FSendCommand} );
if (
my $c = $self->launch(
$req->sessionInfo, $self->conf->{ext2FSendCommand}, $code
)
)
{
$self->logger->error("External send command failed (code $c)");
return PE_ERROR;
}
return PE_ERROR unless $self->sendCode( $req, $req->sessionInfo, $code );
$self->logger->debug("Prepare external 2F verification");
my $tmp = $self->sendCodeForm( $req, TOKEN => $token );
$req->response($tmp);
return PE_SENDRESPONSE;
}
sub sendCodeForm {
my ( $self, $req, %params ) = @_;
my $checkLogins = $req->param('checkLogins');
$self->logger->debug("Ext2F: checkLogins set") if $checkLogins;
my $stayconnected = $req->param('stayconnected');
$self->logger->debug("Ext2F: stayconnected set") if $stayconnected;
# Prepare form
my $tmp = $self->p->sendHtml(
@ -84,20 +105,67 @@ sub run {
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/'
(
$self->resend_interval
? ( RESENDTARGET => '/'
. $self->prefix
. '2fresend?skin='
. $self->p->getSkin($req) )
: ()
),
TARGET => '/'
. $self->prefix
. '2fcheck?skin='
. $self->p->getSkin($req),
CHECKLOGINS => $checkLogins,
STAYCONNECTED => $stayconnected
STAYCONNECTED => $stayconnected,
%params,
}
);
$self->logger->debug("Prepare external 2F verification");
}
$req->response($tmp);
return PE_SENDRESPONSE;
sub _resend {
my ( $self, $req ) = @_;
# Check token
my $token;
unless ( $token = $req->param('token') ) {
$self->userLogger->error( $self->prefix . ' 2F access without token' );
eval { $self->setSecurity($req) };
$req->mustRedirect(1);
return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
}
my $session;
# Do not invalidate the token while getting it
unless ( $session = $self->ott->getToken( $token, 1 ) ) {
$self->userLogger->info('Token expired');
$self->setSecurity($req);
return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] );
}
my $code = $session->{__ext2fcode};
my $legend;
# Timer
my $lastretry =
$session->{__lastRetry} || $session->{tokenSessionStartTimestamp} || time;
if ( $self->resend_interval
and ( $lastretry + $self->resend_interval < time ) )
{
# Resend code and update last retry
return PE_ERROR unless $self->sendCode( $req, $session, $code );
$self->ott->updateToken( $token, __lastRetry => time );
}
else {
$legend = "resendTooSoon";
}
return $self->sendCodeForm( $req, TOKEN => $token, LEGEND => $legend );
}
sub verify {
@ -108,25 +176,34 @@ sub verify {
return PE_FORMEMPTY;
}
unless ( $self->conf->{ext2fCodeActivation} ) {
# Prepare command and launch it
$self->logger->debug( 'Launching "Validate" external 2F command -> '
. $self->conf->{ext2FValidateCommand} );
$self->logger->debug(" code -> $usercode");
if (
my $c = $self->launch(
$session, $self->conf->{ext2FValidateCommand}, $usercode
)
)
{
$self->userLogger->warn( 'Second factor failed for '
. $session->{ $self->conf->{whatToTrace} } );
$self->logger->error("External verify command failed (code $c)");
return PE_BADOTP;
}
return PE_OK;
if ( $self->conf->{ext2fCodeActivation} ) {
return $self->verify_internal( $req, $session, $usercode );
}
else {
return $self->verify_external( $req, $session, $usercode );
}
}
sub verify_external {
my ( $self, $req, $session, $code ) = @_;
# Prepare command and launch it
$self->logger->debug( 'Launching "Validate" external 2F command -> '
. $self->conf->{ext2FValidateCommand} );
$self->logger->debug(" code -> $code");
if ( my $c =
$self->launch( $session, $self->conf->{ext2FValidateCommand}, $code ) )
{
$self->userLogger->warn( 'Second factor failed for '
. $session->{ $self->conf->{whatToTrace} } );
$self->logger->error("External verify command failed (code $c)");
return PE_BADOTP;
}
return PE_OK;
}
sub verify_internal {
my ( $self, $req, $session, $code ) = @_;
my $savedcode = $session->{__ext2fcode};
unless ($savedcode) {
@ -135,12 +212,30 @@ sub verify {
return PE_ERROR;
}
$self->logger->debug("Verifying Ext 2F code: $usercode VS $savedcode");
return PE_OK if ( $usercode eq $savedcode );
$self->logger->debug("Verifying Ext 2F code: $code VS $savedcode");
if ( $code eq $savedcode ) {
return PE_OK;
}
else {
$self->userLogger->warn( 'Second factor failed for '
. $session->{ $self->conf->{whatToTrace} } );
return PE_BADOTP;
}
}
$self->userLogger->warn( 'Second factor failed for '
. $session->{ $self->conf->{whatToTrace} } );
return PE_BADOTP;
sub sendCode {
my ( $self, $req, $sessionInfo, $code ) = @_;
# Prepare command and launch it
$self->logger->debug( 'Launching "Send" external 2F command -> '
. $self->conf->{ext2FSendCommand} );
if ( my $c =
$self->launch( $sessionInfo, $self->conf->{ext2FSendCommand}, $code ) )
{
$self->logger->error("External send command failed (code $c)");
return 0;
}
return 1;
}
# system() is used with an array to avoid shell injection