Add SecondFactor superclass (#1015)

environments/ppa-mbqj77/deployments/1
Xavier Guimard 8 years ago
parent e33a741acf
commit 86d7a3a8c0
  1. 2
      lemonldap-ng-portal/MANIFEST
  2. 12
      lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/U2F.pm
  3. 103
      lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm
  4. 79
      lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/External2F.pm
  5. 110
      lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/U2F.pm

@ -73,6 +73,7 @@ lib/Lemonldap/NG/Portal/Main/Plugins.pm
lib/Lemonldap/NG/Portal/Main/Process.pm
lib/Lemonldap/NG/Portal/Main/Request.pm
lib/Lemonldap/NG/Portal/Main/Run.pm
lib/Lemonldap/NG/Portal/Main/SecondFactor.pm
lib/Lemonldap/NG/Portal/Password/Base.pm
lib/Lemonldap/NG/Portal/Password/Choice.pm
lib/Lemonldap/NG/Portal/Password/Custom.pm
@ -117,6 +118,7 @@ lib/Lemonldap/NG/Portal/UserDB/REST.pm
lib/Lemonldap/NG/Portal/UserDB/SAML.pm
lib/Lemonldap/NG/Portal/UserDB/Slave.pm
lib/Lemonldap/NG/Portal/UserDB/WebID.pm
logs
Makefile.PL
MANIFEST This list of files
META.yml

@ -10,14 +10,7 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
has crypter => ( is => 'rw' );
has origin => (
is => 'rw',
default => sub {
my $p = $_[0]->{conf}->{portal};
$p =~ s#^(https?://[^/]+).*$#$1#;
return $p;
}
);
has origin => ( is => 'rw', );
sub init {
my ($self) = @_;
@ -26,6 +19,9 @@ sub init {
$self->error("Can't load U2F library: $@");
return 0;
}
my $p = $_[0]->{conf}->{portal};
$p =~ s#^(https?://[^/]+).*$#$1#;
$self->origin($p);
unless (
$self->crypter(
Crypt::U2F::Server::Simple->new(

@ -0,0 +1,103 @@
package Lemonldap::NG::Portal::Main::SecondFactor;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_NOTOKEN
PE_TOKENEXPIRED
);
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Plugin';
# INTERFACE
sub afterDatas { '_run' }
# INITIALIZATION
has ott => (
is => 'rw',
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} );
return $ott;
}
);
has rule => ( is => 'rw' );
has prefix => ( is => 'rw' );
sub init {
my ($self) = @_;
$self->addUnauthRoute( $self->prefix . '2fcheck', '_verify', ['POST'] );
my $rule = $self->conf->{ $self->prefix . '2fActivation' };
$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;
}
$self->rule($rule);
1;
}
sub _run {
my ( $self, $req ) = @_;
return PE_OK unless ( $self->rule->( $req->sessionInfo ) );
$self->userLogger->info( 'Second factor required ('
. $self->prefix
. ') for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
$req->sessionInfo->{_2fRealSession} = $req->id;
my $token = $self->ott->createToken( $req->sessionInfo );
$req->id(0);
$self->p->rebuildCookies($req);
my $res = $self->run( $req, $token );
delete $req->{authResult} if($res);
return $res;
}
sub _verify {
my ( $self, $req ) = @_;
# Check token
my $token;
unless ( $token = $req->param('token') ) {
$self->userLogger->error( $self->prefix . ' 2F access without token' );
return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
}
my $session;
unless ( $session = $self->ott->getToken($token) ) {
$self->userLogger->info('Token expired');
return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] );
}
# Launch second factor verification
my $res = $self->verify( $req, $session );
# Case error
if ($res) {
return $self->p->do( $req, [ sub { $res } ] );
}
# Else restore session
$req->sessionInfo($session);
$req->id( delete $req->sessionInfo->{_2fRealSession} );
$self->p->rebuildCookies($req);
$req->mustRedirect(1);
$self->userLogger->notice( $self->prefix
. '2F verification for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
if ( my $l = $self->conf->{ $self->prefix . '2fAuthnLevel' } ) {
$self->p->updateSession( $req, { authenticationLevel => $l } );
}
return $self->p->do( $req, [ sub { PE_OK } ] );
}
1;

@ -6,64 +6,33 @@ use Lemonldap::NG::Portal::Main::Constants qw(
PE_BADCREDENTIALS
PE_ERROR
PE_FORMEMPTY
PE_NOTOKEN
PE_OK
PE_SENDRESPONSE
PE_TOKENEXPIRED
);
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Plugin';
# INTERFACE
sub afterDatas { 'run' }
extends 'Lemonldap::NG::Portal::Main::SecondFactor';
# INITIALIZATION
has ott => (
is => 'rw',
default => sub {
my $ott;
if ( $_[0]->{conf}->{requireToken} ) {
$ott =
$_[0]->{p}
->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} );
}
return $ott;
}
);
has rule => ( is => 'rw' );
has prefix => ( is => 'ro', default => 'ext' );
sub init {
my ($self) = @_;
$self->addUnauthRoute( ext2fcheck => 'verify', ['POST'] );
foreach (qw(ext2FSendCommand ext2FValidateCommand)) {
unless ( $self->conf->{$_} ) {
$self->error("Missing $_ parameter, aborting");
return 0;
}
}
my $rule = $self->conf->{ext2fActivation};
$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;
}
$self->rule($rule);
1;
return $self->SUPER::init();
}
sub run {
my ( $self, $req ) = @_;
return PE_OK unless ( $self->rule->( $req->sessionInfo ) );
# RUNNING METHODS
my $user = $req->sessionInfo->{ $self->conf->{whatToTrace} };
$self->userLogger->info("Second factor required for $user");
sub run {
my ( $self, $req, $token ) = @_;
# Prepare command and launch it
if ( my $c =
@ -74,10 +43,6 @@ sub run {
}
# Prepare form
$req->sessionInfo->{_ext2fRealSession} = $req->id;
my $token = $self->ott->createToken( $req->sessionInfo );
$req->id(0);
$self->p->rebuildCookies($req);
my $tmp = $self->p->sendHtml(
$req,
'ext2fcheck',
@ -86,31 +51,18 @@ sub run {
TOKEN => $token
}
);
$self->logger->debug("Prepare U2F verification for $user");
$self->logger->debug("Prepare U2F verification");
$req->response($tmp);
delete $req->{authResult};
return PE_SENDRESPONSE;
}
sub verify {
my ( $self, $req ) = @_;
# Check token
my $token;
unless ( $token = $req->param('token') ) {
$self->userLogger->error('External 2F access without token');
return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
}
my ( $self, $req, $session ) = @_;
my $code;
unless ( $code = $req->param('code') ) {
$self->userLogger->error('External 2F: no code');
return $self->p->do( $req, [ sub { PE_FORMEMPTY } ] );
}
my $session;
unless ( $session = $self->ott->getToken($token) ) {
$self->userLogger->info('Token expired');
return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] );
return PE_FORMEMPTY;
}
# Prepare command and launch it
@ -119,18 +71,9 @@ sub verify {
{
$self->userLogger->warn( 'Second factor failed for '
. $session->{ $self->conf->{whatToTrace} } );
return $self->p->do( $req, [ sub { PE_BADCREDENTIALS } ] );
}
$req->sessionInfo($session);
$req->id( delete $req->sessionInfo->{_ext2fRealSession} );
$self->p->rebuildCookies($req);
$req->mustRedirect(1);
$self->userLogger->notice( 'External verification for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
if ( my $l = $self->conf->{ext2fAuthnLevel} ) {
$self->p->updateSession( $req, { authenticationLevel => $l } );
return PE_BADCREDENTIALS;
}
return $self->p->do( $req, [ sub { PE_OK } ] );
PE_OK
}
# system() is used with an array to avoid shell injection

@ -9,47 +9,27 @@ use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_ERROR
PE_NOTOKEN
PE_OK
PE_SENDRESPONSE
PE_TOKENEXPIRED
PE_U2FFAILED
);
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Lib::U2F';
# INTERFACE
sub afterDatas { 'run' }
extends 'Lemonldap::NG::Portal::Main::SecondFactor',
'Lemonldap::NG::Portal::Lib::U2F';
# INITIALIZATION
has ott => (
is => 'rw',
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} );
return $ott;
}
);
has rule => ( is => 'rw' );
has prefix => ( is => 'ro', default => 'u' );
sub init {
my ($self) = @_;
$self->addUnauthRoute( u2fcheck => 'verify', ['POST'] );
my $rule = $self->conf->{u2fActivation};
$rule = $self->p->HANDLER->substitute($rule);
unless ( $rule = $self->p->HANDLER->buildSub($rule) ) {
$self->error(
'U2F rule error: ' . $self->p->HANDLER->tsv->{jail}->error );
return 0;
}
$self->rule($rule);
return 0 unless $self->SUPER::init;
return 0
unless ( $self->Lemonldap::NG::Portal::Main::SecondFactor::init()
and $self->Lemonldap::NG::Portal::Lib::U2F::init() );
1;
}
@ -57,23 +37,14 @@ sub init {
# Main method
sub run {
my ( $self, $req ) = @_;
return PE_OK unless ( $self->rule->( $req->sessionInfo ) );
my $user = $req->sessionInfo->{ $self->conf->{whatToTrace} };
$self->userLogger->info("U2F required for $user");
my ( $self, $req, $token ) = @_;
my ( $kh, $uk );
# Check if user is registered
if ( my $res = $self->loadUser($req) ) {
if ( my $res = $self->loadUser( $req->sessionInfo ) ) {
return PE_ERROR if ( $res == -1 );
$req->sessionInfo->{_u2fRealSession} = $req->id;
my $token = $self->ott->createToken( $req->sessionInfo );
$req->id(0);
$self->p->rebuildCookies($req);
my $challenge = $self->crypter->authenticationChallenge;
my $tmp = $self->p->sendHtml(
$req,
@ -84,51 +55,30 @@ sub run {
TOKEN => $token
}
);
$self->logger->debug("Prepare U2F verification for $user");
$self->logger->debug("Prepare U2F verification");
$req->response($tmp);
delete $req->{authResult};
return PE_SENDRESPONSE;
}
return PE_OK;
}
sub verify {
my ( $self, $req ) = @_;
# Check token
my $token;
unless ( $token = $req->param('token') ) {
$self->userLogger->error('U2F access without token');
$req->error(PE_NOTOKEN);
return $self->fail($req);
}
unless ( $req->sessionInfo( $self->ott->getToken($token) ) ) {
$self->userLogger->info('Token expired');
$req->error(PE_TOKENEXPIRED);
return $self->fail($req);
}
my ( $self, $req, $session ) = @_;
# Check U2F signature
if ( my $resp = $req->param('signature') ) {
unless ( $self->loadUser($req) == 1 ) {
unless ( $self->loadUser($session) == 1 ) {
$req->error(PE_ERROR);
return $self->fail($req);
}
if ( $self->crypter->authenticationVerify($resp) ) {
$req->id( delete $req->sessionInfo->{_u2fRealSession} );
$self->p->rebuildCookies($req);
$req->mustRedirect(1);
$self->userLogger->notice( 'U2F signature verified for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
if ( my $l = $self->conf->{u2fAuthnLevel} ) {
$self->p->updateSession( $req, { authenticationLevel => $l } );
}
return $self->p->do( $req, [ sub { PE_OK } ] );
$self->userLogger->info('U2F signature verified');
return PE_OK;
}
else {
$self->userLogger->notice( 'Invalid U2F signature for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } . ' ('
. $session->{ $self->conf->{whatToTrace} } . ' ('
. Crypt::U2F::Server::u2fclib_getError()
. ')' );
$req->error(PE_U2FFAILED);
@ -138,7 +88,7 @@ sub verify {
}
else {
$self->userLogger->notice( 'No U2F response for user'
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
. $session->{ $self->conf->{whatToTrace} } );
$req->authResult(PE_U2FFAILED);
return $self->fail($req);
}
@ -146,23 +96,26 @@ sub verify {
sub fail {
my ( $self, $req ) = @_;
return $self->p->sendHtml(
$req,
'u2fcheck',
params => {
AUTH_ERROR => $req->error,
AUTH_ERROR_TYPE => $req->error_type,
SKIN => $self->conf->{portalSkin},
FAILED => 1
}
$req->response(
$self->p->sendHtml(
$req,
'u2fcheck',
params => {
AUTH_ERROR => $req->error,
AUTH_ERROR_TYPE => $req->error_type,
SKIN => $self->conf->{portalSkin},
FAILED => 1
}
)
);
return PE_SENDRESPONSE;
}
sub loadUser {
my ( $self, $req ) = @_;
my ( $self, $session ) = @_;
my ( $kh, $uk );
if ( ( $kh = $req->sessionInfo->{_u2fKeyHandle} )
and ( $uk = $req->sessionInfo->{_u2fUserKey} ) )
if ( ( $kh = $session->{_u2fKeyHandle} )
and ( $uk = $session->{_u2fUserKey} ) )
{
$self->crypter->{keyHandle} = $self->decode_base64url($kh);
$self->crypter->{publicKey} = $self->decode_base64url($uk);
@ -176,6 +129,7 @@ sub loadUser {
return 1;
}
else {
$self->userLogger->info("U2F: user not registered");
return 0;
}
}

Loading…
Cancel
Save