From 2a15bb05234bdd45f205949c627ce8210def152c Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Tue, 22 Oct 2019 17:07:17 +0200 Subject: [PATCH] SAML per-service macros portal code (#2042) --- .../site/htdocs/static/languages/tr.json | 1 + .../lib/Lemonldap/NG/Portal/Issuer/SAML.pm | 15 +- .../lib/Lemonldap/NG/Portal/Lib/SAML.pm | 17 ++ lemonldap-ng-portal/t/30-SAML-Macros.t | 196 ++++++++++++++++++ 4 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 lemonldap-ng-portal/t/30-SAML-Macros.t diff --git a/lemonldap-ng-manager/site/htdocs/static/languages/tr.json b/lemonldap-ng-manager/site/htdocs/static/languages/tr.json index aa0f1133a..ef99fbd2d 100644 --- a/lemonldap-ng-manager/site/htdocs/static/languages/tr.json +++ b/lemonldap-ng-manager/site/htdocs/static/languages/tr.json @@ -1022,6 +1022,7 @@ "samlSPMetaDataOptionsNotOnOrAfterTimeout":"notOnOrAfter süresi", "samlSPMetaDataOptionsForceUTF8":"UTF-8'e zorla", "samlSPMetaDataOptionsRule":"Erişim kuralı", +"samlSPMetaDataMacros":"Makrolar", "samlIDPName":"SAML IDP Adı", "samlServiceMetaData":"SAML2 Servisi", "samlEntityID":"Varlık Tanımlayıcı", diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/SAML.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/SAML.pm index 70e4b3f62..8662cf314 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/SAML.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/SAML.pm @@ -610,8 +610,17 @@ sub run { # Name is required next unless $name; - # Error if corresponding attribute is not in user session - my $value = $req->{sessionInfo}->{$_}; + # Lookup attribute value in SP macros or session + my $value; + if ( $self->spMacros->{$sp}->{$_} ) { + $value = $self->spMacros->{$sp}->{$_} + ->( $req, $req->{sessionInfo} ); + } + else { + $value = $req->{sessionInfo}->{$_}; + } + + # Check whether the value is required or not unless ( defined $value ) { if ($mandatory) { $self->logger->error( @@ -1478,7 +1487,7 @@ sub sloRelayTerm { my $session = $logout->get_session(); unless ($session) { - $self->logger->error( "Could not get session from logout" ); + $self->logger->error("Could not get session from logout"); return PE_SAML_SLO_ERROR; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm index badbd6950..c7bdd8d0b 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm @@ -30,6 +30,7 @@ has spList => ( is => 'rw', default => sub { {} } ); has idpList => ( is => 'rw', default => sub { {} } ); has idpRules => ( is => 'rw', default => sub { {} } ); has spRules => ( is => 'rw', default => sub { {} } ); +has spMacros => ( is => 'rw', default => sub { {} } ); # return LWP::UserAgent object has ua => ( @@ -417,6 +418,22 @@ sub loadSPs { $self->spRules->{$entityID} = $rule; } + # Load per-SP macros + my $macros = $self->conf->{samlSPMetaDataMacros}->{$_}; + for my $macroAttr ( keys %{$macros} ) { + my $macroRule = $macros->{$macroAttr}; + if ( length $macroRule ) { + $macroRule = $self->p->HANDLER->substitute($macroRule); + unless ( $macroRule = $self->p->HANDLER->buildSub($macroRule) ) + { + $self->error( 'SAML SP macro error: ' + . $self->p->HANDLER->tsv->{jail}->error ); + return 0; + } + $self->spMacros->{$entityID}->{$macroAttr} = $macroRule; + } + } + $self->logger->debug("SP $_ added"); } diff --git a/lemonldap-ng-portal/t/30-SAML-Macros.t b/lemonldap-ng-portal/t/30-SAML-Macros.t new file mode 100644 index 000000000..a3d6183fb --- /dev/null +++ b/lemonldap-ng-portal/t/30-SAML-Macros.t @@ -0,0 +1,196 @@ +use lib 'inc'; +use Test::More; +use strict; +use IO::String; +use LWP::UserAgent; +use LWP::Protocol::PSGI; +use MIME::Base64; +use XML::LibXML; + +BEGIN { + require 't/test-lib.pm'; + require 't/saml-lib.pm'; +} + +my $debug = 'error'; +my ( $issuer, $res ); + +SKIP: { + eval "use Lasso"; + if ($@) { + skip 'Lasso not found'; + } + + # Initialization + ok( $issuer = issuer(), 'Issuer portal' ); + + ok( + $res = $issuer->_post( + '/', IO::String->new('user=french&password=french'), + length => 27 + ), + 'Auth query' + ); + expectOK($res); + my $idpId = expectCookie($res); + + # Query IdP to access to SP + ok( + $res = $issuer->_get( + '/saml/singleSignOn', + query => 'IDPInitiated=1&spConfKey=sp.com', + cookie => "lemonldap=$idpId", + accept => 'test/html' + ), + 'Query IdP to access to SP' + ); + expectOK($res); + ok( + $res->[2]->[0] =~ + m#[2]->[0] =~ + /load_xml( string => $s ); + my $xpc = XML::LibXML::XPathContext->new($dom); + $xpc->registerNs( 'saml', 'urn:oasis:names:tc:SAML:2.0:assertion' ); + + foreach my $value ( + $xpc->findnodes('//saml:Attribute[@Name="sn"]/saml:AttributeValue') ) + { + is( $value->textContent, 'Accents', 'Check Attribute' ); + } +} + +clean_sessions(); +done_testing(); + +sub issuer { + return LLNG::Manager::Test->new( { + ini => { + logLevel => $debug, + domain => 'idp.com', + portal => 'http://auth.idp.com', + authentication => 'Demo', + userDB => 'Same', + issuerDBSAMLActivation => 1, + samlSPMetaDataMacros => { + 'sp.com' => { + extracted_sn => '(split(/\s/, $cn))[1]' + } + }, + samlSPMetaDataOptions => { + 'sp.com' => { + samlSPMetaDataOptionsEncryptionMode => 'none', + samlSPMetaDataOptionsEnableIDPInitiatedURL => 1, + samlSPMetaDataOptionsSignSSOMessage => 1, + samlSPMetaDataOptionsSignSLOMessage => 1, + samlSPMetaDataOptionsCheckSSOMessageSignature => 1, + samlSPMetaDataOptionsCheckSLOMessageSignature => 1, + } + }, + samlSPMetaDataExportedAttributes => { + 'sp.com' => { + cn => +'1;cn;urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + extracted_sn => +'1;sn;urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + uid => +'1;uid;urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + } + }, + samlOrganizationDisplayName => "IDP", + samlOrganizationName => "IDP", + samlOrganizationURL => "http://www.idp.com/", + samlServicePrivateKeyEnc => "-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAnfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADT +csus5Xn3id5+8Q9TuMFsW9kIEeXiaPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46 +Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfSEASVIppEBYjDX203ypmURIzU +6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUkehQIl2JmlFrl2 +Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l +g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQIDAQABAoIBAHnfqjX3eO8SfnP5 +NURp90Td2mNHirCn0qLd9NKl1ySMPR1GgeH9SQ7Umu32EcteAUL5dOw2PiTZVmeW +cKINgsWVftXUQcOQ4xIqWKb51QUBdy0FhxrZRSFjWxXt5iYK1PmzHfsax/g1/S9C +RnqtFyjOy1bywkSt9jiy+9YBR2B7BDhLHlILbijWn5zaecaV4YA+L1UK4M/mehdb ++0FVPavbGpnlqBRTY+7YXfZ/mRPCfn5DvO9lW1O0pJMmNdBh9kmm3DxHf6AkK47a +43gO/dRWiWo2rZ/+Jw7uyqOb23U0MydP7kia0p3tzCUBPsrlgnichYG5RNFp0wqy +3VT1TYECgYEA0Y9vENy1jJd+s7WbGrsRtSKxfZgtJr0yjSlQVYrIlwbZSGn+ndxq +V2vVlwIgLX3pz6T40BMfk6SNx08jjy0Sgn6OAM0ILrinno8yWcSAMCmfCU0S/3O1 +55bqtcnk4XTHBHzJ5OrnrPaW5ourvJz0lcWEKMg3BXxLzaF6ZRy85nECgYEAwPMD +LNAKLCDrUMyYFOpPyPLe7wvszcFvPipGgerSgFP1c6N7xaMUdHDYqBfuis1khPGF +YcMHeNBYmzX6yEGbp3lrB4PHpUySmTU3mv3u9I05aahInK21gXum3uRkCWyyIF6V +T/qeszl9mVOCp0CC4eG3IMVpaD0UKDEHVhERYCkCgYAjuTPRyA4a3Wh38ilysRkf +q75eDqcDx5Tqg3RyYKo5NK2troP9HSnzpSpQB8i8eI53G0RfFCN5479XjqIdMi3J +mRFUCZ+vd0L7wKVwsBK6Ix49U6o9adhElnGEc9pUpLeYiD1SjMjZr1+iBYVNLeRz +86vH1/mpMbsqXrCis/dvwQKBgGttomHr/w3s0jftget7PirrFrbP0+wHfDGHhjRF +kyhCFtJovrwefYALaIXGtVjw3LusYZA570oT7pGUb2naJZkMYEwR0jG1vZWx7KDO +K6JbkxDB0pPxn7JVL2bAkPYyX8boAohCSOQO6WBZ/8+xem3bp4OGhpa0EyoBik0g +OaVpAoGATj4SyYsE10hGT676iie8zy3fi5IPC3E+x4QlVuusaLtuY8LJA50stjtx +gUa/JAKlZZL+gvzvOviQIxyfIChXOdTt5uiOYkdHJDbAF3NSrji7hrXq4v8UZv75 +8hBrwJZIpy6y01dRlrriHmPRtEq1pk7JX2uUg0sP5g4BEcsaCbc= +-----END RSA PRIVATE KEY----- +", + samlServicePrivateKeySig => "-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTej +JlMjUQdgBKBuZXQN+7/29P6UcGq1kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid +65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQzB0SIxSpnrsigqNsE1E94toDM +x4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9MgNOqvSTysr9LX +Wg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz +sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzywIDAQABAoIBAQCQkbvPPfP+bwC/ +IeEk1IO7qkzFWa7czR+safD0jc6OjTdNN4F716Q6yt4zEzLKu8VliiW+C23EBQiD +7asKf4DvdTun0ExVtHDK7aEdeealSlXwz1ZtdypyILbtq1UGo/rR0v4x601rQPl0 +IrBmFf6D6FkqleNtLJmxguXpoVfLdYKNwkxH2ux+GOA9r2o5pUCQmJGDap5YWRuQ +uB71ewJjVWujaL3e1ac/5cP7/tqWmgAiOaN8sYdD6+oWOR47bHj8JKcMBSl4y2QC +dL31cGmmf5KqBbtISki3RXfHHjT7E3Z85CbESkKTZlEb1ar3XmepY6Z7V5UO16oz +fFE5R6khAoGBAOl9Qb+qYVVO5ugE65ORjYVeuXykANhM9ssiY5a6zuAakWzw7Zv3 +k6PXm9p7azlEXAlTnTXVwHYMyuuzZDvQ8LRV1iBOdPuIkUAmaQ5K9ASD7VcoHexh +k8DAKf9Ln7sTRaMdvgceRNczOmJOBIEpTZkssA/jVGXZsoyTWYl1en/ZAoGBAMaW +RnNbSNprEV2b8UeAJ6i77c4SXwu1I8X2NLtiLScb1ETBjfrdHmdlJglfyd/0gmhH +p/43Ku2iGUoY5KtuOI6QmahrJYQscRQhoj252VXadG6fNWWAlpgdCm9houhHb5BF +3zge/bTr0anUe9EA7Z/ymav12rEouoNjIlhI9C5DAoGATR85a2SMt8/TB0owwdJu +62GpZNkLCmcJkXkvaecUVAOSi2hdI4o4MwMRkK35cbX5rH74y4JqCtQY5pefgP53 +sykzDAK+MyMdzxGg2764MRGegI5Yq+5jDmSquo+xF+q6srEtRk6iMG7UVwosBLmu +zuxqzySoiOfKSRKWnYe3SakCgYEAwWMkVkAmETXE4oDzFSsS8/mW2l//mPocTTK3 +JWe1CunJ6+8FYbAlZJEW2ngismp8+CoXybNVpbZ+pC7buKoMf6EHUgCNt0pEEFO0 +mCG9KSMk0XlPWXpArP9S4yaUq1itpzSz7QYZES+4rIcU0HLz9RgeWFyCTJWaFErc +7laVG9sCgYBKOtk5WlIOP4BxSd2y4cYzohgwTZIs1/2kTEn1u4eH73M1xvAlHHFB +wSF5QXgDKJ8pPAOhNWpdLO/PdtnQn91nOvTNc+ShJZzjdbneUdQVpWpoBf72uA+N +6rIVf1JBUL2p7HFHaGdUZC7KGQ+yv6ZHrE1+7202nuDvJdvGEEdFsQ== +-----END RSA PRIVATE KEY----- +", + samlServicePublicKeyEnc => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnfKBDG/K0TnGT7Xu8q1N +45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXiaPKXQa9r +yfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnV +DNfSEASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+t +BlcnMrkv/40DSUkehQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5N +Md0KFa6CwZUUSHJqH5GFy5Y2yl4lg8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxI +GQIDAQAB +-----END PUBLIC KEY----- +", + samlServicePublicKeySig => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtR/wgDqWB4Maho5V6Tjc +L/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1kYalURq6 +S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRy +BIQzB0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjT +EJOD/gHf04JCn9MgNOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5 +yD41mi+hT8Rh+W8Je8rsiML4VMxzsb1l9303asw6suo5bLTISKNSbu1nt1NkpNxz +ywIDAQAB +-----END PUBLIC KEY----- +", + samlSPMetaDataXML => { + "sp.com" => { + samlSPMetaDataXML => + samlSPMetaDataXML( 'sp', 'HTTP-Redirect' ) + }, + }, + } + } + ); +}