lemonldap-ng/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/SAML/Metadata.pm

358 lines
15 KiB
Perl

##@file
# SAML Metadata object for Lemonldap::NG
##@class
# SAML Metadata object for Lemonldap::NG
package Lemonldap::NG::Common::Conf::SAML::Metadata;
use strict;
use Mouse;
use Crypt::OpenSSL::RSA;
use Crypt::OpenSSL::X509;
use HTML::Template;
use MIME::Base64;
use Safe;
use Encode;
our $VERSION = '2.0.0';
my $dataStart = tell(DATA);
## @method public string serviceToXML
# Return all SAML parameters in well formated XML format, corresponding to
# SAML 2 description.
# @return string
sub serviceToXML {
my ( $self, $conf, $type ) = @_;
seek DATA, $dataStart, 0;
my $s = join '', <DATA>;
my $template = HTML::Template->new(
scalarref => \$s,
die_on_bad_params => 0,
cache => 0,
);
# Automatic parameters
my @param_auto = qw(
samlEntityID
samlOrganizationName
samlOrganizationDisplayName
samlOrganizationURL
);
if ( $type and $type eq 'idp' ) {
$template->param( 'hideSPMetadata', 1 );
}
if ( $type and $type eq 'sp' ) {
$template->param( 'hideIDPMetadata', 1 );
}
foreach (@param_auto) {
$template->param( $_, $self->getValue( $_, $conf ) );
}
# When asked to provide only IDP metadata, take into account EntityID override
if ( $type eq "idp" and $conf->{samlOverrideIDPEntityID} ) {
$template->param( 'samlEntityID', $conf->{samlOverrideIDPEntityID} );
}
# Boolean parameters
my @param_boolean = qw(
samlSPSSODescriptorAuthnRequestsSigned
samlSPSSODescriptorWantAssertionsSigned
samlIDPSSODescriptorWantAuthnRequestsSigned
);
foreach (@param_boolean) {
$template->param( $_, $self->getValue( $_, $conf ) ? 'true' : 'false' );
}
# Format public keys
my @param_keys = qw(
samlServicePublicKeySig
samlServicePublicKeyEnc
);
foreach (@param_keys) {
my $str = '';
my $val = $self->getValue( $_, $conf );
# A default value for samlServicePublicKeyEnc parameter
if ( $_ =~ /samlServicePublicKeyEnc/ ) {
unless ( $val && length $val gt 0 ) {
$val = $conf->{samlServicePublicKeySig};
}
}
# Generate XML
if ( defined $val && length $val gt 0 ) {
# Public Key ?
if ( $val =~ /^-----BEGIN PUBLIC KEY-----/
and my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($val) )
{
my @params = $rsa_pub->get_key_parameters();
my $mod = encode_base64( $params[0]->to_bin() );
my $exp = encode_base64( $params[1]->to_bin() );
$str =
'<ds:KeyValue>' . "\n\t"
. '<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">'
. "\n\t\t"
. '<Modulus>'
. $mod
. '</Modulus>'
. "\n\t\t"
. '<Exponent>'
. $exp
. '</Exponent>' . "\n\t"
. '</RSAKeyValue>' . "\n"
. '</ds:KeyValue>';
}
# Certificate ?
if ( $val =~ /^-----BEGIN CERTIFICATE-----/
and my $certificate =
Crypt::OpenSSL::X509->new_from_string($val) )
{
$certificate = $certificate->as_string();
$certificate =~ s/^-----BEGIN CERTIFICATE-----\n?//g;
$certificate =~ s/\n?-----END CERTIFICATE-----$//g;
$str =
'<ds:X509Data>' . "\n\t"
. '<ds:X509Certificate>' . "\n\t"
. $certificate
. '</ds:X509Certificate>' . "\n"
. '</ds:X509Data>';
}
}
$template->param( $_, $str );
}
# Rebuilded parameters for SAML services
# A samlService value is formated like the following:
# "binding;location;responseLocation"
# The last value, responseLocation, is optional.
my @param_service = qw(
samlSPSSODescriptorSingleLogoutServiceHTTPRedirect
samlSPSSODescriptorSingleLogoutServiceHTTPPost
samlSPSSODescriptorSingleLogoutServiceSOAP
samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect
samlIDPSSODescriptorSingleSignOnServiceHTTPPost
samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact
samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect
samlIDPSSODescriptorSingleLogoutServiceHTTPPost
samlIDPSSODescriptorSingleLogoutServiceSOAP
samlAttributeAuthorityDescriptorAttributeServiceSOAP
);
foreach (@param_service) {
my @_tab = split( /;/, $self->getValue( $_, $conf ) );
$template->param( $_ . 'Binding', $_tab[0] );
$template->param( $_ . 'Location', $_tab[1] );
$template->param( $_ . 'ResponseLocation', $_tab[2] );
}
# Rebuilded parameters for SAML assertions
# A samlAssertion value is formated like the following:
# "default;index;binding;location"
my @param_assertion = qw(
samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact
samlSPSSODescriptorAssertionConsumerServiceHTTPPost
samlSPSSODescriptorArtifactResolutionServiceArtifact
samlIDPSSODescriptorArtifactResolutionServiceArtifact
);
foreach (@param_assertion) {
my @_tab = split( /;/, $self->getValue( $_, $conf ) );
$template->param( $_ . 'Default', $_tab[0] ? 'true' : 'false' );
$template->param( $_ . 'Index', $_tab[1] );
$template->param( $_ . 'Binding', $_tab[2] );
$template->param( $_ . 'Location', $_tab[3] );
}
# Return the XML metadata.
return $template->output;
}
#@method string getValue(string key, hashref conf)
# Get the value for a metadata configuration key
# Replace #PORTAL# macro
# @param key Configuration key
# @param conf Configuration hash ref
# @return value
sub getValue {
my ( $self, $key, $conf ) = @_;
# Get portal value
my $portal = $conf->{portal} || "http://auth.example.com/";
$portal =~ s/\/$//;
# Try to get value for the given key in configuraiton
my $value = $conf->{$key};
return unless defined $value;
# Replace #PORTAL# macro
$value =~ s/#PORTAL#/$portal/g;
# Return value
return $value;
}
1;
__DATA__
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
entityID="<TMPL_VAR NAME="samlEntityID">">
<TMPL_UNLESS NAME="hideIDPMetadata">
<IDPSSODescriptor
WantAuthnRequestsSigned="<TMPL_VAR NAME="samlIDPSSODescriptorWantAuthnRequestsSigned">"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<TMPL_VAR NAME="samlServicePublicKeySig">
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<TMPL_VAR NAME="samlServicePublicKeyEnc">
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService
isDefault="<TMPL_VAR NAME="samlIDPSSODescriptorArtifactResolutionServiceArtifactDefault">"
index="<TMPL_VAR NAME="samlIDPSSODescriptorArtifactResolutionServiceArtifactIndex">"
Binding="<TMPL_VAR NAME="samlIDPSSODescriptorArtifactResolutionServiceArtifactBinding">"
Location="<TMPL_VAR NAME="samlIDPSSODescriptorArtifactResolutionServiceArtifactLocation">" />
<SingleLogoutService
Binding="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceSOAPBinding">"
Location="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceSOAPLocation">" />
<SingleLogoutService
Binding="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPRedirectBinding">"
Location="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPRedirectLocation">"
<TMPL_IF NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPRedirectResponseLocation">
ResponseLocation="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPRedirectResponseLocation">"
</TMPL_IF>/>
<SingleLogoutService
Binding="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPPostBinding">"
Location="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPPostLocation">"
<TMPL_IF NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPPostResponseLocation">
ResponseLocation="<TMPL_VAR NAME="samlIDPSSODescriptorSingleLogoutServiceHTTPPostResponseLocation">"
</TMPL_IF>/>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<SingleSignOnService
Binding="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPRedirectBinding">"
Location="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPRedirectLocation">"
<TMPL_IF NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPRedirectResponseLocation">
ResponseLocation="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPRedirectResponseLocation">"
</TMPL_IF>/>
<SingleSignOnService
Binding="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPPostBinding">"
Location="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPPostLocation">"
<TMPL_IF NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPPostResponseLocation">
ResponseLocation="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPPostResponseLocation">"
</TMPL_IF>/>
<SingleSignOnService
Binding="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPArtifactBinding">"
Location="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPArtifactLocation">"
<TMPL_IF NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPArtifactResponseLocation">
ResponseLocation="<TMPL_VAR NAME="samlIDPSSODescriptorSingleSignOnServiceHTTPArtifactResponseLocation">"
</TMPL_IF>/>
</IDPSSODescriptor>
</TMPL_UNLESS>
<TMPL_UNLESS NAME="hideSPMetadata">
<SPSSODescriptor
AuthnRequestsSigned="<TMPL_VAR NAME="samlSPSSODescriptorAuthnRequestsSigned">"
WantAssertionsSigned="<TMPL_VAR NAME="samlSPSSODescriptorWantAssertionsSigned">"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<TMPL_VAR NAME="samlServicePublicKeySig">
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<TMPL_VAR NAME="samlServicePublicKeyEnc">
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService
isDefault="<TMPL_VAR NAME="samlSPSSODescriptorArtifactResolutionServiceArtifactDefault">"
index="<TMPL_VAR NAME="samlSPSSODescriptorArtifactResolutionServiceArtifactIndex">"
Binding="<TMPL_VAR NAME="samlSPSSODescriptorArtifactResolutionServiceArtifactBinding">"
Location="<TMPL_VAR NAME="samlSPSSODescriptorArtifactResolutionServiceArtifactLocation">" />
<SingleLogoutService
Binding="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceSOAPBinding">"
Location="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceSOAPLocation">" />
<SingleLogoutService
Binding="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceHTTPRedirectBinding">"
Location="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceHTTPRedirectLocation">"
<TMPL_IF NAME="samlSPSSODescriptorSingleLogoutServiceHTTPRedirectResponseLocation">
ResponseLocation="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceHTTPRedirectResponseLocation">"
</TMPL_IF>/>
<SingleLogoutService
Binding="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceHTTPPostBinding">"
Location="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceHTTPPostLocation">"
<TMPL_IF NAME="samlSPSSODescriptorSingleLogoutServiceHTTPPostResponseLocation">
ResponseLocation="<TMPL_VAR NAME="samlSPSSODescriptorSingleLogoutServiceHTTPPostResponseLocation">"
</TMPL_IF>/>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<AssertionConsumerService
isDefault="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPArtifactDefault">"
index="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPArtifactIndex">"
Binding="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPArtifactBinding">"
Location="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPArtifactLocation">" />
<AssertionConsumerService
isDefault="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPPostDefault">"
index="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPPostIndex">"
Binding="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPPostBinding">"
Location="<TMPL_VAR NAME="samlSPSSODescriptorAssertionConsumerServiceHTTPPostLocation">" />
</SPSSODescriptor>
</TMPL_UNLESS>
<TMPL_UNLESS NAME="hideIDPMetadata">
<AttributeAuthorityDescriptor
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<TMPL_VAR NAME="samlServicePublicKeySig">
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<TMPL_VAR NAME="samlServicePublicKeyEnc">
</ds:KeyInfo>
</KeyDescriptor>
<AttributeService
Binding="<TMPL_VAR NAME="samlAttributeAuthorityDescriptorAttributeServiceSOAPBinding">"
Location="<TMPL_VAR NAME="samlAttributeAuthorityDescriptorAttributeServiceSOAPLocation">"/>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
</AttributeAuthorityDescriptor>
</TMPL_UNLESS>
<Organization>
<OrganizationName xml:lang="en"><TMPL_VAR NAME="samlOrganizationName"></OrganizationName>
<OrganizationDisplayName xml:lang="en"><TMPL_VAR NAME="samlOrganizationDisplayName"></OrganizationDisplayName>
<OrganizationURL xml:lang="en"><TMPL_VAR NAME="samlOrganizationURL"></OrganizationURL>
</Organization>
</EntityDescriptor>