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' )
+ },
+ },
+ }
+ }
+ );
+}