Skip to content

Commit

Permalink
Add Object::Response to Net::SAML2
Browse files Browse the repository at this point in the history
This is to accomodate responses which do not include Assertions

Signed-off-by: Wesley Schwengle <waterkip@cpan.org>
  • Loading branch information
waterkip committed Apr 18, 2024
1 parent 1ed5866 commit cb098e8
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 0 deletions.
126 changes: 126 additions & 0 deletions lib/Net/SAML2/Object/Response.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package Net::SAML2::Object::Response;
use Moose;


use overload '""' => 'to_string';

# ABSTRACT: A response object

use MooseX::Types::DateTime qw/ DateTime /;
use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
use DateTime;
use DateTime::HiRes;
use DateTime::Format::XSD;
use Net::SAML2::XML::Util qw/ no_comments /;
use Net::SAML2::XML::Sig;
use XML::Enc;
use XML::LibXML::XPathContext;
use List::Util qw(first);
use URN::OASIS::SAML2 qw(STATUS_SUCCESS URN_ASSERTION URN_PROTOCOL);
use Carp qw(croak);

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

# ABSTRACT: SAML2 response object

has _dom => (
is => 'ro',
isa => 'XML::LibXML::Node',
init_arg => 'dom',
required => 1,
);

has status => (
is => 'ro',
isa => 'Str',
required => 1,
);

has sub_status => (
is => 'ro',
isa => 'Str',
required => 0,
predicate => 'has_sub_status',
);

has assertions => (
is => 'ro',
isa => 'XML::LibXML::NodeList',
required => 0,
predicate => 'has_assertions',
);

sub new_from_xml {
my $self = shift;
my %args = @_;

my $xml = no_comments($args{xml});

my $xpath = XML::LibXML::XPathContext->new($xml);
$xpath->registerNs('saml', URN_ASSERTION);
$xpath->registerNs('samlp', URN_PROTOCOL);

my $response = $xpath->findnodes('/samlp:Response|/samlp:ArtifactResponse');
croak("Unable to parse response") unless $response->size;
$response = $response->get_node(1);

my $code_path = 'samlp:Status/samlp:StatusCode';
if ($response->nodePath eq '/samlp:ArtifactResponse') {
$code_path = "samlp:Response/$code_path";
}

my $status = $xpath->findnodes($code_path, $response);
croak("Unable to parse status from response") unless $status->size;

my $status_node = $status->get_node(1);
$status = $status_node->getAttribute('Value');

my $substatus = $xpath->findvalue('samlp:StatusCode/@Value', $status_node);

my $nodes = $xpath->findnodes('//saml:EncryptedAssertion|//saml:Assertion', $response);

return $self->new(
dom => $xml,
status => $status,
$substatus ? ( sub_status => $substatus) : (),
issuer => $xpath->findvalue('saml:Issuer', $response),
id => $response->getAttribute('ID'),
in_response_to => $response->getAttribute('InResponseTo'),
$nodes->size ? (assertions => $nodes) : (),
);
}

sub to_string {
my $self = shift;
return $self->_dom->toString;
}

sub to_assertion {
my $self = shift;
my @args = @_;

if (!$self->has_assertions) {
croak("There are no assertions found in the response object");
}

return Net::SAML2::Protocol::Assertion->new_from_xml(
@args,
xml => $self->to_string,
);
}

1;


__PACKAGE__->meta->make_immutable;

__END__
=head1 DESCRIPTION
=head1 SYNOPSIS
use Net::SAML2::Object::Response;
my $var = Net::SAML2::Object::Response->new(...);
$var->method(...);
42 changes: 42 additions & 0 deletions t/29-response.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use strict;
use warnings;
use Test::Lib;
use Test::Net::SAML2;

use Net::SAML2::Object::Response;
use URN::OASIS::SAML2 qw(STATUS_RESPONDER STATUS_AUTH_FAILED);

sub get_object {
my $xml = path(shift)->slurp;
my $response = Net::SAML2::Object::Response->new_from_xml(xml => $xml);
isa_ok($response, 'Net::SAML2::Object::Response');
return $response;
}

{
my $response = get_object('t/data/digid-anul-artifact-response.xml');
ok(!$response->has_assertions, "We don't have an assertion");
ok(!$response->success, "Unsuccessful response");
is($response->status, STATUS_RESPONDER(), "... because its a status:Responder");
is($response->sub_status, STATUS_AUTH_FAILED(), "... and substatus is also correct");
}


{
my $response = get_object('t/data/eherkenning-assertion.xml');
ok($response->has_assertions, "We have an assertion");
ok($response->success, "It was successful");
is($response->assertions->size, 3, "Got the correct amount or assertions");

my $assertion = $response->to_assertion();
isa_ok($assertion, "Net::SAML2::Protocol::Assertion");
}


{
my $response = get_object('t/data/response-no-assertion.xml');
ok(!$response->has_assertions, "We don't have an assertion");
ok(!$response->success, "Unsuccessful response");
is($response->status, STATUS_RESPONDER(), "... because its a status:Responder");
}
done_testing;
38 changes: 38 additions & 0 deletions t/data/digid-anul-artifact-response.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0"?>
<samlp:ArtifactResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" ID="_5ede0a3002fb0efe627efc4bf3a076b7a3810a80" Version="2.0" IssueInstant="2024-04-16T14:52:40Z" InResponseTo="NETSAML2_10aad5b7776e047b40637fec9793fa1b8259e7e7845ca379cd8a01008e1b93f8">
<saml:Issuer>https://was-preprod1.digid.nl/saml/idp/metadata</saml:Issuer>
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#_5ede0a3002fb0efe627efc4bf3a076b7a3810a80">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces PrefixList="ds saml samlp xs"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>value here</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>some sig here</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyName>7593b799e735055fcd479caa35d44d455576cefc</ds:KeyName>
<ds:X509Data>
<ds:X509Certificate>Some cert here</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<samlp:Response ID="_bee5133d91cddac59c1943a451915948a0563d5b" Version="2.0" IssueInstant="2024-04-16T14:52:40Z" InResponseTo="NETSAML2_730fbcbf9aca0d79eb18aeee3ad03908f805a3b441d586753c6dd68e53cb5d73">
<saml:Issuer>https://was-preprod1.digid.nl/saml/idp/metadata</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder">
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"/>
</samlp:StatusCode>
</samlp:Status>
</samlp:Response>
</samlp:ArtifactResponse>
31 changes: 31 additions & 0 deletions t/data/response-no-assertion.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<samlp:Response ID="_ae896f90-5ecb-4d00-8738-91337236a165" Version="2.0"
IssueInstant="2024-04-10T13:17:32.119Z" Destination="[our SAML callback url]"
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
InResponseTo="NETSAML2_d54e304a5472d4429f20e3d44a7a224b"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://[IdP testing
domain]/adfs/services/trust</Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#_ae896f90-5ecb-4d00-8738-91337236a165">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<ds:DigestValue>K4r9/0ZZqW32TG+jT9tGsKwYKwNzssSSKIo8gvWPCfo=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>[the signature value]</ds:SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>[the certificate]</ds:X509Certificate>
</ds:X509Data>
</KeyInfo>
</ds:Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder" />
</samlp:Status>
</samlp:Response>

0 comments on commit cb098e8

Please sign in to comment.