File Coverage

blib/lib/Net/SAML2/Protocol/AuthnRequest.pm
Criterion Covered Total %
statement 66 66 100.0
branch 20 20 100.0
condition 7 9 77.7
subroutine 12 12 100.0
pod 1 1 100.0
total 106 108 98.1


line stmt bran cond sub pod time code
1             package Net::SAML2::Protocol::AuthnRequest;
2 25     25   5091691 use Moose;
  25         9251165  
  25         160  
3              
4             our $VERSION = '0.72'; # TRIAL VERSION
5 25     25   186501 use MooseX::Types::URI qw/ Uri /;
  25         2307464  
  25         145  
6 25     25   54978 use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
  25         2109056  
  25         180  
7 25     25   83947 use XML::Generator;
  25         177764  
  25         116  
8 25     25   1516 use List::Util qw(any);
  25         56  
  25         1734  
9 25     25   11147 use URN::OASIS::SAML2 qw(:urn BINDING_HTTP_POST);
  25         53633  
  25         30153  
10              
11             with 'Net::SAML2::Role::ProtocolMessage';
12              
13             # ABSTRACT: SAML2 AuthnRequest object
14              
15              
16              
17             has 'nameid' => (
18             isa => NonEmptySimpleStr,
19             is => 'rw',
20             predicate => 'has_nameid'
21             );
22              
23             has 'nameidpolicy_format' => (
24             isa => 'Str',
25             is => 'rw',
26             predicate => 'has_nameidpolicy_format'
27             );
28              
29             has 'nameid_allow_create' => (
30             isa => 'Bool',
31             is => 'rw',
32             required => 0,
33             predicate => 'has_nameid_allow_create'
34             );
35              
36             has 'assertion_url' => (
37             isa => Uri,
38             is => 'rw',
39             coerce => 1,
40             predicate => 'has_assertion_url',
41             );
42              
43             has 'assertion_index' => (
44             isa => 'Int',
45             is => 'rw',
46             predicate => 'has_assertion_index',
47             );
48              
49             has 'attribute_index' => (
50             isa => 'Int',
51             is => 'rw',
52             predicate => 'has_attribute_index',
53             );
54              
55             has 'protocol_binding' => (
56             isa => Uri,
57             is => 'rw',
58             coerce => 1,
59             predicate => 'has_protocol_binding',
60             );
61             has 'provider_name' => (
62             isa => 'Str',
63             is => 'rw',
64             predicate => 'has_provider_name',
65             );
66              
67             has 'AuthnContextClassRef' => (
68             isa => 'ArrayRef[Str]',
69             is => 'rw',
70             default => sub { [] }
71             );
72              
73             has 'AuthnContextDeclRef' => (
74             isa => 'ArrayRef[Str]',
75             is => 'rw',
76             default => sub { [] }
77             );
78              
79             has 'RequestedAuthnContext_Comparison' => (
80             isa => 'Str',
81             is => 'rw',
82             default => 'exact'
83             );
84              
85             has 'force_authn' => (
86             isa => 'Bool',
87             is => 'ro',
88             predicate => 'has_force_authn',
89             );
90              
91             has 'is_passive' => (
92             isa => 'Bool',
93             is => 'ro',
94             predicate => 'has_is_passive',
95             );
96              
97             has identity_providers => (
98             isa => 'ArrayRef[Str]',
99             is => 'ro',
100             predicate => 'has_identity_providers',
101             );
102              
103             around BUILDARGS => sub {
104             my $orig = shift;
105             my $self = shift;
106              
107             my %params = @_;
108             if ($params{nameid_format} && !defined $params{nameidpolicy_format}) {
109             $params{nameidpolicy_format} = $params{nameid_format};
110             }
111              
112             return $self->$orig(%params);
113             };
114              
115              
116             my $samlp = ['samlp' => URN_PROTOCOL];
117             my $saml = ['saml' => URN_ASSERTION];
118              
119             sub as_xml {
120 12     12 1 5195 my ($self) = @_;
121              
122 12         116 my $x = XML::Generator->new(':std');
123              
124 12         1830 my %req_atts = (
125             ID => $self->id,
126             IssueInstant => $self->issue_instant,
127             Version => '2.0',
128             );
129              
130 12         70 my %issuer_attrs = ();
131              
132 12         53 my %protocol_bindings
133             = ('HTTP-POST' => BINDING_HTTP_POST);
134              
135 12         109 my %att_map = (
136             'assertion_url' => 'AssertionConsumerServiceURL',
137             'assertion_index' => 'AssertionConsumerServiceIndex',
138             'attribute_index' => 'AttributeConsumingServiceIndex',
139             'protocol_binding' => 'ProtocolBinding',
140             'provider_name' => 'ProviderName',
141             'destination' => 'Destination',
142             'issuer_namequalifier' => 'NameQualifier',
143             'issuer_format' => 'Format',
144             'force_authn' => 'ForceAuthn',
145             'is_passive' => 'IsPassive',
146             );
147              
148 12         75 my @opts = qw(
149             assertion_url assertion_index protocol_binding
150             attribute_index provider_name destination
151             force_authn is_passive
152             );
153              
154 12         64 foreach my $opt (@opts) {
155 96         210 my $predicate = 'has_' . $opt;
156 96 100 66     3817 next if !$self->can($predicate) || !$self->$predicate;
157              
158 23         729 my $val = $self->$opt;
159 23 100       166 if ($opt eq 'protocol_binding') {
    100          
160 1         50 $req_atts{ $att_map{$opt} } = $protocol_bindings{$val};
161             }
162 41     41   95 elsif (any { $opt eq $_ } qw(force_authn is_passive)) {
163 6 100       33 $req_atts{ $att_map{$opt} } = ($val ? 'true' : 'false');
164             }
165             else {
166 16         93 $req_atts{ $att_map{$opt} } = $val;
167             }
168             }
169              
170 12         36 foreach my $opt (qw(issuer_namequalifier issuer_format)) {
171 24         55 my $predicate = 'has_' . $opt;
172 24 100 66     958 next if !$self->can($predicate) || !$self->$predicate;
173              
174 2         79 my $val = $self->$opt;
175 2         9 $issuer_attrs{ $att_map{$opt} } = $val;
176             }
177              
178 12         405 return $x->AuthnRequest($samlp,
179             \%req_atts,
180             $x->Issuer($saml, \%issuer_attrs, $self->issuer),
181             $self->_set_name_id($x),
182             $self->_set_name_policy_format($x),
183             $self->_set_requested_authn_context($x),
184             $self->_set_scoping($x),
185             );
186              
187             }
188              
189             sub _set_scoping {
190 12     12   496 my $self = shift;
191 12 100       448 return unless $self->has_identity_providers;
192 1         3 my $x = shift;
193              
194 3         279 my @providers = map { $x->IDPEntry($samlp, { ProviderID => $_ }) }
195 1         4 @{ $self->identity_providers };
  1         33  
196 1         127 return $x->Scoping($samlp, $x->IDPList($samlp, @providers));
197             }
198              
199             sub _set_name_id {
200 12     12   2663 my $self = shift;
201 12 100       467 return unless $self->has_nameid;
202 1         3 my $x = shift;
203 1         35 return $x->Subject($saml, $x->NameID($saml, {NameQualifier => $self->nameid}));
204             }
205              
206             sub _set_name_policy_format {
207 12     12   378 my $self = shift;
208 12 100       467 return unless $self->has_nameidpolicy_format;
209 4         21 my $x = shift;
210 4 100       149 return $x->NameIDPolicy(
211             $samlp,
212             {
213             Format => $self->nameidpolicy_format,
214             $self->has_nameid_allow_create
215             ? (AllowCreate => $self->nameid_allow_create)
216             : (),
217             }
218             );
219              
220             }
221              
222             sub _set_requested_authn_context {
223 12     12   631 my ($self, $x) = @_;
224              
225             return
226 12         425 if !@{ $self->AuthnContextClassRef }
227 12 100 100     24 && !@{ $self->AuthnContextDeclRef };
  11         359  
228              
229 2         129 my @class = map { $x->AuthnContextClassRef($saml, undef, $_) }
230 2         5 @{ $self->AuthnContextClassRef };
  2         64  
231              
232 2         147 my @decl = map { $x->AuthnContextDeclRef($saml, undef, $_) }
233 2         109 @{ $self->AuthnContextDeclRef };
  2         67  
234              
235 2         184 return $x->RequestedAuthnContext(
236             $samlp,
237             { Comparison => $self->RequestedAuthnContext_Comparison },
238             @class, @decl
239             );
240             }
241              
242             __PACKAGE__->meta->make_immutable;
243              
244             __END__
245              
246             =pod
247              
248             =encoding UTF-8
249              
250             =head1 NAME
251              
252             Net::SAML2::Protocol::AuthnRequest - SAML2 AuthnRequest object
253              
254             =head1 VERSION
255              
256             version 0.72
257              
258             =head1 SYNOPSIS
259              
260             my $authnreq = Net::SAML2::Protocol::AuthnRequest->new(
261             id => 'NETSAML2_Crypt::OpenSSL::Random::random_pseudo_bytes(16),
262             issuer => $self->{id}, # Service Provider (SP) Entity ID
263             destination => $destination, # Identity Provider (IdP) SSO URL
264             provider_name => $provider_name, # Service Provider (SP) Human Readable Name
265             issue_instant => DateTime->now, # Defaults to Current Time
266             force_authn => $force_authn, # Force new authentication (Default: false)
267             is_passive => $is_passive, # IdP should not take control of UI (Default: false)
268             );
269              
270             my $request_id = $authnreq->id; # Store and Compare to InResponseTo
271              
272             or
273              
274             my $request_id = 'NETSAML2_' . unpack 'H*', Crypt::OpenSSL::Random::random_pseudo_bytes(16);
275              
276             my $authnreq = Net::SAML2::Protocol::AuthnRequest->as_xml(
277             id => $request_id, # Unique Request ID will be returned in response
278             issuer => $self->{id}, # Service Provider (SP) Entity ID
279             destination => $destination, # Identity Provider (IdP) SSO URL
280             provider_name => $provider_name, # Service Provider (SP) Human Readable Name
281             issue_instant => DateTime->now, # Defaults to Current Time
282             force_authn => $force_authn, # Force new authentication (Default: false)
283             is_passive => $is_passive, # IdP should not take control of UI (Default: false)
284             );
285              
286             =head1 NAME
287              
288             Net::SAML2::Protocol::AuthnRequest - SAML2 AuthnRequest object
289              
290             =head1 METHODS
291              
292             =head2 new( ... )
293              
294             Constructor. Creates an instance of the AuthnRequest object.
295              
296             Important Note: Best practice is to always do this first. While it is possible
297             to call C<as_xml()> first you do not have to set the id as it will be set for you
298             automatically.
299              
300             However tracking the id is important for security to ensure that the response
301             has the same id in the InResponseTo attribute.
302              
303             Arguments:
304              
305             =over
306              
307             =item nameidpolicy_format
308              
309             Format attribute for NameIDPolicy
310              
311             =item AuthnContextClassRef, <AuthnContextDeclRef
312              
313             Each one is an arrayref containing values for AuthnContextClassRef and
314             AuthnContextDeclRef. If any is populated, the RequestedAuthnContext will be
315             included in the request.
316              
317             =item RequestedAuthnContext_Comparison
318              
319             Value for the I<Comparison> attribute in case I<RequestedAuthnContext> is
320             included (see above). Default value is I<exact>.
321              
322             =item identity_providers
323              
324             An arrayref of Identity providers, if used the Scoping element is added to the
325             XML
326              
327             =back
328              
329             =head2 as_xml( )
330              
331             Returns the AuthnRequest as XML.
332              
333             =head1 AUTHORS
334              
335             =over 4
336              
337             =item *
338              
339             Chris Andrews <chrisa@cpan.org>
340              
341             =item *
342              
343             Timothy Legge <timlegge@gmail.com>
344              
345             =back
346              
347             =head1 COPYRIGHT AND LICENSE
348              
349             This software is copyright (c) 2023 by Venda Ltd, see the CONTRIBUTORS file for others.
350              
351             This is free software; you can redistribute it and/or modify it under
352             the same terms as the Perl 5 programming language system itself.
353              
354             =cut