Net-SAML2

 view release on metacpan or  search on metacpan

lib/Net/SAML2/Protocol/Assertion.pm  view on Meta::CPAN

package Net::SAML2::Protocol::Assertion;
use Moose;

our $VERSION = '0.85'; # VERSION

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);
use Carp qw(croak);

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

# ABSTRACT: SAML2 assertion object


has 'attributes' => (isa => 'HashRef[ArrayRef]', is => 'ro', required => 1);
has 'audience'   => (isa => NonEmptySimpleStr, is => 'ro', required => 1);
has 'not_after'  => (isa => DateTime,          is => 'ro', required => 1);
has 'not_before' => (isa => DateTime,          is => 'ro', required => 1);
has 'session'         => (isa => 'Str', is => 'ro', required => 1);
has 'in_response_to'  => (isa => 'Str', is => 'ro', required => 1);
has 'response_status' => (isa => 'Str', is => 'ro', required => 1);
has 'response_substatus' => (isa => 'Str', is => 'ro');
has 'xpath' => (isa => 'XML::LibXML::XPathContext', is => 'ro', required => 1);
has 'nameid_object' => (
    isa       => 'XML::LibXML::Element',
    is        => 'ro',
    required  => 0,
    init_arg  => 'nameid',
    predicate => 'has_nameid',
);
has 'authnstatement_object' => (
    isa       => 'XML::LibXML::Element',
    is        => 'ro',
    required  => 0,
    init_arg  => 'authnstatement',
    predicate => 'has_authnstatement',
);



sub _verify_encrypted_assertion {
    my $self     = shift;
    my $xml      = shift;
    my $cacert   = shift;
    my $key_file = shift;
    my $key_name = shift;

    my $xpath = XML::LibXML::XPathContext->new($xml);
    $xpath->registerNs('saml',  'urn:oasis:names:tc:SAML:2.0:assertion');
    $xpath->registerNs('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol');
    $xpath->registerNs('dsig',  'http://www.w3.org/2000/09/xmldsig#');
    $xpath->registerNs('xenc',  'http://www.w3.org/2001/04/xmlenc#');

    return $xml unless $xpath->exists('//saml:EncryptedAssertion');

    croak "Encrypted Assertions require key_file" if !defined $key_file;

    $xml = $self->_decrypt(
        $xml,
        key_file => $key_file,
        key_name => $key_name,
    );
    $xpath->setContextNode($xml);

    my $assert_nodes = $xpath->findnodes('//saml:Assertion');
    return $xml unless $assert_nodes->size;
    my $assert = $assert_nodes->get_node(1);

    return $xml unless $xpath->exists('dsig:Signature', $assert);
    my $xml_opts->{ no_xml_declaration } = 1;
    my $x   = Net::SAML2::XML::Sig->new($xml_opts);
    my $ret = $x->verify($assert->toString());
    die "Decrypted Assertion signature check failed" unless $ret;

    return $xml unless $cacert;
    my $cert = $x->signer_cert;
    die "Certificate not provided in SAML Response, cannot validate" unless $cert;

    my $ca = Crypt::OpenSSL::Verify->new($cacert, { strict_certs => 0 });
    die "Unable to verify signer cert with cacert: " . $cert->subject
        unless $ca->verify($cert);
    return $xml;
}

sub new_from_xml {
    my($class, %args) = @_;

    my $key_file = $args{key_file};
    my $cacert   = delete $args{cacert};

    my $xpath = XML::LibXML::XPathContext->new();
    $xpath->registerNs('saml',  'urn:oasis:names:tc:SAML:2.0:assertion');
    $xpath->registerNs('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol');
    $xpath->registerNs('dsig',  'http://www.w3.org/2000/09/xmldsig#');
    $xpath->registerNs('xenc',  'http://www.w3.org/2001/04/xmlenc#');

    my $xml = no_comments($args{xml});
    $xpath->setContextNode($xml);

    $xml = $class->_verify_encrypted_assertion(
        $xml,
        $cacert,
        $key_file,
        $args{key_name},
    );

    my $dec = $class->_decrypt(
        $xml,
        key_file => $key_file,
        key_name => $args{key_name}
    );
    $xpath->setContextNode($dec);

    my $attributes = {};
    for my $node ($xpath->findnodes('//saml:Assertion/saml:AttributeStatement/saml:Attribute/saml:AttributeValue/..'))
    {
        my @values = $xpath->findnodes("saml:AttributeValue", $node);
        $attributes->{$node->getAttribute('Name')} = [map $_->string_value, @values];
    }

    my $xpath_base = '//samlp:Response/saml:Assertion/saml:Conditions/';

    my $not_before;
    if (my $value = $xpath->findvalue($xpath_base . '@NotBefore')) {
        $not_before = DateTime::Format::XSD->parse_datetime($value);
    }
    elsif (my $global = $xpath->findvalue('//saml:Conditions/@NotBefore')) {
        $not_before = DateTime::Format::XSD->parse_datetime($global);
    }
    else {
        $not_before = DateTime::HiRes->now();
    }

    my $not_after;
    if (my $value = $xpath->findvalue($xpath_base . '@NotOnOrAfter')) {
        $not_after = DateTime::Format::XSD->parse_datetime($value);
    }
    elsif (my $global = $xpath->findvalue('//saml:Conditions/@NotOnOrAfter')) {
        $not_after = DateTime::Format::XSD->parse_datetime($global);
    }
    else {
        $not_after = DateTime->from_epoch(epoch => time() + 1000);
    }

    my $nameid;
    if (my $node = $xpath->findnodes('/samlp:Response/saml:Assertion/saml:Subject/saml:NameID')) {
        $nameid = $node->get_node(1);
    }
    elsif (my $global = $xpath->findnodes('//saml:Subject/saml:NameID')) {
        $nameid = $global->get_node(1);
    }

    my $authnstatement;
    if (my $node = $xpath->findnodes('/samlp:Response/saml:Assertion/saml:AuthnStatement')) {



( run in 1.487 second using v1.01-cache-2.11-cpan-ceb78f64989 )