2018-03-08 16:33:34 +01:00
|
|
|
package Lemonldap::NG::Portal::2F::Engines::Default;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_OK
|
|
|
|
);
|
|
|
|
|
|
|
|
our $VERSION = '2.0.0';
|
|
|
|
|
|
|
|
extends 'Lemonldap::NG::Portal::Main::Plugin';
|
|
|
|
|
2018-03-08 18:51:01 +01:00
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
has sfModules => ( is => 'rw', default => sub { [] } );
|
|
|
|
|
|
|
|
has sfRModules => ( is => 'rw', default => sub { [] } );
|
|
|
|
|
2018-03-08 20:36:32 +01:00
|
|
|
has ott => (
|
|
|
|
is => 'rw',
|
|
|
|
default => sub {
|
|
|
|
my $ott =
|
|
|
|
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
|
|
|
|
$ott->timeout( $_[0]->{conf}->{formTimeout} );
|
|
|
|
return $ott;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-03-08 16:33:34 +01:00
|
|
|
sub init {
|
2018-03-08 18:51:01 +01:00
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
# Set default 2F list
|
|
|
|
$self->conf->{available2F} ||= 'TOTP,U2F,REST,Ext2F';
|
|
|
|
$self->conf->{available2FSelfRegistration} ||= 'TOTP,U2F';
|
|
|
|
|
|
|
|
# Load 2F modules
|
|
|
|
for my $i ( 0 .. 1 ) {
|
|
|
|
foreach (
|
2018-03-08 20:36:32 +01:00
|
|
|
split /,\s*/,
|
2018-03-08 18:51:01 +01:00
|
|
|
$self->conf->{ $i ? 'available2FSelfRegistration' : 'available2F' }
|
|
|
|
)
|
|
|
|
{
|
|
|
|
my $prefix = lc($_);
|
|
|
|
$prefix =~ s/2f$//i;
|
|
|
|
|
|
|
|
# Activation parameter
|
2018-03-08 20:36:32 +01:00
|
|
|
my $ap = $prefix . ( $i ? '2fSelfRegistration' : '2fActivation' );
|
|
|
|
$self->logger->debug("Checking $ap");
|
2018-03-08 18:51:01 +01:00
|
|
|
|
|
|
|
# Unless $rule, skip loading
|
2018-03-08 20:36:32 +01:00
|
|
|
if ( $self->conf->{$ap} ) {
|
2018-03-08 18:51:01 +01:00
|
|
|
$self->logger->debug("Trying to load $_ 2F");
|
|
|
|
my $m =
|
|
|
|
$self->p->loadPlugin( $i ? "::2F::Register::$_" : "::2F::$_" )
|
|
|
|
or return 0;
|
|
|
|
|
|
|
|
# Rule and prefix may be modified by 2F module, reread them
|
|
|
|
my $rule = $self->conf->{$ap};
|
|
|
|
$prefix = $m->prefix;
|
|
|
|
|
|
|
|
# Compile rule
|
|
|
|
$rule = $self->p->HANDLER->substitute($rule);
|
|
|
|
unless ( $rule = $self->p->HANDLER->buildSub($rule) ) {
|
|
|
|
$self->error( 'External 2F rule error: '
|
|
|
|
. $self->p->HANDLER->tsv->{jail}->error );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Store module
|
|
|
|
push @{ $self->{ $i ? 'sfRModules' : 'sfModules' } },
|
|
|
|
{ m => $m, r => $rule };
|
|
|
|
}
|
2018-03-08 20:36:32 +01:00
|
|
|
else {
|
|
|
|
$self->logger->debug(' -> not enabled');
|
|
|
|
}
|
2018-03-08 18:51:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 22:24:02 +01:00
|
|
|
# Enable REST request only if more than 1 2F module is enabled
|
|
|
|
if ( $#{ $self->{sfModules} } ) {
|
|
|
|
$self->addUnauthRoute( '2fchoice' => 'choice', ['POST'] );
|
|
|
|
$self->addUnauthRoute( '2fchoice' => 'redirect', ['GET'] );
|
|
|
|
}
|
|
|
|
|
2018-03-08 16:33:34 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-03-08 18:51:01 +01:00
|
|
|
# RUNNING METHODS
|
|
|
|
|
|
|
|
# run is called at each authentication, just after sesionInfo populate
|
|
|
|
sub run {
|
2018-03-08 20:36:32 +01:00
|
|
|
my ( $self, $req ) = @_;
|
2018-03-08 18:51:01 +01:00
|
|
|
|
|
|
|
# Skip 2F unless a module has been registered
|
2018-03-08 20:36:32 +01:00
|
|
|
return PE_OK unless ( @{ $self->sfModules } );
|
2018-03-08 18:51:01 +01:00
|
|
|
|
|
|
|
# Search for authorizated modules for this user
|
|
|
|
my @am;
|
2018-03-08 20:36:32 +01:00
|
|
|
foreach my $m ( @{ $self->sfModules } ) {
|
|
|
|
$self->logger->debug(
|
|
|
|
'Looking if ' . $m->{m}->prefix . '2F is available' );
|
|
|
|
if ( $m->{r}->( $req, $req->sessionInfo ) ) {
|
2018-03-08 18:51:01 +01:00
|
|
|
$self->logger->debug(' -> OK');
|
2018-03-08 20:36:32 +01:00
|
|
|
push @am, $m->{m};
|
2018-03-08 18:51:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# If no 2F modules are authorizated, skipping 2F
|
|
|
|
# Note that a rule may forbid access after (GrantSession plugin)
|
2018-03-08 20:36:32 +01:00
|
|
|
return PE_OK unless (@am);
|
2018-03-08 18:51:01 +01:00
|
|
|
|
|
|
|
$self->userLogger->info( 'Second factor required for '
|
|
|
|
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
|
2018-03-08 20:36:32 +01:00
|
|
|
|
|
|
|
# Store user data in a token
|
2018-03-08 18:51:01 +01:00
|
|
|
$req->sessionInfo->{_2fRealSession} = $req->id;
|
|
|
|
$req->sessionInfo->{_2fUrldc} = $req->urldc;
|
|
|
|
my $token = $self->ott->createToken( $req->sessionInfo );
|
2018-03-08 20:36:32 +01:00
|
|
|
|
|
|
|
# If only one 2F is authorizated, display it
|
|
|
|
unless ($#am) {
|
|
|
|
my $res = $am[0]->run( $req, $token );
|
2018-03-08 18:51:01 +01:00
|
|
|
delete $req->{authResult} if ($res);
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
# More than 1 2F has been found, display choice
|
2018-03-08 22:24:02 +01:00
|
|
|
$self->logger->debug("Prepare 2F choice");
|
|
|
|
my $tpl = $self->p->sendHtml(
|
|
|
|
$req,
|
|
|
|
'2fchoice',
|
|
|
|
params => {
|
|
|
|
SKIN => $self->conf->{portalSkin},
|
|
|
|
TOKEN => $token,
|
|
|
|
MODULES => JSON::to_json(
|
|
|
|
[ map { { code => $_->prefix, logo => $_->logo } } @am ]
|
|
|
|
),
|
|
|
|
}
|
|
|
|
);
|
2018-03-08 20:36:32 +01:00
|
|
|
return PE_OK;
|
2018-03-08 22:24:02 +01:00
|
|
|
|
|
|
|
# TODO:
|
|
|
|
# - 2fchoice.tpl
|
|
|
|
# - choice() which launch choosenModule->run($req,$token)
|
|
|
|
# - add logos for 2F modules
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _redirect {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
my $arg = $req->env->{QUERY_STRING};
|
|
|
|
return [
|
|
|
|
302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], []
|
|
|
|
];
|
2018-03-08 18:51:01 +01:00
|
|
|
}
|
|
|
|
|
2018-03-08 16:33:34 +01:00
|
|
|
1;
|