2017-02-05 14:11:14 +01:00
|
|
|
package Lemonldap::NG::Portal::Auth::Combination;
|
2017-02-05 13:24:26 +01:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use Lemonldap::NG::Common::Combination::Parser;
|
2019-05-14 19:47:28 +02:00
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_ERROR PE_FIRSTACCESS);
|
2018-10-03 22:31:28 +02:00
|
|
|
use Scalar::Util 'weaken';
|
2017-02-05 13:24:26 +01:00
|
|
|
|
2019-02-12 18:21:38 +01:00
|
|
|
our $VERSION = '2.1.0';
|
2017-02-28 21:53:19 +01:00
|
|
|
|
2017-02-05 13:24:26 +01:00
|
|
|
# TODO: See Lib::Wrapper
|
2018-02-19 22:11:43 +01:00
|
|
|
extends 'Lemonldap::NG::Portal::Main::Auth';
|
2018-10-03 21:48:57 +02:00
|
|
|
with 'Lemonldap::NG::Portal::Lib::OverConf';
|
2017-02-05 13:24:26 +01:00
|
|
|
|
|
|
|
# PROPERTIES
|
|
|
|
|
|
|
|
has stackSub => ( is => 'rw' );
|
|
|
|
|
2017-02-15 15:16:53 +01:00
|
|
|
has wrapUserLogger => (
|
|
|
|
is => 'rw',
|
2017-03-27 18:51:18 +02:00
|
|
|
lazy => 1,
|
2017-02-15 15:16:53 +01:00
|
|
|
default => sub {
|
2017-02-15 15:17:02 +01:00
|
|
|
Lemonldap::NG::Portal::Lib::Combination::UserLogger->new(
|
2018-11-16 17:30:57 +01:00
|
|
|
$_[0]->userLogger );
|
2017-02-15 15:16:53 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2017-02-05 13:24:26 +01:00
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
sub init {
|
2019-04-05 22:58:48 +02:00
|
|
|
my $self = shift;
|
2017-02-06 00:04:28 +01:00
|
|
|
|
|
|
|
# Check if expression exists
|
2017-02-05 14:11:14 +01:00
|
|
|
unless ( $self->conf->{combination} ) {
|
2017-02-05 13:24:26 +01:00
|
|
|
$self->error('No combination found');
|
|
|
|
return 0;
|
|
|
|
}
|
2017-02-06 00:04:28 +01:00
|
|
|
|
|
|
|
# Load all declared modules
|
2017-02-05 13:24:26 +01:00
|
|
|
my %mods;
|
2017-02-06 13:36:27 +01:00
|
|
|
foreach my $key ( keys %{ $self->conf->{combModules} } ) {
|
2017-02-05 13:24:26 +01:00
|
|
|
my @tmp = ( undef, undef );
|
2017-02-06 13:36:27 +01:00
|
|
|
my $mod = $self->conf->{combModules}->{$key};
|
|
|
|
|
|
|
|
unless ( $mod->{type} and defined $mod->{for} ) {
|
|
|
|
$self->error("Malformed combination module $key");
|
|
|
|
return 0;
|
|
|
|
}
|
2017-02-05 13:24:26 +01:00
|
|
|
|
2017-02-06 00:04:28 +01:00
|
|
|
# Override parameters
|
|
|
|
|
|
|
|
# "for" key can have 3 values:
|
|
|
|
# 0: this module will be used for Auth and UserDB
|
2018-08-29 18:51:20 +02:00
|
|
|
# 1: this module will be used for Auth only
|
|
|
|
# 2: this module will be used for UserDB only
|
2017-02-06 00:04:28 +01:00
|
|
|
|
|
|
|
# Load Auth module
|
2017-02-05 13:24:26 +01:00
|
|
|
if ( $mod->{for} < 2 ) {
|
2017-02-06 13:36:27 +01:00
|
|
|
$tmp[0] = $self->loadPlugin( "::Auth::$mod->{type}", $mod->{over} );
|
2017-02-05 13:24:26 +01:00
|
|
|
unless ( $tmp[0] ) {
|
|
|
|
$self->error("Unable to load Auth::$mod->{type}");
|
|
|
|
return 0;
|
|
|
|
}
|
2018-10-03 22:31:28 +02:00
|
|
|
$tmp[0]->{userLogger} = $self->wrapUserLogger;
|
|
|
|
weaken $tmp[0]->{userLogger};
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
2017-02-06 00:04:28 +01:00
|
|
|
|
|
|
|
# Load UserDB module
|
2017-02-05 13:24:26 +01:00
|
|
|
unless ( $mod->{for} == 1 ) {
|
2017-02-06 13:36:27 +01:00
|
|
|
$tmp[1] =
|
|
|
|
$self->loadPlugin( "::UserDB::$mod->{type}", $mod->{over} );
|
2017-02-05 13:24:26 +01:00
|
|
|
unless ( $tmp[1] ) {
|
|
|
|
$self->error("Unable to load UserDB::$mod->{type}");
|
|
|
|
return 0;
|
|
|
|
}
|
2018-10-03 22:31:28 +02:00
|
|
|
$tmp[1]->{userLogger} = $self->wrapUserLogger;
|
|
|
|
weaken $tmp[1]->{userLogger};
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
2017-02-06 00:04:28 +01:00
|
|
|
|
|
|
|
# Store modules as array
|
2017-02-06 13:36:27 +01:00
|
|
|
$mods{$key} = \@tmp;
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
2017-02-06 00:04:28 +01:00
|
|
|
|
|
|
|
# Compile expression
|
2017-02-05 13:24:26 +01:00
|
|
|
eval {
|
|
|
|
$self->stackSub(
|
|
|
|
Lemonldap::NG::Common::Combination::Parser->parse(
|
2017-02-05 14:11:14 +01:00
|
|
|
\%mods, $self->conf->{combination}
|
2017-02-05 13:24:26 +01:00
|
|
|
)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->error("Bad combination: $@");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-02-06 00:04:28 +01:00
|
|
|
# Each first method must call getStack() to get the auth scheme available for
|
|
|
|
# the current user
|
2019-08-10 11:13:56 +02:00
|
|
|
|
|
|
|
## Auth steps
|
|
|
|
#############
|
2017-02-05 13:24:26 +01:00
|
|
|
sub extractFormInfo {
|
|
|
|
my ( $self, $req ) = @_;
|
2017-02-05 18:05:33 +01:00
|
|
|
return $self->try( 0, 'extractFormInfo', $req );
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub authenticate {
|
2017-02-05 18:05:33 +01:00
|
|
|
return $_[0]->try( 0, 'authenticate', $_[1] );
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub setAuthSessionInfo {
|
2017-02-05 18:05:33 +01:00
|
|
|
return $_[0]->try( 0, 'setAuthSessionInfo', $_[1] );
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
|
|
|
|
2019-03-19 05:56:36 +01:00
|
|
|
sub getForm {
|
2017-02-07 07:21:23 +01:00
|
|
|
my ( $self, $req ) = @_;
|
2019-03-19 05:56:36 +01:00
|
|
|
return [ split /[, ]\s*/, $self->conf->{combinationForms} ]
|
2017-02-16 18:22:03 +01:00
|
|
|
if ( $self->conf->{combinationForms} );
|
2019-08-06 21:04:43 +02:00
|
|
|
|
2017-03-08 21:56:45 +01:00
|
|
|
my ( $nb, $stack ) = (
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{dataKeep}->{combinationTry},
|
|
|
|
$req->data->{combinationStack}
|
2017-03-08 21:56:45 +01:00
|
|
|
);
|
2019-03-19 05:56:36 +01:00
|
|
|
my ( $res, $name ) = $stack->[$nb]->[0]->( 'getForm', $req );
|
2017-02-07 07:21:23 +01:00
|
|
|
return $res;
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
|
|
|
|
2017-02-16 18:22:03 +01:00
|
|
|
sub authLogout {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
$self->getStack( $req, 'extractFormInfo' ) or return PE_ERROR;
|
2019-02-05 23:12:17 +01:00
|
|
|
|
2019-02-03 20:05:14 +01:00
|
|
|
# Avoid warning msg at first access
|
2019-08-02 22:58:36 +02:00
|
|
|
$req->userData->{_combinationTry} ||= 0;
|
2017-02-16 18:22:03 +01:00
|
|
|
my ( $res, $name ) =
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{combinationStack}->[ $req->userData->{_combinationTry} ]
|
2017-02-16 18:22:03 +01:00
|
|
|
->[0]->( 'authLogout', $req );
|
|
|
|
$self->logger->debug(qq'User disconnected using scheme "$name"');
|
|
|
|
return $res;
|
|
|
|
}
|
2017-02-05 13:24:26 +01:00
|
|
|
|
2019-08-17 17:26:39 +02:00
|
|
|
sub authFinish {
|
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub authForce {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-08-10 11:13:56 +02:00
|
|
|
## UserDB steps
|
|
|
|
###############
|
|
|
|
# Note that UserDB::Combination uses the same object.
|
|
|
|
sub getUser {
|
|
|
|
return $_[0]->try( 1, 'getUser', $_[1] );
|
|
|
|
}
|
|
|
|
|
|
|
|
sub setSessionInfo {
|
|
|
|
return $_[0]->try( 1, 'setSessionInfo', $_[1] );
|
|
|
|
}
|
|
|
|
|
|
|
|
sub setGroups {
|
|
|
|
return $_[0]->try( 1, 'setGroups', $_[1] );
|
|
|
|
}
|
|
|
|
|
2017-02-05 13:24:26 +01:00
|
|
|
sub getStack {
|
2017-02-05 18:05:33 +01:00
|
|
|
my ( $self, $req, @steps ) = @_;
|
2018-07-05 22:56:16 +02:00
|
|
|
return $req->data->{combinationStack}
|
|
|
|
if ( $req->data->{combinationStack} );
|
2018-07-05 23:00:40 +02:00
|
|
|
my $stack = $req->data->{combinationStack} = $self->stackSub->( $req->env );
|
2017-02-05 13:24:26 +01:00
|
|
|
unless ($stack) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error('No authentication scheme for this user');
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
2018-07-05 22:56:16 +02:00
|
|
|
@{ $req->data->{combinationSteps} } = ( @steps, @{ $req->steps } );
|
|
|
|
$req->data->{dataKeep}->{combinationTry} ||= 0;
|
2017-02-05 13:24:26 +01:00
|
|
|
return $stack;
|
|
|
|
}
|
|
|
|
|
2017-02-06 00:04:28 +01:00
|
|
|
# Main running method: launch the next scheme if the current fails
|
2017-02-05 13:24:26 +01:00
|
|
|
sub try {
|
|
|
|
my ( $self, $type, $subname, $req ) = @_;
|
2017-03-08 21:56:47 +01:00
|
|
|
|
|
|
|
# Get available authentication schemes for this user if not done
|
2018-07-05 22:56:16 +02:00
|
|
|
unless ( defined $req->data->{combinationStack} ) {
|
2017-03-08 21:56:47 +01:00
|
|
|
$self->getStack( $req, $subname ) or return PE_ERROR;
|
|
|
|
}
|
2017-03-08 21:56:45 +01:00
|
|
|
my ( $nb, $stack ) = (
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{dataKeep}->{combinationTry},
|
|
|
|
$req->data->{combinationStack}
|
2017-03-08 21:56:45 +01:00
|
|
|
);
|
2017-02-05 13:24:26 +01:00
|
|
|
|
|
|
|
# If more than 1 scheme is available
|
2017-02-05 18:05:33 +01:00
|
|
|
my ( $res, $name );
|
2019-08-24 23:10:09 +02:00
|
|
|
|
2017-02-05 22:12:06 +01:00
|
|
|
if ( $nb < @$stack - 1 ) {
|
2017-02-05 13:24:26 +01:00
|
|
|
|
|
|
|
# TODO: change logLevel for userLog()
|
2017-02-05 18:05:33 +01:00
|
|
|
( $res, $name ) = $stack->[$nb]->[$type]->( $subname, $req );
|
2017-02-05 13:24:26 +01:00
|
|
|
|
|
|
|
# On error, restart authentication with next scheme
|
|
|
|
if ( $res > PE_OK ) {
|
2018-03-13 07:14:01 +01:00
|
|
|
$self->logger->info(qq'Scheme "$name" returned $res, trying next');
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{dataKeep}->{combinationTry}++;
|
|
|
|
$req->steps( [ @{ $req->data->{combinationSteps} } ] );
|
2017-03-22 19:11:40 +01:00
|
|
|
$req->continue(1);
|
2017-02-05 13:24:26 +01:00
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
}
|
2017-02-05 18:05:33 +01:00
|
|
|
else {
|
2017-02-05 22:12:06 +01:00
|
|
|
( $res, $name ) = $stack->[$nb]->[$type]->( $subname, $req );
|
2017-02-05 18:05:33 +01:00
|
|
|
}
|
|
|
|
$req->sessionInfo->{ [ '_auth', '_userDB' ]->[$type] } = $name;
|
2017-03-08 21:56:45 +01:00
|
|
|
$req->sessionInfo->{_combinationTry} =
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{dataKeep}->{combinationTry};
|
2019-05-14 19:47:28 +02:00
|
|
|
if ( $res > 0 and $res != PE_FIRSTACCESS ) {
|
2017-02-15 15:16:53 +01:00
|
|
|
$self->userLogger->warn( 'All schemes failed'
|
|
|
|
. ( $req->user ? ' for user ' . $req->user : '' ) );
|
|
|
|
}
|
2017-02-05 18:05:33 +01:00
|
|
|
return $res;
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
|
|
|
|
2017-02-06 00:04:28 +01:00
|
|
|
# try() stores real Auth/UserDB module in sessionInfo
|
|
|
|
# This method reads them. It is called by getModule()
|
|
|
|
# (see Main::Run)
|
|
|
|
sub name {
|
|
|
|
my ( $self, $req, $type ) = @_;
|
|
|
|
return $req->sessionInfo->{ ( $type eq 'auth' ? '_auth' : '_userDB' ) }
|
|
|
|
|| 'Combination';
|
|
|
|
}
|
|
|
|
|
2017-02-15 15:17:02 +01:00
|
|
|
package Lemonldap::NG::Portal::Lib::Combination::UserLogger;
|
2017-02-15 15:16:53 +01:00
|
|
|
|
|
|
|
# This logger rewrite "warn" to "notice"
|
|
|
|
sub new {
|
|
|
|
my ( $class, $realLogger ) = @_;
|
|
|
|
return bless { logger => $realLogger }, $class;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub warn {
|
|
|
|
my ($auth) = caller(0);
|
|
|
|
$_[0]->{logger}->notice("Combination ($auth): $_[1]");
|
2017-02-06 00:04:28 +01:00
|
|
|
}
|
|
|
|
|
2017-02-15 15:17:02 +01:00
|
|
|
sub AUTOLOAD {
|
|
|
|
no strict;
|
|
|
|
return $_[0]->{logger}->$AUTOLOAD( $_[1] )
|
|
|
|
if ( $AUTOLOAD =~ /^(?:notice|debug|error|info)$/ );
|
|
|
|
}
|
|
|
|
|
2017-02-05 13:24:26 +01:00
|
|
|
1;
|