SAML: add Service Providers node

This commit is contained in:
Clément Oudot 2010-03-25 14:38:54 +00:00
parent dec9d562d8
commit 3cd3050e9e
7 changed files with 188 additions and 63 deletions

View File

@ -54,7 +54,7 @@ sub unserialize {
# Manage hashes # Manage hashes
if ( $k =~ if ( $k =~
/^(?:exportedVars|locationRules|groups|exportedHeaders|macros|globalStorageOptions|remoteGlobalStorageOptions|notificationStorageOptions|samlIDPMetaDataXML|samlIDPMetaDataExportedAttributes|samlIDPMetaDataOptions)$/ /^(?:exportedVars|locationRules|groups|exportedHeaders|macros|globalStorageOptions|remoteGlobalStorageOptions|notificationStorageOptions|samlIDPMetaDataXML|samlIDPMetaDataExportedAttributes|samlIDPMetaDataOptions|samlSPMetaDataXML|samlSPMetaDataExportedAttributes|samlSPMetaDataOptions)$/
and $v ||= {} and $v ||= {}
and not ref($v) ) and not ref($v) )
{ {

View File

@ -281,7 +281,8 @@ function display(div,title) {
$('#content_'+div).addClass('content'); $('#content_'+div).addClass('content');
$('#content_title').html(title); $('#content_title').html(title);
$('#newkb,#newrb,#delkb,#newkbr,#newrbr,#bdelvh,#bnewvh').hide(); $('#newkb,#newrb,#delkb,#newkbr,#newrbr,#bdelvh,#bnewvh').hide();
$('#newsamlmetadatab,#delsamlmetadatab').hide(); $('#newidpsamlmetadatab,#delidpsamlmetadatab').hide();
$('#newspsamlmetadatab,#delspsamlmetadatab').hide();
$('#newsamlattributeb,#delsamlattributeb').hide(); $('#newsamlattributeb,#delsamlattributeb').hide();
$('#newsamlattributebr').hide(); $('#newsamlattributebr').hide();
var height_cur=$('#buttons').height()+$('#edition').height()+$('#help').height(); var height_cur=$('#buttons').height()+$('#edition').height()+$('#help').height();
@ -308,7 +309,12 @@ function vhostRoot(id){
function samlIdpRoot(id){ function samlIdpRoot(id){
currentId=id; currentId=id;
display('default',''); display('default','');
$('#newsamlmetadatab').show(); $('#newidpsamlmetadatab').show();
}
function samlSpRoot(id){
currentId=id;
display('default','');
$('#newspsamlmetadatab').show();
} }
function authParams(id) { function authParams(id) {
currentId=id; currentId=id;
@ -418,8 +424,9 @@ function filearea(id) {
currentId=id; currentId=id;
$('#urlinput').hide(); $('#urlinput').hide();
$('#filearea').attr('value',lmdata(id)); $('#filearea').attr('value',lmdata(id));
/* Only samlIDPMetaDataXML element could be loaded from URL */ /* Only samlIDPMetaDataXML and samlSPMetaDataXML element could be loaded from URL */
if(lmtext(id)=='samlIDPMetaDataXML'){$('#urlinput').show();} if(lmtext(id)=='samlIDPMetaDataXML'){$('#urlinput').show();}
if(lmtext(id)=='samlSPMetaDataXML') {$('#urlinput').show();}
display('filearea',lmtext(id)); display('filearea',lmtext(id));
} }
function samlAssertion(id) { function samlAssertion(id) {
@ -459,16 +466,27 @@ function samlAttributeRoot(){
display('default',''); display('default','');
$('#newsamlattributebr').show(); $('#newsamlattributebr').show();
} }
function samlMetaData(id){ function samlIdpMetaData(id){
currentId=id; currentId=id;
$('#samlMetaData').attr('value',lmtext(id)); $('#samlIdpMetaData').attr('value',lmtext(id));
display('samlMetaData',lmtext(id)); display('samlIdpMetaData',lmtext(id));
if($('#li_'+myB64('/samlIDPMetaDataNode')).find('span').size()==1){ if($('#li_'+myB64('/samlIDPMetaDataNode')).find('span').size()==1){
$('#delsamlmetadatab').hide(); $('#delidpsamlmetadatab').hide();
}else{ }else{
$('#delsamlmetadatab').show(); $('#delidpsamlmetadatab').show();
} }
$('#newsamlmetadatab').show(); $('#newidpsamlmetadatab').show();
}
function samlSpMetaData(id){
currentId=id;
$('#samlSpMetaData').attr('value',lmtext(id));
display('samlSpMetaData',lmtext(id));
if($('#li_'+myB64('/samlSPMetaDataNode')).find('span').size()==1){
$('#delspsamlmetadatab').hide();
}else{
$('#delspsamlmetadatab').show();
}
$('#newspsamlmetadatab').show();
} }
function samlService(id) { function samlService(id) {
currentId=id; currentId=id;
@ -524,11 +542,6 @@ function rulesRoot(id){
display('default',''); display('default','');
$('#newrbr').show(); $('#newrbr').show();
} }
function samlMetaDataRoot(id){
currentId=id;
display('default', '');
$('#newsamlmetadatab').show();
}
function reloadAuthParams() { function reloadAuthParams() {
var value=$('#authText').attr('value'); var value=$('#authText').attr('value');
if($('#authOptions').is(':visible')==true){ if($('#authOptions').is(':visible')==true){
@ -631,13 +644,22 @@ function newSamlAttributeR(){
}); });
return false; return false;
} }
function newSamlMetaData(){ function newIdpSamlMetaData(){
var name = prompt(text4newSamlMetaData,'authentic'); var name = prompt(text4newSamlMetaData,'idp-example');
if(!name){return false;} if(!name){return false;}
var idpId='li_'+myB64('/samlIDPMetaDataExportedAttributes/'+name); var idpId='li_'+myB64('/samlIDPMetaDataExportedAttributes/'+name);
simpleTreeCollection[0].newAjaxNodeIn($('#li_L3NhbWxJRFBNZXRhRGF0YU5vZGU1'),idpId,name,scriptname+'?type=new&node=/samlIDPMetaDataNode/'+name,function(d,s){ simpleTreeCollection[0].newAjaxNodeIn($('#li_L3NhbWxJRFBNZXRhRGF0YU5vZGU1'),idpId,name,scriptname+'?type=new&node=/samlIDPMetaDataNode/'+name,function(d,s){
$('>span',s).attr('name',name).attr('help','default').attr('id','text_'+idpId).attr('onclick','samlMetaData(\''+idpId+'\')'); $('>span',s).attr('name',name).attr('help','default').attr('id','text_'+idpId).attr('onclick','samlIdpMetaData(\''+idpId+'\')');
samlMetaData(idpId); samlIdpMetaData(idpId);
});
}
function newSpSamlMetaData(){
var name = prompt(text4newSamlMetaData,'sp-example');
if(!name){return false;}
var spId='li_'+myB64('/samlSPMetaDataExportedAttributes/'+name);
simpleTreeCollection[0].newAjaxNodeIn($('#li_L3NhbWxTUE1ldGFEYXRhTm9kZQ2'),spId,name,scriptname+'?type=new&node=/samlSPMetaDataNode/'+name,function(d,s){
$('>span',s).attr('name',name).attr('help','default').attr('id','text_'+spId).attr('onclick','samlSpMetaData(\''+spId+'\')');
samlSpMetaData(spId);
}); });
} }
var cfgAttrDone=0; var cfgAttrDone=0;

View File

@ -104,12 +104,20 @@
<lang en="Delete key" fr="Effacer la clef" /> <lang en="Delete key" fr="Effacer la clef" />
</button> </button>
<button id="newsamlmetadatab" style="display:none;" onclick="newSamlMetaData();return false;" class="ui-state-default ui-corner-all"> <button id="newidpsamlmetadatab" style="display:none;" onclick="newIdpSamlMetaData();return false;" class="ui-state-default ui-corner-all">
<lang en="New metadatas" fr="Nouvelles métadatas" /> <lang en="New identity provider" fr="Nouveau fournisseur d'identité" />
</button> </button>
<button id="delsamlmetadatab" style="display:none;" onclick="delSamlMetaData(currentId);" class="ui-state-default ui-corner-all"> <button id="delidpsamlmetadatab" style="display:none;" onclick="delIdpSamlMetaData(currentId);" class="ui-state-default ui-corner-all">
<lang en="Delete metadatas" fr="Supprimer les métadatas" /> <lang en="Delete identity provider" fr="Supprimer le fournisseur d'identité" />
</button>
<button id="newspsamlmetadatab" style="display:none;" onclick="newSpSamlMetaData();return false;" class="ui-state-default ui-corner-all">
<lang en="New service provider" fr="Nouveau fournisseur de service" />
</button>
<button id="delspsamlmetadatab" style="display:none;" onclick="delSpSamlMetaData(currentId);" class="ui-state-default ui-corner-all">
<lang en="Delete service provider" fr="Supprimer le fournisseur de service" />
</button> </button>
<button id="newsamlattributeb" style="display:none;" onclick="newSamlAttribute();return false;" class="ui-state-default ui-corner-all"> <button id="newsamlattributeb" style="display:none;" onclick="newSamlAttribute();return false;" class="ui-state-default ui-corner-all">
@ -281,11 +289,20 @@
</button> </button>
</div> </div>
<!-- samlMetaData --> <!-- samlIdpMetaData -->
<div id="content_samlMetaData" class="hidden"> <div id="content_samlIdpMetaData" class="hidden">
<input type="text" id="samlMetaData" /> <input type="text" id="samlIdpMetaData" />
<br /> <br />
<button onclick="setlminputtext(currentId,samlMetaData);return false;" class="ui-state-default ui-corner-all"> <button onclick="setlminputtext(currentId,samlIdpMetaData);return false;" class="ui-state-default ui-corner-all">
<lang en="Apply" fr="Appliquer" />
</button>
</div>
<!-- samlSpMetaData -->
<div id="content_samlSpMetaData" class="hidden">
<input type="text" id="samlSpMetaData" />
<br />
<button onclick="setlminputtext(currentId,samlSpMetaData);return false;" class="ui-state-default ui-corner-all">
<lang en="Apply" fr="Appliquer" /> <lang en="Apply" fr="Appliquer" />
</button> </button>
</div> </div>

View File

@ -190,6 +190,7 @@ sub confNode {
elsif ( $target =~ s/^samlmetadata:// ) { elsif ( $target =~ s/^samlmetadata:// ) {
my $h = $self->keyToH( $target, $self->conf ); my $h = $self->keyToH( $target, $self->conf );
$h = $h->{samlIDPMetaDataXML} if ( $h->{samlIDPMetaDataXML} ); $h = $h->{samlIDPMetaDataXML} if ( $h->{samlIDPMetaDataXML} );
$h = $h->{samlSPMetaDataXML} if ( $h->{samlSPMetaDataXML} );
my $metadata = Lemonldap::NG::Common::Conf::SAML::Metadata->new(); my $metadata = Lemonldap::NG::Common::Conf::SAML::Metadata->new();
$metadata->initializeFromConfHash($h); $metadata->initializeFromConfHash($h);
my $text = $target; my $text = $target;
@ -204,7 +205,6 @@ sub confNode {
target => "samlmetadata", target => "samlmetadata",
) . "</li>"; ) . "</li>";
} }
else { else {
$target =~ s/^(\w+)://; $target =~ s/^(\w+)://;
my $type = $1 || 'text'; my $type = $1 || 'text';
@ -213,7 +213,17 @@ sub confNode {
$text =~ s/^.*\///; $text =~ s/^.*\///;
my $h = $self->keyToH( $target, $self->conf ); my $h = $self->keyToH( $target, $self->conf );
# Try to get value from defaultConf
$h = $self->keyToH( $target, $self->defaultConf ) unless ( defined $h ); $h = $self->keyToH( $target, $self->defaultConf ) unless ( defined $h );
# If no value found, try to remove 2 first target components
# to manage complex hash like samlIDPMetaDataOptions
unless ( defined $h ) {
$target =~ /([^\/]*)$/;
$h = $self->keyToH( $1, $self->defaultConf );
}
# If still no value, set a default value depending on type
unless ( defined $h ) { unless ( defined $h ) {
$self->lmLog( "$target does not exists in menu hash", "debug" ); $self->lmLog( "$target does not exists in menu hash", "debug" );
$h = { $h = {

View File

@ -35,6 +35,7 @@ sub confUpload {
# Variables to store current vhost and IDP name # Variables to store current vhost and IDP name
my $vhostname; my $vhostname;
my $idpname; my $idpname;
my $spname;
# 1. ANALYSE DATAS # 1. ANALYSE DATAS
@ -88,11 +89,20 @@ sub confUpload {
$idpname = $name; $idpname = $name;
} }
# Get SAML SP name
if ( $id =~ /samlSPMetaDataExportedAttributes\/([^\/]*)?$/ ) {
$self->lmLog( "Entering SP $name", 'debug' );
$spname = $name;
}
# Manage new keys # Manage new keys
if ($NK) { if ($NK) {
# Special case: avoid bug with node created from parent node # Special case: avoid bug with node created from parent node
if ( $id =~ /^(virtualHosts|samlIDPMetaDataExportedAttributes)/ ) { if ( $id =~
/^(virtualHosts|samlIDPMetaDataExportedAttributes|samlSPMetaDataExportedAttributes)/
)
{
$self->lmLog( "Special trigger for $id (attribute $name)", $self->lmLog( "Special trigger for $id (attribute $name)",
'debug' ); 'debug' );
@ -110,6 +120,11 @@ s/^virtualHosts\/([^\/]*)?\/header.*/exportedHeaders\/$1\/$name/;
# SAML IDP attribute # SAML IDP attribute
$id =~ $id =~
s/^samlIDPMetaDataExportedAttributes\/([^\/]*)?.*/samlIDPMetaDataExportedAttributes\/$1\/$name/; s/^samlIDPMetaDataExportedAttributes\/([^\/]*)?.*/samlIDPMetaDataExportedAttributes\/$1\/$name/;
# SAML SP attribute
$id =~
s/^samlSPMetaDataExportedAttributes\/([^\/]*)?.*/samlSPMetaDataExportedAttributes\/$1\/$name/;
} }
# Normal case # Normal case
@ -126,11 +141,16 @@ s/^(exportedHeaders|locationRules)\/([^\/]*)?\/(.*)$/$1\/$vhostname\/$3/;
$id =~ $id =~
s/^(samlIDPMetaDataXML|samlIDPMetaDataExportedAttributes|samlIDPMetaDataOptions)\/([^\/]*)?\/(.*)$/$1\/$idpname\/$3/; s/^(samlIDPMetaDataXML|samlIDPMetaDataExportedAttributes|samlIDPMetaDataOptions)\/([^\/]*)?\/(.*)$/$1\/$idpname\/$3/;
# Set current SAML SP name
$id =~
s/^(samlSPMetaDataXML|samlSPMetaDataExportedAttributes|samlSPMetaDataOptions)\/([^\/]*)?\/(.*)$/$1\/$spname\/$3/;
$self->lmLog( "id transformed into $id", 'debug' ); $self->lmLog( "id transformed into $id", 'debug' );
next next
if ( $id =~ if ( $id =~
/^(generalParameters|variables|virtualHosts|samlIDPMetaDataNode)/ ); /^(generalParameters|variables|virtualHosts|samlIDPMetaDataNode|samlSPMetaDataNode)/
);
my ( $confKey, $test ) = $self->getConfTests($id); my ( $confKey, $test ) = $self->getConfTests($id);
my ( $res, $m ); my ( $res, $m );
@ -467,7 +487,7 @@ sub findAllConfKeys {
sub formatValue { sub formatValue {
my ( $self, $key, $value ) = @_; my ( $self, $key, $value ) = @_;
my $newvalue = $value; my $newvalue = $value;
if ( $key =~ /^samlIDPMetaDataXML/ ) { if ( $key =~ /^(samlIDPMetaDataXML|samlSPMetaDataXML)/ ) {
my $metadata = Lemonldap::NG::Common::Conf::SAML::Metadata->new(); my $metadata = Lemonldap::NG::Common::Conf::SAML::Metadata->new();
if ( ref($value) ) { if ( ref($value) ) {
$metadata->initializeFromConfHash($value); $metadata->initializeFromConfHash($value);

View File

@ -83,6 +83,33 @@ sub cstruct {
} }
); );
} }
elsif ( $k1 =~ /^samlSPMetaDataNode/i ) {
%$h = (
%$h,
samlSPMetaDataNode => {
$k2 => {
_nodes => [
qw(samlSPMetaDataXML samlSPMetaDataExportedAttributes samlSPMetaDataOptions)
],
samlSPMetaDataExportedAttributes => {
_nodes => [
"hash:/samlSPMetaDataExportedAttributes/$k2"
. ":samlSPMetaDataExportedAttributes:samlAttribute"
],
_js => 'samlAttributeRoot'
},
samlSPMetaDataXML => "samlmetadata:/samlSPMetaDataXML/$k2"
. ":samlSPMetaDataXML:filearea",
samlSPMetaDataOptions => {
_nodes =>
['samlSPMetaDataOptionsRequestedAuthnContext'],
samlSPMetaDataOptionsRequestedAuthnContext =>
"text:/samlSPMetaDataOptions/$k2/samlSPMetaDataOptionsRequestedAuthnContext",
},
}
}
);
}
return $h; return $h;
} }
@ -93,7 +120,7 @@ sub struct {
my $self = shift; my $self = shift;
return { return {
_nodes => [ _nodes => [
qw(n:generalParameters n:variables n:virtualHosts n:samlServiceMetaData n:samlIDPMetaDataNode) qw(n:generalParameters n:variables n:virtualHosts n:samlServiceMetaData n:samlIDPMetaDataNode n:samlSPMetaDataNode)
], ],
_help => 'default', _help => 'default',
@ -503,13 +530,22 @@ sub struct {
# virtual keys should not begin like configuration keys. # virtual keys should not begin like configuration keys.
samlIDPMetaDataNode => { samlIDPMetaDataNode => {
_nodes => [ _nodes => [
'nhash:/samlIDPMetaDataExportedAttributes:samlIDPMetaDataNode:samlMetaData' 'nhash:/samlIDPMetaDataExportedAttributes:samlIDPMetaDataNode:samlIdpMetaData'
], ],
_upload => [ '/samlIDPMetaDataXML', '/samlIDPMetaDataOptions' ], _upload => [ '/samlIDPMetaDataXML', '/samlIDPMetaDataOptions' ],
_help => 'default', _help => 'default',
_js => 'samlIdpRoot', _js => 'samlIdpRoot',
}, },
samlSPMetaDataNode => {
_nodes => [
'nhash:/samlSPMetaDataExportedAttributes:samlSPMetaDataNode:samlSpMetaData'
],
_upload => [ '/samlSPMetaDataXML', '/samlSPMetaDataOptions' ],
_help => 'default',
_js => 'samlSpRoot',
},
samlServiceMetaData => { samlServiceMetaData => {
_nodes => [ _nodes => [
qw(samlEntityID qw(samlEntityID
@ -958,6 +994,31 @@ sub testStruct {
keyTest => sub { return 1; }, keyTest => sub { return 1; },
}, },
}, },
samlSPMetaDataExportedAttributes => {
keyTest => qr/^[a-zA-Z](?:[\w\-\.]*\w)?$/,
keyMsgFail => 'Bad metadata name',
'*' => {
keyTest => qr/^\w([\w\-]*\w)?$/,
keyMsgFail => 'Bad attribute name',
test => sub { return 1; },
},
},
samlSPMetaDataXML => {
keyTest => qr/^[a-zA-Z](?:[\w\-\.]*\w)?$/,
keyMsgFail => 'Bad metadata name',
'*' => {
test => sub { return 1; },
keyTest => sub { return 1; },
},
},
samlSPMetaDataOptions => {
keyTest => qr/^[a-zA-Z](?:[\w\-\.]*\w)?$/,
keyMsgFail => 'Bad metadata name',
'*' => {
test => sub { return 1; },
keyTest => sub { return 1; },
},
},
samlServicePrivateKey => $testNotDefined, samlServicePrivateKey => $testNotDefined,
samlEntityID => $testNotDefined, samlEntityID => $testNotDefined,
samlOrganizationDisplayName => $testNotDefined, samlOrganizationDisplayName => $testNotDefined,
@ -1096,26 +1157,20 @@ sub defaultConf {
######## ########
# SAML # # SAML #
######## ########
samlIDPMetaDataXML => { 'authentic' => {} },
samlIDPMetaDataExportedAttributes =>
{ 'authentic' => { 'uid' => '0;uid;;' } },
samlIDPMetaDataOptions => {
'authentic' => {
'samlIDPMetaDataOptionsNameIDFormat' => '',
'samlIDPMetaDataOptionsForceAuthn' => '0',
'samlIDPMetaDataOptionsAllowProxiedAuthn' => '1',
'samlIDPMetaDataOptionsSSOBinding' => '',
'samlIDPMetaDataOptionsSLOBinding' => '',
'samlIDPMetaDataOptionsResolutionRule' => '',
'samlIDPMetaDataOptionsAllowLoginFromIDP' => '1',
'samlIDPMetaDataOptionsAdaptSessionUtime' => '1',
}
},
samlServicePrivateKey => '', samlServicePrivateKey => '',
samlEntityID => $portal . '/saml/metadata', samlEntityID => $portal . '/saml/metadata',
samlOrganizationDisplayName => 'Example', samlOrganizationDisplayName => 'Example',
samlOrganizationName => 'Example', samlOrganizationName => 'Example',
samlOrganizationURL => 'http://www.example.com', samlOrganizationURL => 'http://www.example.com',
samlIDPMetaDataOptionsNameIDFormat => '',
samlIDPMetaDataOptionsForceAuthn => '0',
samlIDPMetaDataOptionsAllowProxiedAuthn => '1',
samlIDPMetaDataOptionsSSOBinding => '',
samlIDPMetaDataOptionsSLOBinding => '',
samlIDPMetaDataOptionsResolutionRule => '',
samlIDPMetaDataOptionsAllowLoginFromIDP => '1',
samlIDPMetaDataOptionsAdaptSessionUtime => '1',
samlSPMetaDataOptionsRequestedAuthnContext => '',
samlSPSSODescriptorAuthnRequestsSigned => '0', samlSPSSODescriptorAuthnRequestsSigned => '0',
samlSPSSODescriptorKeyDescriptorSigning => '', samlSPSSODescriptorKeyDescriptorSigning => '',
samlSPSSODescriptorSingleLogoutServiceHTTP => samlSPSSODescriptorSingleLogoutServiceHTTP =>
@ -1191,18 +1246,8 @@ sub subDefaultConf {
'proxy' => 'https://remote/index.pl/sessions', 'proxy' => 'https://remote/index.pl/sessions',
'ns' => 'https://remote/Lemonldap/NG/Common/CGI/SOAPService', 'ns' => 'https://remote/Lemonldap/NG/Common/CGI/SOAPService',
}, },
samlIDPMetaDataXML => { {} },
samlIDPMetaDataExportedAttributes => { 'uid' => '0;uid;;' }, samlIDPMetaDataExportedAttributes => { 'uid' => '0;uid;;' },
samlIDPMetaDataOptions => { samlSPMetaDataExportedAttributes => { 'uid' => '0;uid;;' },
'samlIDPMetaDataOptionsNameIDFormat' => '',
'samlIDPMetaDataOptionsForceAuthn' => '0',
'samlIDPMetaDataOptionsAllowProxiedAuthn' => '1',
'samlIDPMetaDataOptionsSSOBinding' => '',
'samlIDPMetaDataOptionsSLOBinding' => '',
'samlIDPMetaDataOptionsResolutionRule' => '',
'samlIDPMetaDataOptionsAllowLoginFromIDP' => '1',
'samlIDPMetaDataOptionsAdaptSessionUtime' => '1',
},
}; };
} }

View File

@ -198,8 +198,8 @@ sub en {
saml => 'SAML', saml => 'SAML',
samlServicePrivateKey => 'Private Key', samlServicePrivateKey => 'Private Key',
samlIDPMetaDataNode => 'Identity providers', samlIDPMetaDataNode => 'SAML identity providers',
samlIDPMetaDataXML => 'Metadata XML', samlIDPMetaDataXML => 'Metadata',
samlIDPMetaDataExportedAttributes => 'Exported attributes', samlIDPMetaDataExportedAttributes => 'Exported attributes',
samlIDPMetaDataOptions => 'Options', samlIDPMetaDataOptions => 'Options',
samlIDPMetaDataOptionsNameIDFormat => 'NameID format', samlIDPMetaDataOptionsNameIDFormat => 'NameID format',
@ -211,6 +211,11 @@ sub en {
samlIDPMetaDataOptionsSSOBinding => 'SSO binding', samlIDPMetaDataOptionsSSOBinding => 'SSO binding',
samlIDPMetaDataOptionsSLOBinding => 'SLO binding', samlIDPMetaDataOptionsSLOBinding => 'SLO binding',
samlIDPMetaDataOptionsResolutionRule => 'Resolution rule', samlIDPMetaDataOptionsResolutionRule => 'Resolution rule',
samlSPMetaDataNode => 'SAML service providers',
samlSPMetaDataXML => 'Metadata',
samlSPMetaDataExportedAttributes => 'Exported attributes',
samlSPMetaDataOptions => 'Options',
samlSPMetaDataOptionsRequestedAuthnContext => 'Authentication context',
samlServiceMetaData => 'SAML 2 Service', samlServiceMetaData => 'SAML 2 Service',
samlEntityID => 'Entity Identifier', samlEntityID => 'Entity Identifier',
samlOrganization => 'Organization', samlOrganization => 'Organization',
@ -413,8 +418,8 @@ sub fr {
saml => 'SAML', saml => 'SAML',
samlServicePrivateKey => 'Clé privée', samlServicePrivateKey => 'Clé privée',
samlIDPMetaDataNode => 'Fournisseurs d\'identités', samlIDPMetaDataNode => 'Fournisseurs d\'identité SAML',
samlIDPMetaDataXML => 'XML Metadata', samlIDPMetaDataXML => 'Metadonnées',
samlIDPMetaDataExportedAttributes => 'Attributs exportés', samlIDPMetaDataExportedAttributes => 'Attributs exportés',
samlIDPMetaDataOptions => 'Options', samlIDPMetaDataOptions => 'Options',
samlIDPMetaDataOptionsNameIDFormat => 'Format NameID', samlIDPMetaDataOptionsNameIDFormat => 'Format NameID',
@ -428,6 +433,12 @@ sub fr {
samlIDPMetaDataOptionsSSOBinding => 'Méthode SSO', samlIDPMetaDataOptionsSSOBinding => 'Méthode SSO',
samlIDPMetaDataOptionsSLOBinding => 'Méthode SLO', samlIDPMetaDataOptionsSLOBinding => 'Méthode SLO',
samlIDPMetaDataOptionsResolutionRule => 'Règle de résolution', samlIDPMetaDataOptionsResolutionRule => 'Règle de résolution',
samlSPMetaDataNode => 'Fournisseurs de service SAML',
samlSPMetaDataXML => 'Metadonnées',
samlSPMetaDataExportedAttributes => 'Attributs exportés',
samlSPMetaDataOptions => 'Options',
samlSPMetaDataOptionsRequestedAuthnContext =>
'Contexte d\'authentification',
samlServiceMetaData => 'Service SAML 2', samlServiceMetaData => 'Service SAML 2',
samlEntityID => 'Identifiant d\'entité', samlEntityID => 'Identifiant d\'entité',
samlOrganization => 'Organisation', samlOrganization => 'Organisation',