File Coverage

blib/lib/Net/SAML2/Protocol/Assertion.pm
Criterion Covered Total %
statement 49 53 92.4
branch 11 14 78.5
condition 3 3 100.0
subroutine 10 11 90.9
pod 3 3 100.0
total 76 84 90.4


line stmt bran cond sub pod time code
1             package Net::SAML2::Protocol::Assertion;
2 4     4   16484 use Moose;
  4         11  
  4         34  
3 4     4   31852 use MooseX::Types::DateTime qw/ DateTime /;
  4         282736  
  4         25  
4 4     4   8076 use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
  4         12  
  4         42  
5 4     4   12108 use DateTime;
  4         16  
  4         120  
6 4     4   2375 use DateTime::HiRes;
  4         10636  
  4         159  
7 4     4   1905 use DateTime::Format::XSD;
  4         541182  
  4         190  
8 4     4   40 use Net::SAML2::XML::Util qw/ no_comments /;
  4         10  
  4         250  
9 4     4   30 use XML::LibXML;
  4         12  
  4         47  
10              
11             with 'Net::SAML2::Role::ProtocolMessage';
12              
13             our $VERSION = '0.42';
14              
15             # ABSTRACT: Net::SAML2::Protocol::Assertion - SAML2 assertion object
16              
17              
18             has 'attributes' => (isa => 'HashRef[ArrayRef]', is => 'ro', required => 1);
19             has 'session' => (isa => 'Str', is => 'ro', required => 1);
20             has 'nameid' => (isa => 'Str', is => 'ro', required => 1);
21             has 'not_before' => (isa => DateTime, is => 'ro', required => 1);
22             has 'not_after' => (isa => DateTime, is => 'ro', required => 1);
23             has 'audience' => (isa => NonEmptySimpleStr, is => 'ro', required => 1);
24             has 'xpath' => (isa => 'XML::LibXML::XPathContext', is => 'ro', required => 1);
25             has 'in_response_to' => (isa => 'Str', is => 'ro', required => 1);
26             has 'response_status' => (isa => 'Str', is => 'ro', required => 1);
27              
28              
29              
30             sub new_from_xml {
31 3     3 1 2073 my($class, %args) = @_;
32              
33 3         22 my $dom = no_comments($args{xml});
34              
35 3         52 my $xpath = XML::LibXML::XPathContext->new($dom);
36 3         24 $xpath->registerNs('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
37 3         18 $xpath->registerNs('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol');
38              
39 3         9 my $attributes = {};
40 3         18 for my $node (
41             $xpath->findnodes('//saml:Assertion/saml:AttributeStatement/saml:Attribute'))
42             {
43             # We can't select by saml:AttributeValue
44             # because of https://rt.cpan.org/Public/Bug/Display.html?id=8784
45 9         427 my @values = $node->findnodes("*[local-name()='AttributeValue']");
46 9         486 $attributes->{$node->getAttribute('Name')} = [map $_->string_value, @values];
47             }
48              
49 3         88 my $not_before;
50 3 50       84 if($xpath->findvalue('//saml:Conditions/@NotBefore')) {
51 3         406 $not_before = DateTime::Format::XSD->parse_datetime(
52             $xpath->findvalue('//saml:Conditions/@NotBefore'));
53             }
54             else {
55 0         0 $not_before = DateTime::HiRes->now();
56             }
57              
58 3         3050 my $not_after;
59 3 50       19 if($xpath->findvalue('//saml:Conditions/@NotOnOrAfter')) {
60 3         246 $not_after = DateTime::Format::XSD->parse_datetime(
61             $xpath->findvalue('//saml:Conditions/@NotOnOrAfter'));
62             }
63             else {
64 0         0 $not_after = DateTime->from_epoch(epoch => time() + 1000);
65             }
66              
67 3         1645 my $self = $class->new(
68             issuer => $xpath->findvalue('//saml:Assertion/saml:Issuer'),
69             destination => $xpath->findvalue('/samlp:Response/@Destination'),
70             attributes => $attributes,
71             session => $xpath->findvalue('//saml:AuthnStatement/@SessionIndex'),
72             nameid => $xpath->findvalue('//saml:Subject/saml:NameID'),
73             audience => $xpath->findvalue('//saml:Conditions/saml:AudienceRestriction/saml:Audience'),
74             not_before => $not_before,
75             not_after => $not_after,
76             xpath => $xpath,
77             in_response_to => $xpath->findvalue('//saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData/@InResponseTo'),
78             response_status => $xpath->findvalue('//samlp:Response/samlp:Status/samlp:StatusCode/@Value'),
79             );
80              
81 3         24767 return $self;
82             }
83              
84              
85             sub name {
86 0     0 1 0 my($self) = @_;
87 0         0 return $self->attributes->{CN}->[0];
88             }
89              
90              
91             sub valid {
92 8     8 1 4772 my ($self, $audience, $in_response_to) = @_;
93              
94 8 50       32 return 0 unless defined $audience;
95 8 100       482 return 0 unless($audience eq $self->audience);
96              
97 7 100 100     213 return 0 unless !defined $in_response_to
98             or $in_response_to eq $self->in_response_to;
99              
100 6         50 my $now = DateTime::HiRes->now;
101              
102             # not_before is "NotBefore" element - exact match is ok
103             # not_after is "NotOnOrAfter" element - exact match is *not* ok
104 6 100       2289 return 0 unless DateTime::->compare($now, $self->not_before) > -1;
105 4 100       500 return 0 unless DateTime::->compare($self->not_after, $now) > 0;
106              
107 3         412 return 1;
108             }
109              
110             1;
111              
112             __END__
113              
114             =pod
115              
116             =encoding UTF-8
117              
118             =head1 NAME
119              
120             Net::SAML2::Protocol::Assertion - Net::SAML2::Protocol::Assertion - SAML2 assertion object
121              
122             =head1 VERSION
123              
124             version 0.42
125              
126             =head1 SYNOPSIS
127              
128             my $assertion = Net::SAML2::Protocol::Assertion->new_from_xml(
129             xml => decode_base64($SAMLResponse)
130             );
131              
132             =head1 NAME
133              
134             Net::SAML2::Protocol::Assertion - SAML2 assertion object
135              
136             =head1 METHODS
137              
138             =head2 new_from_xml( ... )
139              
140             Constructor. Creates an instance of the Assertion object, parsing the
141             given XML to find the attributes, session and nameid.
142              
143             Arguments:
144              
145             =over
146              
147             =item B<xml>
148              
149             XML data
150              
151             =back
152              
153             =head2 name( )
154              
155             Returns the CN attribute, if provided.
156              
157             =head2 valid( $audience, $in_response_to )
158              
159             Returns true if this Assertion is currently valid for the given audience.
160              
161             Also accepts $in_response_to which it checks against the returned
162             Assertion. This is very important for security as it helps ensure
163             that the assertion that was received was for the request that was made.
164              
165             Checks the audience matches, and that the current time is within the
166             Assertions validity period as specified in its Conditions element.
167              
168             =head1 AUTHOR
169              
170             Chris Andrews <chrisa@cpan.org>
171              
172             =head1 COPYRIGHT AND LICENSE
173              
174             This software is copyright (c) 2021 by Chris Andrews and Others, see the git log.
175              
176             This is free software; you can redistribute it and/or modify it under
177             the same terms as the Perl 5 programming language system itself.
178              
179             =cut