diff --git a/fastcgi-server/man/llng-fastcgi-server.8p b/fastcgi-server/man/llng-fastcgi-server.8p index ed4180f64..3bc402340 100644 --- a/fastcgi-server/man/llng-fastcgi-server.8p +++ b/fastcgi-server/man/llng-fastcgi-server.8p @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) +.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -54,20 +54,16 @@ .\" Avoid warning from groff about undefined register 'F'. .de IX .. -.nr rF 0 -.if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{\ -. if \nF \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" +.if !\nF .nr F 0 +.if \nF>0 \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{\ -. nr % 0 -. nr F 2 -. \} +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 . \} .\} -.rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. @@ -133,7 +129,7 @@ .\" ======================================================================== .\" .IX Title "llng-fastcgi-server 8" -.TH llng-fastcgi-server 8 "2019-09-24" "perl v5.28.1" "User Contributed Perl Documentation" +.TH llng-fastcgi-server 8 "2019-10-30" "perl v5.26.1" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm index bc12b02ee..3df96e59b 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm @@ -195,9 +195,11 @@ sub virtualHosts { type => 'keyText', }; - # If rule contains a comment, split it + # If rule contains a comment or an AuthLevel, split them if ( $query eq 'locationRules' ) { $res->{comment} = ''; + $res->{level} = ''; + $res->{level} = $1 if ( $r =~ s/\(\?#AuthnLevel=(-?\d+)\)// ); if ( $r =~ s/\(\?#(.*?)\)// ) { $res->{title} = $res->{comment} = $1; } 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 e146b27b7..71c5a3a84 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm @@ -281,6 +281,7 @@ sub locationRulesInit { $class->tsv->{locationProtection}->{$vhost} = []; $class->tsv->{locationRegexp}->{$vhost} = []; $class->tsv->{locationConditionText}->{$vhost} = []; + $class->tsv->{locationAuthnLevel}->{$vhost} = []; foreach my $url ( sort keys %{$rules} ) { my ( $cond, $prot ) = $class->conditionSub( $rules->{$url} ); @@ -300,10 +301,14 @@ sub locationRulesInit { push @{ $class->tsv->{locationCondition}->{$vhost} }, $cond; push @{ $class->tsv->{locationProtection}->{$vhost} }, $prot; push @{ $class->tsv->{locationRegexp}->{$vhost} }, qr/$url/; + push @{ $class->tsv->{locationAuthnLevel}->{$vhost} }, + $url =~ /\(\?#AuthnLevel=(-?\d+)\)/ + ? $1 + : undef; push @{ $class->tsv->{locationConditionText}->{$vhost} }, $url =~ /^\(\?#(.*?)\)/ ? $1 : $url =~ /^(.*?)##(.+)$/ ? $2 - : $url; + : $url; $class->tsv->{locationCount}->{$vhost}++; } } @@ -451,6 +456,7 @@ sub postUrlInit { # @return array (ref(sub), int) sub conditionSub { my ( $class, $cond ) = @_; + $cond =~ s/\(\?#(\d+)\)$//; my ( $OK, $NOK ) = ( sub { 1 }, sub { 0 } ); # Simple cases : accept and deny 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 5478d2325..b43dd4337 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm @@ -267,10 +267,31 @@ sub checkMaintenanceMode { # @return True if the user is granted to access to the current URL sub grant { my ( $class, $req, $session, $uri, $cond, $vhost ) = @_; + my $level; + return $cond->( $req, $session ) if ($cond); $vhost ||= $class->resolveAlias($req); - if ( my $level = $class->tsv->{authnLevel}->{$vhost} ) { + + # Using URL authentification level if exists + for ( + my $i = 0 ; + $i < ( $class->tsv->{locationCount}->{$vhost} || 0 ) ; + $i++ + ) + { + if ( $uri =~ $class->tsv->{locationRegexp}->{$vhost}->[$i] ) { + $level = $class->tsv->{locationAuthnLevel}->{$vhost}->[$i]; + last; + } + } + $level + ? $class->logger->debug( + 'Found AuthnLevel=' . $level . ' for "' . "$vhost$uri" . '"' ) + : $class->logger->debug("No URL authentication level found..."); + + # Using VH authentification level if exists + if ( $level ||= $class->tsv->{authnLevel}->{$vhost} ) { if ( $session->{authenticationLevel} < $level ) { $class->logger->debug( "User authentication level = $session->{authenticationLevel}"); diff --git a/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t b/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t index 1282334bc..22fb21e00 100644 --- a/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t +++ b/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t @@ -10,6 +10,7 @@ init('Lemonldap::NG::Handler::PSGI'); my $res; # Unauthentified query +# -------------------- ok( $res = $client->_get('/'), 'Unauthentified query' ); ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' ); ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); @@ -24,26 +25,47 @@ ok( 'Location => http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ) ); - count(4); # Authentified queries # -------------------- - # Authorized query ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ), 'Authentified query' ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); - count(2); # Denied query ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ), 'Denied query' ); ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 ); - count(2); +# Required AuthnLevel = 1 +ok( $res = $client->_get( '/AuthWeak', undef, undef, "lemonldap=$sessionId" ), + 'Weak Authentified query' ); +ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); +count(2); + +# Required AuthnLevel = 5 +ok( + $res = $client->_get( '/AuthStrong', undef, undef, "lemonldap=$sessionId" ), + 'Strong Authentified query' +); +ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 ); +%h = @{ $res->[1] }; +ok( + $h{Location} eq 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test1.example.com/AuthStrong', '' ), + 'Redirection points to http://test1.example.com/AuthStrong' + ) + or explain( + \%h, + 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test1.example.com/AuthStrong', '' ) + ); +count(3); + # Bad cookie ok( $res = $client->_get( @@ -58,9 +80,38 @@ ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); unlink( 't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock' ); - count(2); +# Required AuthnLevel = 1 +ok( + $res = $client->_get( + '/AuthWeak', undef, 'test2.example.com', "lemonldap=$sessionId" + ), + 'Weak Authentified query' +); +ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); +count(2); + +# Required AuthnLevel = 5 +ok( + $res = + $client->_get( '/', undef, 'test2.example.com', "lemonldap=$sessionId" ), + 'Default Authentified query' +); +ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 ); +%h = @{ $res->[1] }; +ok( + $h{Location} eq 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test2.example.com/', '' ), + 'Redirection points to http://test2.example.com/' + ) + or explain( + \%h, + 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test2.example.com/', '' ) + ); +count(3); + done_testing( count() ); clean(); diff --git a/lemonldap-ng-handler/t/61-Lemonldap-NG-Handler-PSGI-Server.t b/lemonldap-ng-handler/t/61-Lemonldap-NG-Handler-PSGI-Server.t index 2fb6f0cf6..03b8f52fd 100644 --- a/lemonldap-ng-handler/t/61-Lemonldap-NG-Handler-PSGI-Server.t +++ b/lemonldap-ng-handler/t/61-Lemonldap-NG-Handler-PSGI-Server.t @@ -9,6 +9,7 @@ init('Lemonldap::NG::Handler::Server'); my $res; # Unauthentified query +# -------------------- ok( $res = $client->_get('/'), 'Unauthentified query' ); ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' ); ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); @@ -23,17 +24,14 @@ ok( 'Location => http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ) ); - count(4); # Authentified queries # -------------------- - # Authorized query ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ), 'Authentified query' ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); - count(2); # Check headers @@ -46,9 +44,33 @@ count(1); ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ), 'Denied query' ); ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 ); - count(2); +# Required AuthnLevel = 1 +ok( $res = $client->_get( '/AuthWeak', undef, undef, "lemonldap=$sessionId" ), + 'Weak Authentified query' ); +ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); +count(2); + +# Required AuthnLevel = 5 +ok( + $res = $client->_get( '/AuthStrong', undef, undef, "lemonldap=$sessionId" ), + 'Strong Authentified query' +); +ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 ); +%h = @{ $res->[1] }; +ok( + $h{Location} eq 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test1.example.com/AuthStrong', '' ), + 'Redirection points to http://test1.example.com/AuthStrong' + ) + or explain( + \%h, + 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test1.example.com/AuthStrong', '' ) + ); +count(3); + # Bad cookie ok( $res = $client->_get( @@ -63,9 +85,38 @@ ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); unlink( 't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock' ); - count(2); +# Required AuthnLevel = 1 +ok( + $res = $client->_get( + '/AuthWeak', undef, 'test2.example.com', "lemonldap=$sessionId" + ), + 'Weak Authentified query' +); +ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); +count(2); + +# Required AuthnLevel = 5 +ok( + $res = + $client->_get( '/', undef, 'test2.example.com', "lemonldap=$sessionId" ), + 'Default Authentified query' +); +ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 ); +%h = @{ $res->[1] }; +ok( + $h{Location} eq 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test2.example.com/', '' ), + 'Redirection points to http://test2.example.com/' + ) + or explain( + \%h, + 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test2.example.com/', '' ) + ); +count(3); + done_testing( count() ); clean(); 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 148ceef80..784af80e8 100644 --- a/lemonldap-ng-handler/t/62-Lemonldap-NG-Handler-Nginx.t +++ b/lemonldap-ng-handler/t/62-Lemonldap-NG-Handler-Nginx.t @@ -34,7 +34,6 @@ count(4); ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ), 'Authentified query' ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); - count(2); # Check headers @@ -49,9 +48,33 @@ count(2); ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ), 'Denied query' ); ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 ); - count(2); +# Required AuthnLevel = 1 +ok( $res = $client->_get( '/AuthWeak', undef, undef, "lemonldap=$sessionId" ), + 'Weak Authentified query' ); +ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); +count(2); + +# Required AuthnLevel = 5 +ok( + $res = $client->_get( '/AuthStrong', undef, undef, "lemonldap=$sessionId" ), + 'Strong Authentified query' +); +ok( $res->[0] == 401, 'Code is 401' ) or explain( $res, 401 ); +%h = @{ $res->[1] }; +ok( + $h{Location} eq 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test1.example.com/AuthStrong', '' ), + 'Redirection points to http://test1.example.com/AuthStrong' + ) + or explain( + \%h, + 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test1.example.com/AuthStrong', '' ) + ); +count(3); + # Bad cookie ok( $res = $client->_get( @@ -66,9 +89,38 @@ ok( $res->[0] == 401, 'Code is 401' ) or explain( $res->[0], 401 ); unlink( 't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock' ); - count(2); +# Required AuthnLevel = 1 +ok( + $res = $client->_get( + '/AuthWeak', undef, 'test2.example.com', "lemonldap=$sessionId" + ), + 'Weak Authentified query' +); +ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); +count(2); + +# Required AuthnLevel = 5 +ok( + $res = + $client->_get( '/', undef, 'test2.example.com', "lemonldap=$sessionId" ), + 'Default Authentified query' +); +ok( $res->[0] == 401, 'Code is 401' ) or explain( $res, 401 ); +%h = @{ $res->[1] }; +ok( + $h{Location} eq 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test2.example.com/', '' ), + 'Redirection points to http://test2.example.com/' + ) + or explain( + \%h, + 'http://auth.example.com//upgradesession?url=' + . encode_base64( 'http://test2.example.com/', '' ) + ); +count(3); + done_testing( count() ); clean(); diff --git a/lemonldap-ng-handler/t/lmConf-1.json b/lemonldap-ng-handler/t/lmConf-1.json index 855abb617..ca9175cdb 100644 --- a/lemonldap-ng-handler/t/lmConf-1.json +++ b/lemonldap-ng-handler/t/lmConf-1.json @@ -41,11 +41,14 @@ "default": "$uid eq \"dwho\"" }, "test1.example.com": { + "^/AuthStrong(?#AuthnLevel=5)": "accept", + "^/AuthWeak(?#AuthnLevel=1)": "accept", "^/logout": "logout_sso", "^/deny": "deny", "default": "accept" }, "test2.example.com": { + "^/AuthWeak(?#AuthnLevel=1)": "accept", "^/logout": "logout_sso", "default": "accept" }, @@ -60,5 +63,10 @@ "portal": "http://auth.example.com/", "reloadUrls": {}, "userDB": "Demo", + "vhostOptions": { + "test2.example.com": { + "vhostAuthnLevel": 5 + } + }, "whatToTrace": "_whatToTrace" } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm index 07ed66f02..96aaec34f 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm @@ -266,6 +266,7 @@ sub _scanNodes { $leaf->{comment} ? "(?#$leaf->{comment})$leaf->{re}" : $leaf->{re}; + $k .= "(?#AuthnLevel=$leaf->{level})" if $leaf->{level}; $self->set( $target, $key, $k, $leaf->{data} ); } else { diff --git a/lemonldap-ng-manager/site/coffee/manager.coffee b/lemonldap-ng-manager/site/coffee/manager.coffee index 75ebb1a0b..1aa5c3b57 100644 --- a/lemonldap-ng-manager/site/coffee/manager.coffee +++ b/lemonldap-ng-manager/site/coffee/manager.coffee @@ -592,6 +592,10 @@ llapp.controller 'TreeCtrl', [ if a.template a._nodes = templates a.template, a.title node.nodes.push a + if a.type.match /^rule$/ + console.log "Parse rule AuthnLevel as integer" + if a.level and typeof a.level == 'string' + a.level = parseInt(a.level, 10) d.resolve 'OK' $scope.waiting = false , (response) -> diff --git a/lemonldap-ng-manager/site/htdocs/static/forms/rule.html b/lemonldap-ng-manager/site/htdocs/static/forms/rule.html index 002f36641..64811cfc2 100644 --- a/lemonldap-ng-manager/site/htdocs/static/forms/rule.html +++ b/lemonldap-ng-manager/site/htdocs/static/forms/rule.html @@ -17,6 +17,10 @@