Skip to content

Commit d03bb23

Browse files
authored
Merge pull request #157 from waterkip/GL-sub-status_for_consumers_in_assertion
Get the substatus of a failed assertion
2 parents d8ca5b9 + fc6a199 commit d03bb23

File tree

3 files changed

+117
-10
lines changed

3 files changed

+117
-10
lines changed

lib/Net/SAML2/Protocol/Assertion.pm

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use strict;
2-
use warnings;
31
package Net::SAML2::Protocol::Assertion;
42
# VERSION
53

@@ -13,6 +11,8 @@ use Net::SAML2::XML::Util qw/ no_comments /;
1311
use Net::SAML2::XML::Sig;
1412
use XML::Enc;
1513
use XML::LibXML;
14+
use List::Util qw(first);
15+
use URN::OASIS::SAML2 qw(STATUS_SUCCESS);
1616

1717
with 'Net::SAML2::Role::ProtocolMessage';
1818

@@ -37,12 +37,13 @@ has 'not_before' => (isa => DateTime, is => 'ro', required => 1);
3737
has 'session' => (isa => 'Str', is => 'ro', required => 1);
3838
has 'in_response_to' => (isa => 'Str', is => 'ro', required => 1);
3939
has 'response_status' => (isa => 'Str', is => 'ro', required => 1);
40+
has 'response_substatus' => (isa => 'Str', is => 'ro');
4041
has 'xpath' => (isa => 'XML::LibXML::XPathContext', is => 'ro', required => 1);
4142
has 'nameid_object' => (
42-
isa => 'XML::LibXML::Element',
43-
is => 'ro',
44-
required => 0,
45-
init_arg => 'nameid',
43+
isa => 'XML::LibXML::Element',
44+
is => 'ro',
45+
required => 0,
46+
init_arg => 'nameid',
4647
predicate => 'has_nameid',
4748
);
4849

@@ -175,6 +176,17 @@ sub new_from_xml {
175176
$nameid = $global->get_node(1);
176177
}
177178

179+
my $nodeset = $xpath->findnodes('/samlp:Response/samlp:Status/samlp:StatusCode');
180+
croak("Unable to parse status from assertion") unless ($nodeset->size);
181+
182+
my $status_node = $nodeset->get_node(1);
183+
my $status = $status_node->getAttribute('Value');
184+
my $sub_status;
185+
186+
if (my $s = first { $_->isa('XML::LibXML::Element') } $status_node->childNodes) {
187+
$sub_status = $s->getAttribute('Value');
188+
}
189+
178190
my $self = $class->new(
179191
id => $xpath->findvalue('//saml:Assertion/@ID'),
180192
issuer => $xpath->findvalue('//saml:Assertion/saml:Issuer'),
@@ -187,21 +199,32 @@ sub new_from_xml {
187199
not_after => $not_after,
188200
xpath => $xpath,
189201
in_response_to => $xpath->findvalue('//saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData/@InResponseTo'),
190-
response_status => $xpath->findvalue('//samlp:Response/samlp:Status/samlp:StatusCode/@Value'),
202+
response_status => $status,
203+
$sub_status ? (response_substatus => $sub_status) : (),
191204
);
192205

193206
return $self;
194207
}
195208

196-
=head2 name( )
209+
=head2 response_status
210+
211+
Returns the response status
212+
213+
=head2 response_substatus
214+
215+
SAML errors are usually "nested" ("Responder -> RequestDenied" for instance,
216+
means that the responder in this transaction (the IdP) denied the login
217+
request). For proper error message generation, both levels are needed.
218+
219+
=head2 name
197220
198221
Returns the CN attribute, if provided.
199222
200223
=cut
201224

202225
sub name {
203-
my($self) = @_;
204-
return $self->attributes->{CN}->[0];
226+
my $self = shift;
227+
return $self->attributes->{CN}[0];
205228
}
206229

207230
=head2 nameid
@@ -296,4 +319,17 @@ sub valid {
296319
return 1;
297320
}
298321

322+
=head2 success
323+
324+
Returns true if the response status is a success, returns false otherwise.
325+
In case the assertion isn't successfull, the L</response_status> and L</response_substatus> calls can be use to see why the assertion wasn't successful.
326+
327+
=cut
328+
329+
sub success {
330+
my $self = shift;
331+
return 1 if $self->response_status eq STATUS_SUCCESS;
332+
return 0;
333+
}
334+
299335
1;

t/03-assertions.t

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,24 @@ lives_ok(
159159
"Correct parsing of plain ADFS"
160160
);
161161

162+
lives_ok(
163+
sub {
164+
my $xml = path('t/data/failed-assertion.xml')->slurp;
165+
$assertion = Net::SAML2::Protocol::Assertion->new_from_xml(xml => $xml);
166+
},
167+
"Correct parsing of failed assertion"
168+
);
169+
170+
ok(!$assertion->success, ".. not a success");
171+
is(
172+
$assertion->response_status,
173+
"main status",
174+
"... and the status also indicates not a success"
175+
);
176+
is(
177+
$assertion->response_substatus,
178+
"sub status",
179+
"... and the sub status yay"
180+
);
181+
162182
done_testing;

t/data/failed-assertion.xml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?xml version="1.0"?>
2+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_f0b0652f-1382-43af-a70e-97254820f180" Version="2.0" IssueInstant="2018-07-25T08:02:24.342Z" Destination="https://testsuite.zaaksysteem.nl/auth/saml/consumer-post" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_c217d2c8502884bf418552907827f430d745fa6c">
3+
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://adfs.dev.mintlab.nl/adfs/services/trust</Issuer>
4+
<samlp:Status>
5+
<samlp:StatusCode Value="main status">
6+
<samlp:StatusCode Value="sub status"/>
7+
</samlp:StatusCode>
8+
<samlp:StatusMessage>Authentication Failed</samlp:StatusMessage>
9+
<samlp:StatusDetail>
10+
<Cause>org.sourceid.websso.profiles.idp.FailedAuthnSsoException</Cause>
11+
</samlp:StatusDetail>
12+
</samlp:Status>
13+
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_0024c3a6-3b4e-4920-8c59-0602ae4a3c5d" IssueInstant="2018-07-25T08:02:24.341Z" Version="2.0">
14+
<Issuer>http://adfs.dev.mintlab.nl/adfs/services/trust</Issuer>
15+
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
16+
<ds:SignedInfo>
17+
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
18+
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
19+
<ds:Reference URI="#_0024c3a6-3b4e-4920-8c59-0602ae4a3c5d">
20+
<ds:Transforms>
21+
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
22+
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
23+
</ds:Transforms>
24+
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
25+
<ds:DigestValue>/UGIkccgX9xPwusXApqxm7KDyLpgx7W/tp6gANTvNsw=</ds:DigestValue>
26+
</ds:Reference>
27+
</ds:SignedInfo>
28+
<ds:SignatureValue/>
29+
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
30+
<ds:X509Data>
31+
<ds:X509Certificate/>
32+
</ds:X509Data>
33+
</KeyInfo>
34+
</ds:Signature>
35+
<Subject>
36+
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
37+
<SubjectConfirmationData InResponseTo="_c217d2c8502884bf418552907827f430d745fa6c" NotOnOrAfter="2018-07-25T08:07:24.342Z" Recipient="https://testsuite.zaaksysteem.nl/auth/saml/consumer-post"/>
38+
</SubjectConfirmation>
39+
</Subject>
40+
<Conditions NotBefore="2018-07-25T08:02:24.338Z" NotOnOrAfter="2018-07-25T09:02:24.338Z">
41+
<AudienceRestriction>
42+
<Audience>TestUPN</Audience>
43+
</AudienceRestriction>
44+
</Conditions>
45+
<AuthnStatement AuthnInstant="2018-07-25T07:54:35.599Z">
46+
<AuthnContext>
47+
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
48+
</AuthnContext>
49+
</AuthnStatement>
50+
</Assertion>
51+
</samlp:Response>

0 commit comments

Comments
 (0)