# Manager main component # # This package contains these parts: # - Properties and private methods # - Initialization method (launched by Lemonldap::NG::Common::PSGI) # that declares routes # - Upload methods (launched by Lemonldap::NG::Common::PSGI::Router) # # It inherits from Conf.pm to responds to display methods and from # Sessions.pm to manage sessions package Lemonldap::NG::Manager; use strict; use utf8; use Mouse; use JSON; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; our $VERSION = '2.0.15.1'; extends qw( Lemonldap::NG::Handler::PSGI::Router Lemonldap::NG::Common::Conf::AccessLib ); has csp => ( is => 'rw' ); has loadedPlugins => ( is => 'rw', default => sub { [] } ); has hLoadedPlugins => ( is => 'rw', default => sub { {} } ); ## @method boolean init($args) # Launch initialization method # # @param $args hashref to merge with object # @return 0 in case of error, 1 else sub init { my ( $self, $args ) = @_; $args ||= {}; if ( my $localconf = $self->confAcc->getLocalConf(MANAGERSECTION) ) { foreach ( keys %$localconf ) { $args->{$_} //= $localconf->{$_}; $self->{$_} = $args->{$_} unless (/^(?:l|userL)ogger$/); } } # Manager needs to keep new Ajax behaviour $args->{noAjaxHook} = 0; return 0 unless ( $self->Lemonldap::NG::Handler::PSGI::Router::init($args) ); # TODO: manage errors unless ( -r $self->{templateDir} ) { $self->error("Unable to read $self->{templateDir}"); return 0; } my $conf = $self->confAcc->getConf; $conf->{$_} = $args->{$_} foreach ( keys %$args ); $self->{enabledModules} ||= "conf, sessions, notifications, 2ndFA, api"; my @links; my @enabledModules = map { my @res = ( "Lemonldap::NG::Manager::" . ucfirst($_) ); if ( my $tmp = $self->loadPlugin( @res, $conf ) ) { $self->logger->debug("Plugin $_ loaded"); push @links, $_; push @{ $self->loadedPlugins }, $tmp; $self->hLoadedPlugins->{$_} = $tmp; } else { $self->logger->error("Unable to load $_, skipping"); @res = (); } (@res); } split( /[,\s]+/, $self->{enabledModules} ); unless (@enabledModules) { $self->logger->error('No plugins loaded, aborting'); return 0; } unless ($conf) { require Lemonldap::NG::Manager::Conf::Zero; $conf = Lemonldap::NG::Manager::Conf::Zero::zeroConf(); } # TODO: -> loadPlugin #for ( my $i = 0 ; $i < @enabledModules ; $i++ ) { # my $mod = $enabledModules[$i]; # no strict 'refs'; # if ( &{"${mod}::addRoutes"}( $self, $conf ) ) { # $self->logger->debug("Module $mod enabled"); # push @working, $mod; # } # else { # $links[$i] = undef; # $self->logger->error( # "Module $mod can not be enabled: " . $self->error ); # } #} $self->addRoute( links => 'links', ['GET'] ); $self->addRoute( 'psgi.js' => 'sendJs', ['GET'] ); my $portal = $conf->{portal}; $portal =~ s#https?://([^/]*).*#$1#; $self->csp( "default-src 'self' $portal;frame-ancestors 'none';form-action 'self';" ); # Avoid restricted users to access configuration by default route my $defaultMod = $self->{defaultModule} = $self->{defaultModule} || $enabledModules[0]; $self->logger->debug("Default module -> $defaultMod"); my ($index) = grep { $enabledModules[$_] eq $defaultMod } ( 0 .. $#enabledModules ); $index //= 0; $self->logger->debug("Default index -> $index"); $self->defaultRoute( $self->loadedPlugins->[$index]->defaultRoute ); # Find out more glyphicones at https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp my $linksIcons = { 'conf' => 'cog', 'sessions' => 'duplicate', 'notifications' => 'bell', '2ndFA' => 'wrench', 'viewer' => 'eye-open', }; $self->links( [] ); for ( my $i = 0 ; $i < @links ; $i++ ) { next unless ( defined $links[$i] ); push @{ $self->links }, { target => $self->loadedPlugins->[$i]->defaultRoute, title => $links[$i], icon => $linksIcons->{ $links[$i] } }; } $self->menuLinks( [] ); if ( my $portal = $conf->{cfgNum} ? Lemonldap::NG::Handler::PSGI::Main->tsv->{portal}->() : $conf->{portal} ) { push @{ $self->menuLinks }, { target => $portal, title => 'backtoportal', icon => 'home' }, { target => "$portal?logout=1", title => 'logout', icon => 'log-out' }; } 1; } sub tplParams { my ( $self, $req ) = @_; my $res = eval { $self->hLoadedPlugins->{viewer}->brwRule->( $req, $req->{userData} ); } || 0; return ( VERSION => $VERSION, ALLOWBROWSER => $res, DOC_PREFIX => ( $self->{docPrefix} || '/doc' ), ); } sub javascript { my ( $self, $req ) = @_; my $res = eval { $self->hLoadedPlugins->{viewer} && $self->hLoadedPlugins->{viewer} ->diffRule->( $req, $req->{userData} ); } || 0; print STDERR $@ if $@; my $impPrefix = $self->{impersonationPrefix} || 'real_'; my $ttl = $self->{timeout} || 72000; return 'var formPrefix=staticPrefix+"forms/";var confPrefix=scriptname+"confs/";var viewPrefix=scriptname+"view/";' . "var allowDiff=$res;" . "var sessionTTL=$ttl;" . "var impPrefix='$impPrefix';" . ( $self->links ? 'var links=' . to_json( $self->links ) . ';' : '' ) . ( $self->menuLinks ? 'var menulinks=' . to_json( $self->menuLinks ) . ';' : '' ); } sub sendHtml { my ( $self, $req, $template, %args ) = @_; my $res = $self->SUPER::sendHtml( $req, $template, %args ); push @{ $res->[1] }, 'Content-Security-Policy' => $self->csp, 'X-Content-Type-Options' => 'nosniff', 'X-Frame-Options' => 'DENY', 'X-XSS-Protection' => '1; mode=block'; return $res; } sub loadPlugin { my ( $self, $plugin, $conf ) = @_; unless ($plugin) { require Carp; Carp::confess('Calling loadPugin without arg !'); } my $obj; $plugin = "Lemonldap::NG::Manager$plugin" if ( $plugin =~ /^::/ ); eval "require $plugin"; if ($@) { $self->logger->error("$plugin load error: $@"); return 0; } eval { $obj = $plugin->new( { p => $self, conf => $conf } ); $self->logger->debug("Module $plugin loaded"); }; if ($@) { $self->error("Unable to build $plugin object: $@"); return 0; } ( $obj and $obj->init($conf) ) or return 0; return $obj; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager - Perl extension for managing Lemonldap::NG Web-SSO system. =head1 SYNOPSIS Use any of Plack launcher. Example: #!/usr/bin/env plackup use Lemonldap::NG::Manager; # This must be the last instruction ! See PSGI for more Lemonldap::NG::Manager->run($opts); =head1 DESCRIPTION Lemonldap::NG::Manager provides a web interface to manage Lemonldap::NG Web-SSO system. The Perl part of Lemonldap::NG::Manager is the REST server. Web interface is written in Javascript, using AngularJS framework and can be found in `site` directory. The REST API is described in REST-API.md file provided in source tree. Lemonldap::NG Manager uses L to be CGI, FastCGI and so on compatible. It inherits of L =head1 ORGANIZATION Lemonldap::NG Manager contains 6 parts: =over =item Configuration management =item Session explorer =item Notification explorer =item Second Factors manager =item Configuration builder (see L =item Command line interface (see L =back =head2 Static files generation `scripts/jsongenerator.pl` file uses Lemonldap::NG::Manager::Build::Attributes, Lemonldap::NG::Manager::Build::Tree and Lemonldap::NG::Manager::Build::CTrees to generate =over =item `site/htdocs/static/struct.json`: main file containing the tree view; =item `site/htdocs/static/js/conftree.js`: generates Virtualhosts, SAML and OpenID-Connect partners sub-trees; =item `Lemonldap::NG::Common::Conf::ReConstants`: constants used by all Perl manager components; =item `Lemonldap::NG::Common::Conf::DefaultValues`: constants used to read configuration. =back =head1 PARAMETERS You can use a hash ref to override any LemonLDAP::NG parameter. Currently, you can specify where your lemonldap-ng.ini file is: Lemonldap::NG::Manager->run( { confFile => '/path/to/lemonldap-ng.ini' } ); =head2 lemonldap-ng.ini parameters You can override any configuration parameter in lemonldap-ng.ini, but some are required and can't be set to global configuration (as any Lemonldap::NG module, you can also fix them in $opts hash ref passed as argument to run() or new()). [manager] ;protection: choose one of none, authenticate, manager as explain in ; Lemonldap::NG::Handler::PSGI::Router doc. protection = manager ;enabledModules: Modules to display. Default to `conf, sessions, notifications, 2ndFA` enabledModules = conf, sessions, notifications, 2ndFA ;logLevel: choose one of error, warn, notice, info, debug ; See Lemonldap::NG::Common::PSGI doc for more logLevel = notice ;staticPrefix: set here the URI path to static content ; See Lemonldap::NG::Common::PSGI doc for more staticPrefix = static/ ;languages: Available interface languages languages = en, fr ;templateDir: path to the directory containing HTML templates ; See Lemonldap::NG::Common::PSGI doc for more templateDir = /usr/share/lemonldap-ng/manager/ =head1 SEE ALSO L =head1 AUTHORS =over =item LemonLDAP::NG team L =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L Note that if you want to post a ticket for a conf upload problem, please see L before. =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE See COPYING file for details. This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see L. =cut