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   5956880 use Moose;
  25         10717960  
  25         183  
3              
4             our $VERSION = '0.73'; # VERSION
5 25     25   212845 use MooseX::Types::URI qw/ Uri /;
  25         2684295  
  25         162  
6 25     25   64487 use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
  25         2467772  
  25         214  
7 25     25   97539 use XML::Generator;
  25         211506  
  25         126  
8 25     25   1644 use List::Util qw(any);
  25         60  
  25         1887  
9 25     25   12722 use URN::OASIS::SAML2 qw(:urn BINDING_HTTP_POST);
  25         61852  
  25         35381  
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 3835 my ($self) = @_;
121              
122 12         88 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         63 my %issuer_attrs = ();
131              
132 12         37 my %protocol_bindings
133             = ('HTTP-POST' => BINDING_HTTP_POST);
134              
135 12         163 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         176 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         86 foreach my $opt (@opts) {
155 96         238 my $predicate = 'has_' . $opt;
156 96 100 66     4085 next if !$self->can($predicate) || !$self->$predicate;
157              
158 23         749 my $val = $self->$opt;
159 23 100       177 if ($opt eq 'protocol_binding') {
    100          
160 1         50 $req_atts{ $att_map{$opt} } = $protocol_bindings{$val};
161             }
162 41     41   93 elsif (any { $opt eq $_ } qw(force_authn is_passive)) {
163 6 100       29 $req_atts{ $att_map{$opt} } = ($val ? 'true' : 'false');
164             }
165             else {
166 16         97 $req_atts{ $att_map{$opt} } = $val;
167             }
168             }
169              
170 12         46 foreach my $opt (qw(issuer_namequalifier issuer_format)) {
171 24         63 my $predicate = 'has_' . $opt;
172 24 100 66     962 next if !$self->can($predicate) || !$self->$predicate;
173              
174 2         64 my $val = $self->$opt;
175 2         7 $issuer_attrs{ $att_map{$opt} } = $val;
176             }
177              
178 12         396 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   561 my $self = shift;
191 12 100       483 return unless $self->has_identity_providers;
192 1         11 my $x = shift;
193              
194 3         273 my @providers = map { $x->IDPEntry($samlp, { ProviderID => $_ }) }
195 1         2 @{ $self->identity_providers };
  1         32  
196 1         142 return $x->Scoping($samlp, $x->IDPList($samlp, @providers));
197             }
198              
199             sub _set_name_id {
200 12     12   2768 my $self = shift;
201 12 100       496 return unless $self->has_nameid;
202 1         5 my $x = shift;
203 1         33 return $x->Subject($saml, $x->NameID($saml, {NameQualifier => $self->nameid}));
204             }
205              
206             sub _set_name_policy_format {
207 12     12   369 my $self = shift;
208 12 100       489 return unless $self->has_nameidpolicy_format;
209 4         15 my $x = shift;
210 4 100       157 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   755 my ($self, $x) = @_;
224              
225             return
226 12         405 if !@{ $self->AuthnContextClassRef }
227 12 100 100     25 && !@{ $self->AuthnContextDeclRef };
  11         375  
228              
229 2         131 my @class = map { $x->AuthnContextClassRef($saml, undef, $_) }
230 2         26 @{ $self->AuthnContextClassRef };
  2         63  
231              
232 2         127 my @decl = map { $x->AuthnContextDeclRef($saml, undef, $_) }
233 2         117 @{ $self->AuthnContextDeclRef };
  2         69  
234              
235 2         180 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.73
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