lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Auth/Combination.pm

237 lines
6.4 KiB
Perl
Raw Normal View History

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);
use Scalar::Util 'weaken';
2017-02-05 13:24:26 +01:00
2019-02-05 18:44:31 +01:00
our $VERSION = '2.0.2';
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' );
has wrapUserLogger => (
is => 'rw',
lazy => 1,
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-05 13:24:26 +01:00
# INITIALIZATION
sub init {
my ($self) = @_;
# 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;
}
# 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
# 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
# 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;
}
$tmp[0]->{userLogger} = $self->wrapUserLogger;
weaken $tmp[0]->{userLogger};
2017-02-05 13:24:26 +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;
}
$tmp[1]->{userLogger} = $self->wrapUserLogger;
weaken $tmp[1]->{userLogger};
2017-02-05 13:24:26 +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
}
# 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;
}
# 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
}
2018-08-29 18:51:20 +02:00
# Note that UserDB::Combination uses 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} );
my ( $nb, $stack ) = (
$req->data->{dataKeep}->{combinationTry},
$req->data->{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;
2019-02-05 23:12:17 +01:00
2019-02-03 20:05:14 +01:00
# Avoid warning msg at first access
$req->userData->{_combinationTry} ||= '';
2017-02-16 18:22:03 +01:00
my ( $res, $name ) =
$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
sub getStack {
2017-02-05 18:05:33 +01:00
my ( $self, $req, @steps ) = @_;
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
}
@{ $req->data->{combinationSteps} } = ( @steps, @{ $req->steps } );
$req->data->{dataKeep}->{combinationTry} ||= 0;
2017-02-05 13:24:26 +01:00
return $stack;
}
# 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 ) = @_;
# Get available authentication schemes for this user if not done
unless ( defined $req->data->{combinationStack} ) {
$self->getStack( $req, $subname ) or return PE_ERROR;
}
my ( $nb, $stack ) = (
$req->data->{dataKeep}->{combinationTry},
$req->data->{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');
$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;
$req->sessionInfo->{_combinationTry} =
$req->data->{dataKeep}->{combinationTry};
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
}
# 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;
# 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-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;