diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main.pm index cdab8b330..174c285a9 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main.pm @@ -11,6 +11,7 @@ use Lemonldap::NG::Portal::Main::Init; use Lemonldap::NG::Portal::Main::Run; use Lemonldap::NG::Portal::Main::Process; use Lemonldap::NG::Portal::Main::Display; +use Lemonldap::NG::Portal::Main::Menu; our $VERSION = '2.0.0'; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Display.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Display.pm index c22c9559b..ccfee766c 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Display.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Display.pm @@ -75,25 +75,16 @@ sub display { # 1.4 Case : display menu else { - # Initialize menu elements - $self->menuInit; - $skinfile = 'menu'; - my $auth_user = - $req->{sessionInfo}->{ $self->conf->{portalUserAttr} }; - #utf8::decode($auth_user); %templateParams = ( - AUTH_USER => $auth_user, + AUTH_USER => $req->{sessionInfo}->{ $self->conf->{portalUserAttr} }, NEWWINDOW => $self->conf->{portalOpenLinkInNewWindow}, - AUTH_ERROR => $req->errorString( $req->{menuError} ), - AUTH_ERROR_TYPE => $req->error_type( $req->{menuError} ), - DISPLAY_TAB => $self->conf->{menuDisplayTab}, LOGOUT_URL => $self->conf->{portal} . "?logout=1", - DISPLAY_MODULES => $self->conf->{menuDisplayModules} || [], APPSLIST_ORDER => $req->{sessionInfo}->{'appsListOrder'}, PING => $self->conf->{portalPingInterval}, + $self->menu->params($req), ); } @@ -419,10 +410,4 @@ sub getCustomTemplateParameters { return $customTplParams; } -sub menuInit { -} - -sub get_url { -} - 1; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm index d7ee28787..a533bce5a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Init.pm @@ -75,6 +75,7 @@ sub init { ->addUnauthRoute( '*', 'login', ['GET'] ) ->addUnauthRoute( '*', 'postLogin', ['POST'] ) ->addAuthRoute( '*', 'authenticatedRequest', ['GET'] ) + ->addAuthRoute( '*', 'postAuthenticatedRequest', ['POST'] ) # Core REST API ->addUnauthRoute( 'test', 'pleaseAuth', ['GET'] ) @@ -109,6 +110,8 @@ sub reloadConf { $self->{templateDir} = $self->conf->{templateDir} . '/' . $self->conf->{portalSkin}; + $self->{staticPrefix} = $self->conf->{staticPrefix} || '/'; + # Initialize session DBs unless ( $self->conf->{globalStorage} ) { $self->error( @@ -222,6 +225,8 @@ sub reloadConf { foreach my $plugin ( $self->enabledPlugins ) { $self->loadPlugin($plugin) or return 0; } + + $self->menu = $self->loadModule('::Main::Menu'); 1; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Menu.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Menu.pm new file mode 100644 index 000000000..fd166bdb5 --- /dev/null +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Menu.pm @@ -0,0 +1,555 @@ +##@file +# menu for lemonldap::ng portal +package Lemonldap::NG::Portal::Main::Menu; + +use strict; +use Mouse; +use Clone 'clone'; + +extends 'Lemonldap::NG::Portal::Main::Module'; + +has menuModules => ( is => 'rw', builder => sub { + return $_[0]->{conf}->{menuModule} || 'Appslist ChangePassword LoginHistory Logout'; +}); + +has imgPath => ( is => 'rw', builder => sub { + return $_[0]->{conf}->{impgPath} || '/static/logos'; +}); + +# Prepare menu template elements +# Returns hash (=list) containing : +# - DISPLAY_MODULES +# - DISPLAY_TAB +# - AUTH_ERROR +# - AUTH_ERROR_TYPE +sub params { + my ($self,$req) = @_; + $self->{conf}->{imgPath} ||= '/static/'; + +#TODO: Change all after here + + # Modules to display + $self->{menuModules} ||= "Appslist ChangePassword LoginHistory Logout"; + $self->{menuDisplayModules} = $self->displayModules(); + + # Extract password from POST data + $self->{oldpassword} = $self->param('oldpassword'); + $self->{newpassword} = $self->param('newpassword'); + $self->{confirmpassword} = $self->param('confirmpassword'); + $self->{dn} = $self->{sessionInfo}->{dn}; + $self->{user} = $self->{sessionInfo}->{_user}; + + # Try to change password + $self->{menuError} = + $self->_subProcess( + qw(passwordDBInit modifyPassword passwordDBFinish sendPasswordMail)) + unless $self->{ignorePasswordChange}; + + # Default menu error code + $self->{menuError} = PE_PASSWORD_OK if ( $self->{passwordWasChanged} ); + $self->{menuError} ||= $self->{error}; + + # Tab to display + # Get the tab URL parameter + $self->{menuDisplayTab} = $self->param("tab") || "none"; + + # Default to appslist if invalid tab URL parameter + $self->{menuDisplayTab} = "appslist" + unless ( $self->{menuDisplayTab} =~ /^(password|logout|loginHistory)$/ ); + + # Force password tab in case of password error + $self->{menuDisplayTab} = "password" + if ( + ( + scalar( + grep { $_ == $self->{menuError} } ( + 25, #PE_PP_CHANGE_AFTER_RESET + 26, #PE_PP_PASSWORD_MOD_NOT_ALLOWED + 27, #PE_PP_MUST_SUPPLY_OLD_PASSWORD + 28, #PE_PP_INSUFFICIENT_PASSWORD_QUALITY + 29, #PE_PP_PASSWORD_TOO_SHORT + 30, #PE_PP_PASSWORD_TOO_YOUNG + 31, #PE_PP_PASSWORD_IN_HISTORY + 32, #PE_PP_GRACE + 33, #PE_PP_EXP_WARNING + 34, #PE_PASSWORD_MISMATCH + 39, #PE_BADOLDPASSWORD + 74, #PE_MUST_SUPPLY_OLD_PASSWORD + ) + ) + ) + ); + + # Application list for old templates + if ( $self->{useOldMenuItems} ) { + $self->{menuAppslistMenu} = $self->appslistMenu(); + $self->{menuAppslistDesc} = $self->appslistDescription(); + } + + return; +} + +## @method arrayref displayModules() +# List modules that can be displayed in Menu +# @return modules list +sub displayModules { + my $self = shift; + my $displayModules = []; + + # Modules list + my @modules = split( /\s/, $self->{menuModules} ); + + # Foreach module, eval condition + # Store module in result if condition is valid + foreach my $module (@modules) { + my $cond = $self->{ 'portalDisplay' . $module }; + $cond = 1 unless defined $cond; + + $self->lmLog( "Evaluate condition $cond for module $module", 'debug' ); + + if ( $self->safe->reval($cond) ) { + my $moduleHash = { $module => 1 }; + $moduleHash->{'APPSLIST_LOOP'} = $self->appslist() + if ( $module eq 'Appslist' ); + if ( $module eq 'LoginHistory' ) { + $moduleHash->{'SUCCESS_LOGIN'} = + $self->mkSessionArray( + $self->{sessionInfo}->{loginHistory}->{successLogin}, + "", 0, 0 ); + $moduleHash->{'FAILED_LOGIN'} = + $self->mkSessionArray( + $self->{sessionInfo}->{loginHistory}->{failedLogin}, + "", 0, 1 ); + } + push @$displayModules, $moduleHash; + } + } + + return $displayModules; +} + +## @method arrayref appslist() +# Returns categories and applications list as HTML::Template loop +# @return categories and applications list +sub appslist { + my ($self) = @_; + my $appslist = []; + + return $appslist unless defined $self->{applicationList}; + + # Reset level + my $catlevel = 0; + + my $applicationList = clone( $self->{applicationList} ); + my $filteredList = $self->_filter($applicationList); + push @$appslist, $self->_buildCategoryHash( "", $filteredList, $catlevel ); + + # We must return an ARRAY ref + return ( ref $appslist->[0]->{categories} eq "ARRAY" ) + ? $appslist->[0]->{categories} + : []; +} + +## @method private hashref _buildCategoryHash(string catname,hashref cathash, int catlevel) +# Build hash for a category +# @param catname Category name +# @param cathash Hash of category elements +# @param catlevel Category level +# @return Category Hash +sub _buildCategoryHash { + my ( $self, $catid, $cathash, $catlevel ) = @_; + my $catname = $cathash->{catname} || $catid; + my $applications; + my $categories; + + # Extract applications from hash + my $apphash; + foreach my $catkey ( sort keys %$cathash ) { + next if $catkey =~ /(type|options|catname)/; + if ( $cathash->{$catkey}->{type} eq "application" ) { + $apphash->{$catkey} = $cathash->{$catkey}; + } + } + + # Display applications first + if ( scalar keys %$apphash > 0 ) { + foreach my $appkey ( sort keys %$apphash ) { + push @$applications, + $self->_buildApplicationHash( $appkey, $apphash->{$appkey} ); + } + } + + # Display subcategories + foreach my $catkey ( sort keys %$cathash ) { + next if $catkey =~ /(type|options|catname)/; + if ( $cathash->{$catkey}->{type} eq "category" ) { + push @$categories, + $self->_buildCategoryHash( $catkey, $cathash->{$catkey}, + $catlevel + 1 ); + } + } + + my $categoryHash = { + category => 1, + catname => $catname, + catid => $catid, + catlevel => $catlevel + }; + $categoryHash->{applications} = $applications if $applications; + $categoryHash->{categories} = $categories if $categories; + return $categoryHash; +} + +## @method private hashref _buildApplicationHash(string appid, hashref apphash) +# Build hash for an application +# @param $appid Application ID +# @param $apphash Hash of application elements +# @return Application Hash +sub _buildApplicationHash { + my ( $self, $appid, $apphash ) = @_; + my $applications; + + # Get application items + my $appname = $apphash->{options}->{name} || $appid; + my $appuri = $apphash->{options}->{uri} || ""; + my $appdesc = $apphash->{options}->{description}; + my $applogo = $apphash->{options}->{logo}; + + # Detect sub applications + my $subapphash; + foreach my $key ( sort keys %$apphash ) { + next if $key =~ /(type|options|catname)/; + if ( $apphash->{$key}->{type} eq "application" ) { + $subapphash->{$key} = $apphash->{$key}; + } + } + + # Display sub applications + if ( scalar keys %$subapphash > 0 ) { + foreach my $appkey ( sort keys %$subapphash ) { + push @$applications, + $self->_buildApplicationHash( $appkey, $subapphash->{$appkey} ); + } + } + + my $applicationHash = { + application => 1, + appname => $appname, + appuri => $appuri, + appdesc => $appdesc, + applogo => $applogo, + appid => $appid, + }; + $applicationHash->{applications} = $applications if $applications; + return $applicationHash; +} + +## @method string appslistMenu() +# Returns HTML code for application list menu. +# @return HTML string +sub appslistMenu { + my $self = shift; + + # We no more use XML file for menu configuration + unless ( defined $self->{applicationList} ) { + $self->abort( + "XML menu configuration is deprecated", +"Please use lmMigrateConfFiles2ini to migrate your menu configuration" + ); + } + + # Use configuration to get menu parameters + my $applicationList = clone( $self->{applicationList} ); + my $filteredList = $self->_filter($applicationList); + + return $self->_displayConfCategory( "", $filteredList, $catlevel ); +} + +## @method string appslistDescription() +# Returns HTML code for application description. +# @return HTML string +sub appslistDescription { + my $self = shift; + + # We no more use XML file for menu configuration + unless ( defined $self->{applicationList} ) { + $self->lmLog( +"XML menu configuration is deprecated. Please use lmMigrateConfFiles2ini to migrate your menu configuration", + 'error' + ); + return " "; + } + + # Use configuration to get menu parameters + my $applicationList = clone( $self->{applicationList} ); + return $self->_displayConfDescription( "", $applicationList ); +} + +## @method string _displayConfCategory(string catname, hashref cathash, int catlevel) +# Creates and returns HTML code for a category. +# @param catname Category name +# @param cathash Hash of category elements +# @param catlevel Category level +# @return HTML string +sub _displayConfCategory { + my ( $self, $catname, $cathash, $catlevel ) = @_; + my $html; + my $key; + + # Init HTML list + $html .= "\n"; + + return $html; +} + +## @method private string _displayConfApplication(string appid, hashref apphash) +# Creates HTML code for an application. +# @param $appid Application ID +# @param $apphash Hash of application elements +# @return HTML string +sub _displayConfApplication { + my $self = shift; + my ( $appid, $apphash ) = @_; + my $html; + my $key; + + # Get application items + my $appname = $apphash->{options}->{name} || $appid; + my $appuri = $apphash->{options}->{uri} || ""; + + # Display application + $html .= + "
  • " + . ( $appuri ? "$appname" : "$appname" ) + . "\n"; + + # Detect sub applications + my $subapphash; + foreach $key ( keys %$apphash ) { + next if $key =~ /(type|options|catname)/; + if ( $apphash->{$key}->{type} eq "application" ) { + $subapphash->{$key} = $apphash->{$key}; + } + } + + # Display sub applications + if ( scalar keys %$subapphash > 0 ) { + $html .= ""; + } + + $html .= "
  • "; + return $html; +} + +## @method private string _displayConfDescription(string appid, hashref apphash) +# Create HTML code for application description. +# @param $appid Application ID +# @param $apphash Hash +# @return HTML string +sub _displayConfDescription { + my $self = shift; + my ( $appid, $apphash ) = @_; + my $html = ""; + my $key; + + if ( defined $apphash->{type} and $apphash->{type} eq "application" ) { + + # Get application items + my $appname = $apphash->{options}->{name} || $appid; + my $appuri = $apphash->{options}->{uri} || ""; + my $appdesc = $apphash->{options}->{description}; + my $applogofile = $apphash->{options}->{logo}; + my $applogo = $self->imgPath . $applogofile + if $applogofile; + + # Display application description + $html .= "
    \n"; + $html .= +"\"$appid\n" + if $applogofile; + $html .= "

    $appname

    \n" if defined $appname; + $html .= "

    $appdesc

    \n" if defined $appdesc; + $html .= "
    \n"; + } + + # Sublevels + foreach $key ( keys %$apphash ) { + next if $key =~ /(type|options|catname)/; + $html .= $self->_displayConfDescription( $key, $apphash->{$key} ); + } + + return $html; +} + +## @method private string _filter(hashref apphash) +# Duplicate hash reference +# Remove unauthorized menu elements +# Hide empty categories +# @param $apphash Menu elements +# @return filtered hash +sub _filter { + my ( $self, $apphash ) = @_; + my $filteredHash; + my $key; + + # Copy hash reference into a new hash + foreach $key ( keys %$apphash ) { + $filteredHash->{$key} = $apphash->{$key}; + } + + # Filter hash + $self->_filterHash($filteredHash); + + # Hide empty categories + $self->_isCategoryEmpty($filteredHash); + + return $filteredHash; +} + +## @method private string _filterHash(hashref apphash) +# Remove unauthorized menu elements +# @param $apphash Menu elements +# @return filtered hash +sub _filterHash { + my $self = shift; + my ($apphash) = @_; + my $key; + my $appkey; + + foreach $key ( keys %$apphash ) { + next if $key =~ /(type|options|catname)/; + if ( $apphash->{$key}->{type} + and $apphash->{$key}->{type} eq "category" ) + { + + # Filter the category + $self->_filterHash( $apphash->{$key} ); + } + if ( $apphash->{$key}->{type} + and $apphash->{$key}->{type} eq "application" ) + { + + # Find sub applications and filter them + foreach $appkey ( keys %{ $apphash->{$key} } ) { + next if $appkey =~ /(type|options|catname)/; + + # We have sub elements, so we filter them + $self->_filterHash( $apphash->{$key} ); + } + + # Check rights + my $appdisplay = $apphash->{$key}->{options}->{display} + || "auto"; + my $appuri = $apphash->{$key}->{options}->{uri}; + + # Remove if display is "no" or "off" + delete $apphash->{$key} and next if ( $appdisplay =~ /^(no|off)$/ ); + + # Keep node if display is "yes" or "on" + next if ( $appdisplay =~ /^(yes|on)$/ ); + + # Check grant function if display is "auto" (this is the default) + delete $apphash->{$key} unless ( $self->_grant($appuri) ); + next; + } + } + +} + +## @method private void _isCategoryEmpty(hashref apphash) +# Check if a category is empty +# @param $apphash Menu elements +# @return boolean +sub _isCategoryEmpty { + my $self = shift; + my ($apphash) = @_; + my $key; + + # Test sub categories + foreach $key ( keys %$apphash ) { + next if $key =~ /(type|options|catname)/; + if ( $apphash->{$key}->{type} + and $apphash->{$key}->{type} eq "category" ) + { + delete $apphash->{$key} + if $self->_isCategoryEmpty( $apphash->{$key} ); + } + } + + # Test this category + if ( $apphash->{type} and $apphash->{type} eq "category" ) { + + # Temporary store 'options' + my $tmp_options = $apphash->{options}; + my $tmp_catname = $apphash->{catname}; + + delete $apphash->{type}; + delete $apphash->{options}; + delete $apphash->{catname}; + + if ( scalar( keys %$apphash ) ) { + + # There are sub categories or sub applications + # Restore type and options + $apphash->{type} = "category"; + $apphash->{options} = $tmp_options; + $apphash->{catname} = $tmp_catname; + + # Return false + return 0; + } + else { + + # Return true + return 1; + } + } + return 0; +} + +1; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm index 2e9fff8dd..34c06267e 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm @@ -86,6 +86,12 @@ sub authenticatedRequest { return $self->do( $req, [ 'controlUrl', @{ $self->forAuthUser } ] ); } +sub postAuthenticatedRequest { + my ( $self, $req ) = @_; + return $self->do( $req, + [ 'restoreArgs', 'controlUrl', @{ $self->forAuthUser } ] ); +} + # RUNNING METHODS # ---------------