Skip to content

Commit 83e30e1

Browse files
committed
SOAP binding should support multiple certs in the IdP Metadata
Use Try::Tiny to handle exceptions
1 parent a00f908 commit 83e30e1

File tree

8 files changed

+360
-26
lines changed

8 files changed

+360
-26
lines changed

Makefile.PL

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ my %WriteMakefileArgs = (
4242
"MooseX::Types::DateTime" => 0,
4343
"MooseX::Types::Moose" => 0,
4444
"MooseX::Types::URI" => 0,
45+
"Try::Tiny" => 0,
4546
"Types::Serialiser" => 0,
4647
"URI" => 0,
4748
"URI::Encode" => 0,
@@ -122,6 +123,7 @@ my %FallbackPrereqs = (
122123
"Test::NoTabs" => 0,
123124
"Test::Pod" => "1.14",
124125
"Test::Pod::Coverage" => "1.04",
126+
"Try::Tiny" => 0,
125127
"Types::Serialiser" => 0,
126128
"URI" => 0,
127129
"URI::Encode" => 0,

cpanfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ requires "MooseX::Types::Common::String" => "0";
2626
requires "MooseX::Types::DateTime" => "0";
2727
requires "MooseX::Types::Moose" => "0";
2828
requires "MooseX::Types::URI" => "0";
29+
requires "Try::Tiny" => "0";
2930
requires "Types::Serialiser" => "0";
3031
requires "URI" => "0";
3132
requires "URI::Encode" => "0";

lib/Net/SAML2/Binding/SOAP.pm

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use Moose;
66
use MooseX::Types::URI qw/ Uri /;
77
use Net::SAML2::XML::Util qw/ no_comments /;
88
use Carp qw(croak);
9+
use Try::Tiny;
910

1011
with 'Net::SAML2::Role::VerifyXML';
1112

@@ -90,7 +91,7 @@ sub build_user_agent {
9091
has 'url' => (isa => Uri, is => 'ro', required => 1, coerce => 1);
9192
has 'key' => (isa => 'Str', is => 'ro', required => 1);
9293
has 'cert' => (isa => 'Str', is => 'ro', required => 1);
93-
has 'idp_cert' => (isa => 'Str', is => 'ro', required => 1);
94+
has 'idp_cert' => (isa => 'ArrayRef[Str]', is => 'ro', required => 1, predicate => 'has_idp_cert');
9495
has 'cacert' => (
9596
is => 'ro',
9697
isa => 'Str',
@@ -104,6 +105,26 @@ has 'anchors' => (
104105
predicate => 'has_anchors'
105106
);
106107

108+
# BUILDARGS
109+
110+
# Earlier versions expected the idp_cert to be a string. However, metadata
111+
# can include multiple signing certificates so the $idp->cert is now
112+
# expected to be an arrayref to the certificates. To avoid breaking existing
113+
# applications this changes the the cert to an arrayref if it is not
114+
# already an array ref.
115+
116+
around BUILDARGS => sub {
117+
my $orig = shift;
118+
my $self = shift;
119+
120+
my %params = @_;
121+
if ($params{idp_cert} && ref($params{idp_cert}) ne 'ARRAY') {
122+
$params{idp_cert} = [$params{idp_cert}];
123+
}
124+
125+
return $self->$orig(%params);
126+
};
127+
107128
=head2 request( $message )
108129
109130
Submit the message to the IdP's service.
@@ -152,15 +173,27 @@ sub handle_response {
152173
my ($self, $response) = @_;
153174

154175
my $saml = _get_saml_from_soap($response);
155-
$self->verify_xml(
156-
$saml,
157-
no_xml_declaration => 1,
158-
cert_text => $self->idp_cert,
159-
cacert => $self->cacert,
160-
anchors => $self->anchors
161-
);
162-
return $saml;
176+
my @errors;
177+
foreach my $cert (@{$self->idp_cert}) {
178+
my $success = try {
179+
$self->verify_xml(
180+
$saml,
181+
no_xml_declaration => 1,
182+
cert_text => $cert,
183+
cacert => $self->cacert,
184+
anchors => $self->anchors
185+
);
186+
return 1;
187+
}
188+
catch { push (@errors, $_); return 0; };
189+
190+
return $saml if $success;
191+
}
163192

193+
if (@errors) {
194+
croak "Unable to verify XML with the given certificates: "
195+
. join(", ", @errors);
196+
}
164197
}
165198

166199
=head2 handle_request( $request )
@@ -175,13 +208,25 @@ sub handle_request {
175208
my ($self, $request) = @_;
176209

177210
my $saml = _get_saml_from_soap($request);
211+
my @errors;
178212
if (defined $saml) {
179-
$self->verify_xml(
180-
$saml,
181-
cert_text => $self->idp_cert,
182-
cacert => $self->cacert
183-
);
184-
return $saml;
213+
foreach my $cert (@{$self->idp_cert}) {
214+
my $success = try {
215+
$self->verify_xml(
216+
$saml,
217+
cert_text => $cert,
218+
cacert => $self->cacert
219+
);
220+
return 1;
221+
}
222+
catch { push (@errors, $_); return 0; };
223+
return $saml if $success;
224+
}
225+
226+
if (@errors) {
227+
croak "Unable to verify XML with the given certificates: "
228+
. join(", ", @errors);
229+
}
185230
}
186231

187232
return;

lib/Net/SAML2/IdP.pm

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use Crypt::OpenSSL::X509;
3434
use HTTP::Request::Common;
3535
use LWP::UserAgent;
3636
use XML::LibXML;
37+
use Try::Tiny;
3738
use Net::SAML2::XML::Util qw/ no_comments /;
3839

3940
=head2 new( )
@@ -62,6 +63,7 @@ has 'formats' => (
6263
default => sub { {} }
6364
);
6465
has 'default_format' => (isa => 'Str', is => 'ro', required => 0);
66+
has 'debug' => (isa => 'Bool', is => 'ro', required => 0, default => 0);
6567

6668
=head2 new_from_url( url => $url, cacert => $cacert, ssl_opts => {} )
6769
@@ -171,6 +173,7 @@ sub new_from_xml {
171173
art_urls => $data->{Art} || {},
172174
certs => \%certs,
173175
cacert => $args{cacert},
176+
debug => $args{debug},
174177
$data->{DefaultFormat}
175178
? (
176179
default_format => $data->{DefaultFormat},
@@ -222,20 +225,23 @@ around BUILDARGS => sub {
222225
my $ca = Crypt::OpenSSL::Verify->new($params{cacert}, { strict_certs => 0, });
223226

224227
my %certificates;
228+
my @errors;
225229
for my $use (keys %{$params{certs}}) {
226230
my $certs = $params{certs}{$use};
227231
for my $pem (@{$certs}) {
228232
my $cert = Crypt::OpenSSL::X509->new_from_string($pem);
229-
## BUGBUG this is failing for valid things ...
230-
eval { $ca->verify($cert) };
231-
if ($@) {
232-
warn "Can't verify IdP cert: $@";
233-
next;
233+
try {
234+
$ca->verify($cert);
235+
push(@{$certificates{$use}}, $pem);
234236
}
235-
push(@{$certificates{$use}}, $pem);
237+
catch { push (@errors, $_); };
236238
}
237239
}
238240

241+
if ( $params{debug} && @errors ) {
242+
warn "Can't verify IdP cert(s): " . join(", ", @errors);
243+
}
244+
239245
$params{certs} = \%certificates;
240246
}
241247

lib/Net/SAML2/Role/VerifyXML.pm

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use Crypt::OpenSSL::Verify;
77
use Crypt::OpenSSL::X509;
88
use Carp qw(croak);
99
use List::Util qw(none);
10+
use Try::Tiny;
1011

1112
# ABSTRACT: A role to verify the SAML response XML
1213

@@ -85,10 +86,10 @@ sub verify_xml {
8586

8687
if ($cacert) {
8788
my $ca = Crypt::OpenSSL::Verify->new($cacert, { strict_certs => 0 });
88-
eval { $ca->verify($cert) };
89-
if ($@) {
90-
croak("Could not verify CA certificate: $@");
91-
}
89+
try { $ca->verify($cert) }
90+
catch {
91+
croak("Could not verify CA certificate: $_");
92+
};
9293
}
9394

9495
return 1 if !$anchors;

t/05-soap-binding.t

Lines changed: 138 additions & 1 deletion
Large diffs are not rendered by default.

t/data/cacert-samlid.pem

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDEjCCAfqgAwIBAgIVAMECQ1tjghafm5OxWDh9hwZfxthWMA0GCSqGSIb3DQEB
3+
CwUAMBYxFDASBgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4
4+
MDgyNDIxMTQwOVowFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3
5+
DQEBAQUAA4IBDwAwggEKAoIBAQC0Z4QX1NFKs71ufbQwoQoW7qkNAJRIANGA4iM0
6+
ThYghul3pC+FwrGv37aTxWXfA1UG9njKbbDreiDAZKngCgyjxj0uJ4lArgkr4AOE
7+
jj5zXA81uGHARfUBctvQcsZpBIxDOvUUImAl+3NqLgMGF2fktxMG7kX3GEVNc1kl
8+
bN3dfYsaw5dUrw25DheL9np7G/+28GwHPvLb4aptOiONbCaVvh9UMHEA9F7c0zfF
9+
/cL5fOpdVa54wTI0u12CsFKt78h6lEGG5jUs/qX9clZncJM7EFkN3imPPy+0HC8n
10+
spXiH/MZW8o2cqWRkrw3MzBZW3Ojk5nQj40V6NUbjb7kfejzAgMBAAGjVzBVMB0G
11+
A1UdDgQWBBQT6Y9J3Tw/hOGc8PNV7JEE4k2ZNTA0BgNVHREELTArggtzYW1sdGVz
12+
dC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lkcDANBgkqhkiG9w0BAQsF
13+
AAOCAQEASk3guKfTkVhEaIVvxEPNR2w3vWt3fwmwJCccW98XXLWgNbu3YaMb2RSn
14+
7Th4p3h+mfyk2don6au7Uyzc1Jd39RNv80TG5iQoxfCgphy1FYmmdaSfO8wvDtHT
15+
TNiLArAxOYtzfYbzb5QrNNH/gQEN8RJaEf/g/1GTw9x/103dSMK0RXtl+fRs2nbl
16+
D1JJKSQ3AdhxK/weP3aUPtLxVVJ9wMOQOfcy02l+hHMb6uAjsPOpOVKqi3M8XmcU
17+
ZOpx4swtgGdeoSpeRyrtMvRwdcciNBp9UZome44qZAYH1iqrpmmjsfI9pJItsgWu
18+
3kXPjhSfj1AJGR1l9JGvJrHki1iHTA==
19+
-----END CERTIFICATE-----

t/data/idp-samlid-metadata.xml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<!-- The entity describing the SAMLtest IdP, named by the entityID below -->
2+
3+
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="SAMLtestIdP" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" validUntil="2100-01-01T00:00:42Z" entityID="https://samltest.id/saml/idp">
4+
5+
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0">
6+
7+
<Extensions>
8+
<!-- An enumeration of the domains this IdP is able to assert scoped attributes, which are
9+
typically those with a @ delimiter, like mail. Most IdP's serve only a single domain. It's crucial
10+
for the SP to check received attribute values match permitted domains to prevent a recognized IdP from
11+
sending attribute values for which a different recognized IdP is authoritative. -->
12+
<shibmd:Scope regexp="false">samltest.id</shibmd:Scope>
13+
14+
<!-- Display information about this IdP that can be used by SP's and discovery
15+
services to identify the IdP meaningfully for end users -->
16+
<mdui:UIInfo>
17+
<mdui:DisplayName xml:lang="en">SAMLtest IdP</mdui:DisplayName>
18+
<mdui:Description xml:lang="en">A free and basic IdP for testing SAML deployments</mdui:Description>
19+
<mdui:Logo height="90" width="225">https://samltest.id/saml/logo.png</mdui:Logo>
20+
</mdui:UIInfo>
21+
</Extensions>
22+
23+
<KeyDescriptor use="signing">
24+
<ds:KeyInfo>
25+
<ds:X509Data>
26+
<ds:X509Certificate>
27+
MIIDETCCAfmgAwIBAgIUZRpDhkNKl5eWtJqk0Bu1BgTTargwDQYJKoZIhvcNAQEL
28+
BQAwFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwHhcNMTgwODI0MjExNDEwWhcNMzgw
29+
ODI0MjExNDEwWjAWMRQwEgYDVQQDDAtzYW1sdGVzdC5pZDCCASIwDQYJKoZIhvcN
30+
AQEBBQADggEPADCCAQoCggEBAJrh9/PcDsiv3UeL8Iv9rf4WfLPxuOm9W6aCntEA
31+
8l6c1LQ1Zyrz+Xa/40ZgP29ENf3oKKbPCzDcc6zooHMji2fBmgXp6Li3fQUzu7yd
32+
+nIC2teejijVtrNLjn1WUTwmqjLtuzrKC/ePoZyIRjpoUxyEMJopAd4dJmAcCq/K
33+
k2eYX9GYRlqvIjLFoGNgy2R4dWwAKwljyh6pdnPUgyO/WjRDrqUBRFrLQJorR2kD
34+
c4seZUbmpZZfp4MjmWMDgyGM1ZnR0XvNLtYeWAyt0KkSvFoOMjZUeVK/4xR74F8e
35+
8ToPqLmZEg9ZUx+4z2KjVK00LpdRkH9Uxhh03RQ0FabHW6UCAwEAAaNXMFUwHQYD
36+
VR0OBBYEFJDbe6uSmYQScxpVJhmt7PsCG4IeMDQGA1UdEQQtMCuCC3NhbWx0ZXN0
37+
LmlkhhxodHRwczovL3NhbWx0ZXN0LmlkL3NhbWwvaWRwMA0GCSqGSIb3DQEBCwUA
38+
A4IBAQBNcF3zkw/g51q26uxgyuy4gQwnSr01Mhvix3Dj/Gak4tc4XwvxUdLQq+jC
39+
cxr2Pie96klWhY/v/JiHDU2FJo9/VWxmc/YOk83whvNd7mWaNMUsX3xGv6AlZtCO
40+
L3JhCpHjiN+kBcMgS5jrtGgV1Lz3/1zpGxykdvS0B4sPnFOcaCwHe2B9SOCWbDAN
41+
JXpTjz1DmJO4ImyWPJpN1xsYKtm67Pefxmn0ax0uE2uuzq25h0xbTkqIQgJzyoE/
42+
DPkBFK1vDkMfAW11dQ0BXatEnW7Gtkc0lh2/PIbHWj4AzxYMyBf5Gy6HSVOftwjC
43+
voQR2qr2xJBixsg+MIORKtmKHLfU
44+
</ds:X509Certificate>
45+
</ds:X509Data>
46+
</ds:KeyInfo>
47+
48+
</KeyDescriptor>
49+
<KeyDescriptor use="signing">
50+
<ds:KeyInfo>
51+
<ds:X509Data>
52+
<ds:X509Certificate>
53+
MIIDEjCCAfqgAwIBAgIVAMECQ1tjghafm5OxWDh9hwZfxthWMA0GCSqGSIb3DQEB
54+
CwUAMBYxFDASBgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4
55+
MDgyNDIxMTQwOVowFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3
56+
DQEBAQUAA4IBDwAwggEKAoIBAQC0Z4QX1NFKs71ufbQwoQoW7qkNAJRIANGA4iM0
57+
ThYghul3pC+FwrGv37aTxWXfA1UG9njKbbDreiDAZKngCgyjxj0uJ4lArgkr4AOE
58+
jj5zXA81uGHARfUBctvQcsZpBIxDOvUUImAl+3NqLgMGF2fktxMG7kX3GEVNc1kl
59+
bN3dfYsaw5dUrw25DheL9np7G/+28GwHPvLb4aptOiONbCaVvh9UMHEA9F7c0zfF
60+
/cL5fOpdVa54wTI0u12CsFKt78h6lEGG5jUs/qX9clZncJM7EFkN3imPPy+0HC8n
61+
spXiH/MZW8o2cqWRkrw3MzBZW3Ojk5nQj40V6NUbjb7kfejzAgMBAAGjVzBVMB0G
62+
A1UdDgQWBBQT6Y9J3Tw/hOGc8PNV7JEE4k2ZNTA0BgNVHREELTArggtzYW1sdGVz
63+
dC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lkcDANBgkqhkiG9w0BAQsF
64+
AAOCAQEASk3guKfTkVhEaIVvxEPNR2w3vWt3fwmwJCccW98XXLWgNbu3YaMb2RSn
65+
7Th4p3h+mfyk2don6au7Uyzc1Jd39RNv80TG5iQoxfCgphy1FYmmdaSfO8wvDtHT
66+
TNiLArAxOYtzfYbzb5QrNNH/gQEN8RJaEf/g/1GTw9x/103dSMK0RXtl+fRs2nbl
67+
D1JJKSQ3AdhxK/weP3aUPtLxVVJ9wMOQOfcy02l+hHMb6uAjsPOpOVKqi3M8XmcU
68+
ZOpx4swtgGdeoSpeRyrtMvRwdcciNBp9UZome44qZAYH1iqrpmmjsfI9pJItsgWu
69+
3kXPjhSfj1AJGR1l9JGvJrHki1iHTA==
70+
</ds:X509Certificate>
71+
</ds:X509Data>
72+
</ds:KeyInfo>
73+
74+
</KeyDescriptor>
75+
<KeyDescriptor use="encryption">
76+
<ds:KeyInfo>
77+
<ds:X509Data>
78+
<ds:X509Certificate>
79+
MIIDEjCCAfqgAwIBAgIVAPVbodo8Su7/BaHXUHykx0Pi5CFaMA0GCSqGSIb3DQEB
80+
CwUAMBYxFDASBgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4
81+
MDgyNDIxMTQwOVowFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3
82+
DQEBAQUAA4IBDwAwggEKAoIBAQCQb+1a7uDdTTBBFfwOUun3IQ9nEuKM98SmJDWa
83+
MwM877elswKUTIBVh5gB2RIXAPZt7J/KGqypmgw9UNXFnoslpeZbA9fcAqqu28Z4
84+
sSb2YSajV1ZgEYPUKvXwQEmLWN6aDhkn8HnEZNrmeXihTFdyr7wjsLj0JpQ+VUlc
85+
4/J+hNuU7rGYZ1rKY8AA34qDVd4DiJ+DXW2PESfOu8lJSOteEaNtbmnvH8KlwkDs
86+
1NvPTsI0W/m4SK0UdXo6LLaV8saIpJfnkVC/FwpBolBrRC/Em64UlBsRZm2T89ca
87+
uzDee2yPUvbBd5kLErw+sC7i4xXa2rGmsQLYcBPhsRwnmBmlAgMBAAGjVzBVMB0G
88+
A1UdDgQWBBRZ3exEu6rCwRe5C7f5QrPcAKRPUjA0BgNVHREELTArggtzYW1sdGVz
89+
dC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lkcDANBgkqhkiG9w0BAQsF
90+
AAOCAQEABZDFRNtcbvIRmblnZItoWCFhVUlq81ceSQddLYs8DqK340//hWNAbYdj
91+
WcP85HhIZnrw6NGCO4bUipxZXhiqTA/A9d1BUll0vYB8qckYDEdPDduYCOYemKkD
92+
dmnHMQWs9Y6zWiYuNKEJ9mf3+1N8knN/PK0TYVjVjXAf2CnOETDbLtlj6Nqb8La3
93+
sQkYmU+aUdopbjd5JFFwbZRaj6KiHXHtnIRgu8sUXNPrgipUgZUOVhP0C0N5OfE4
94+
JW8ZBrKgQC/6vJ2rSa9TlzI6JAa5Ww7gMXMP9M+cJUNQklcq+SBnTK8G+uBHgPKR
95+
zBDsMIEzRtQZm4GIoHJae4zmnCekkQ==
96+
</ds:X509Certificate>
97+
</ds:X509Data>
98+
</ds:KeyInfo>
99+
100+
</KeyDescriptor>
101+
102+
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
103+
<!-- An endpoint for artifact resolution. Please see Wikipedia for more details about SAML
104+
artifacts and when you may find them useful. -->
105+
106+
<ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://samltest.id/idp/profile/SAML2/SOAP/ArtifactResolution" index="1" />
107+
108+
<!-- A set of endpoints where the IdP can receive logout messages. These must match the public
109+
facing addresses if this IdP is hosted behind a reverse proxy. -->
110+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://samltest.id/idp/profile/SAML2/Redirect/SLO"/>
111+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://samltest.id/idp/profile/SAML2/POST/SLO"/>
112+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://samltest.id/idp/profile/SAML2/POST-SimpleSign/SLO"/>
113+
114+
<!-- A set of endpoints the SP can send AuthnRequests to in order to trigger user authentication. -->
115+
<SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://samltest.id/idp/profile/Shibboleth/SSO"/>
116+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://samltest.id/idp/profile/SAML2/POST/SSO"/>
117+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://samltest.id/idp/profile/SAML2/POST-SimpleSign/SSO"/>
118+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://samltest.id/idp/profile/SAML2/Redirect/SSO"/>
119+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://samltest.id/idp/profile/SAML2/SOAP/ECP"/>
120+
121+
</IDPSSODescriptor>
122+
123+
</EntityDescriptor>

0 commit comments

Comments
 (0)