diff --git a/doc/sources/admin/extendedfunctions.rst b/doc/sources/admin/extendedfunctions.rst index 90a4942e207201a4be1b00b8501eec355ee3c550..7cbaac514b0c6315931d0a34fefdf3bf96c3a418 100644 --- a/doc/sources/admin/extendedfunctions.rst +++ b/doc/sources/admin/extendedfunctions.rst @@ -40,6 +40,7 @@ Inside this jail, you can access to: * iso2unicode_ * iso2unicodeSafe_ (|new| in version 2.0.15) * listMatch_ (|new| in version 2.0.7) + * subjectid_ (|new| in version 2.17) * token_ * unicode2iso_ * unicode2isoSafe_ (|new| in version 2.0.15) @@ -422,6 +423,38 @@ Simple usage example: The function returns 1 if the value was found, and 0 if it was not found. +.. _subjectid: + +subjectid +~~~~~~~~~ + +.. versionadded:: 2.17 + +This function lets you generate an identifier suitable with the requirements of `urn:oasis:names:tc:SAML:profiles:subject-id `__ + +It takes an optional salt that makes it suitable for pairwise IDs as well. + +.. warning:: + + The generated ID will only be as stable as the underlying attribute + +Function parameter: + +- **value**: Value to hash +- **scope**: DNS suffix to append +- **salt**: optional salt to be added at the end of the value before hashing + +Simple usage example: + +:: + + # Generate a subject-id from uid, in a global macro + subjectid($uid, "scope.edu", "myglobalsalt") + + # Generate a pairwise-id, in a per-service macro + subjectid($uid, "scope.edu", "myservice") + + token ~~~~~ diff --git a/doc/sources/admin/samlfederation.rst b/doc/sources/admin/samlfederation.rst index 9e318fd6d91f7ee1a3c34b0db354170d086a8657..73e5b555a548c211cbfffa36a83623d40d21d70d 100644 --- a/doc/sources/admin/samlfederation.rst +++ b/doc/sources/admin/samlfederation.rst @@ -83,4 +83,40 @@ To do this: * Set *Options* » *Federation* » *Entity Identifier* to the entityID of the provider you want to override * Set the desired configuration options on this provider +Configure SAML attributes +========================= + +Federation metadata files contains information about attributes required or +simply requested by federated service providers (SP). In order for those +attributes to be successfully sent to providers, they must exist in the +LemonLDAP::NG session, under the same name as the FriendlyName declared in +metadata. + +For example, the following attribute definition:: + + + +requires an ``uid`` attribute in the session. Make sure you configure your +exported variables accordingly, and use macros to fill the gaps if needed. + +Subject ID +---------- + +.. versionadded:: 2.17 + +Some SAML SPs can now use a ``subject-id`` attribute instead of a NameID or +``eduPersonTargetedID`` (not supported in LemonLDAP::NG). + +In order to provide a ``subject-id`` attribute, you must create a macro with + +* Key: ``subjectId`` +* Value: ``subjectid($uid, "scope.edu", "myrandomsalt1")`` + +The ``subject-id`` attribute will only be sent if the SP metadata contains the +``urn:oasis:names:tc:SAML:profiles:subject-id:req`` extension with a value of +``any`` or ``subject-id`` + .. |beta| image:: /documentation/beta.png diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Safelib.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Safelib.pm index d3f5b6e676385cd07bc5182d9f18d51ab44f0ebc..9c9ee31a44cd385a515699e7f4ba24efb1fb0144 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Safelib.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Safelib.pm @@ -11,6 +11,7 @@ use MIME::Base64; use Lemonldap::NG::Common::IPv6; use JSON::XS; use Net::CIDR; +use Digest::SHA; use Date::Parse; our $VERSION = '2.17.0'; @@ -19,7 +20,7 @@ our $VERSION = '2.17.0'; # Note that only functions, not methods, can be written here our $functions = [ - qw(&checkLogonHours &date &dateToTime &checkDate &basic &unicode2iso &unicode2isoSafe &iso2unicode &iso2unicodeSafe &groupMatch &isInNet6 &varIsInUri &has2f_internal &ipInSubnet) + qw(&checkLogonHours &date &dateToTime &checkDate &basic &unicode2iso &unicode2isoSafe &iso2unicode &iso2unicodeSafe &groupMatch &isInNet6 &varIsInUri &has2f_internal &ipInSubnet &subjectid) ]; ## @function boolean ipInSubnet(string ip, string network, ... ) @@ -33,6 +34,38 @@ sub ipInSubnet { return Net::CIDR::cidrlookup( $ip, @networks ); } +## @function boolean subjectid(string alg, string value, string scope, string salt) +# Function to compute an opaque identifier from a value and optional salt +# @param $ip IP address to test +# @param $network Network in CIDR notation. +# You can call the function with multiple networks +# @return 1 true, 0 else +sub subjectid { + my ( $value, $scope, $salt ) = @_; + $salt //= ""; + $scope = $scope ? "\@$scope" : ""; + return ( lc ( encode_base32( Digest::SHA::sha256( $value . $salt ) ) ). $scope ); +} + +# This function is copied from MIME::Base32 under GPL/Artistic license +# by Daniel Peder +sub encode_base32 { + my $arg = shift; + return '' unless defined($arg); # mimic MIME::Base64 + + $arg = unpack( 'B*', $arg ); + $arg =~ s/(.....)/000$1/g; + my $l = length($arg); + if ( $l & 7 ) { + my $e = substr( $arg, $l & ~7 ); + $arg = substr( $arg, 0, $l & ~7 ); + $arg .= "000$e" . '0' x ( 5 - length $e ); + } + $arg = pack( 'B*', $arg ); + $arg =~ tr|\0-\37|A-Z2-7|; + return $arg; +} + ## @function boolean checkLogonHours(string logon_hours, string syntax, string time_correction, boolean default_access) # Function to check logon hours # @param $logon_hours string representing allowed logon hours (GMT) diff --git a/lemonldap-ng-common/scripts/importMetadata b/lemonldap-ng-common/scripts/importMetadata index 0a5c2d1c2671757e3828d113520209d26cf95095..7ce868a3f33d1d4f664edf2bbfa2f7bf478c1bbc 100755 --- a/lemonldap-ng-common/scripts/importMetadata +++ b/lemonldap-ng-common/scripts/importMetadata @@ -224,9 +224,8 @@ sub _compute_requested_attributes { my $result = {}; for my $name ( keys %$requestedAttributes ) { - my $attrconf = $requestedAttributes->{$name}; - my $is_required = - $config->{"$entityID"}->{"attribute_required_$name"} + my $attrconf = $requestedAttributes->{$name}; + my $is_required = $config->{"$entityID"}->{"attribute_required_$name"} // $config->{"$entityID"}->{"attribute_required"} // $config->{"ALL"}->{"attribute_required_$name"} // $config->{"ALL"}->{"attribute_required"} // $attrconf->{required}; @@ -371,9 +370,6 @@ sub transform_config { my $dom = XML::LibXML->load_xml( string => $xml_metadata ); - # Remove extensions - foreach ( $dom->findnodes('//md:Extensions') ) { $_->unbindNode; } - # Browse all partners foreach my $partner ( $dom->findnodes('/md:EntitiesDescriptor/md:EntityDescriptor') ) @@ -383,10 +379,30 @@ sub transform_config { # Add required XML namespaces $partner->setNamespace( "urn:oasis:names:tc:SAML:2.0:metadata", "md", 0 ); + $partner->setNamespace( "urn:oasis:names:tc:SAML:metadata:attribute", + "mdattr", 0 ); $partner->setNamespace( "urn:oasis:names:tc:SAML:2.0:assertion", "saml", 0 ); $partner->setNamespace( "http://www.w3.org/2000/09/xmldsig#", "ds", 0 ); + # Parse subject-id:req extension + my $requested_subject_id = "none"; + if ( + my $subjectid = $partner->findnodes( + './md:Extensions' + . '/mdattr:EntityAttributes' + . '/saml:Attribute[@Name="urn:oasis:names:tc:SAML:profiles:subject-id:req"]' + . '/saml:AttributeValue[1]' + . '/text()' + )->shift() + ) + { + $requested_subject_id = $subjectid->toString; + } + + # Remove other extensions + foreach ( $partner->findnodes('.//md:Extensions') ) { $_->unbindNode; } + # Check IDP or SP if ( my $idp = $partner->findnodes('./md:IDPSSODescriptor') ) { $idpCounter->{found}++; @@ -491,14 +507,16 @@ sub transform_config { my $required = ( $requestedAttribute->getAttribute("isRequired") =~ /true/i ) ? 1 : 0; - $requestedAttributes->{$friendlyname} = { - required => $required, - name => $name, - name_format => $nameformat, - friendly_name => $friendlyname - }; - printlog( "Attribute $friendlyname ($name)" - . " requested by SP $entityID\n" ); + if ($friendlyname) { + $requestedAttributes->{$friendlyname} = { + required => $required, + name => $name, + name_format => $nameformat, + friendly_name => $friendlyname + }; + printlog( "Attribute $friendlyname ($name)" + . " requested by SP $entityID\n" ); + } } } else { @@ -508,6 +526,17 @@ sub transform_config { mail => { required => 1, name => 'mail' }, }; } + if ( $requested_subject_id eq "any" + or $requested_subject_id eq "subject-id" ) + { + $requestedAttributes->{"subjectId"} = { + required => 0, + name => "urn:oasis:names:tc:SAML:attribute:subject-id", + name_format => + "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + friendly_name => "subject-id", + }; + } # Remove AttributeConsumingService node foreach ( @@ -670,7 +699,7 @@ sub main { printlog("[INFO] run mod EntityID will be inserted\n"); $numConf = $conf->saveConf( $lastConf, ( cfgNumFixed => 1 ) ); printlog("[OK] Configuration $numConf saved\n"); - unless ($numConf > 0) { + unless ( $numConf > 0 ) { print "[ERROR] Unable to save configuration\n"; $exitCode = 1; } diff --git a/lemonldap-ng-common/t/45-importMetadata-config.t b/lemonldap-ng-common/t/45-importMetadata-config.t index 5f74fbed6c5a83b49d3177220e70283aa715ab76..2602563926c510f038e89c06dd22778687791338 100644 --- a/lemonldap-ng-common/t/45-importMetadata-config.t +++ b/lemonldap-ng-common/t/45-importMetadata-config.t @@ -13,6 +13,10 @@ my $xml; close XML; } +# Update this if you change the content of the file +my $idp_in_file = 12; +my $sp_in_file = 48; + subtest 'Ignore SP' => sub { my $lmConf = {}; my $importConf = { @@ -27,9 +31,9 @@ subtest 'Ignore SP' => sub { # Run import my ( $spCounters, $idpCounters ) = transform_config( $importConf, $lmConf, $xml ); - is( $spCounters->{created}, 45 ); + is( $spCounters->{created}, $sp_in_file - 2 ); is( $spCounters->{ignored}, 2 ); - is( $idpCounters->{created}, 12 ); + is( $idpCounters->{created}, $idp_in_file ); is( $idpCounters->{ignored}, 0 ); }; @@ -47,9 +51,9 @@ subtest 'Ignore IDP' => sub { # Run import my ( $spCounters, $idpCounters ) = transform_config( $importConf, $lmConf, $xml ); - is( $spCounters->{created}, 47 ); + is( $spCounters->{created}, $sp_in_file ); is( $spCounters->{ignored}, 0 ); - is( $idpCounters->{created}, 10 ); + is( $idpCounters->{created}, $idp_in_file - 2 ); is( $idpCounters->{ignored}, 2 ); }; @@ -65,11 +69,11 @@ subtest 'Conf Prefix' => sub { # Run import transform_config( $importConf, $lmConf, $xml ); is( scalar grep( /^renater-sp/, keys( %{ $lmConf->{samlSPMetaDataXML} } ) ), - 47 ); + $sp_in_file ); is( scalar grep( /^renater-idp/, keys( %{ $lmConf->{samlIDPMetaDataXML} } ) ), - 12 + $idp_in_file ); }; diff --git a/lemonldap-ng-common/t/45-importMetadata.t b/lemonldap-ng-common/t/45-importMetadata.t index 6400e5e39eaccc86ea914ba22444bc7b2f81aa43..bbfc0152beb66a507bacb56e082711dd788feaa8 100644 --- a/lemonldap-ng-common/t/45-importMetadata.t +++ b/lemonldap-ng-common/t/45-importMetadata.t @@ -13,6 +13,10 @@ my $xml; close XML; } +# Update this if you change the content of the file +my $idp_in_file = 12; +my $sp_in_file = 48; + my $lmConf = {}; my $importConf = {}; @@ -24,8 +28,8 @@ my ( $spCounters, $idpCounters ) = is_deeply( $spCounters, { - 'created' => 47, - 'found' => 48, + 'created' => $sp_in_file, + 'found' => $sp_in_file + 1, 'ignored' => 0, 'rejected' => 1, 'removed' => 0, @@ -36,8 +40,8 @@ is_deeply( is_deeply( $idpCounters, { - 'created' => 12, - 'found' => 13, + 'created' => $idp_in_file, + 'found' => $idp_in_file + 1, 'ignored' => 0, 'rejected' => 1, 'removed' => 0, @@ -46,20 +50,23 @@ is_deeply( "IDP counters are expected" ); -is( keys %{ $lmConf->{samlIDPMetaDataXML} }, 12, - "Correct amount of providers" ); +is( keys %{ $lmConf->{samlIDPMetaDataXML} }, + $idp_in_file, "Correct amount of providers" ); is( keys %{ $lmConf->{samlIDPMetaDataExportedAttributes} }, - 12, "Correct amount of providers" ); + $idp_in_file, "Correct amount of providers" ); is( keys %{ $lmConf->{samlIDPMetaDataOptions} }, - 12, "Correct amount of providers" ); -is( keys %{ $lmConf->{samlSPMetaDataXML} }, 47, "Correct amount of providers" ); + $idp_in_file, "Correct amount of providers" ); +is( keys %{ $lmConf->{samlSPMetaDataXML} }, + $sp_in_file, "Correct amount of providers" ); is( keys %{ $lmConf->{samlSPMetaDataExportedAttributes} }, - 47, "Correct amount of providers" ); + $sp_in_file, "Correct amount of providers" ); is( keys %{ $lmConf->{samlSPMetaDataOptions} }, - 47, "Correct amount of providers" ); + $sp_in_file, "Correct amount of providers" ); my $idp = "idp-idp-test-insa-rennes-fr-idp-shibboleth"; my $sp = "sp-ucopia-univ-brest-fr"; +my $eduroam = + "sp-monitor-eduroam-org-sp-module-php-saml-sp-metadata-php-default-sp"; is( $lmConf->{samlIDPMetaDataExportedAttributes}->{$idp} @@ -86,6 +93,16 @@ is( "Found required attribute" ); +is( + + $lmConf->{samlSPMetaDataExportedAttributes}->{$eduroam}->{'subjectId'}, + join( ';', + 0, + 'urn:oasis:names:tc:SAML:attribute:subject-id', + 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'subject-id' ), + "Found subject ID" +); + # Check update $lmConf->{samlSPMetaDataOptions}->{$sp} ->{samlSPMetaDataOptionsCheckSSOMessageSignature} = 0; @@ -98,7 +115,7 @@ is_deeply( $spCounters, { 'created' => 0, - 'found' => 48, + 'found' => $sp_in_file + 1, 'ignored' => 0, 'rejected' => 1, 'removed' => 0, @@ -110,7 +127,7 @@ is_deeply( $idpCounters, { 'created' => 0, - 'found' => 13, + 'found' => $idp_in_file + 1, 'ignored' => 0, 'rejected' => 1, 'removed' => 0, @@ -137,7 +154,7 @@ is_deeply( $spCounters, { 'created' => 0, - 'found' => 48, + 'found' => $sp_in_file + 1, 'ignored' => 0, 'rejected' => 1, 'removed' => 0, @@ -149,7 +166,7 @@ is_deeply( $idpCounters, { 'created' => 0, - 'found' => 13, + 'found' => $idp_in_file + 1, 'ignored' => 0, 'rejected' => 1, 'removed' => 0, diff --git a/lemonldap-ng-common/t/data/preview-all-test-metadata.xml b/lemonldap-ng-common/t/data/preview-all-test-metadata.xml index 621ab81b5188656e240fc8074d30814cb2b0372f..98e60bfafdec3c83646ef8d4055fd8269820c139 100644 --- a/lemonldap-ng-common/t/data/preview-all-test-metadata.xml +++ b/lemonldap-ng-common/t/data/preview-all-test-metadata.xml @@ -5781,4 +5781,72 @@ KICvQeYKdOcV mailto:sylvain.brachotte@uhp-nancy.fr + + + + + http://www.geant.net/uri/dataprotection-code-of-conduct/v1 + http://refeds.org/category/research-and-scholarship + + + any + + + + http://www.aaiedu.hr/docs/AAI@EduHr-pravilnik-ver1.3.1.pdf + + + + + + eduroam supporting services + eduroam supporting services include: eduroam database, CAT, monitoring, F-Ticks + https://monitor.eduroam.org + https://monitor.eduroam.org/sp/CoC/privacystatement_en.html + https://monitor.eduroam.org/sp/resources/eduroamtheme/eduroam_logo.png + + + + + + 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 + + + + + + + + + + + + eduroam supporting services + eduroam supporting services include: eduroam database, CAT, monitoring, F-Ticks + + + + + + + eduroam + eduroam + http://www.eduroam.org + + + eduroam operations team + mailto:monitor@eduroam.org + + + eduroam operations team + mailto:monitor@eduroam.org + + diff --git a/lemonldap-ng-handler/t/12-Lemonldap-NG-Handler-Jail.t b/lemonldap-ng-handler/t/12-Lemonldap-NG-Handler-Jail.t index c3bc747fcc748af34ff7b1c9f9f33ba68005375a..2d14b8794fc2668d925238df895d18676d134f72 100644 --- a/lemonldap-ng-handler/t/12-Lemonldap-NG-Handler-Jail.t +++ b/lemonldap-ng-handler/t/12-Lemonldap-NG-Handler-Jail.t @@ -6,7 +6,7 @@ # change 'tests => 1' to 'tests => last_test_to_print'; use strict; -use Test::More tests => 26; +use Test::More tests => 30; require 't/test.pm'; BEGIN { use_ok('Lemonldap::NG::Handler::Main::Jail') } @@ -125,3 +125,27 @@ like( qr/Missing right curly or square bracket/, 'Found correct error message' ); + +$sub = "sub { return(subjectid(\@_)) }"; +$code = $jail->jail_reval($sub); +ok( + ( defined($code) and ref($code) eq 'CODE' ), + 'subjectid extended function is defined' +); +is( + $code->( "abc", "def" ), + "xj4bnp4pahh6uqkbidpf3lrceoyagyndsylxvhfucd7wd4qacwwq\@def", + "subjectid works as expected" +); + +is( + $code->( "abc", "def", "salt" ), + "c2ntukyjo7v64lvr7u4sy2veocrsqhkdt7zs6if25jzqtv7ub3wa\@def", + "subjectid works as expected" +); + +is( + $code->("abc"), + "xj4bnp4pahh6uqkbidpf3lrceoyagyndsylxvhfucd7wd4qacwwq", + "subjectid works as expected" +); diff --git a/lemonldap-ng-handler/t/13-Lemonldap-NG-Handler-Fake-Safe.t b/lemonldap-ng-handler/t/13-Lemonldap-NG-Handler-Fake-Safe.t index 944983b07d753bbaa06464daed31d76be646359d..1899a318e1d9c359257134ab71f89e622641c438 100644 --- a/lemonldap-ng-handler/t/13-Lemonldap-NG-Handler-Fake-Safe.t +++ b/lemonldap-ng-handler/t/13-Lemonldap-NG-Handler-Fake-Safe.t @@ -5,7 +5,7 @@ # change 'tests => 1' to 'tests => last_test_to_print'; -use Test::More tests => 20; +use Test::More tests => 24; require 't/test.pm'; BEGIN { use_ok('Lemonldap::NG::Handler::Main::Jail') } @@ -118,3 +118,27 @@ like( qr/Missing right curly or square bracket/, 'Found correct error message' ); + +$sub = "sub { return(subjectid(\@_)) }"; +$code = $jail->jail_reval($sub); +ok( + ( defined($code) and ref($code) eq 'CODE' ), + 'subjectid extended function is defined' +); +is( + $code->( "abc", "def" ), + "xj4bnp4pahh6uqkbidpf3lrceoyagyndsylxvhfucd7wd4qacwwq\@def", + "subjectid works as expected" +); + +is( + $code->( "abc", "def", "salt" ), + "c2ntukyjo7v64lvr7u4sy2veocrsqhkdt7zs6if25jzqtv7ub3wa\@def", + "subjectid works as expected" +); + +is( + $code->("abc"), + "xj4bnp4pahh6uqkbidpf3lrceoyagyndsylxvhfucd7wd4qacwwq", + "subjectid works as expected" +); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SamlFederation.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SamlFederation.pm index be6843cbbb06df47116b466c39005d2a043a4a46..56054f7fc56dfdeb3efdae79afaa84d4c9616350 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SamlFederation.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/SamlFederation.pm @@ -171,10 +171,27 @@ sub get_config_info_from_xml_federation { # Add required XML namespaces $partner->setNamespace( "urn:oasis:names:tc:SAML:2.0:metadata", "md", 0 ); + $partner->setNamespace( "urn:oasis:names:tc:SAML:metadata:attribute", + "mdattr", 0 ); $partner->setNamespace( "urn:oasis:names:tc:SAML:2.0:assertion", "saml", 0 ); $partner->setNamespace( "http://www.w3.org/2000/09/xmldsig#", "ds", 0 ); + # Parse subject-id:req extension + my $requested_subject_id = "none"; + if ( + my $subjectid = $partner->findnodes( + './md:Extensions' + . '/mdattr:EntityAttributes' + . '/saml:Attribute[@Name="urn:oasis:names:tc:SAML:profiles:subject-id:req"]' + . '/saml:AttributeValue[1]' + . '/text()' + )->shift() + ) + { + $requested_subject_id = $subjectid->toString; + } + # Check IDP or SP if ( my $idp = $partner->findnodes('./md:IDPSSODescriptor') ) { @@ -229,14 +246,27 @@ sub get_config_info_from_xml_federation { ( $requestedAttribute->getAttribute("isRequired") || '' =~ /true/i ) ? 1 : 0; - $self->logger->debug( "Attribute $friendlyname ($name)" - . " requested by SP $entityID\n" ); + if ($friendlyname) { + $self->logger->debug( "Attribute $friendlyname ($name)" + . " requested by SP $entityID\n" ); - $requestedAttributes->{$friendlyname} = - "$required;$name;$nameformat;$friendlyname"; + $requestedAttributes->{$friendlyname} = + "$required;$name;$nameformat;$friendlyname"; + } } } + if ( $requested_subject_id eq "any" + or $requested_subject_id eq "subject-id" ) + { + # any or subject-id means that the attribute is required + $requestedAttributes->{"subjectId"} = join( ";", + "1", + "urn:oasis:names:tc:SAML:attribute:subject-id", + "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + "subject-id" ); + } + # Remove AttributeConsumingService node foreach ( $partner->findnodes( diff --git a/lemonldap-ng-portal/t/30-SAML-Federation-Config.t b/lemonldap-ng-portal/t/30-SAML-Federation-Config.t index a39bf692bb169c6eb0a120ae7e437876e1d06e84..c6f1a906fd1f74e131b9e201e419b744aa6999a0 100644 --- a/lemonldap-ng-portal/t/30-SAML-Federation-Config.t +++ b/lemonldap-ng-portal/t/30-SAML-Federation-Config.t @@ -41,6 +41,10 @@ SKIP: { $saml->lazy_load_metadata($entityID); $entityID = "https://www.numistral.fr/shibboleth"; $saml->lazy_load_metadata($entityID); + $entityID = +"https://monitor.eduroam.org/sp/module.php/saml/sp/metadata.php/default-sp"; + $saml->lazy_load_metadata($entityID); + is( $saml->spList->{'https://podcast.mines-nantes.fr/shibboleth'} ->{confKey}, @@ -96,6 +100,13 @@ SKIP: { "SP attributes have been imported as configured by policy", ); + is( + $saml->spAttributes->{ +'https://monitor.eduroam.org/sp/module.php/saml/sp/metadata.php/default-sp' + }->{'subjectId'}, +'0;urn:oasis:names:tc:SAML:attribute:subject-id;urn:oasis:names:tc:SAML:2.0:attrname-format:uri;subject-id' + ); + is( $saml->spOptions->{'https://www.numistral.fr/shibboleth'} ->{samlSPMetaDataOptionsNameIDFormat}, @@ -145,8 +156,7 @@ clean_sessions(); done_testing(); sub issuer { - return LLNG::Manager::Test->new( - { + return LLNG::Manager::Test->new( { ini => { samlFederationFiles => "t/main-idps-renater-metadata.xml t/main-sps-renater-metadata.xml", @@ -219,8 +229,7 @@ sub issuer { } sub sp { - return LLNG::Manager::Test->new( - { + return LLNG::Manager::Test->new( { ini => { samlFederationFiles => "t/main-idps-renater-metadata.xml t/main-sps-renater-metadata.xml", diff --git a/lemonldap-ng-portal/t/main-sps-renater-metadata.xml b/lemonldap-ng-portal/t/main-sps-renater-metadata.xml index d465ab7618fe50d5e9df420f354620e30910a039..cd14024fbc17bf9bf074fbacf7b8a31d4dbc4685 100644 --- a/lemonldap-ng-portal/t/main-sps-renater-metadata.xml +++ b/lemonldap-ng-portal/t/main-sps-renater-metadata.xml @@ -221,4 +221,72 @@ fsDwhBGpV6YTK/uR07UTqBRQB0MlStBw88D6Ju7oFeHBpA== mailto:cri-contact@bnu.fr + + + + + http://www.geant.net/uri/dataprotection-code-of-conduct/v1 + http://refeds.org/category/research-and-scholarship + + + any + + + + http://www.aaiedu.hr/docs/AAI@EduHr-pravilnik-ver1.3.1.pdf + + + + + + eduroam supporting services + eduroam supporting services include: eduroam database, CAT, monitoring, F-Ticks + https://monitor.eduroam.org + https://monitor.eduroam.org/sp/CoC/privacystatement_en.html + https://monitor.eduroam.org/sp/resources/eduroamtheme/eduroam_logo.png + + + + + + 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 + + + + + + + + + + + + eduroam supporting services + eduroam supporting services include: eduroam database, CAT, monitoring, F-Ticks + + + + + + + eduroam + eduroam + http://www.eduroam.org + + + eduroam operations team + mailto:monitor@eduroam.org + + + eduroam operations team + mailto:monitor@eduroam.org + +