Split Ext2F into a common lib (Code2F) + specific code (#2762)
This commit is contained in:
parent
9ef3a4b839
commit
ee05bb5c8a
|
@ -64,6 +64,7 @@ lib/Lemonldap/NG/Portal/Lib/_tokenRule.pm
|
|||
lib/Lemonldap/NG/Portal/Lib/Captcha.pm
|
||||
lib/Lemonldap/NG/Portal/Lib/CAS.pm
|
||||
lib/Lemonldap/NG/Portal/Lib/Choice.pm
|
||||
lib/Lemonldap/NG/Portal/Lib/Code2F.pm
|
||||
lib/Lemonldap/NG/Portal/Lib/CustomModule.pm
|
||||
lib/Lemonldap/NG/Portal/Lib/DBI.pm
|
||||
lib/Lemonldap/NG/Portal/Lib/LDAP.pm
|
||||
|
|
|
@ -14,176 +14,41 @@ use Lemonldap::NG::Portal::Main::Constants qw(
|
|||
|
||||
our $VERSION = '2.0.10';
|
||||
|
||||
extends 'Lemonldap::NG::Portal::Main::SecondFactor';
|
||||
extends 'Lemonldap::NG::Portal::Lib::Code2F';
|
||||
|
||||
# INITIALIZATION
|
||||
|
||||
# Prefix can overriden by sfExtra and is used for routes
|
||||
has prefix => ( is => 'rw', default => 'ext' );
|
||||
|
||||
has resend_interval => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->{conf}->{ext2fResendInterval} || 0;
|
||||
}
|
||||
);
|
||||
|
||||
has random => ( is => 'rw' );
|
||||
# Type is used to lookup config
|
||||
has type => ( is => 'ro', default => 'ext' );
|
||||
has legend => ( is => 'rw', default => 'enterExt2fCode' );
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->addUnauthRoute(
|
||||
$self->prefix . '2fresend' => '_resend',
|
||||
['POST']
|
||||
);
|
||||
|
||||
$self->addAuthRoute(
|
||||
$self->prefix . '2fresend' => '_resend',
|
||||
['POST']
|
||||
);
|
||||
|
||||
unless ( $self->conf->{ext2fCodeActivation} ) {
|
||||
if ( $self->code_activation ) {
|
||||
unless ( $self->conf->{ext2FSendCommand} ) {
|
||||
$self->error("Missing 'ext2FSendCommand' parameter, aborting");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach (qw(ext2FSendCommand ext2FValidateCommand)) {
|
||||
unless ( $self->conf->{$_} ) {
|
||||
$self->error("Missing $_ parameter, aborting");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->prefix( $self->conf->{sfPrefix} )
|
||||
if ( $self->conf->{sfPrefix} );
|
||||
return $self->SUPER::init();
|
||||
}
|
||||
if ( $self->conf->{ext2fCodeActivation} ) {
|
||||
unless ( $self->conf->{ext2FSendCommand} ) {
|
||||
$self->error("Missing 'ext2FSendCommand' parameter, aborting");
|
||||
return 0;
|
||||
}
|
||||
$self->random( Lemonldap::NG::Common::Crypto::srandom() );
|
||||
$self->prefix( $self->conf->{sfPrefix} )
|
||||
if ( $self->conf->{sfPrefix} );
|
||||
return $self->SUPER::init();
|
||||
}
|
||||
return 0;
|
||||
|
||||
$self->prefix( $self->conf->{sfPrefix} ) if ( $self->conf->{sfPrefix} );
|
||||
return $self->SUPER::init();
|
||||
}
|
||||
|
||||
# RUNNING METHODS
|
||||
|
||||
sub run {
|
||||
my ( $self, $req, $token ) = @_;
|
||||
|
||||
# Generate Code to send
|
||||
my $code;
|
||||
if ( $self->conf->{ext2fCodeActivation} ) {
|
||||
$code = $self->random->randregex( $self->conf->{ext2fCodeActivation} );
|
||||
$self->logger->debug("Generated ext2f code : $code");
|
||||
$self->ott->updateToken( $token, __ext2fcode => $code );
|
||||
}
|
||||
|
||||
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(
|
||||
$req,
|
||||
'ext2fcheck',
|
||||
params => {
|
||||
MAIN_LOGO => $self->conf->{portalMainLogo},
|
||||
SKIN => $self->p->getSkin($req),
|
||||
PREFIX => $self->prefix,
|
||||
(
|
||||
$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,
|
||||
%params,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
my ( $self, $req, $session ) = @_;
|
||||
my $usercode;
|
||||
unless ( $usercode = $req->param('code') ) {
|
||||
$self->userLogger->error('External 2F: no code found');
|
||||
return PE_FORMEMPTY;
|
||||
}
|
||||
|
||||
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 ) = @_;
|
||||
|
||||
|
@ -202,27 +67,6 @@ sub verify_external {
|
|||
return PE_OK;
|
||||
}
|
||||
|
||||
sub verify_internal {
|
||||
my ( $self, $req, $session, $code ) = @_;
|
||||
|
||||
my $savedcode = $session->{__ext2fcode};
|
||||
unless ($savedcode) {
|
||||
$self->logger->error(
|
||||
'Unable to find generated 2F code in token session');
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
sub sendCode {
|
||||
my ( $self, $req, $sessionInfo, $code ) = @_;
|
||||
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
package Lemonldap::NG::Portal::Lib::Code2F;
|
||||
|
||||
# Base class for 2F methods that work by
|
||||
# * optionally generating a code
|
||||
# * delivering the code through an external system (mail/rest/shell...)
|
||||
# * validating that the input code is the correct one
|
||||
|
||||
use strict;
|
||||
use Mouse;
|
||||
|
||||
use Lemonldap::NG::Portal::Main::Constants qw(
|
||||
PE_OK
|
||||
PE_NOTOKEN
|
||||
PE_TOKENEXPIRED
|
||||
PE_ERROR
|
||||
PE_BADOTP
|
||||
PE_FORMEMPTY
|
||||
PE_SENDRESPONSE
|
||||
);
|
||||
|
||||
has resend_interval => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->{conf}->{ $_[0]->type . '2fResendInterval' } || 0;
|
||||
}
|
||||
);
|
||||
|
||||
has code_activation => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->{conf}->{ $_[0]->type . '2fCodeActivation' };
|
||||
}
|
||||
);
|
||||
|
||||
has random => ( is => 'rw' );
|
||||
|
||||
our $VERSION = '2.0.15';
|
||||
|
||||
extends 'Lemonldap::NG::Portal::Main::SecondFactor';
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
if ( $self->code_activation ) {
|
||||
$self->random( Lemonldap::NG::Common::Crypto::srandom() );
|
||||
}
|
||||
|
||||
$self->addUnauthRoute(
|
||||
$self->prefix . '2fresend' => '_resend',
|
||||
['POST']
|
||||
);
|
||||
|
||||
$self->addAuthRoute(
|
||||
$self->prefix . '2fresend' => '_resend',
|
||||
['POST']
|
||||
);
|
||||
|
||||
return $self->SUPER::init();
|
||||
}
|
||||
|
||||
sub run {
|
||||
my ( $self, $req, $token ) = @_;
|
||||
|
||||
# Generate Code to send
|
||||
my $code;
|
||||
if ( $self->code_activation ) {
|
||||
$code = $self->random->randregex( $self->code_activation );
|
||||
$self->logger->debug( "Generated " . $self->type . "2f code : $code" );
|
||||
$self->ott->updateToken( $token,
|
||||
'__' . $self->type . '2fcode' => $code );
|
||||
}
|
||||
|
||||
return PE_ERROR unless $self->sendCode( $req, $req->sessionInfo, $code );
|
||||
|
||||
$self->logger->debug("Prepare external 2F verification");
|
||||
my $tmp =
|
||||
$self->sendCodeForm( $req, TOKEN => $token, LEGEND => $self->legend );
|
||||
|
||||
$req->response($tmp);
|
||||
return PE_SENDRESPONSE;
|
||||
}
|
||||
|
||||
sub _resend {
|
||||
my ( $self, $req ) = @_;
|
||||
|
||||
# Check token
|
||||
my $token;
|
||||
unless ( $token = $req->param('token') ) {
|
||||
$self->userLogger->error( $self->type . ' 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->{ '__' . $self->type . '2fcode' };
|
||||
|
||||
my $legend = $self->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 {
|
||||
my ( $self, $req, $session ) = @_;
|
||||
my $usercode;
|
||||
unless ( $usercode = $req->param('code') ) {
|
||||
$self->userLogger->error( $self->type . '2f: no code found' );
|
||||
return PE_FORMEMPTY;
|
||||
}
|
||||
|
||||
if ( $self->code_activation ) {
|
||||
return $self->verify_internal( $req, $session, $usercode );
|
||||
}
|
||||
else {
|
||||
return $self->verify_external( $req, $session, $usercode );
|
||||
}
|
||||
}
|
||||
|
||||
sub verify_internal {
|
||||
my ( $self, $req, $session, $code ) = @_;
|
||||
|
||||
my $savedcode = $session->{ '__' . $self->type . '2fcode' };
|
||||
unless ($savedcode) {
|
||||
$self->logger->error(
|
||||
'Unable to find generated 2F code in token session');
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
$self->logger->debug(
|
||||
"Verifying " . $self->type . "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;
|
||||
}
|
||||
}
|
||||
|
||||
sub sendCodeForm {
|
||||
my ( $self, $req, %params ) = @_;
|
||||
|
||||
my $checkLogins = $req->param('checkLogins');
|
||||
$self->logger->debug( $self->type . "2f: checkLogins set" )
|
||||
if $checkLogins;
|
||||
|
||||
my $stayconnected = $req->param('stayconnected');
|
||||
$self->logger->debug( $self->type . "2f: stayconnected set" )
|
||||
if $stayconnected;
|
||||
|
||||
# Prepare form
|
||||
return $self->p->sendHtml(
|
||||
$req,
|
||||
'ext2fcheck',
|
||||
params => {
|
||||
MAIN_LOGO => $self->conf->{portalMainLogo},
|
||||
SKIN => $self->p->getSkin($req),
|
||||
PREFIX => $self->prefix,
|
||||
(
|
||||
$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,
|
||||
%params,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
Loading…
Reference in New Issue