Net-SAML2

 view release on metacpan or  search on metacpan

lib/Net/SAML2/Object/Response.pm  view on Meta::CPAN

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

has 'cacert'     => (
    isa => 'Str',
    is => 'ro',
    required => 0);

has 'insecure_trust_embedded_cert' => (
    isa       => 'Bool',
    is        => 'ro',
    default   => 0,
);

has 'require_signed_response' => (
    isa       => 'Bool',
    is        => 'ro',
    default   => 0,
);

# BUILDARGS

around BUILDARGS => sub {
    my $orig = shift;
    my $self = shift;

    my %params = @_;
    unless ($params{cacert}
         || $params{insecure_trust_embedded_cert}) {
        croak(
            "Net::SAML2::Object::Response->new() requires 'cacert' "
          . "on the object to verify SAML response signatures. "
          . "To explicitly disable signature verification (test/dev only) "
          . ", pass insecure_trust_embedded_cert => 1 to new()."
        );
    }

    return $self->$orig(%params);
};


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

    my $xml            = no_comments($args{xml});
    my $destination    = delete $args{destination};
    my $cacert         = delete $args{cacert};
    my $insecure_trust_embedded_cert    = delete $args{insecure_trust_embedded_cert};

    # The default may change in the future
    my $require_signed_response         = delete $args{require_signed_response} // 0;

    my $xpath = XML::LibXML::XPathContext->new($xml);
    $xpath->registerNs('saml',  URN_ASSERTION);
    $xpath->registerNs('samlp', URN_PROTOCOL);
    $xpath->registerNs('dsig',  'http://www.w3.org/2000/09/xmldsig#');

    my $actual_destination = $xpath->findvalue(
        '/samlp:Response/@Destination | /samlp:ArtifactResponse/@Destination');
    if (defined $destination && $destination ne $actual_destination) {
        croak(sprintf("Response Destination (%s) does not match expected value (%s)",
                $actual_destination,
                $destination)
        );
    }

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

    # require_signed_response only applies to front-channel Responses.
    #
    # ArtifactResponse is retrieved by the SP over a mutually authenticated
    # TLS back-channel (the SP initiates the connection to the IdP's known
    # artifact resolution endpoint), so transport already authenticates it
    # and it is exempt from this check.

    my $is_artifact = $response->nodePath eq '/samlp:ArtifactResponse';

    if ($require_signed_response && !$is_artifact) {
        my $signature = $xpath->findnodes('/samlp:Response/dsig:Signature', $response);
        # FIXME: should check that a Response-level Signature cryptographically
        # validates and it actually covers this Response - Assertion verification
        # happens downstream.
        if ($signature->size != 1) {
            croak "Net::SAML2::Object::Response requires that the Response "
                . "include exactly one Signature.  Pass `require_signed_response` = 0 "
                . "to allow unsigned Responses";
        }
    }

    my $code_path = 'samlp:Status/samlp:StatusCode';
    if ($is_artifact) {
      $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 ? ( substatus => $substatus) : (),
        issuer => $xpath->findvalue('saml:Issuer', $response),
        id     => $response->getAttribute('ID'),
        in_response_to => $response->getAttribute('InResponseTo'),
        $nodes->size ? (assertions => $nodes) : (),
        $cacert ? (cacert => $cacert) : (),
        $insecure_trust_embedded_cert ? (insecure_trust_embedded_cert => $insecure_trust_embedded_cert) : (),
        require_signed_response => $require_signed_response,
    );
}


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");
    }

    # Propagate the trust configuration from the Response so the Assertion
    # inherits the same trust anchor.  Without this the caller would have to
    # supply cacert (or insecure_trust_embedded_cert) a second time, and a
    # Response built with a trust anchor could silently produce an Assertion
    # built with none.  Caller-supplied %args still override these defaults.
    return Net::SAML2::Protocol::Assertion->new_from_xml(



( run in 0.443 second using v1.01-cache-2.11-cpan-5fbc6bb55f2 )