Skip to content

Commit b9d4786

Browse files
authored
Merge pull request #44 from timlegge/azure-lowercase
Support for Azure lowercase LogoutResponse and PingIdentity Double Encoded URLs
2 parents 378c815 + 4f2a40c commit b9d4786

File tree

10 files changed

+101
-21
lines changed

10 files changed

+101
-21
lines changed

Makefile.PL

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ my %WriteMakefileArgs = (
4444
"MooseX::Types::DateTime" => 0,
4545
"MooseX::Types::URI" => 0,
4646
"URI" => 0,
47+
"URI::Encode" => 0,
4748
"URI::QueryParam" => 0,
4849
"XML::Generator" => 0,
4950
"XML::LibXML" => 0,
@@ -70,7 +71,7 @@ my %WriteMakefileArgs = (
7071
"URI::URL" => 0,
7172
"XML::LibXML::XPathContext" => 0
7273
},
73-
"VERSION" => "0.40",
74+
"VERSION" => "0.42",
7475
"test" => {
7576
"TESTS" => "t/*.t t/author/*.t"
7677
}
@@ -117,6 +118,7 @@ my %FallbackPrereqs = (
117118
"Test::Pod" => "1.14",
118119
"Test::Pod::Coverage" => "1.04",
119120
"URI" => 0,
121+
"URI::Encode" => 0,
120122
"URI::QueryParam" => 0,
121123
"URI::URL" => 0,
122124
"XML::Generator" => 0,

README

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
NAME
2-
Net::SAML2
2+
Net::SAML2 - SAML2 bindings and protocol implementation
33

44
VERSION
5-
version 0.40
5+
version 0.42
66

77
SYNOPSIS
88
See TUTORIAL.md for implementation documentation and

cpanfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ requires "MooseX::Types::Common::String" => "0";
2828
requires "MooseX::Types::DateTime" => "0";
2929
requires "MooseX::Types::URI" => "0";
3030
requires "URI" => "0";
31+
requires "URI::Encode" => "0";
3132
requires "URI::QueryParam" => "0";
3233
requires "XML::Generator" => "0";
3334
requires "XML::LibXML" => "0";

dist.ini

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ match = ico
4646

4747
[CPANFile]
4848

49-
;[CopyFilesFromBuild::Filtered]
50-
;copy = cpanfile
51-
;copy = Makefile.PL
52-
;copy = README
49+
[CopyFilesFromBuild::Filtered]
50+
copy = cpanfile
51+
copy = Makefile.PL
52+
copy = README
5353

5454
[CopyFilesFromRelease]
5555
copy = cpanfile, Makefile.PL, README

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
}

xt/testapp/views/user.tt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
<th>Key</th>
1414
<th>Value</th>
1515
</tr>
16+
<tr>
17+
<td>Issuer</td>
18+
<td><% assertion.issuer %></td>
19+
</tr>
1620
<tr>
1721
<td>DN</td>
1822
<td><% assertion.attributes.DN %></td>

0 commit comments

Comments
 (0)