Skip to content

Commit 1049084

Browse files
committed
Fix issues with LogoutResponse
1 parent 378c815 commit 1049084

File tree

5 files changed

+87
-14
lines changed

5 files changed

+87
-14
lines changed

lib/Net/SAML2/Binding/Redirect.pm

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use URI::QueryParam;
4040
use Crypt::OpenSSL::RSA;
4141
use Crypt::OpenSSL::X509;
4242
use File::Slurp qw/ read_file /;
43+
use URI::Encode qw/uri_decode/;
4344

4445
=head2 new( ... )
4546
@@ -79,6 +80,18 @@ sha1, sha224, sha256, sha384, sha512
7980
8081
sha1 is current default but will change by version 44
8182
83+
=item B<sls_force_lcase_url_encoding>
84+
85+
Specifies that the IdP requires the encoding of a URL to be in lowercase.
86+
Necessary for a HTTP-Redirect of a LogoutResponse from Azure in particular.
87+
True (1) or False (0). Some web frameworks and underlying http requests assume
88+
that the encoding should be in the standard uppercase (%2F not %2f)
89+
90+
=item B<sls_double_encoded_response>
91+
92+
Specifies that the IdP response sent to the HTTP-Redirect is double encoded.
93+
The double encoding requires it to be decoded prior to processing.
94+
8295
=back
8396
8497
=cut
@@ -88,6 +101,8 @@ has 'cert' => (isa => 'Str', is => 'ro', required => 1);
88101
has 'url' => (isa => Uri, is => 'ro', required => 1, coerce => 1);
89102
has 'param' => (isa => 'Str', is => 'ro', required => 1);
90103
has 'sig_hash' => (isa => 'Str', is => 'ro', required => 0);
104+
has 'sls_force_lcase_url_encoding' => (isa => 'Bool', is => 'ro', required => 0);
105+
has 'sls_double_encoded_response' => (isa => 'Bool', is => 'ro', required => 0);
91106

92107
=head2 sign( $request, $relaystate )
93108
@@ -162,6 +177,32 @@ sub verify {
162177
my $cert = Crypt::OpenSSL::X509->new_from_string($self->cert);
163178
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey);
164179

180+
my $signed;
181+
my $saml_request;
182+
my $sig = $u->query_param_delete('Signature');
183+
184+
# Some IdPs (PingIdentity) seem to double encode the LogoutResponse URL
185+
if (defined $self->sls_double_encoded_response and $self->sls_double_encoded_response == 1) {
186+
#if ($sigalg =~ m/%/) {
187+
$signed = uri_decode($u->query);
188+
$sig = uri_decode($sig);
189+
$sigalg = uri_decode($sigalg);
190+
$saml_request = uri_decode($u->query_param($self->param));
191+
} else {
192+
$signed = $u->query;
193+
$saml_request = $u->query_param($self->param);
194+
}
195+
196+
# What can we say about this one Microsoft Azure uses lower case in the
197+
# URL encoding %2f not %2F. As it is signed as %2f the resulting signed
198+
# needs to change it to lowercase if the application layer reencoded the URL.
199+
if (defined $self->sls_force_lcase_url_encoding and $self->sls_force_lcase_url_encoding == 1) {
200+
# TODO: This is a hack.
201+
$signed =~ s/(%..)/lc($1)/ge;
202+
}
203+
204+
$sig = decode_base64($sig);
205+
165206
if ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') {
166207
$rsa_pub->use_sha256_hash;
167208
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224') {
@@ -176,12 +217,10 @@ sub verify {
176217
die "Unsupported Signature Algorithim: $sigalg";
177218
}
178219

179-
my $sig = decode_base64($u->query_param_delete('Signature'));
180-
my $signed = $u->query;
181220
die "bad sig" unless $rsa_pub->verify($signed, $sig);
182221

183222
# unpack the SAML request
184-
my $deflated = decode_base64($u->query_param($self->param));
223+
my $deflated = decode_base64($saml_request);
185224
my $request = '';
186225
rawinflate \$deflated => \$request;
187226

lib/Net/SAML2/IdP.pm

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ Constructor
4343
4444
=item B<entityid>
4545
46+
=item B<sls_force_lcase_url_encoding>
47+
48+
Specifies that the IdP requires the encoding of a URL to be in lowercase.
49+
Necessary for a HTTP-Redirect of a LogoutResponse from Azure in particular.
50+
True (1) or False (0). Some web frameworks and underlying http requests assume
51+
that the encoding should be in the standard uppercase (%2F not %2f)
52+
53+
=item B<sls_double_encoded_response>
54+
55+
Specifies that the IdP response sent to the HTTP-Redirect is double encoded.
56+
The double encoding requires it to be decoded prior to processing.
57+
4658
=back
4759
4860
=cut
@@ -54,7 +66,9 @@ has 'slo_urls' => (isa => 'Maybe[HashRef[Str]]', is => 'ro');
5466
has 'art_urls' => (isa => 'Maybe[HashRef[Str]]', is => 'ro');
5567
has 'certs' => (isa => 'HashRef[Str]', is => 'ro', required => 1);
5668
has 'formats' => (isa => 'HashRef[Str]', is => 'ro', required => 1);
57-
has 'default_format' => (isa => 'Str', is => 'ro', required => 1);
69+
has 'sls_force_lcase_url_encoding' => (isa => 'Bool', is => 'ro', required => 0);
70+
has 'sls_double_encoded_response' => (isa => 'Bool', is => 'ro', required => 0);
71+
has 'default_format' => (isa => 'Str', is => 'ro', required => 1);
5872

5973
=head2 new_from_url( url => $url, cacert => $cacert, ssl_opts => {} )
6074
@@ -83,7 +97,12 @@ sub new_from_url {
8397

8498
my $xml = $res->content;
8599

86-
return $class->new_from_xml(xml => $xml, cacert => $args{cacert});
100+
return $class->new_from_xml(
101+
xml => $xml,
102+
cacert => $args{cacert},
103+
sls_force_lcase_url_encoding => $args{sls_force_lcase_url_encoding},
104+
sls_double_encoded_response => $args{sls_double_encoded_response},
105+
);
87106
}
88107

89108
=head2 new_from_xml( xml => $xml, cacert => $cacert )
@@ -189,6 +208,8 @@ sub new_from_xml {
189208
formats => $data->{NameIDFormat},
190209
default_format => $data->{DefaultFormat},
191210
cacert => $args{cacert},
211+
sls_force_lcase_url_encoding => $args{sls_force_lcase_url_encoding},
212+
sls_double_encoded_response => $args{sls_double_encoded_response},
192213
);
193214

194215
return $self;

lib/Net/SAML2/SP.pm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,9 @@ sub slo_redirect_binding {
257257
cert => $idp->cert('signing'),
258258
key => $self->key,
259259
param => $param,
260+
sls_force_lcase_url_encoding => $idp->{sls_force_lcase_url_encoding},
261+
sls_double_encoded_response => $idp->{sls_double_encoded_response},
260262
);
261-
262263
return $redirect;
263264
}
264265

xt/testapp/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ org_name: "Net::SAML2 Saml2Test"
1717
org_display_name: "Saml2Test app for Net::SAML2"
1818
org_contact: "saml2test@example.com"
1919
error_url: "/error"
20+
sls_force_lcase_url_encoding: "0"
21+
sls_double_encoded_response: "0"

xt/testapp/lib/Saml2Test.pm

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Demo app to show use of Net::SAML2 as an SP.
1616
use Dancer ':syntax';
1717
use Net::SAML2;
1818
use MIME::Base64 qw/ decode_base64 /;
19-
use URI::Encode qw(uri_encode uri_decode);
2019

2120
our $VERSION = '0.1';
2221

@@ -31,7 +30,7 @@ get '/login' => sub {
3130
$idp->sso_url('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
3231
$idp->format, # default format.
3332
)->as_xml;
34-
33+
3534
my $redirect = $sp->sso_redirect_binding($idp, 'SAMLRequest');
3635
my $url = $redirect->sign($authnreq);
3736
redirect $url, 302;
@@ -153,10 +152,19 @@ get '/sls-redirect-response' => sub {
153152
my $sp = _sp();
154153
my $redirect = $sp->slo_redirect_binding($idp, 'SAMLResponse');
155154

156-
my $decoded = uri_decode(request->uri);
157-
158-
my ($response, $relaystate) = $redirect->verify($decoded);
155+
my ($response, $relaystate) = $redirect->verify(request->uri);
159156

157+
if ($response) {
158+
my $logout = Net::SAML2::Protocol::LogoutResponse->new_from_xml(
159+
xml => $response
160+
);
161+
if ($logout->status eq 'urn:oasis:names:tc:SAML:2.0:status:Success') {
162+
print STDERR "\nLogout Success Status - $logout->{issuer}\n";
163+
}
164+
}
165+
else {
166+
return "<html><pre>Bad Logout Response</pre></html>";
167+
}
160168
redirect $relaystate || '/', 302;
161169
return "Redirected\n";
162170
};
@@ -177,7 +185,7 @@ post '/sls-post-response' => sub {
177185
xml => decode_base64(params->{SAMLResponse})
178186
);
179187
if ($logout->status eq 'urn:oasis:names:tc:SAML:2.0:status:Success') {
180-
print STDERR "Logout Success Status\n";
188+
print STDERR "\nLogout Success Status - $logout->{issuer}\n";
181189
}
182190
}
183191
else {
@@ -213,12 +221,14 @@ sub _sp {
213221
org_contact => config->{org_contact},
214222
);
215223
return $sp;
216-
}
224+
}
217225

218226
sub _idp {
219227
my $idp = Net::SAML2::IdP->new_from_url(
220228
url => config->{idp},
221-
cacert => 'saml_cacert.pem'
229+
cacert => 'saml_cacert.pem',
230+
sls_force_lcase_url_encoding => config->{sls_force_lcase_url_encoding},
231+
sls_double_encoded_response => config->{sls_double_encoded_response}
222232
);
223233
return $idp;
224234
}

0 commit comments

Comments
 (0)