diff --git a/doc/sources/admin/configvhost.rst b/doc/sources/admin/configvhost.rst index 6db92de1c660e8a6802707f7d1494f9185f8fc40..4ffdb283e617f18126d88d4ad6e4a43285f507cf 100644 --- a/doc/sources/admin/configvhost.rst +++ b/doc/sources/admin/configvhost.rst @@ -503,6 +503,9 @@ Some options are available: - Maintenance mode: reject all requests with a maintenance message - Aliases: list of aliases for this virtual host *(avoid to rewrite rules,...)* +- Access to trace: can be used for overwriting REMOTE_CUSTOM with a custom function. + Provide a comma separated list with custom function path and args. + By example: My::accessToTrace, 'Dr Who', 'dwho@badwolf.org' - Type: handler type (normal, :doc:`ServiceToken Handler`, :doc:`DevOps Handler`,...) @@ -515,6 +518,29 @@ Some options are available: seconds. This TTL can be customized for each virtual host. +.. attention:: + + A hash reference containing $req, $session, $vhost, $custom and an array reference + with provided parameters is passed to accessToTrace custom function. + + :: + + package My; + + sub accessToTrace { + my $hash = shift; + my $custom = $hash->{custom}; + my $req = $hash->{req}; + my $vhost = $hash->{vhost}; + my $custom = hash->{custom}; + + return + "$custom alias $hash->{params}->[0]#$hash->{params}->[1]:$hash->{session}->{groups}"; + } + + 1; + + .. danger:: A same virtual host can serve many locations. Each diff --git a/e2e-tests/custom.pm b/e2e-tests/custom.pm index bf9f3244ff83ff637536c7e81adb8e1b28691ac5..0ac7012f2e4c0d1b88c2667d5d63665645ad7f7b 100644 --- a/e2e-tests/custom.pm +++ b/e2e-tests/custom.pm @@ -8,4 +8,10 @@ sub get_additional_arg { return $_[0]; } +sub accessToTrace { + my $hash = shift; + return +"$hash->{custom} alias $hash->{params}->[0] $hash->{params}->[1]:$hash->{session}->{groups}"; +} + 1; diff --git a/e2e-tests/lmConf-1.json b/e2e-tests/lmConf-1.json index 51f89b8f3584a097798b3fde54f1e2016fac7f48..cc40fcc936a2636075b9480a49807d026a625b36 100644 --- a/e2e-tests/lmConf-1.json +++ b/e2e-tests/lmConf-1.json @@ -164,6 +164,16 @@ "default": "accept" } }, + "vhostOptions":{ + "manager.example.com": { + "vhostMaintenance": 0, + "vhostPort": -1, + "vhostHttps": -1, + "vhostAliases": "", + "vhostServiceTokenTTL": -1, + "vhostAccessToTrace": "My::accessToTrace, Doctor, Who","vhostType":"Main" + } + }, "loginHistoryEnabled": 1, "macros": { "UA" : "$ENV{HTTP_USER_AGENT}", diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm index 6b8191455801f357db709faade83e8c29c131314..01a0a259768353e02b59f951abbfd0e3f9c152aa 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm @@ -30,7 +30,7 @@ our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID) our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:uth(?:orizationCodeExpiration|nLevel)|llow(?:PasswordGrant|Offline)|ccessTokenExpiration|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|Macro)s)'; our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ign(?:S[LS]OMessage|atureMethod)|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)'; our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:S(?:ign(?:S[LS]OMessage|atureMethod)|essionNotOnOrAfterTimeout)|N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)'; -our $virtualHostKeys = '(?:vhost(?:A(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)'; +our $virtualHostKeys = '(?:vhost(?:A(?:ccessToTrace|uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)'; our $authParameters = { adParams => [qw(ADPwdMaxAge ADPwdExpireWarning)], diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm index 3cce88e90e37a43138d3541240612e0eac2c2b4b..861e8d5e1a794ec9c02dca8a61bc74c740960862 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm @@ -238,6 +238,8 @@ sub defaultValuesInit { $conf->{vhostOptions}->{$vhost}->{vhostAuthnLevel}; $class->tsv->{serviceTokenTTL}->{$vhost} = $conf->{vhostOptions}->{$vhost}->{vhostServiceTokenTTL}; + $class->tsv->{accessToTrace}->{$vhost} = + $conf->{vhostOptions}->{$vhost}->{vhostAccessToTrace}; } } return 1; 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 f552a5b68b62241f0b0e517e4f2279b740a19d04..bf6e7844b4f0820604ae7c575501347237a7df2b 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm @@ -1,7 +1,7 @@ # Main running methods file package Lemonldap::NG::Handler::Main::Run; -our $VERSION = '2.0.9'; +our $VERSION = '2.0.10'; package Lemonldap::NG::Handler::Main; @@ -105,6 +105,7 @@ sub checkType { sub run { my ( $class, $req, $rule, $protection ) = @_; my ( $id, $session ); + my $vhost = $class->resolveAlias($req); return $class->DECLINED unless ( $class->is_initial_req($req) ); @@ -149,9 +150,41 @@ sub run { # ACCOUNTING (1. Inform web server) $class->set_user( $req, $session->{ $class->tsv->{whatToTrace} } ); - $class->set_custom( $req, $session->{ $class->tsv->{customToTrace} } ) - if $class->tsv->{customToTrace} - and $session->{ $class->tsv->{customToTrace} }; + + my $custom; + $custom = $session->{ $class->tsv->{customToTrace} } + if ( $class->tsv->{customToTrace} + and $session->{ $class->tsv->{customToTrace} } ); + if ( $class->tsv->{accessToTrace}->{$vhost} ) { + my ( $function, @params ) = split /\s*,\s*/, + $class->tsv->{accessToTrace}->{$vhost}; + if ( $function =~ qr/^(?:\w+(?:::\w+)*(?:\s+\w+(?:::\w+)*)*)?$/ ) { + my $c = eval { + no strict 'refs'; + &{$function}( { + req => $req, + vhost => $vhost, + session => $session, + custom => $custom, + params => \@params + } + ); + }; + if ($@) { + $class->logger->error( + "Failed to overwrite customToTrace: $@"); + } + else { + $class->logger->debug("Overwrite customToTrace with: $c"); + $custom = $c; + } + } + else { + $class->logger->error( + "accessToTrace: Bad custom function name"); + } + } + $class->set_custom( $req, $custom ) if $custom; # AUTHORIZATION return ( $class->forbidden( $req, $session ), $session ) diff --git a/lemonldap-ng-handler/t/62-Lemonldap-NG-Handler-Nginx.t b/lemonldap-ng-handler/t/62-Lemonldap-NG-Handler-Nginx.t index 6ca9fe484ca9ebf627331b171646c8f548738496..c76c57e529f9e3d29124f16741cfa98b456bf26f 100644 --- a/lemonldap-ng-handler/t/62-Lemonldap-NG-Handler-Nginx.t +++ b/lemonldap-ng-handler/t/62-Lemonldap-NG-Handler-Nginx.t @@ -4,7 +4,10 @@ use MIME::Base64; use Data::Dumper; use URI::Escape; -require 't/test-psgi-lib.pm'; +BEGIN { + require 't/test-psgi-lib.pm'; + require 't/custom.pm'; +} init('Lemonldap::NG::Handler::Server::Nginx'); @@ -31,6 +34,26 @@ count(4); # Authentified queries # -------------------- +# Authorized query +ok( + $res = + $client->_get( '/', undef, 'test4.example.com', "lemonldap=$sessionId" ), + 'Authentified query' +); +ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); +count(2); + +# Check headers +%h = @{ $res->[1] }; +ok( + $h{'Lm-Remote-Custom'} eq + 'dwho@badwolf.org alias Doctor Who:users; timelords', + 'Lm-Remote-User is overwriten' + ) + or explain( \%h, + 'Lm-Remote-User => "dwho@badwolf.org alias Doctor Who:users; timelords"' ); +count(1); + # Authorized query ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ), 'Authentified query' ); @@ -43,7 +66,11 @@ ok( $h{'Headername1'} eq 'Auth-User', 'Headername1 is set to "Auth-User"' ) or explain( \%h, 'Headername1 => "Auth-User"' ); ok( $h{'Headervalue1'} eq 'dwho', 'Headervalue1 is set to "dwho"' ) or explain( \%h, 'Headervalue1 => "dwho"' ); -count(2); +ok( + $h{'Lm-Remote-Custom'} eq 'dwho@badwolf.org', + 'Lm-Remote-User is set "dwho@badwolf.org"' +) or explain( \%h, 'Lm-Remote-User => "dwho@badwolf.org"' ); +count(3); # Request an URI protected by custom function -> allowed ok( diff --git a/lemonldap-ng-handler/t/66-Lemonldap-NG-Handler-PSGI-wildcard.t b/lemonldap-ng-handler/t/66-Lemonldap-NG-Handler-PSGI-wildcard.t index 2af79884a3f1489dae013310d210853ce506c35d..642eb7339676eae6cb9469f5a3f305d5b4396f6a 100644 --- a/lemonldap-ng-handler/t/66-Lemonldap-NG-Handler-PSGI-wildcard.t +++ b/lemonldap-ng-handler/t/66-Lemonldap-NG-Handler-PSGI-wildcard.t @@ -4,6 +4,7 @@ use MIME::Base64; BEGIN { require 't/test-psgi-lib.pm'; + require 't/custom.pm'; } init('Lemonldap::NG::Handler::PSGI'); @@ -39,7 +40,6 @@ ok( 'Authentified query' ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); - count(2); # Denied query @@ -50,7 +50,6 @@ ok( 'Denied query' ); ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 ); - count(2); # Bad cookie @@ -67,11 +66,9 @@ ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); unlink( 't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock' ); - count(2); done_testing( count() ); - clean(); sub Lemonldap::NG::Handler::PSGI::handler { diff --git a/lemonldap-ng-handler/t/custom.pm b/lemonldap-ng-handler/t/custom.pm new file mode 100644 index 0000000000000000000000000000000000000000..e14af01df1cf7007e341bb8789172607254c3461 --- /dev/null +++ b/lemonldap-ng-handler/t/custom.pm @@ -0,0 +1,9 @@ +package My; + +sub accessToTrace { + my $hash = shift; + return +"$hash->{custom} alias $hash->{params}->[0] $hash->{params}->[1]:$hash->{session}->{groups}"; +} + +1; diff --git a/lemonldap-ng-handler/t/lmConf-1.json b/lemonldap-ng-handler/t/lmConf-1.json index 7edecf6135f1fd1759c140e39bf03f1efd173e9a..37756dd93f5bb89add1897d64c7876717e54b2bd 100644 --- a/lemonldap-ng-handler/t/lmConf-1.json +++ b/lemonldap-ng-handler/t/lmConf-1.json @@ -58,6 +58,9 @@ "^/logout": "logout_sso", "default": "accept" }, + "test4.example.com": { + "default": "accept" + }, "*.example.org": { "^/orgdeny": "deny", "default": "accept" @@ -80,8 +83,12 @@ "userDB": "Demo", "vhostOptions": { "test2.example.com": { - "vhostAuthnLevel": 5 - } + "vhostAuthnLevel": 5 + }, + "test4.example.com": { + "vhostAccessToTrace": "My::accessToTrace, Doctor, Who" + } }, - "whatToTrace": "_whatToTrace" + "whatToTrace": "_whatToTrace", + "customToTrace": "mail" } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm index fabda04aeca291ba7efc1f9ddee5781504bf12c4..7e2c73bc14e85aec04865113e9c2874675f0a4cd 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm @@ -4136,6 +4136,10 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a- 'utotp2fLogo' => { 'type' => 'text' }, + 'vhostAccessToTrace' => { + 'default' => '', + 'type' => 'text' + }, 'vhostAliases' => { 'default' => '', 'type' => 'text' diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm index 4124220fa9e9c1b54504fec56da3f550b20329b2..f95b48e029b5a8f361b9a3b3caf4b24fa5492b90 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm @@ -2227,8 +2227,9 @@ sub attributes { type => 'int', default => -1, }, - vhostAliases => { type => 'text', default => '' }, - vhostType => { + vhostAccessToTrace => { type => 'text', default => '' }, + vhostAliases => { type => 'text', default => '' }, + vhostType => { type => 'select', select => [ { k => 'AuthBasic', v => 'AuthBasic' }, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm index 3218e74c87aa5e8590b5445521e0af170efee152..2a20e522aaeaf31a486ce2be60b8274ad2cbbb91 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm @@ -27,10 +27,10 @@ sub cTrees { help => 'configvhost.html#options', form => 'simpleInputContainer', nodes => [ - 'vhostPort', 'vhostHttps', - 'vhostMaintenance', 'vhostAliases', - 'vhostType', 'vhostAuthnLevel', - 'vhostServiceTokenTTL' + 'vhostPort', 'vhostHttps', + 'vhostMaintenance', 'vhostAliases', + 'vhostAccessToTrace', 'vhostType', + 'vhostAuthnLevel', 'vhostServiceTokenTTL' ], }, ], diff --git a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js index fbb4a9f51c3da7ca39241846820988d86872bef7..28a769f75a9fd9c4cea055a898d154eb48b42109 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js @@ -1 +1 @@ -(function(){var S,o,f,g,e;e=function(e,t){return $("#msg").html(window.translate(e)),$("#color").removeClass("message-positive message-warning alert-success alert-warning"),$("#color").addClass("message-"+t),"positive"===t&&(t="success"),$("#color").addClass("alert-"+t)},g={_whatToTrace:[function(e,t){return"groupBy=substr("+e+",1)"},function(e,t){return e+"="+t+"*"}]},f={_whatToTrace:function(e,t,n,a){return console.log("overSchema => level",n,"over",a),1===n&&t.length>a?e+"="+t+"*&groupBy=substr("+e+","+(n+a+1)+")":null}},S={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},o={home:[]},angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(k,t,e,n,i){var p,a,r,d;return k.links=links,k.menulinks=menulinks,k.staticPrefix=staticPrefix,k.scriptname=scriptname,k.formPrefix=formPrefix,k.availableLanguages=availableLanguages,k.waiting=!0,k.showM=!1,k.showT=!0,k.data=[],k.currentScope=null,k.currentSession=null,k.menu=o,k.searchString="",k.U2FCheck="1",k.TOTPCheck="1",k.UBKCheck="1",k.translateP=t.translateP,k.translate=t.translate,k.translateTitle=function(e){return t.translateField(e,"title")},d="persistent",k.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(k.currentNode,k),k[e.action]();break;case"string":k[e.action]();break;default:console.log(typeof e.action)}return k.showM=!1},k.search2FA=function(e){return e&&(k.searchString=""),k.currentSession=null,k.data=[],k.updateTree2("",k.data,0,0)},k.delete2FA=function(e,t){var n,a,r;for(n=0,r=(a=document.querySelectorAll(".data-"+t)).length;n level",n,"over",a),1===n&&t.length>a?e+"="+t+"*&groupBy=substr("+e+","+(n+a+1)+")":null}},S={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},o={home:[]},angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(k,t,e,n,i){var p,a,r,d;return k.links=links,k.menulinks=menulinks,k.staticPrefix=staticPrefix,k.scriptname=scriptname,k.formPrefix=formPrefix,k.availableLanguages=availableLanguages,k.waiting=!0,k.showM=!1,k.showT=!0,k.data=[],k.currentScope=null,k.currentSession=null,k.menu=o,k.searchString="",k.U2FCheck="1",k.TOTPCheck="1",k.UBKCheck="1",k.translateP=t.translateP,k.translate=t.translate,k.translateTitle=function(e){return t.translateField(e,"title")},d="persistent",k.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(k.currentNode,k),k[e.action]();break;case"string":k[e.action]();break;default:console.log(typeof e.action)}return k.showM=!1},k.search2FA=function(e){return e&&(k.searchString=""),k.currentSession=null,k.data=[],k.updateTree2("",k.data,0,0)},k.delete2FA=function(e,t){var n,a,r;for(n=0,r=(a=document.querySelectorAll(".data-"+t)).length;n