2016-06-29 21:34:36 +02:00
|
|
|
package Lemonldap::NG::Portal::Lib::Choice;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
2018-04-25 15:40:19 +02:00
|
|
|
use Safe;
|
2019-03-19 05:56:36 +01:00
|
|
|
use IO::String;
|
2016-06-29 21:34:36 +02:00
|
|
|
|
|
|
|
extends 'Lemonldap::NG::Portal::Lib::Wrapper';
|
2018-10-03 22:01:13 +02:00
|
|
|
with 'Lemonldap::NG::Portal::Lib::OverConf';
|
2016-06-29 21:34:36 +02:00
|
|
|
|
2019-02-12 18:21:38 +01:00
|
|
|
our $VERSION = '2.1.0';
|
2016-06-29 21:34:36 +02:00
|
|
|
|
|
|
|
has modules => ( is => 'rw', default => sub { {} } );
|
|
|
|
|
2018-04-26 07:38:17 +02:00
|
|
|
has rules => ( is => 'rw', default => sub { {} } );
|
|
|
|
|
2016-07-02 21:09:45 +02:00
|
|
|
has type => ( is => 'rw' );
|
|
|
|
|
2018-06-18 23:19:33 +02:00
|
|
|
has catch => ( is => 'rw', default => sub { {} } );
|
|
|
|
|
2018-06-20 22:29:48 +02:00
|
|
|
has sessionKey => ( is => 'ro', default => '_choice' );
|
|
|
|
|
2018-04-26 12:01:40 +02:00
|
|
|
my $_choiceRules;
|
2018-04-26 07:46:49 +02:00
|
|
|
|
2016-06-29 21:34:36 +02:00
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
# init() must be called by module::init() with a number:
|
|
|
|
# - 0 for auth
|
|
|
|
# - 1 for userDB
|
|
|
|
# - 2 for passwordDB ?
|
|
|
|
sub init {
|
|
|
|
my ( $self, $type ) = @_;
|
2016-07-02 21:09:45 +02:00
|
|
|
$self->type($type);
|
2016-06-29 21:34:36 +02:00
|
|
|
|
2016-12-26 10:23:35 +01:00
|
|
|
unless ( $self->conf->{authChoiceModules}
|
|
|
|
and %{ $self->conf->{authChoiceModules} } )
|
|
|
|
{
|
2016-07-07 22:55:27 +02:00
|
|
|
$self->error("'authChoiceModules' is empty");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-29 21:34:36 +02:00
|
|
|
foreach my $name ( keys %{ $self->conf->{authChoiceModules} } ) {
|
2018-11-26 14:40:21 +01:00
|
|
|
my @mods =
|
|
|
|
split( /[;\|]/, $self->conf->{authChoiceModules}->{$name} );
|
|
|
|
my $module = '::'
|
|
|
|
. [ 'Auth', 'UserDB', 'Password' ]->[$type] . '::'
|
|
|
|
. $mods[$type];
|
2018-10-08 16:57:24 +02:00
|
|
|
my $over;
|
|
|
|
if ( $mods[5] ) {
|
2018-10-15 20:58:16 +02:00
|
|
|
eval { $over = JSON::from_json( $mods[5] ) };
|
2018-10-08 16:57:24 +02:00
|
|
|
if ($@) {
|
|
|
|
$self->logger->error("Bad over value ($@), skipped");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( $module = $self->loadModule( $module, $over ) ) {
|
2016-06-29 21:34:36 +02:00
|
|
|
$self->modules->{$name} = $module;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
2016-06-29 21:34:36 +02:00
|
|
|
[qw(Authentication User Password)]->[$type]
|
2018-11-26 14:40:21 +01:00
|
|
|
. " module $name selected" );
|
2016-06-29 21:34:36 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
2018-11-26 14:40:21 +01:00
|
|
|
"Choice: unable to load $name, disabling it: " . $self->error );
|
2016-07-02 10:51:00 +02:00
|
|
|
$self->error('');
|
2016-06-29 21:34:36 +02:00
|
|
|
}
|
2018-04-26 07:38:17 +02:00
|
|
|
|
2018-06-18 23:19:33 +02:00
|
|
|
# Test if auth module wants to catch some path
|
|
|
|
unless ($type) {
|
|
|
|
if ( $module->can('catch') ) {
|
|
|
|
$self->catch->{$name} = $module->catch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-26 07:38:17 +02:00
|
|
|
# Display conditions
|
|
|
|
my $safe = Safe->new;
|
2018-04-27 20:10:31 +02:00
|
|
|
my $cond = $mods[4];
|
2018-10-16 15:07:56 +02:00
|
|
|
if ( defined $cond and $cond !~ /^$/ ) {
|
|
|
|
$self->logger->debug("Found rule $cond for $name");
|
2018-11-26 14:40:21 +01:00
|
|
|
$_choiceRules->{$name} =
|
|
|
|
$safe->reval("sub{my(\$env)=\@_;return ($cond)}");
|
2018-04-26 07:38:17 +02:00
|
|
|
if ($@) {
|
|
|
|
$self->logger->error("Bad condition $cond: $@");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2018-04-26 12:01:40 +02:00
|
|
|
$self->logger->debug("No rule for $name");
|
2018-11-26 14:40:21 +01:00
|
|
|
$_choiceRules->{$name} = sub { 1 };
|
2018-04-26 07:38:17 +02:00
|
|
|
}
|
2016-06-29 21:34:36 +02:00
|
|
|
}
|
2016-07-02 21:09:45 +02:00
|
|
|
unless ( keys %{ $self->modules } ) {
|
2016-07-02 10:51:00 +02:00
|
|
|
$self->error('Choice: no available modules found, aborting');
|
|
|
|
return 0;
|
|
|
|
}
|
2016-06-30 22:42:50 +02:00
|
|
|
return 1;
|
2016-06-29 21:34:36 +02:00
|
|
|
}
|
|
|
|
|
2018-07-10 07:11:08 +02:00
|
|
|
# RUNNING METHODS
|
|
|
|
|
2016-06-29 21:34:36 +02:00
|
|
|
sub checkChoice {
|
|
|
|
my ( $self, $req ) = @_;
|
2018-06-18 23:19:33 +02:00
|
|
|
my $name;
|
2018-09-27 15:12:38 +02:00
|
|
|
|
|
|
|
# Check Choice from pdata
|
|
|
|
if ( defined $req->pdata->{_choice} ) {
|
|
|
|
|
|
|
|
$name = $req->pdata->{_choice};
|
|
|
|
$self->logger->debug("Choice $name selected from pdata");
|
|
|
|
}
|
|
|
|
|
|
|
|
unless ($name) {
|
|
|
|
|
|
|
|
# Check with catch method
|
|
|
|
foreach ( keys %{ $self->catch } ) {
|
|
|
|
if ( $req->path_info =~ $self->catch->{$_} ) {
|
|
|
|
$name = $_;
|
|
|
|
$self->logger->debug(
|
|
|
|
"Choice $name selected from " . $req->path_info );
|
|
|
|
last;
|
|
|
|
}
|
2018-06-18 23:19:33 +02:00
|
|
|
}
|
|
|
|
}
|
2018-09-27 15:12:38 +02:00
|
|
|
|
|
|
|
unless ($name) {
|
|
|
|
|
|
|
|
# Check with other methods
|
2018-11-26 14:40:21 +01:00
|
|
|
$name ||=
|
|
|
|
$req->param( $self->conf->{authChoiceParam} )
|
|
|
|
|| $req->userData->{_choice}
|
|
|
|
|| $req->sessionInfo->{_choice}
|
|
|
|
or return 0;
|
2018-09-27 15:12:38 +02:00
|
|
|
|
|
|
|
$self->logger->debug("Choice $name selected");
|
|
|
|
}
|
|
|
|
|
2016-07-01 17:56:16 +02:00
|
|
|
unless ( defined $self->modules->{$name} ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Unknown choice '$name'");
|
2016-07-01 17:56:16 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2018-06-18 22:37:28 +02:00
|
|
|
|
|
|
|
# Store choice if module loops
|
2018-10-16 17:25:43 +02:00
|
|
|
$req->pdata->{_choice} = $name;
|
|
|
|
$req->data->{_authChoice} = $name;
|
2018-07-05 22:56:16 +02:00
|
|
|
return $name if ( $req->data->{ "enabledMods" . $self->type } );
|
2016-07-01 17:56:16 +02:00
|
|
|
$req->sessionInfo->{_choice} = $name;
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{ "enabledMods" . $self->type } = [ $self->modules->{$name} ];
|
2016-06-30 22:42:50 +02:00
|
|
|
$self->p->_authentication->authnLevel("${name}AuthnLevel");
|
2016-07-02 10:51:00 +02:00
|
|
|
return $name;
|
2016-06-29 21:34:36 +02:00
|
|
|
}
|
|
|
|
|
2016-07-01 18:59:59 +02:00
|
|
|
sub name {
|
|
|
|
my ( $self, $req, $type ) = @_;
|
2016-07-02 21:09:45 +02:00
|
|
|
unless ($req) {
|
2016-07-02 10:51:00 +02:00
|
|
|
return 'Choice';
|
|
|
|
}
|
2018-07-05 22:56:16 +02:00
|
|
|
my $n = ref( $req->data->{ "enabledMods" . $self->type }->[0] );
|
2016-07-01 18:59:59 +02:00
|
|
|
$n =~ s/^Lemonldap::NG::Portal::(?:(?:UserDB|Auth)::)?//;
|
|
|
|
return $n;
|
|
|
|
}
|
|
|
|
|
2019-03-19 05:56:36 +01:00
|
|
|
sub getForm {
|
2016-06-29 21:34:36 +02:00
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
my @authLoop;
|
|
|
|
|
|
|
|
# Test authentication choices
|
|
|
|
unless ( ref $self->conf->{authChoiceModules} eq 'HASH' ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->warn("No authentication choices defined");
|
2016-06-29 21:34:36 +02:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( sort keys %{ $self->conf->{authChoiceModules} } ) {
|
|
|
|
|
|
|
|
my $name = $_;
|
|
|
|
|
|
|
|
# Name can have a digit as first character
|
|
|
|
# for sorting purpose
|
|
|
|
# Remove it in displayed name
|
|
|
|
$name =~ s/^(\d*)?(\s*)?//;
|
|
|
|
|
|
|
|
# Replace also _ by space for a nice display
|
|
|
|
$name =~ s/\_/ /g;
|
|
|
|
|
|
|
|
# Find modules associated to authChoice
|
2018-11-26 14:40:21 +01:00
|
|
|
my ( $auth, $userDB, $passwordDB, $url, $condition ) =
|
|
|
|
split( /[;\|]/, $self->conf->{authChoiceModules}->{$_} );
|
2016-06-29 21:34:36 +02:00
|
|
|
|
2018-04-27 13:54:06 +02:00
|
|
|
unless ( $_choiceRules->{$_} ) {
|
|
|
|
$self->logger->error("$_ has no rule !!!");
|
2018-11-26 14:40:21 +01:00
|
|
|
$_choiceRules->{$_} = sub { 1 };
|
2018-04-26 12:01:40 +02:00
|
|
|
}
|
2018-04-27 13:54:06 +02:00
|
|
|
unless ( $_choiceRules->{$_}->( $req->env ) ) {
|
2018-04-25 15:40:19 +02:00
|
|
|
$self->logger->debug(
|
2018-11-26 14:40:21 +01:00
|
|
|
"Condition returns false, authentication choice $_ will not be displayed"
|
2018-04-25 15:40:19 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
2018-04-28 09:09:34 +02:00
|
|
|
$self->logger->debug("Displaying authentication choice $_");
|
2018-04-25 15:40:19 +02:00
|
|
|
if ( $auth and $userDB and $passwordDB ) {
|
|
|
|
|
|
|
|
# Default URL
|
2018-10-31 22:46:03 +01:00
|
|
|
$req->{cspFormAction} ||= '';
|
2018-11-26 14:40:21 +01:00
|
|
|
if (
|
|
|
|
defined $url
|
|
|
|
and not $self->checkXSSAttack( 'URI',
|
|
|
|
$req->env->{'REQUEST_URI'} )
|
|
|
|
and $url =~
|
|
|
|
q%^(https?://)?[^\s/.?#$].[^\s]+$% # URL must be well formatted
|
|
|
|
)
|
2018-04-25 15:40:19 +02:00
|
|
|
{
|
2018-10-31 22:46:03 +01:00
|
|
|
#$url .= $req->env->{'REQUEST_URI'};
|
|
|
|
|
|
|
|
# Avoid append same URL
|
2018-10-29 22:14:51 +01:00
|
|
|
$req->{cspFormAction} .= " $url"
|
2018-11-26 14:40:21 +01:00
|
|
|
unless $req->{cspFormAction} =~ qr%\b$url\b%;
|
2018-04-25 15:40:19 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$url .= '#';
|
|
|
|
}
|
|
|
|
$self->logger->debug("Use URL $url");
|
|
|
|
|
|
|
|
# Options to store in the loop
|
2018-10-29 22:14:51 +01:00
|
|
|
my $optionsLoop = {
|
2019-03-19 05:56:36 +01:00
|
|
|
name => $name,
|
|
|
|
key => $_,
|
|
|
|
module => $auth,
|
|
|
|
url => $url,
|
|
|
|
CHOICE_VALUE => $req->data->{_authChoice},
|
|
|
|
CHOICE_PARAM => $self->conf->{authChoiceParam},
|
2018-10-29 22:14:51 +01:00
|
|
|
};
|
2018-04-25 15:40:19 +02:00
|
|
|
|
|
|
|
# Get displayType for this module
|
|
|
|
no strict 'refs';
|
|
|
|
my $displayType = "Lemonldap::NG::Portal::Auth::${auth}"
|
2019-03-19 05:56:36 +01:00
|
|
|
->can('getForm')->( $self, $req );
|
2018-04-25 15:40:19 +02:00
|
|
|
|
2019-03-19 05:56:36 +01:00
|
|
|
$self->logger->debug( 'Display type '
|
|
|
|
. ( ref $displayType ? '[ref]' : $displayType )
|
|
|
|
. ' for module $auth' );
|
|
|
|
|
|
|
|
#$optionsLoop->{$displayType} = 1 unless(ref $displayType);
|
|
|
|
$optionsLoop->{form} = $displayType;
|
2018-11-23 22:08:06 +01:00
|
|
|
my $logo = $_;
|
|
|
|
if ( $auth eq 'Custom' ) {
|
2018-11-26 14:40:21 +01:00
|
|
|
$logo =
|
|
|
|
( $self->{conf}->{customAuth} =~ /::(\w+)$/ )[0];
|
2018-11-23 22:08:06 +01:00
|
|
|
}
|
2016-06-29 21:34:36 +02:00
|
|
|
|
2018-09-07 20:11:36 +02:00
|
|
|
# If displayType is logo, check if key.png is available
|
|
|
|
if ( -e $self->conf->{templateDir}
|
|
|
|
. "/../htdocs/static/common/modules/"
|
2018-11-23 22:08:06 +01:00
|
|
|
. $logo
|
2018-09-07 20:11:36 +02:00
|
|
|
. ".png" )
|
|
|
|
{
|
2018-11-23 22:08:06 +01:00
|
|
|
$optionsLoop->{logoFile} = $logo . ".png";
|
2018-09-07 20:11:36 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$optionsLoop->{logoFile} = $auth . ".png";
|
|
|
|
}
|
2016-06-29 21:34:36 +02:00
|
|
|
|
2018-04-25 15:40:19 +02:00
|
|
|
# Register item in loop
|
|
|
|
push @authLoop, $optionsLoop;
|
2016-06-29 21:34:36 +02:00
|
|
|
|
2018-04-25 15:40:19 +02:00
|
|
|
$self->logger->debug(
|
|
|
|
"Authentication choice $name will be displayed");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$req->error("Authentication choice $_ value is invalid");
|
|
|
|
return 0;
|
|
|
|
}
|
2016-06-29 21:34:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-03-19 05:56:36 +01:00
|
|
|
open my $fh,
|
|
|
|
$self->conf->{templateDir} . '/'
|
|
|
|
. $self->p->getSkin($req)
|
|
|
|
. "/choice.tpl"
|
|
|
|
or die $!;
|
|
|
|
my $res;
|
|
|
|
{
|
|
|
|
local $/ = undef;
|
|
|
|
$res = readline $fh;
|
|
|
|
}
|
|
|
|
close $fh;
|
|
|
|
while ( $res =~ m#<TMPL_LOOP NAME="AUTH_LOOP">(.*?)</TMPL_LOOP>#s ) {
|
|
|
|
my $content = $1;
|
|
|
|
my $new = '';
|
|
|
|
foreach (@authLoop) {
|
|
|
|
my $tmp = $content;
|
|
|
|
if ( my @match = ( $tmp =~ m#<TMPL_VAR NAME="(\w+)">#gs ) ) {
|
|
|
|
foreach my $key (@match) {
|
|
|
|
$tmp =~ s#<TMPL_VAR NAME="$key">#$_->{$key}#gs
|
|
|
|
if defined $_->{$key};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$tmp =~ s#__LLNG_FORM__#<TMPL_INCLUDE NAME="$_->{form}.tpl">#gs;
|
|
|
|
$new .= $tmp;
|
|
|
|
}
|
|
|
|
$res =~ s#<TMPL_LOOP NAME="AUTH_LOOP">.*?</TMPL_LOOP>#$new#s;
|
|
|
|
}
|
|
|
|
$req->tplParams->{CHOICE_PARAM} = $self->conf->{authChoiceParam};
|
|
|
|
return IO::String->new($res);
|
2016-06-29 21:34:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|