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;
|
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_ERROR);
|
2017-02-15 15:17:02 +01:00
|
|
|
use Scalar::Util 'weaken';
|
2017-02-05 13:24:26 +01:00
|
|
|
|
2017-02-28 21:53:19 +01:00
|
|
|
our $VERSION = '2.0.0';
|
|
|
|
|
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';
|
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(
|
|
|
|
$_[0]->{userLogger} );
|
2017-02-15 15:16:53 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2017-02-05 13:24:26 +01:00
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
sub init {
|
|
|
|
my ($self) = @_;
|
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
|
|
|
|
# 1: this module will be user for Auth only
|
|
|
|
# 2: this module will be user for UserDB only
|
|
|
|
|
|
|
|
# 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;
|
|
|
|
}
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
}
|
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
|
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
|
|
|
}
|
|
|
|
|
2017-02-06 00:04:28 +01:00
|
|
|
# Note that UserDB::Combination use the same object.
|
2017-02-05 13:24:26 +01:00
|
|
|
sub getUser {
|
2017-02-05 18:05:33 +01:00
|
|
|
return $_[0]->try( 1, 'getUser', $_[1] );
|
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
|
|
|
}
|
|
|
|
|
|
|
|
sub setSessionInfo {
|
2017-02-05 18:05:33 +01:00
|
|
|
return $_[0]->try( 1, 'setSessionInfo', $_[1] );
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub setGroups {
|
2017-02-05 18:05:33 +01:00
|
|
|
return $_[0]->try( 1, 'setGroups', $_[1] );
|
2017-02-05 13:24:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub getDisplayType {
|
2017-02-07 07:21:23 +01:00
|
|
|
my ( $self, $req ) = @_;
|
2017-02-16 18:22:03 +01:00
|
|
|
return $self->conf->{combinationForms}
|
|
|
|
if ( $self->conf->{combinationForms} );
|
2017-03-08 21:56:45 +01:00
|
|
|
my ( $nb, $stack ) = (
|
|
|
|
$req->datas->{dataKeep}->{combinationTry},
|
|
|
|
$req->datas->{combinationStack}
|
|
|
|
);
|
2017-02-07 07:21:23 +01:00
|
|
|
my ( $res, $name ) = $stack->[$nb]->[0]->( 'getDisplayType', $req );
|
|
|
|
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;
|
|
|
|
my ( $res, $name ) =
|
|
|
|
$req->datas->{combinationStack}->[ $req->userData->{_combinationTry} ]
|
|
|
|
->[0]->( 'authLogout', $req );
|
|
|
|
$self->logger->debug(qq'User disconnected using scheme "$name"');
|
|
|
|
return $res;
|
|
|
|
}
|
2017-02-05 13:24:26 +01:00
|
|
|
|
|
|
|
sub getStack {
|
2017-02-05 18:05:33 +01:00
|
|
|
my ( $self, $req, @steps ) = @_;
|
2017-02-16 18:22:03 +01:00
|
|
|
return $req->datas->{combinationStack}
|
|
|
|
if ( $req->datas->{combinationStack} );
|
|
|
|
my $stack = $req->datas->{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
|
|
|
}
|
2017-02-16 18:22:03 +01:00
|
|
|
@{ $req->datas->{combinationSteps} } = ( @steps, @{ $req->steps } );
|
2017-03-08 21:56:47 +01:00
|
|
|
$req->datas->{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
|
|
|
|
unless ( defined $req->datas->{combinationStack} ) {
|
|
|
|
$self->getStack( $req, $subname ) or return PE_ERROR;
|
|
|
|
}
|
2017-03-08 21:56:45 +01:00
|
|
|
my ( $nb, $stack ) = (
|
|
|
|
$req->datas->{dataKeep}->{combinationTry},
|
|
|
|
$req->datas->{combinationStack}
|
|
|
|
);
|
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 );
|
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');
|
2017-03-08 21:56:45 +01:00
|
|
|
$req->datas->{dataKeep}->{combinationTry}++;
|
2017-02-16 18:22:03 +01:00
|
|
|
$req->steps( [ @{ $req->datas->{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} =
|
|
|
|
$req->datas->{dataKeep}->{combinationTry};
|
2017-02-15 15:16:53 +01:00
|
|
|
if ( $res > 0 ) {
|
|
|
|
$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';
|
|
|
|
}
|
|
|
|
|
|
|
|
# To avoid "tied" destroy, tied configurations are kept here
|
|
|
|
our %overC;
|
|
|
|
|
|
|
|
# Override portal loadPlugin() to use a wrapped configuration
|
|
|
|
sub loadPlugin {
|
|
|
|
my ( $self, $plugin, $over ) = @_;
|
|
|
|
my $obj;
|
|
|
|
my $nc;
|
2017-02-06 13:36:27 +01:00
|
|
|
if ($over) {
|
2017-02-06 00:04:28 +01:00
|
|
|
require Lemonldap::NG::Common::Conf::Wrapper;
|
|
|
|
tie %$nc, 'Lemonldap::NG::Common::Conf::Wrapper', $self->conf, $over;
|
|
|
|
$overC{$plugin} = $nc;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$nc = $self->conf;
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
unless ( $obj = $self->p->loadModule( "$plugin", $nc ) );
|
2017-07-30 10:20:15 +02:00
|
|
|
return 0
|
|
|
|
unless $obj = $self->p->findEP( $plugin, $obj );
|
2017-02-15 15:17:02 +01:00
|
|
|
$obj->{userLogger} = $self->wrapUserLogger;
|
|
|
|
weaken $obj->{userLogger};
|
2017-02-15 15:16:53 +01:00
|
|
|
return $obj;
|
|
|
|
}
|
|
|
|
|
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;
|