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 26     26   18242883 use Moose;
  26         10636136  
  26         188  
3              
4             our $VERSION = '0.74'; # VERSION
5 26     26   212723 use MooseX::Types::URI qw/ Uri /;
  26         2825582  
  26         217  
6 26     26   64744 use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
  26         2016111  
  26         180  
7 26     26   95608 use XML::Generator;
  26         229442  
  26         187  
8 26     26   1649 use List::Util qw(any);
  26         68  
  26         1902  
9 26     26   12355 use URN::OASIS::SAML2 qw(:urn BINDING_HTTP_POST);
  26         62430  
  26         35512  
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 3846 my ($self) = @_;
121              
122 12         96 my $x = XML::Generator->new(':std');
123              
124 12         1853 my %req_atts = (
125             ID => $self->id,
126             IssueInstant => $self->issue_instant,
127             Version => '2.0',
128             );
129              
130 12         61 my %issuer_attrs = ();
131              
132 12         36 my %protocol_bindings
133             = ('HTTP-POST' => BINDING_HTTP_POST);
134              
135 12         100 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         40 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         35 foreach my $opt (@opts) {
155 96         222 my $predicate = 'has_' . $opt;
156 96 100 66     3904 next if !$self->can($predicate) || !$self->$predicate;
157              
158 23         779 my $val = $self->$opt;
159 23 100       144 if ($opt eq 'protocol_binding') {
    100          
160 1         58 $req_atts{ $att_map{$opt} } = $protocol_bindings{$val};
161             }
162 41     41   99 elsif (any { $opt eq $_ } qw(force_authn is_passive)) {
163 6 100       30 $req_atts{ $att_map{$opt} } = ($val ? 'true' : 'false');
164             }
165             else {
166 16         96 $req_atts{ $att_map{$opt} } = $val;
167             }
168             }
169              
170 12         35 foreach my $opt (qw(issuer_namequalifier issuer_format)) {
171 24         63 my $predicate = 'has_' . $opt;
172 24 100 66     973 next if !$self->can($predicate) || !$self->$predicate;
173              
174 2         77 my $val = $self->$opt;
175 2         9 $issuer_attrs{ $att_map{$opt} } = $val;
176             }
177              
178 12         414 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   468 my $self = shift;
191 12 100       461 return unless $self->has_identity_providers;
192 1         4 my $x = shift;
193              
194 3         273 my @providers = map { $x->IDPEntry($samlp, { ProviderID => $_ }) }
195 1         3 @{ $self->identity_providers };
  1         34  
196 1         126 return $x->Scoping($samlp, $x->IDPList($samlp, @providers));
197             }
198              
199             sub _set_name_id {
200 12     12   2756 my $self = shift;
201 12 100       493 return unless $self->has_nameid;
202 1         4 my $x = shift;
203 1         36 return $x->Subject($saml, $x->NameID($saml, {NameQualifier => $self->nameid}));
204             }
205              
206             sub _set_name_policy_format {
207 12     12   344 my $self = shift;
208 12 100       458 return unless $self->has_nameidpolicy_format;
209 4         12 my $x = shift;
210 4 100       136 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   760 my ($self, $x) = @_;
224              
225             return
226 12         414 if !@{ $self->AuthnContextClassRef }
227 12 100 100     33 && !@{ $self->AuthnContextDeclRef };
  11         369  
228              
229 2         129 my @class = map { $x->AuthnContextClassRef($saml, undef, $_) }
230 2         7 @{ $self->AuthnContextClassRef };
  2         61  
231              
232 2         129 my @decl = map { $x->AuthnContextDeclRef($saml, undef, $_) }
233 2         115 @{ $self->AuthnContextDeclRef };
  2         68  
234              
235 2         179 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.74
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