|
| 1 | +package Net::SAML2::Object::Response; |
| 2 | +use Moose; |
| 3 | + |
| 4 | +# VERSION |
| 5 | + |
| 6 | +use overload '""' => 'to_string'; |
| 7 | + |
| 8 | +# ABSTRACT: A response object |
| 9 | + |
| 10 | +use MooseX::Types::DateTime qw/ DateTime /; |
| 11 | +use MooseX::Types::Common::String qw/ NonEmptySimpleStr /; |
| 12 | +use DateTime; |
| 13 | +use DateTime::HiRes; |
| 14 | +use DateTime::Format::XSD; |
| 15 | +use Net::SAML2::XML::Util qw/ no_comments /; |
| 16 | +use Net::SAML2::XML::Sig; |
| 17 | +use XML::Enc; |
| 18 | +use XML::LibXML::XPathContext; |
| 19 | +use List::Util qw(first); |
| 20 | +use URN::OASIS::SAML2 qw(STATUS_SUCCESS URN_ASSERTION URN_PROTOCOL); |
| 21 | +use Carp qw(croak); |
| 22 | + |
| 23 | +with 'Net::SAML2::Role::ProtocolMessage'; |
| 24 | + |
| 25 | +=head1 DESCRIPTION |
| 26 | +
|
| 27 | +A generic response object to be able to deal with an response from the IdP. If |
| 28 | +the status is successful you can grab an assertion and continue your flow. |
| 29 | +
|
| 30 | +=head1 SYNOPSIS |
| 31 | +
|
| 32 | + use Net::SAML2::Object::Response; |
| 33 | +
|
| 34 | + my $xml = ...; |
| 35 | + my $response = Net::SAML2::Object::Response->new_from_xml(xml => $xml); |
| 36 | +
|
| 37 | + if (!$response->is_success) { |
| 38 | + warn "Got a response but isn't successful"; |
| 39 | +
|
| 40 | + my $status = $response->status; |
| 41 | + my $sub_status = $response->sub_status; |
| 42 | +
|
| 43 | + warn "We got a $status back with the following sub status $sub_status"; |
| 44 | + } |
| 45 | + else { |
| 46 | + $response->to_assertion( |
| 47 | + # See Net::SAML2::Protocol::Assertion->new_from_xml for the other |
| 48 | + # construction options |
| 49 | + key_file => ..., |
| 50 | + key_name => ..., |
| 51 | + ) |
| 52 | + } |
| 53 | +
|
| 54 | +=head1 ATTRIBUTES |
| 55 | +
|
| 56 | +=head2 status |
| 57 | +
|
| 58 | +Returns the status of the response |
| 59 | +
|
| 60 | +=head2 sub_status |
| 61 | +
|
| 62 | +Returns the sub status of the response |
| 63 | +
|
| 64 | +=head2 assertions |
| 65 | +
|
| 66 | +Returns the nodes of the assertion |
| 67 | +
|
| 68 | +=cut |
| 69 | + |
| 70 | +has _dom => ( |
| 71 | + is => 'ro', |
| 72 | + isa => 'XML::LibXML::Node', |
| 73 | + init_arg => 'dom', |
| 74 | + required => 1, |
| 75 | +); |
| 76 | + |
| 77 | +has status => ( |
| 78 | + is => 'ro', |
| 79 | + isa => 'Str', |
| 80 | + required => 1, |
| 81 | +); |
| 82 | + |
| 83 | +has sub_status => ( |
| 84 | + is => 'ro', |
| 85 | + isa => 'Str', |
| 86 | + required => 0, |
| 87 | + predicate => 'has_sub_status', |
| 88 | +); |
| 89 | + |
| 90 | +has assertions => ( |
| 91 | + is => 'ro', |
| 92 | + isa => 'XML::LibXML::NodeList', |
| 93 | + required => 0, |
| 94 | + predicate => 'has_assertions', |
| 95 | +); |
| 96 | + |
| 97 | +=head1 METHODS |
| 98 | +
|
| 99 | +=head2 $self->new_from_xml(xml => $xml) |
| 100 | +
|
| 101 | +Creates the response object based on the response XML |
| 102 | +
|
| 103 | +=cut |
| 104 | + |
| 105 | +sub new_from_xml { |
| 106 | + my $self = shift; |
| 107 | + my %args = @_; |
| 108 | + |
| 109 | + my $xml = no_comments($args{xml}); |
| 110 | + |
| 111 | + my $xpath = XML::LibXML::XPathContext->new($xml); |
| 112 | + $xpath->registerNs('saml', URN_ASSERTION); |
| 113 | + $xpath->registerNs('samlp', URN_PROTOCOL); |
| 114 | + |
| 115 | + my $response = $xpath->findnodes('/samlp:Response|/samlp:ArtifactResponse'); |
| 116 | + croak("Unable to parse response") unless $response->size; |
| 117 | + $response = $response->get_node(1); |
| 118 | + |
| 119 | + my $code_path = 'samlp:Status/samlp:StatusCode'; |
| 120 | + if ($response->nodePath eq '/samlp:ArtifactResponse') { |
| 121 | + $code_path = "samlp:Response/$code_path"; |
| 122 | + } |
| 123 | + |
| 124 | + my $status = $xpath->findnodes($code_path, $response); |
| 125 | + croak("Unable to parse status from response") unless $status->size; |
| 126 | + |
| 127 | + my $status_node = $status->get_node(1); |
| 128 | + $status = $status_node->getAttribute('Value'); |
| 129 | + |
| 130 | + my $substatus = $xpath->findvalue('samlp:StatusCode/@Value', $status_node); |
| 131 | + |
| 132 | + my $nodes = $xpath->findnodes('//saml:EncryptedAssertion|//saml:Assertion', $response); |
| 133 | + |
| 134 | + return $self->new( |
| 135 | + dom => $xml, |
| 136 | + status => $status, |
| 137 | + $substatus ? ( sub_status => $substatus) : (), |
| 138 | + issuer => $xpath->findvalue('saml:Issuer', $response), |
| 139 | + id => $response->getAttribute('ID'), |
| 140 | + in_response_to => $response->getAttribute('InResponseTo'), |
| 141 | + $nodes->size ? (assertions => $nodes) : (), |
| 142 | + ); |
| 143 | +} |
| 144 | + |
| 145 | +=head2 $self->to_string |
| 146 | +
|
| 147 | +Stringify the object to the full response XML |
| 148 | +
|
| 149 | +=cut |
| 150 | + |
| 151 | +sub to_string { |
| 152 | + my $self = shift; |
| 153 | + return $self->_dom->toString; |
| 154 | +} |
| 155 | + |
| 156 | +=head2 $self->to_assertion(%args) |
| 157 | +
|
| 158 | +Create a L<Net::SAML2::Protocol::Assertion> from the response. See |
| 159 | +L<Net::SAML2::Protocol::Assertion/new_from_xml> for more. |
| 160 | +
|
| 161 | +=cut |
| 162 | + |
| 163 | +sub to_assertion { |
| 164 | + my $self = shift; |
| 165 | + my %args = @_; |
| 166 | + |
| 167 | + if (!$self->has_assertions) { |
| 168 | + croak("There are no assertions found in the response object"); |
| 169 | + } |
| 170 | + |
| 171 | + return Net::SAML2::Protocol::Assertion->new_from_xml(%args, |
| 172 | + xml => $self->to_string,); |
| 173 | +} |
| 174 | + |
| 175 | +1; |
| 176 | + |
| 177 | + |
| 178 | +__PACKAGE__->meta->make_immutable; |
| 179 | + |
| 180 | +__END__ |
| 181 | +
|
0 commit comments