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 c664ced4e54dc11b92c224600a69d2032fe338d1..3b74678055b3297078c1eeb81f47d489de2b6bd6 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-manager/lib/Lemonldap/NG/Manager/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm index 8e362e47f72135ae6f4b97dac284ba0b73f731dd..48d1cd0881c26c4613340fd1f1a533f99946d051 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm @@ -4137,6 +4137,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 f5b49ce30988de379257e8f1b0c06a175d750830..7a134b500f065cef2785334c158872b7dfa4cc64 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;ndata->{dn} || $req->sessionInfo->{_dn}; unless ($dn) { - $self->logger->error('"dn" is not set, aborting password modification'); + $self->logger->error('"dn" is not set, abort password modification'); return PE_ERROR; } - my $rule = $self->p->HANDLER->buildSub( - $self->p->HANDLER->substitute( - $self->conf->{portalRequireOldPassword} - ) - ); - unless ($rule) { - my $error = $self->p->HANDLER->tsv->{jail}->error || '???'; - } + my $requireOldPassword = ( $req->userData - ? $rule->( $req, $req->userData ) - : $rule->( $req, $req->sessionInfo ) + ? $self->requireOldPwdRule->( $req, $req->userData ) + : $self->requireOldPwdRule->( $req, $req->sessionInfo ) ); + $requireOldPassword = 0 if $useMail; # Ensure connection is valid $self->bind; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/Base.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/Base.pm index 3ab1c76c78d3a6a44866437430424f0645dca941..2c9b0b94f84160d5a9a749a70ae5b38dc0ad6b56 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/Base.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/Base.pm @@ -21,8 +21,19 @@ our $VERSION = '2.0.10'; # INITIALIZATION +has requireOldPwdRule => ( is => 'rw', default => sub { 0 } ); + sub init { - $_[0]->p->{_passwordDB} = $_[0]; + my ($self) = shift; + $self->requireOldPwdRule( + $self->p->buildRule( + $self->conf->{portalRequireOldPassword}, + 'portalRequireOldPassword' + ) + ); + return 0 unless $self->requireOldPwdRule; + + $self->p->{_passwordDB} = $self; } # INTERFACE diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/LDAP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/LDAP.pm index 56b3e31ba9d76e99fb5466f8c73e52387a587af2..f8bee8aa0e926d91862ba9c34fc90b6d80c9396a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/LDAP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Password/LDAP.pm @@ -29,31 +29,25 @@ sub confirm { } sub modifyPassword { - my ( $self, $req, $pwd ) = @_; + my ( $self, $req, $pwd, $useMail ) = @_; my $dn; my $requireOldPassword; - my $rule = $self->p->HANDLER->buildSub( - $self->p->HANDLER->substitute( - $self->conf->{portalRequireOldPassword} - ) - ); - unless ($rule) { - my $error = $self->p->HANDLER->tsv->{jail}->error || '???'; - } + if ( $req->data->{dn} ) { $dn = $req->data->{dn}; - $requireOldPassword = $rule->( $req, $req->userData ); + $requireOldPassword = $self->requireOldPwdRule->( $req, $req->userData ); $self->logger->debug("Get DN from request data: $dn"); } else { $dn = $req->sessionInfo->{_dn}; - $requireOldPassword = $rule->( $req, $req->sessionInfo ); + $requireOldPassword = $self->requireOldPwdRule->( $req, $req->sessionInfo ); $self->logger->debug("Get DN from session data: $dn"); } unless ($dn) { $self->logger->error('"dn" is not set, aborting password modification'); return PE_ERROR; } + $requireOldPassword = 0 if $useMail; # Ensure connection is valid $self->bind; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/MailPasswordReset.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/MailPasswordReset.pm index e581ddbdb5944f63c97f40ce4add990b626c53b2..63af6aa1a1e9ef5a76bcc257ea6eceda2d5786a4 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/MailPasswordReset.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/MailPasswordReset.pm @@ -473,16 +473,11 @@ sub changePwd { return $cpq; } - # Modify the password TODO: change this - # Populate $req->{user} for logging purpose - my $tmp = $self->conf->{portalRequireOldPassword}; - $self->conf->{portalRequireOldPassword} = 0; $req->user( $req->{sessionInfo}->{_user} ); my $result = $self->p->_passwordDB->modifyPassword( $req, $req->data->{newpassword}, 1 ); $req->{user} = undef; - $self->conf->{portalRequireOldPassword} = $tmp; # Mail token can be used only one time, delete the session if all is ok unless ( $result == PE_PASSWORD_OK or $result == PE_OK ) {