diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm index abb4744e1..8f01267a2 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm @@ -397,47 +397,60 @@ sub getDBConf { return $conf; } +sub _launch { + my $self = shift; + my $sub = shift; + my $res; + eval { + local $SIG{ALRM} = sub { die "TIMEOUT\n" }; + alarm ($self->{confTimeout} || 10); + $res = &{ $self->{type} . "::$sub" }( $self, @_ ); + alarm 0; + }; + $msg .= $@ if $@; + return $res; +} + ## @method boolean prereq() # Call prereq() from the $self->{type} package. # @return True if succeed sub prereq { - return &{ $_[0]->{type} . '::prereq' }(@_); + return shift->_launch( 'prereq', @_ ); } ## @method @ available() # Call available() from the $self->{type} package. # @return list of available configuration numbers sub available { - return &{ $_[0]->{type} . '::available' }(@_); + return shift->_launch( 'available', @_ ); } ## @method int lastCfg() # Call lastCfg() from the $self->{type} package. # @return Number of the last configuration available sub lastCfg { - my $result = &{ $_[0]->{type} . '::lastCfg' }(@_) || "0"; - return $result; + return shift->_launch( 'lastCfg', @_ ) || 0; } ## @method boolean lock() # Call lock() from the $self->{type} package. # @return True if succeed sub lock { - return &{ $_[0]->{type} . '::lock' }(@_); + return shift->_launch( 'lock', @_ ); } ## @method boolean isLocked() # Call isLocked() from the $self->{type} package. # @return True if database is locked sub isLocked { - return &{ $_[0]->{type} . '::isLocked' }(@_); + return shift->_launch( 'isLocked', @_ ); } ## @method boolean unlock() # Call unlock() from the $self->{type} package. # @return True if succeed sub unlock { - return &{ $_[0]->{type} . '::unlock' }(@_); + return shift->_launch( 'unlock', @_ ); } ## @method int store(hashRef conf) @@ -445,14 +458,14 @@ sub unlock { # @param $conf Lemondlap configuration serialized # @return Number of new configuration stored if succeed, 0 else. sub store { - return &{ $_[0]->{type} . '::store' }(@_); + return shift->_launch( 'store', @_ ); } ## @method load(int cfgNum, arrayRef fields) # Call load() from the $self->{type} package. # @return Lemonldap::NG Configuration hashRef if succeed, 0 else. sub load { - return &{ $_[0]->{type} . '::load' }(@_); + return shift->_launch( 'load', @_ ); } ## @method boolean delete(int cfgNum) @@ -463,7 +476,7 @@ sub delete { my ( $self, $c ) = @_; my @a = $self->available(); if ( grep( /^$c$/, @a ) ) { - return &{ $self->{type} . '::delete' }( $self, $c ); + return $self->_launch( 'delete', $self, $c ); } else { return 0; @@ -471,7 +484,7 @@ sub delete { } sub logError { - return &{ $_[0]->{type} . '::logError' }(@_); + return shift->_launch( 'logError', @_ ); } 1; diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session.pm index b7b40bc92..b7b8ac901 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session.pm @@ -82,6 +82,8 @@ has 'error' => ( has info => ( is => 'rw' ); +has timeout => ( is => 'rw', default => 5 ); + sub BUILD { my ($self) = @_; @@ -93,7 +95,9 @@ sub BUILD { # Register options for common Apache::Session module my $moduleOptions = $self->storageModuleOptions || {}; - my %options = ( + $self->timeout( delete $moduleOptions->{timeout} ) + if $moduleOptions->{timeout}; + my %options = ( %$moduleOptions, backend => $self->storageModule, localStorage => $self->cacheModule, @@ -158,10 +162,12 @@ sub BUILD { sub _tie_session { my $self = $_[0]; my $options = $_[1] || {}; - my %h; eval { + local $SIG{ALRM} = sub { die "TIMEOUT\n" }; + alarm $self->timeout; + # SOAP/REST session module must be directly tied if ( $self->storageModule =~ /^Lemonldap::NG::Common::Apache::Session/ ) { @@ -172,8 +178,9 @@ sub _tie_session { tie %h, 'Lemonldap::NG::Common::Apache::Session', $self->id, { %{ $self->options }, %$options }; } - }; + alarm 0; + }; if ( $@ or not tied(%h) ) { my $msg = "Session cannot be tied"; $msg .= ": $@" if $@; diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/Fail.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/Fail.pm new file mode 100644 index 000000000..52f3d1314 --- /dev/null +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/Fail.pm @@ -0,0 +1,11 @@ +package Lemonldap::NG::Handler::Lib::Fail; + +use base Lemonldap::NG::Handler::Main; + +sub run { + return $_[0]->SERVER_ERROR; +} + +our $VERSION = '2.0.6'; + +1; diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/PSGI.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/PSGI.pm index 43b12899e..f1f2bf42a 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/PSGI.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/PSGI.pm @@ -21,7 +21,7 @@ sub init { return 0; } unless ( $self->api->checkConf($self) - or $self->{protection} eq 'none' ) + or ( $self->{protection} and $self->{protection} eq 'none' ) ) { $self->error( "Unable to protect this server ($Lemonldap::NG::Common::Conf::msg)" @@ -127,7 +127,8 @@ sub _authAndTrace { $type = $tmp . $type; Lemonldap::NG::Handler::Main->buildAndLoadType($type); my ( $res, $session ) = $type->run( $req, $self->{rule} ); - $self->portal( $type->tsv->{portal}->() ); + eval { $self->portal( $type->tsv->{portal}->() ) }; + $self->logger->warn($@) if $@; $req->userData($session) if ($session); if ( $res < 300 ) { @@ -145,7 +146,7 @@ sub _authAndTrace { return [ $res, [ $req->spliceHdrs ], [] ]; } else { - my $s = $type->tsv->{portal}->() . "/lmerror/$res"; + my $s = ( $self->portal ? $self->portal . "/lmerror/$res" : '' ); $s = 'Redirection' . qq{} diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm index 1976606b6..e8e91599b 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm @@ -83,8 +83,11 @@ sub checkType { my ( $class, $req ) = @_; if ( time() - $class->lastCheck > $class->checkTime ) { - die("$class: No configuration found") - unless ( $class->checkConf ); + unless ( $class->checkConf ) { + $class->logger->error("$class: No configuration found"); + $req->data->{noTry} = 1; + return 'Fail'; + } } my $vhost = $class->resolveAlias($req); return ( defined $class->tsv->{type}->{$vhost} ) diff --git a/lemonldap-ng-manager/site/coffee/sessions.coffee b/lemonldap-ng-manager/site/coffee/sessions.coffee index 4608dc5f2..1ccd08866 100644 --- a/lemonldap-ng-manager/site/coffee/sessions.coffee +++ b/lemonldap-ng-manager/site/coffee/sessions.coffee @@ -60,6 +60,16 @@ schemes = (t,v,q) -> q.replace(/\&groupBy.*$/, '') + "&ipAddr=#{v}" ] + _session_uid: [ + # First level: display 1 letter + (t,v) -> + "groupBy=substr(#{t},1)" + # Second level (if no overScheme), display usernames + (t,v) -> + "#{t}=#{v}*&groupBy=#{t}" + (t,v) -> + "#{t}=#{v}" + ] # When number of children nodes exceeds "max" value and if "overScheme." # is available and does not return "null", a level is added. See @@ -86,6 +96,12 @@ overScheme = "#{t}=#{v}*&groupBy=substr(#{t},#{(10+level+over)})" else null + _session_uid: (t,v,level,over) -> + console.log 'overSchema => level', level, 'over', over + if level == 1 and v.length > over + "#{t}=#{v}*&groupBy=substr(#{t},#{(level+over+1)})" + else + null hiddenAttributes = '_password' diff --git a/lemonldap-ng-manager/site/htdocs/static/js/sessions.js b/lemonldap-ng-manager/site/htdocs/static/js/sessions.js index 51b9120e1..f4f5a6c3d 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/sessions.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/sessions.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.8 +// Generated by CoffeeScript 1.12.7 /* * Sessions explorer @@ -69,6 +69,15 @@ }, function(t, v, q) { return q.replace(/\&groupBy.*$/, '') + ("&ipAddr=" + v); } + ], + _session_uid: [ + function(t, v) { + return "groupBy=substr(" + t + ",1)"; + }, function(t, v) { + return t + "=" + v + "*&groupBy=" + t; + }, function(t, v) { + return t + "=" + v; + } ] }; @@ -96,6 +105,14 @@ } else { return null; } + }, + _session_uid: function(t, v, level, over) { + console.log('overSchema => level', level, 'over', over); + if (level === 1 && v.length > over) { + return t + "=" + v + "*&groupBy=substr(" + t + "," + (level + over + 1) + ")"; + } else { + return null; + } } }; diff --git a/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js b/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js index f6e4233cc..7c3208eb7 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js @@ -1 +1 @@ -(function(){var M,s,h,f;f={_whatToTrace:[function(e,t){return"groupBy=substr("+e+",1)"},function(e,t){return e+"="+t+"*&groupBy="+e},function(e,t){return e+"="+t}],ipAddr:[function(e,t){return"groupBy=net("+e+",16,1)"},function(e,t){return t.match(/:/)||(t+="."),e+"="+t+"*&groupBy=net("+e+",32,2)"},function(e,t){return t.match(/:/)||(t+="."),e+"="+t+"*&groupBy=net("+e+",48,3)"},function(e,t){return t.match(/:/)||(t+="."),e+"="+t+"*&groupBy=net("+e+",128,4)"},function(e,t){return e+"="+t+"&groupBy=_whatToTrace"},function(e,t,n){return n.replace(/\&groupBy.*$/,"")+"&_whatToTrace="+t}],_startTime:[function(e,t){return"groupBy=substr("+e+",8)"},function(e,t){return e+"="+t+"*&groupBy=substr("+e+",10)"},function(e,t){return e+"="+t+"*&groupBy=substr("+e+",11)"},function(e,t){return e+"="+t+"*&groupBy=substr("+e+",12)"},function(e,t){return e+"="+t+"*&groupBy=_whatToTrace"},function(e,t,n){return console.log(e),console.log(t),console.log(n),n.replace(/\&groupBy.*$/,"")+"&_whatToTrace="+t}],doubleIp:[function(e,t){return e},function(e,t){return"_whatToTrace="+t+"&groupBy=ipAddr"},function(e,t,n){return n.replace(/\&groupBy.*$/,"")+"&ipAddr="+t}]},h={_whatToTrace:function(e,t,n,o){return console.log("overSchema => level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null},ipAddr:function(e,t,n,o){return console.log("overSchema => level",n,"over",o),0 level",n,"over",o),3t.title?1:e.title real attribute"),D.push(u)):C.push(u);return R=C.concat(D),P.push({title:"__attributesAndMacros__",nodes:R}),{_utime:k,id:c,nodes:P}},t=(H.currentScope=e).$modelValue.session,o.get(scriptname+"sessions/"+g+"/"+t).then(function(e){return H.currentSession=n(e.data)}),H.showT=!1},H.localeDate=function(e){return new Date(1e3*e).toLocaleString()},H.isValid=function(e,t){var n,o,r;return r=i.path(),o=Date.now()/1e3,console.log("Path",r),console.log("Session epoch",e),console.log("Current date",o),console.log("Session TTL",sessionTTL),n=o-e level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null},ipAddr:function(e,t,n,o){return console.log("overSchema => level",n,"over",o),0 level",n,"over",o),3 level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null}},M={dateTitle:["_utime","_startTime","_updateTime","_lastAuthnUTime","_lastSeen"],connectionTitle:["ipAddr","_timezone","_url"],authenticationTitle:["_session_id","_user","_password","authenticationLevel"],modulesTitle:["_auth","_userDB","_passwordDB","_issuerDB","_authChoice","_authMulti","_userDBMulti"],saml:["_idp","_idpConfKey","_samlToken","_lassoSessionDump","_lassoIdentityDump"],groups:["groups","hGroups"],ldap:["dn"],BrowserID:["_browserIdAnswer","_browserIdAnswerRaw"],OpenIDConnect:["_oidc_id_token","_oidc_OP","_oidc_access_token"],sfaTitle:["_2fDevices"],oidcConsents:["_oidcConsents"]},s={session:[{title:"deleteSession",icon:"trash"}],home:[]},angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(H,t,i,e,o){var d,n,r,g;return H.links=links,H.menulinks=menulinks,H.staticPrefix=staticPrefix,H.scriptname=scriptname,H.formPrefix=formPrefix,H.impPrefix=impPrefix,H.sessionTTL=sessionTTL,H.availableLanguages=availableLanguages,H.waiting=!0,H.showM=!1,H.showT=!0,H.data=[],H.currentScope=null,H.currentSession=null,H.menu=s,H.translateP=t.translateP,H.translate=t.translate,H.translateTitle=function(e){return t.translateField(e,"title")},g="global",H.menuClick=function(e){if(e.popup)window.open(e.popup);else switch(e.action||(e.action=e.title),typeof e.action){case"function":e.action(H.currentNode,H);break;case"string":H[e.action]();break;default:console.log(typeof e.action)}return H.showM=!1},H.deleteOIDCConsent=function(e,t){return angular.element(".data-"+t).remove(),H.waiting=!0,o.delete(scriptname+"sessions/OIDCConsent/"+g+"/"+H.currentSession.id+"?rp="+e+"&epoch="+t).then(function(e){return H.waiting=!1},function(e){return H.waiting=!1}),H.showT=!1},H.deleteSession=function(){return H.waiting=!0,o.delete(scriptname+"sessions/"+g+"/"+H.currentSession.id).then(function(e){return H.currentSession=null,H.currentScope.remove(),H.waiting=!1},function(e){return H.currentSession=null,H.currentScope.remove(),H.waiting=!1})},H.stoggle=function(e){var t;return 0===(t=e.$modelValue).nodes.length&&H.updateTree(t.value,t.nodes,t.level,t.over,t.query,t.count),e.toggle()},H.displaySession=function(e){var t,n;return n=function(s){var e,t,n,o,r,i,u,a,l,c,p,d,g,h,f,_,m,T,y,w,v,S,$,B,b,D,L,A,P,x,C,I,k,O,R,E;for(g in e=function(e,t){var n,o,r,i;for(n in r=[],o=new RegExp(e),s)i=s[n],n.match(o)&&i&&(r.push({title:n,value:i}),delete s[n]);if(0t.title?1:e.title real attribute"),D.push(u)):C.push(u);return R=C.concat(D),P.push({title:"__attributesAndMacros__",nodes:R}),{_utime:k,id:c,nodes:P}},t=(H.currentScope=e).$modelValue.session,o.get(scriptname+"sessions/"+g+"/"+t).then(function(e){return H.currentSession=n(e.data)}),H.showT=!1},H.localeDate=function(e){return new Date(1e3*e).toLocaleString()},H.isValid=function(e,t){var n,o,r;return r=i.path(),o=Date.now()/1e3,console.log("Path",r),console.log("Session epoch",e),console.log("Current date",o),console.log("Session TTL",sessionTTL),n=o-enew( { + confFailure => 1, + ini => { + configStorage => { + type => 'Timeout', + dirName => 't', + confTimeout => 1, + }, + logLevel => 'error', + useSafeJail => 1, + globalStorage => 'Apache::Session::Timeout', + globalStorageOptions => { + Directory => $tmpDir, + LockDirectory => "$tmpDir/lock", + timeout => 1, + }, + } + } +); + +diag "Waiting"; +ok( !$client->{p}->init( $client->ini ) ); +ok( $client->app( $client->{p}->run ) ); +ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23, + ), + 'Auth query' +); +ok( $res->[0] == 500 ); +count(4); +clean_sessions(); + +done_testing( count() ); diff --git a/lemonldap-ng-portal/t/03-SessionTimeout.t b/lemonldap-ng-portal/t/03-SessionTimeout.t new file mode 100644 index 000000000..6b3a1c34d --- /dev/null +++ b/lemonldap-ng-portal/t/03-SessionTimeout.t @@ -0,0 +1,41 @@ +use Test::More; +use strict; +use IO::String; +use lib 't/lib'; + +require 't/test-lib.pm'; + +my $res; + +my $client = LLNG::Manager::Test->new( + { + ini => { + logLevel => 'error', + useSafeJail => 1, + globalStorage => 'Apache::Session::Timeout', + globalStorageOptions => { + Directory => 't/sessions', + LockDirectory => 't/sessions/lock', + timeout => 1, + }, + } + } +); + +# Try to authenticate with good password +# -------------------------------------- +diag 'Waiting'; +ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23, + ), + 'Auth query' +); +count(1); +expectReject( $res, 401, 8 ); + +clean_sessions(); + +done_testing( count() ); diff --git a/lemonldap-ng-portal/t/lib/Apache/Session/Timeout.pm b/lemonldap-ng-portal/t/lib/Apache/Session/Timeout.pm new file mode 100644 index 000000000..7c161f8a0 --- /dev/null +++ b/lemonldap-ng-portal/t/lib/Apache/Session/Timeout.pm @@ -0,0 +1,14 @@ +package Apache::Session::Timeout; + +use strict; +use Apache::Session::File; + +our @ISA = ('Apache::Session::File'); + +sub populate { + my $self = shift; + sleep 6; + return $self->SUPER::populate(@_); +} + +1; diff --git a/lemonldap-ng-portal/t/lib/Lemonldap/NG/Common/Conf/Backends/Timeout.pm b/lemonldap-ng-portal/t/lib/Lemonldap/NG/Common/Conf/Backends/Timeout.pm new file mode 100644 index 000000000..516e558ec --- /dev/null +++ b/lemonldap-ng-portal/t/lib/Lemonldap/NG/Common/Conf/Backends/Timeout.pm @@ -0,0 +1,17 @@ +package Lemonldap::NG::Common::Conf::Backends::Timeout; + +use Lemonldap::NG::Common::Conf::Backends::File; +our @ISA = ('Lemonldap::NG::Common::Conf::Backends::File'); + +sub load { + my $self = shift; + sleep 5; + return $self->SUPER::load(@_); +} + +sub AUTOLOAD { + $AUTOLOAD =~ s/Lemonldap::NG::Common::Conf::Backends::Timeout:://; + return &{"Lemonldap::NG::Common::Conf::Backends::File::$AUTOLOAD"}(@_); +} + +1; diff --git a/lemonldap-ng-portal/t/test-lib.pm b/lemonldap-ng-portal/t/test-lib.pm index 77d556113..92b3dddf2 100644 --- a/lemonldap-ng-portal/t/test-lib.pm +++ b/lemonldap-ng-portal/t/test-lib.pm @@ -140,7 +140,8 @@ sub count_sessions { sub getCache { require Cache::FileCache; - return Cache::FileCache->new( { + return Cache::FileCache->new( + { namespace => 'lemonldap-ng-session', cache_root => $tmpDir, cache_depth => 0, @@ -562,6 +563,8 @@ has p => ( is => 'rw' ); =cut +has confFailure => ( is => 'rw' ); + has ini => ( is => 'rw', lazy => 1, @@ -573,27 +576,30 @@ has ini => ( } $self->{ini} = $ini; main::ok( $self->{p} = $self->class->new(), 'Portal object' ); - main::ok( $self->{p}->init($ini), 'Init' ); - main::ok( $self->{app} = $self->{p}->run(), 'Portal app' ); - main::count(3); - no warnings 'redefine'; - eval + main::count(1); + unless ( $self->confFailure ) { + main::ok( $self->{p}->init($ini), 'Init' ); + main::ok( $self->{app} = $self->{p}->run(), 'Portal app' ); + main::count(2); + no warnings 'redefine'; + eval 'sub Lemonldap::NG::Common::Logger::Std::error {return $_[0]->warn($_[1])}'; - $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{french} = { - uid => 'french', - cn => 'Frédéric Accents', - mail => 'fa@badwolf.org', - }; - $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{davros} = { - uid => 'davros', - cn => 'Bad Guy', - mail => 'davros@badguy.org', - }; - $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{russian} = { - uid => 'russian', - cn => 'Русский', - mail => 'ru@badwolf.org', - }; + $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{french} = { + uid => 'french', + cn => 'Frédéric Accents', + mail => 'fa@badwolf.org', + }; + $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{davros} = { + uid => 'davros', + cn => 'Bad Guy', + mail => 'davros@badguy.org', + }; + $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{russian} = { + uid => 'russian', + cn => 'Русский', + mail => 'ru@badwolf.org', + }; + } $self; } ); @@ -677,7 +683,8 @@ to test content I<(to launch a C for example)>. sub _get { my ( $self, $path, %args ) = @_; - my $res = $self->app->( { + my $res = $self->app->( + { 'HTTP_ACCEPT' => $args{accept} || 'application/json, text/plain, */*', 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', @@ -729,7 +736,8 @@ sub _post { my ( $self, $path, $body, %args ) = @_; die "$body must be a IO::Handle" unless ( ref($body) and $body->can('read') ); - my $res = $self->app->( { + my $res = $self->app->( + { 'HTTP_ACCEPT' => $args{accept} || 'application/json, text/plain, */*', 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', diff --git a/scripts/parameters-for-wiki.pl b/scripts/parameters-for-wiki.pl index 905042c8b..5c2bc9df5 100755 --- a/scripts/parameters-for-wiki.pl +++ b/scripts/parameters-for-wiki.pl @@ -64,6 +64,7 @@ print <