lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm

373 lines
11 KiB
Perl
Raw Normal View History

2016-03-28 09:46:05 +02:00
##@class Lemonldap::NG::Portal::Main::Init
2016-03-29 23:09:55 +02:00
# Initialization part of Lemonldap::NG portal
2016-03-30 21:51:12 +02:00
#
2016-04-03 10:44:58 +02:00
# 2 public methods:
2016-03-30 21:51:12 +02:00
# - init(): launch at startup. Load 'portal' section of lemonldap-ng.ini,
# initialize default route and launch reloadConf()
# - reloadConf(): (re)load configuration using localConf (ie 'portal' section
# of lemonldap-ng.ini) and underlying handler configuration
2016-03-28 09:46:05 +02:00
package Lemonldap::NG::Portal::Main::Init;
2016-03-24 07:23:40 +01:00
2016-04-07 23:31:56 +02:00
our $VERSION = '2.0.0';
package Lemonldap::NG::Portal::Main;
2016-03-24 07:23:40 +01:00
use strict;
use Mouse;
2016-04-03 18:27:22 +02:00
use Regexp::Assemble;
2016-03-24 07:23:40 +01:00
2016-06-09 20:40:20 +02:00
# PROPERTIES
2016-03-28 09:46:02 +02:00
# Configuration storage
2016-03-27 20:10:36 +02:00
has localConfig => ( is => 'rw', default => sub { {} } );
has conf => ( is => 'rw', default => sub { {} } );
2016-04-13 23:06:04 +02:00
has menu => ( is => 'rw', default => sub { {} } );
2016-03-24 07:23:40 +01:00
2016-03-28 09:46:02 +02:00
# Sub modules
has _authentication => ( is => 'rw' );
has _userDB => ( is => 'rw' );
2016-07-20 22:47:43 +02:00
has _passwordDB => ( is => 'rw' );
2016-03-28 09:46:02 +02:00
2016-11-16 11:30:27 +01:00
has loadedModules => ( is => 'rw' );
2016-04-01 07:24:27 +02:00
# Macros and groups
2016-05-26 23:26:47 +02:00
has _macros => ( is => 'rw' );
has _groups => ( is => 'rw' );
has _jsRedirect => ( is => 'rw' );
2016-04-01 07:24:27 +02:00
2016-04-03 18:27:22 +02:00
# TrustedDomain regexp
2016-05-23 13:53:09 +02:00
has trustedDomainsRe => ( is => 'rw' );
2016-04-03 18:27:22 +02:00
2016-03-29 23:09:55 +02:00
# Lists to store plugins entry-points
2016-03-31 22:08:43 +02:00
has beforeAuth => (
2016-03-29 23:09:55 +02:00
is => 'rw',
isa => 'ArrayRef',
default => sub { [] }
);
2016-03-31 22:08:43 +02:00
has betweenAuthAndDatas => (
2016-03-29 23:09:55 +02:00
is => 'rw',
isa => 'ArrayRef',
default => sub { [] }
);
2016-03-31 22:08:43 +02:00
has afterDatas => (
2016-03-29 23:09:55 +02:00
is => 'rw',
isa => 'ArrayRef',
default => sub { [] }
);
has forAuthUser => (
is => 'rw',
isa => 'ArrayRef',
default => sub { [] }
);
2016-04-18 22:23:40 +02:00
has beforeLogout => (
is => 'rw',
isa => 'ArrayRef',
default => sub { [] }
);
2016-03-28 09:46:02 +02:00
2017-11-11 21:16:52 +01:00
has spRules => (
is => 'rw',
default => sub { {} }
);
2016-05-23 23:52:29 +02:00
# Custom template parameters
has customParameters => ( is => 'rw', default => sub { {} } );
# Content-Security-Policy header
has csp => ( is => 'rw' );
2016-06-09 20:40:20 +02:00
# INITIALIZATION
2016-03-24 07:23:40 +01:00
sub init {
my ( $self, $args ) = @_;
$args ||= {};
$self->localConfig(
{
%{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} )
->getLocalConf('portal')
},
%$args
}
);
2016-05-24 07:05:51 +02:00
foreach my $k ( keys %{ $self->localConfig } ) {
2016-05-23 23:52:29 +02:00
if ( $k =~ /tpl_(.*)/ ) {
$self->customParameters->{$1} = $self->localConfig->{$k};
}
}
2016-11-16 11:30:27 +01:00
# Purge loaded module list
$self->loadedModules( {} );
2017-02-08 23:18:52 +01:00
Lemonldap::NG::Handler::Main->onReload( $self, 'reloadConf' );
2017-02-15 07:41:50 +01:00
return 0 unless ( $self->SUPER::init( $self->localConfig ) );
return 0 if ( $self->error );
2016-03-24 07:23:40 +01:00
2016-03-31 07:27:59 +02:00
# Handle requests (other path may be declared in enabled plugins)
$self
2016-03-30 21:51:12 +02:00
2016-03-31 07:27:59 +02:00
# "/"
2016-04-18 22:23:40 +02:00
->addUnauthRoute( '*' => 'login', ['GET'] )
->addUnauthRoute( '*' => 'postLogin', ['POST'] )
->addAuthRoute( '*' => 'authenticatedRequest', ['GET'] )
->addAuthRoute( '*' => 'postAuthenticatedRequest', ['POST'] )
2016-03-31 07:27:59 +02:00
2017-02-21 06:38:59 +01:00
# psgi.js
->addUnauthRoute( 'psgi.js' => 'sendJs', ['GET'] )
->addAuthRoute( 'psgi.js' => 'sendJs', ['GET'] )
# portal.css
2017-03-16 12:38:52 +01:00
->addUnauthRoute( 'portal.css' => 'sendCss', ['GET'] )
->addAuthRoute( 'portal.css' => 'sendCss', ['GET'] )
# lmerror
->addUnauthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] )
->addAuthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] )
2016-03-31 07:27:59 +02:00
# Core REST API
2016-04-18 22:23:40 +02:00
->addUnauthRoute( ping => 'pleaseAuth', ['GET'] )
->addAuthRoute( ping => 'authenticated', ['GET'] )
# Refresh session
->addAuthRoute( refresh => 'refresh', ['GET'] )
2016-04-18 22:23:40 +02:00
# Logout
->addAuthRoute( logout => 'logout', ['GET'] );
2016-03-31 07:27:59 +02:00
2016-04-01 07:24:27 +02:00
# Default routes must point to routines declared above
$self->defaultAuthRoute('');
2016-03-30 21:51:12 +02:00
$self->defaultUnauthRoute('');
2016-11-16 11:30:27 +01:00
2016-05-24 22:50:22 +02:00
return 1;
2016-03-30 07:47:38 +02:00
}
sub reloadConf {
my ( $self, $conf ) = @_;
2016-03-30 07:47:38 +02:00
2016-04-06 07:16:47 +02:00
# Reinitialize $self->conf
%{ $self->{conf} } = %{ $self->localConfig };
2016-03-24 07:23:40 +01:00
2016-04-01 07:24:27 +02:00
# Reinitialize arrays
2016-04-03 08:33:50 +02:00
foreach (
2016-05-30 22:20:53 +02:00
qw(_macros _groups beforeAuth betweenAuthAndDatas afterDatas forAuthUser beforeLogout)
2016-04-03 08:33:50 +02:00
)
{
2016-04-01 07:24:27 +02:00
$self->{$_} = [];
}
2016-03-30 07:47:38 +02:00
# Load conf in portal object
foreach my $key ( keys %$conf ) {
2016-04-06 07:16:47 +02:00
$self->{conf}->{$key} ||= $conf->{$key};
2016-03-30 07:47:38 +02:00
}
2016-03-24 07:23:40 +01:00
# Initialize content-security-policy header
my $csp = '';
foreach (qw(default img src style font connect)) {
my $prm = $self->conf->{ 'csp' . ucfirst($_) };
$csp .= "$_-src $prm;" if ($prm);
}
$self->csp($csp);
2016-04-06 07:16:47 +02:00
# Initialize templateDir
$self->{templateDir} =
$self->conf->{templateDir} . '/' . $self->conf->{portalSkin};
2016-12-30 08:03:48 +01:00
$self->{staticPrefix} = $self->conf->{staticPrefix} || '/static';
2016-05-23 13:53:09 +02:00
$self->{languages} = $self->conf->{languages} || '/';
2016-04-13 07:32:10 +02:00
2016-03-30 07:47:38 +02:00
# Initialize session DBs
unless ( $self->conf->{globalStorage} ) {
$self->error(
'globalStorage not defined (perhaps configuration can not be read)'
);
2017-01-05 16:19:57 +01:00
return $self->fail;
2016-03-30 07:47:38 +02:00
}
2016-03-24 07:23:40 +01:00
2016-04-05 22:46:11 +02:00
# Initialize persistent session DB
unless ( $self->conf->{persistentStorage} ) {
$self->conf->{persistentStorage} = $self->conf->{globalStorage};
2016-04-06 07:16:47 +02:00
$self->conf->{persistentStorageOptions} =
$self->conf->{globalStorageOptions};
2016-04-05 22:46:11 +02:00
}
2016-03-30 07:47:38 +02:00
# Initialize cookie domain
unless ( $self->conf->{domain} ) {
$self->error('Configuration error: no domain');
2017-01-05 16:19:57 +01:00
return $self->fail;
2016-03-30 07:47:38 +02:00
}
$self->conf->{domain} =~ s/^([^\.])/.$1/;
2016-03-24 07:23:40 +01:00
2016-03-30 07:47:38 +02:00
# Load authentication/userDB
# --------------------------
my $mod;
2016-03-30 07:47:38 +02:00
for my $type (qw(authentication userDB)) {
unless ( $self->conf->{$type} ) {
$self->error("$type is not set");
2017-01-05 16:19:57 +01:00
return $self->fail;
2016-03-24 07:23:40 +01:00
}
$mod = $self->conf->{$type} unless ( $self->conf->{$type} eq 'Same' );
my $module = '::' . ucfirst($type) . '::' . $mod;
2016-04-01 12:46:12 +02:00
$module =~ s/Authentication/Auth/;
2016-03-30 07:47:38 +02:00
# Launch and initialize module
2017-01-05 16:19:57 +01:00
return $self->fail
unless ( $self->{"_$type"} = $self->loadPlugin($module) );
2016-03-30 07:47:38 +02:00
}
2016-03-29 23:09:55 +02:00
2016-04-03 10:44:58 +02:00
# Initialize trusted domain regexp
2016-04-03 18:27:22 +02:00
if ( $self->conf->{trustedDomains}
and $self->conf->{trustedDomains} =~ /^\s*\*\s*$/ )
{
2016-05-23 13:53:09 +02:00
$self->trustedDomainsRe(qr#^https?://#);
2016-04-03 10:44:58 +02:00
}
else {
my $re = Regexp::Assemble->new();
if ( my $td = $self->conf->{trustedDomains} ) {
$td =~ s/^\s*(.*?)\s*/$1/;
foreach ( split( /\s+/, $td ) ) {
2016-05-23 23:52:29 +02:00
next unless ($td);
2016-04-03 10:44:58 +02:00
s#^\.#([^/]+\.)?#;
2017-02-15 07:41:50 +01:00
$self->logger->debug("Domain $_ added in trusted domains");
2016-05-23 13:53:09 +02:00
s/\./\\./g;
# This regexp is valid for the followings hosts:
# - $td
# - $domainlabel.$td
# $domainlabel is build looking RFC2396
# (see Regexp::Common::URI::RFC2396)
2016-05-23 23:52:29 +02:00
$_ =~
s/\*\\\./(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9]\\.)*/g;
2016-05-23 13:53:09 +02:00
$re->add("$_");
2016-04-03 10:44:58 +02:00
}
}
2017-03-20 19:14:10 +01:00
my $p = $self->conf->{portal};
$p =~ s#https?://([^/]*).*$#$1#;
$re->add( quotemeta($p) );
2016-04-03 10:44:58 +02:00
foreach my $vhost ( keys %{ $self->conf->{locationRules} } ) {
2017-02-15 07:41:50 +01:00
$self->logger->debug("Vhost $vhost added in trusted domains");
2016-04-03 10:44:58 +02:00
$re->add( quotemeta($vhost) );
$self->conf->{vhostOptions} ||= {};
2016-04-03 10:44:58 +02:00
if ( my $tmp =
$self->conf->{vhostOptions}->{$vhost}->{vhostAliases} )
{
foreach my $alias ( split /\s+/, $tmp ) {
2017-02-15 07:41:50 +01:00
$self->logger->debug(
"Alias $alias added in trusted domains");
2016-04-03 10:44:58 +02:00
$re->add( quotemeta($alias) );
}
}
}
2016-05-23 13:53:09 +02:00
my $tmp = 'https?://' . $re->as_string . '(?::\d+)?(?:/|$)';
$self->trustedDomainsRe(qr/$tmp/);
2016-03-30 07:47:38 +02:00
}
2016-03-29 23:09:55 +02:00
2016-04-03 18:27:19 +02:00
# Compile macros in _macros, groups in _groups
foreach my $type (qw(macros groups)) {
$self->{"_$type"} = {};
if ( $self->conf->{$type} ) {
for my $name ( sort keys %{ $self->conf->{$type} } ) {
2016-04-05 22:46:11 +02:00
my $sub =
2016-08-02 15:52:29 +02:00
HANDLER->buildSub(
HANDLER->substitute( $self->conf->{$type}->{$name} ) );
2016-04-03 18:27:19 +02:00
if ($sub) {
$self->{"_$type"}->{$name} = $sub;
}
else {
2017-02-15 07:41:50 +01:00
$self->logger->error( "$type $name returns an error: "
. HANDLER->tsv->{jail}->error );
2016-04-03 18:27:19 +02:00
}
}
}
}
2016-05-26 23:26:47 +02:00
$self->{_jsRedirect} =
2016-08-02 15:52:29 +02:00
HANDLER->buildSub( HANDLER->substitute( $self->conf->{jsRedirect} ) )
2017-02-15 07:41:50 +01:00
or $self->logger->error(
'jsRedirect returns an error: ' . HANDLER->tsv->{jail}->error );
2016-04-01 07:24:27 +02:00
2017-02-03 07:23:39 +01:00
$self->menu( $self->loadPlugin('::Main::Menu') );
$self->displayInit;
2016-03-30 07:47:38 +02:00
# Load plugins
foreach my $plugin ( $self->enabledPlugins ) {
2017-01-05 16:19:57 +01:00
$self->loadPlugin($plugin) or return $self->fail;
2016-03-24 07:23:40 +01:00
}
2016-04-13 07:32:10 +02:00
2016-03-24 07:23:40 +01:00
1;
}
# Method used to load plugins
2016-03-29 23:09:55 +02:00
sub loadPlugin {
my ( $self, $plugin ) = @_;
my $obj;
return 0
2016-04-03 08:33:50 +02:00
unless ( $obj = $self->loadModule("$plugin") );
return $self->findEP( $plugin, $obj );
}
# Insert declared entry points into corresponding arrays
sub findEP {
my ( $self, $plugin, $obj ) = @_;
# Standards entry points
2016-03-29 23:09:55 +02:00
foreach my $sub (
2016-05-30 22:20:53 +02:00
qw(beforeAuth betweenAuthAndDatas afterDatas forAuthUser beforeLogout))
2016-03-29 23:09:55 +02:00
{
if ( $obj->can($sub) ) {
2017-02-15 07:41:50 +01:00
$self->logger->debug(" Found $sub entry point:");
2016-03-31 07:27:59 +02:00
if ( my $callback = $obj->$sub ) {
2016-05-30 22:20:53 +02:00
push @{ $self->{$sub} }, sub { $obj->$callback( $_[0] ) };
2017-02-15 07:41:50 +01:00
$self->logger->debug(" -> $callback");
2016-03-31 07:27:59 +02:00
}
2016-03-29 23:09:55 +02:00
}
}
2017-11-11 21:16:52 +01:00
( $obj and $obj->init ) or return 0;
$self->logger->debug("Plugin $plugin initializated");
# Rules for menu
if ( $obj->can('spRules') ) {
foreach my $k ( keys %{ $obj->{spRules} } ) {
$self->logger->info(
"$k is defined more than one time, it can have some bad effect on Menu display"
) if ( $self->spRules->{$k} );
$self->spRules->{$k} = $obj->{spRules}->{$k};
}
}
return $obj;
2016-03-29 23:09:55 +02:00
}
2016-03-24 23:16:13 +01:00
sub loadModule {
my ( $self, $module, $conf ) = @_;
$conf //= $self->conf;
2016-03-29 23:09:55 +02:00
my $obj;
2016-04-03 08:33:50 +02:00
$module = "Lemonldap::NG::Portal$module" if ( $module =~ /^::/ );
2016-03-24 23:16:13 +01:00
eval "require $module";
if ($@) {
2017-02-15 07:41:50 +01:00
$self->logger->error("$module load error: $@");
2016-03-24 23:16:13 +01:00
return 0;
}
2016-03-28 09:46:02 +02:00
eval {
$obj = $module->new( { p => $self, conf => $conf } );
2017-02-15 07:41:50 +01:00
$self->logger->debug("Module $module loaded");
2016-03-28 09:46:02 +02:00
};
if ($@) {
$self->error("Unable to build $module object: $@");
return 0;
}
2016-11-16 11:30:27 +01:00
$self->loadedModules->{$module} = $obj;
2016-03-29 23:09:55 +02:00
return $obj;
2016-03-24 23:16:13 +01:00
}
2017-01-05 16:19:57 +01:00
sub fail {
2017-02-15 07:41:50 +01:00
$_[0]->userLogger->error( $_[0]->error );
2017-01-15 23:04:33 +01:00
$_[0]->addUnauthRoute( '*' => 'displayError' );
$_[0]->addAuthRoute( '*' => 'displayError' );
2017-01-05 16:19:57 +01:00
return 0;
}
2017-01-15 23:04:33 +01:00
sub displayError {
my ( $self, $req ) = @_;
return $self->sendError( $req, 'Portal error, contact your administrator',
500 );
}
2016-03-24 07:23:40 +01:00
1;