Append an option in Manager to define RULES_URL param (#2627)

This commit is contained in:
Christophe Maudoux 2021-10-01 22:59:30 +02:00
parent ed56a171b9
commit 732c8631eb
29 changed files with 276 additions and 22 deletions

View File

@ -515,6 +515,8 @@ Some options are available:
required level, he is redirected to an upgrade page in the portal.
This default level is required for ALL locations relative to this virtual host.
It can be overrided for each locations.
- **DevOps rules file URL**: option to define URL to retreive DevOps rules file.
This option can be overriden with ``uwsgi_param/fastcgi_param RULES_URL`` parameter.
- **ServiceToken timeout**: by default, ServiceToken is just valid during 30
seconds. This TTL can be customized for each virtual host.

View File

@ -30,7 +30,7 @@ our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Expiration|SignAlg|Claims|JWT)|uth(?:orizationCodeExpiration|nLevel)|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|UserI(?:nfoSignAlg|DAttr)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims)|(?:ExportedVar|ScopeRule|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(?:ccessToTrace|uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
our $virtualHostKeys = '(?:vhost(?:A(?:ccessToTrace|uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|DevOpsRulesUrl|Https|Port)|(?:exportedHeader|locationRule)s|post)';
our $authParameters = {
adParams => [qw(ADPwdMaxAge ADPwdExpireWarning)],

View File

@ -74,12 +74,14 @@ t/60-Lemonldap-NG-Handler-PSGI.t
t/61-Lemonldap-NG-Handler-PSGI-Server.t
t/62-Lemonldap-NG-Handler-Nginx.t
t/63-Lemonldap-NG-Handler-PSGI-Try.t
t/64-Lemonldap-NG-Handler-PSGI-DevOps-vhostOptions.t
t/64-Lemonldap-NG-Handler-PSGI-DevOps-with-param.t
t/64-Lemonldap-NG-Handler-PSGI-DevOps.t
t/65-Lemonldap-NG-Handler-Nginx-ServiceToken.t
t/65-Lemonldap-NG-Handler-PSGI-ServiceToken.t
t/66-Lemonldap-NG-Handler-PSGI-wildcard.t
t/67-Lemonldap-NG-Handler-PSGI-vhostoptions-with-reload.t
t/67-Lemonldap-NG-Handler-PSGI-vhostoptions.t
t/67-Lemonldap-NG-Handler-PSGI-vhostOptions-with-reload.t
t/67-Lemonldap-NG-Handler-PSGI-vhostOptions.t
t/68-Lemonldap-NG-Handler-PSGI-Zimbra.t
t/69-Lemonldap-NG-Handler-PSGI-SecureToken.t
t/70-Lemonldap-NG-Handler-PSGI-AuthBasic.t

View File

@ -31,9 +31,11 @@ sub _loadVhostConfig {
my ( $class, $req, $vhost ) = @_;
my ( $json, $rUrl, $rVhost );
if ( $class->tsv->{useSafeJail} ) {
if ( $req->env->{RULES_URL} ) {
$rUrl = $req->{env}->{RULES_URL};
$rVhost = ( $req->env->{RULES_URL} =~ m#^https?://([^/]*).*# )[0];
if ( $req->env->{RULES_URL} || $class->tsv->{devOpsRulesUrl}->{$vhost} )
{
$rUrl = $req->{env}->{RULES_URL}
|| $class->tsv->{devOpsRulesUrl}->{$vhost};
$rVhost = ( $rUrl =~ m#^https?://([^/]*).*# )[0];
$rVhost =~ s/:\d+$//;
}
else {
@ -44,22 +46,25 @@ sub _loadVhostConfig {
$rVhost = $vhost;
}
$class->logger->debug("Try to retrieve 'rules.json' from $rUrl");
$class->logger->debug("Try to retrieve rules file from $rUrl");
my $get = HTTP::Request->new( GET => $rUrl );
$class->logger->debug("Set Host header with $rVhost");
$get->header( Host => $rVhost );
my $resp = $class->ua->request($get);
if ( $resp->is_success ) {
$class->logger->debug('Response is success');
eval {
$json = from_json( $resp->content, { allow_nonref => 1 } ); };
if ($@) {
$class->logger->error(
"Bad 'rules.json' retrieved from $rVhost for $vhost, skipping ($@)"
$class->logger->debug('Bad json file received');
$class->logger->error(
"Bad rules file retrieved from $rUrl for $vhost, skipping ($@)"
);
}
else {
$class->logger->debug('Good json file received');
$class->logger->info(
"Compiling 'rules.json' retrieved from $rVhost for $vhost");
"Compiling rules retrieved from $rUrl for $vhost");
}
}
}

View File

@ -248,6 +248,8 @@ sub defaultValuesInit {
$conf->{vhostOptions}->{$vhost}->{vhostServiceTokenTTL};
$class->tsv->{accessToTrace}->{$vhost} =
$conf->{vhostOptions}->{$vhost}->{vhostAccessToTrace};
$class->tsv->{devOpsRulesUrl}->{$vhost} =
$conf->{vhostOptions}->{$vhost}->{vhostDevOpsRulesUrl};
}
}
return 1;

View File

@ -0,0 +1,112 @@
use Test::More;
use JSON;
use MIME::Base64;
use LWP::UserAgent;
use Data::Dumper;
BEGIN {
require 't/test-psgi-lib.pm';
}
init(
'Lemonldap::NG::Handler::Server',
{
vhostOptions => {
'test3.example.com' => {
vhostDevOpsRulesUrl => 'http://devops.example.com/myfile.json',
},
},
}
);
my $res;
# Authorized queries
ok(
$res = $client->_get(
'/', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
),
'Authorized query'
);
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 );
my %headers = @{ $res->[1] };
ok( $headers{User} eq 'dwho', "'User' => 'dwho'" )
or explain( \%headers, 'dwho' );
ok( $headers{Name} eq '', "'Name' => ''" ) or explain( \%headers, 'No Name' );
ok( $headers{Mail} eq '', "'Mail' => ''" ) or explain( \%headers, 'No Mail' );
ok( keys %headers == 7, "Seven headers sent" )
or explain( \%headers, 'Seven headers' );
count(6);
ok(
$res = $client->_get(
'/testyes', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
),
'Authorized query'
);
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 );
count(2);
# Denied queries
ok(
$res = $client->_get(
'/deny', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
),
'Denied query'
);
ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 );
count(2);
ok(
$res = $client->_get(
'/testno', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
),
'Denied query'
);
ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 );
count(2);
done_testing( count() );
clean();
# Redefine LWP methods for tests
no warnings 'redefine';
sub LWP::UserAgent::request {
my ( $self, $req ) = @_;
ok( $req->header('host') eq 'devops.example.com',
'Host header found' )
or explain( $req->headers(), 'devops.example.com' );
ok( $req->as_string() =~ m#http://devops.example.com/myfile.json#,
'Rules file URL found' )
or explain( $req->as_string(), 'GET http://devops.example.com/myfile.json' );
count(2);
my $httpResp;
my $s = '{
"rules": {
"^/deny": "deny",
"^/testno": "$uid ne qq{dwho}",
"^/testyes": "$uid eq qq{dwho}",
"default": "accept"
},
"headers": {
"User": "$uid",
"Mail": "$mail",
"Name": "$cn"
}
}';
$httpResp = HTTP::Response->new( 200, 'OK' );
$httpResp->header( 'Content-Type', 'application/json' );
$httpResp->header( 'Content-Length', length($s) );
$httpResp->content($s);
return $httpResp;
}

View File

@ -0,0 +1,107 @@
use Test::More;
use JSON;
use MIME::Base64;
use LWP::UserAgent;
use Data::Dumper;
BEGIN {
require 't/test-psgi-lib.pm';
}
init('Lemonldap::NG::Handler::Server');
my $res;
# Authorized queries
ok(
$res = $client->_get(
'/', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com/file.json'
),
'Authorized query'
);
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 );
my %headers = @{ $res->[1] };
ok( $headers{User} eq 'dwho', "'User' => 'dwho'" )
or explain( \%headers, 'dwho' );
ok( $headers{Name} eq '', "'Name' => ''" ) or explain( \%headers, 'No Name' );
ok( $headers{Mail} eq '', "'Mail' => ''" ) or explain( \%headers, 'No Mail' );
ok( keys %headers == 7, "Seven headers sent" )
or explain( \%headers, 'Seven headers' );
count(6);
ok(
$res = $client->_get(
'/testyes', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com/file.json'
),
'Authorized query'
);
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 );
count(2);
# Denied queries
ok(
$res = $client->_get(
'/deny', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com/file.json'
),
'Denied query'
);
ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 );
count(2);
ok(
$res = $client->_get(
'/testno', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com/file.json'
),
'Denied query'
);
ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 );
count(2);
done_testing( count() );
clean();
# Redefine LWP methods for tests
no warnings 'redefine';
sub LWP::UserAgent::request {
my ( $self, $req ) = @_;
ok( $req->header('host') eq 'devops.example.com',
'Host header found' )
or explain( $req->headers(), 'devops.example.com' );
ok( $req->as_string() =~ m#http://devops.example.com/file.json#,
'Rules file URL found' )
or explain( $req->as_string(), 'GET http://devops.example.com/file.json' );
count(2);
my $httpResp;
my $s = '{
"rules": {
"^/deny": "deny",
"^/testno": "$uid ne qq{dwho}",
"^/testyes": "$uid eq qq{dwho}",
"default": "accept"
},
"headers": {
"User": "$uid",
"Mail": "$mail",
"Name": "$cn"
}
}';
$httpResp = HTTP::Response->new( 200, 'OK' );
$httpResp->header( 'Content-Type', 'application/json' );
$httpResp->header( 'Content-Length', length($s) );
$httpResp->content($s);
return $httpResp;
}

View File

@ -18,7 +18,6 @@ ok(
'/', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com'
),
'Authorized query'
);
@ -37,7 +36,6 @@ ok(
'/testyes', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com'
),
'Authorized query'
);
@ -50,7 +48,6 @@ ok(
'/deny', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com'
),
'Denied query'
);
@ -62,7 +59,6 @@ ok(
'/testno', undef,
'test3.example.com', "lemonldap=$sessionId",
VHOSTTYPE => 'DevOps',
RULES_URL => 'http://devops.example.com'
),
'Denied query'
);
@ -78,10 +74,13 @@ no warnings 'redefine';
sub LWP::UserAgent::request {
my ( $self, $req ) = @_;
ok( $req->header('host') eq 'devops.example.com',
ok( $req->header('host') eq 'test3.example.com',
'Host header found' )
or explain( $req->headers(), 'Header' );
count(1);
or explain( $req->headers(), 'test3.example.com' );
ok( $req->as_string() =~ m#http://127.0.0.1:80/rules.json#,
'Rules file URL found' )
or explain( $req->as_string(), 'GET http://127.0.0.1:80/rules.json' );
count(2);
my $httpResp;
my $s = '{
"rules": {

View File

@ -211,6 +211,7 @@ site/htdocs/static/logos/it.png
site/htdocs/static/logos/llng-icon-32.png
site/htdocs/static/logos/llng-logo-32.png
site/htdocs/static/logos/pl.png
site/htdocs/static/logos/pt_BR.png
site/htdocs/static/logos/tr.png
site/htdocs/static/logos/vi.png
site/htdocs/static/logos/zh.png

View File

@ -4375,6 +4375,9 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'vhostAuthnLevel' => {
'type' => 'int'
},
'vhostDevOpsRulesUrl' => {
'type' => 'url'
},
'vhostHttps' => {
'default' => -1,
'type' => 'trool'

View File

@ -2374,7 +2374,8 @@ sub attributes {
default => 'Main',
documentation => 'Handler type',
},
vhostAuthnLevel => { type => 'int' },
vhostAuthnLevel => { type => 'int' },
vhostDevOpsRulesUrl => { type => 'url' },
# SecureToken parameters
secureTokenAllowOnError => {

View File

@ -14,7 +14,7 @@
package Lemonldap::NG::Manager::Build::CTrees;
our $VERSION = '2.0.12';
our $VERSION = '2.0.14';
sub cTrees {
return {
@ -30,7 +30,8 @@ sub cTrees {
'vhostPort', 'vhostHttps',
'vhostMaintenance', 'vhostAliases',
'vhostAccessToTrace', 'vhostType',
'vhostAuthnLevel', 'vhostServiceTokenTTL'
'vhostAuthnLevel', 'vhostDevOpsRulesUrl',
'vhostServiceTokenTTL'
],
},
],

View File

@ -1475,6 +1475,11 @@ function templates(tpl,key) {
"title" : "vhostAuthnLevel",
"type" : "int"
},
{
"get" : tpl+"s/"+key+"/"+"vhostDevOpsRulesUrl",
"id" : tpl+"s/"+key+"/"+"vhostDevOpsRulesUrl",
"title" : "vhostDevOpsRulesUrl"
},
{
"default" : -1,
"get" : tpl+"s/"+key+"/"+"vhostServiceTokenTTL",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Access to trace",
"vhostAliases":"اسماء مستعارة",
"vhostAuthnLevel":"مستوى إثبات الهوية واجب",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"إتش تي تي بي س",
"vhostMaintenance":"وضع الصيانة",
"vhostOptions":"الخيارات",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Access to trace",
"vhostAliases":"Aliases",
"vhostAuthnLevel":"Required authentication level",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Maintenance mode",
"vhostOptions":"Options",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Access to trace",
"vhostAliases":"Aliases",
"vhostAuthnLevel":"Required authentication level",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Maintenance mode",
"vhostOptions":"Options",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Access to trace",
"vhostAliases":"Aliases",
"vhostAuthnLevel":"Nivel de autentificación requerido",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Maintenance mode",
"vhostOptions":"Options",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Accès à tracer",
"vhostAliases":"Alias",
"vhostAuthnLevel":"Niveau d'authentification requis",
"vhostDevOpsRulesUrl": "URL du fichier de règles DevOps",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Mode maintenance",
"vhostOptions":"Options",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Access to trace",
"vhostAliases":"Alias",
"vhostAuthnLevel":"Livello di autenticazione richiesto",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Modalità di manutenzione",
"vhostOptions":"Opzioni",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Dostęp do śledzenia",
"vhostAliases":"Aliasy",
"vhostAuthnLevel":"Wymagany poziom uwierzytelnienia",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Tryb konserwacji",
"vhostOptions":"Opcje",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"İzlemeye erişim",
"vhostAliases":"Takma adlar",
"vhostAuthnLevel":"Gereken doğrulama seviyesi",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Bakım modu",
"vhostOptions":"Seçenekler",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Access to trace",
"vhostAliases":"Bí danh",
"vhostAuthnLevel":"Mức xác thực bắt buộc",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Chế độ bảo trì",
"vhostOptions":"Tùy chọn",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"Access to trace",
"vhostAliases":"Aliases",
"vhostAuthnLevel":"Required authentication level",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"Maintenance mode",
"vhostOptions":"Options",

View File

@ -1186,6 +1186,7 @@
"vhostAccessToTrace":"存取追蹤",
"vhostAliases":"別名",
"vhostAuthnLevel":"需要的驗證等級",
"vhostDevOpsRulesUrl": "DevOps rules file URL",
"vhostHttps":"HTTPS",
"vhostMaintenance":"維護模式",
"vhostOptions":"選項",

View File

@ -367,6 +367,7 @@ site/htdocs/static/common/modules/WebID.png
site/htdocs/static/common/nl.png
site/htdocs/static/common/pl.png
site/htdocs/static/common/pt.png
site/htdocs/static/common/pt_BR.png
site/htdocs/static/common/ro.png
site/htdocs/static/common/tr.png
site/htdocs/static/common/vi.png
@ -382,6 +383,7 @@ site/htdocs/static/languages/it.json
site/htdocs/static/languages/nl.json
site/htdocs/static/languages/pl.json
site/htdocs/static/languages/pt.json
site/htdocs/static/languages/pt_BR.json
site/htdocs/static/languages/ro.json
site/htdocs/static/languages/tr.json
site/htdocs/static/languages/vi.json
@ -461,6 +463,7 @@ site/templates/common/mail/fi.json
site/templates/common/mail/fr.json
site/templates/common/mail/it.json
site/templates/common/mail/ms.json
site/templates/common/mail/pt_BR.json
site/templates/common/mail/tr.json
site/templates/common/mail/vi.json
site/templates/common/mail/zh_CN.json