File Coverage

blib/lib/Net/SAML2/SP.pm
Criterion Covered Total %
statement 62 72 86.1
branch 3 6 50.0
condition n/a
subroutine 18 21 85.7
pod 10 10 100.0
total 93 109 85.3


line stmt bran cond sub pod time code
1             package Net::SAML2::SP;
2 13     13   2738169 use Moose;
  13         5839333  
  13         90  
3 13     13   107920 use MooseX::Types::URI qw/ Uri /;
  13         1476285  
  13         110  
4              
5             our $VERSION = '0.43';
6              
7             # ABSTRACT: Net::SAML2::SP - SAML Service Provider object
8              
9              
10 13     13   33996 use Crypt::OpenSSL::X509;
  13         49843  
  13         942  
11 13     13   9069 use XML::Generator;
  13         116380  
  13         71  
12              
13 13     13   7754 use Net::SAML2::Binding::POST;
  13         210079  
  13         743  
14 13     13   8889 use Net::SAML2::Binding::Redirect;
  13         5642  
  13         985  
15 13     13   8659 use Net::SAML2::Binding::SOAP;
  13         5781  
  13         709  
16 13     13   9096 use Net::SAML2::Protocol::AuthnRequest;
  13         5972  
  13         662  
17 13     13   8885 use Net::SAML2::Protocol::LogoutRequest;
  13         6006  
  13         11408  
18              
19              
20             has 'url' => (isa => Uri, is => 'ro', required => 1, coerce => 1);
21             has 'id' => (isa => 'Str', is => 'ro', required => 1);
22             has 'cert' => (isa => 'Str', is => 'ro', required => 1);
23             has 'key' => (isa => 'Str', is => 'ro', required => 1);
24             has 'cacert' => (isa => 'Maybe[Str]', is => 'ro', required => 1);
25              
26             has 'error_url' => (isa => 'Str', is => 'ro', required => 1);
27             has 'slo_url_soap' => (isa => 'Str', is => 'ro', required => 1);
28             has 'slo_url_redirect' => (isa => 'Str', is => 'ro', required => 1);
29             has 'slo_url_post' => (isa => 'Str', is => 'ro', required => 1);
30             has 'acs_url_post' => (isa => 'Str', is => 'ro', required => 1);
31             has 'acs_url_artifact' => (isa => 'Str', is => 'ro', required => 1);
32              
33             has 'org_name' => (isa => 'Str', is => 'ro', required => 1);
34             has 'org_display_name' => (isa => 'Str', is => 'ro', required => 1);
35             has 'org_contact' => (isa => 'Str', is => 'ro', required => 1);
36             has 'org_url' => (isa => 'Str', is => 'ro', required => 0);
37              
38             has '_cert_text' => (isa => 'Str', is => 'rw', required => 0);
39              
40             has 'authnreq_signed' => (isa => 'Bool', is => 'ro', required => 0);
41             has 'want_assertions_signed' => (isa => 'Bool', is => 'ro', required => 0);
42              
43              
44             sub BUILD {
45 4     4 1 14 my ($self) = @_;
46              
47 4         126 my $cert = Crypt::OpenSSL::X509->new_from_file($self->cert);
48 4         179 my $text = $cert->as_string;
49 4         61 $text =~ s/-----[^-]*-----//gm;
50 4         156 $self->_cert_text($text);
51              
52 4         174 return $self;
53             }
54              
55              
56             sub authn_request {
57 1     1 1 4 my ($self, $destination, $nameid_format) = @_;
58              
59 1         9 my $authnreq = Net::SAML2::Protocol::AuthnRequest->new(
60             issueinstant => DateTime->now,
61             issuer => $self->id,
62             destination => $destination,
63             nameid_format => $nameid_format,
64             );
65              
66 1         11 return $authnreq;
67             }
68              
69              
70             sub logout_request {
71 1     1 1 4 my ($self, $destination, $nameid, $nameid_format, $session) = @_;
72              
73 1         26 my $logout_req = Net::SAML2::Protocol::LogoutRequest->new(
74             issuer => $self->id,
75             destination => $destination,
76             nameid => $nameid,
77             nameid_format => $nameid_format,
78             session => $session,
79             );
80              
81 1         3 return $logout_req;
82             }
83              
84              
85             sub logout_response {
86 0     0 1 0 my ($self, $destination, $status, $response_to) = @_;
87              
88 0         0 my $status_uri = Net::SAML2::Protocol::LogoutResponse->status_uri($status);
89 0         0 my $logout_req = Net::SAML2::Protocol::LogoutResponse->new(
90             issuer => $self->id,
91             destination => $destination,
92             status => $status_uri,
93             response_to => $response_to,
94             );
95              
96 0         0 return $logout_req;
97             }
98              
99              
100             sub artifact_request {
101 0     0 1 0 my ($self, $destination, $artifact) = @_;
102              
103 0         0 my $artifact_request = Net::SAML2::Protocol::ArtifactResolve->new(
104             issuer => $self->id,
105             destination => $destination,
106             artifact => $artifact,
107             issueinstant => DateTime->now,
108             );
109              
110 0         0 return $artifact_request;
111             }
112              
113              
114             sub sso_redirect_binding {
115 1     1 1 320 my ($self, $idp, $param) = @_;
116              
117 1         6 my $redirect = Net::SAML2::Binding::Redirect->new(
118             url => $idp->sso_url('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
119             cert => $idp->cert('signing'),
120             key => $self->key,
121             param => $param,
122             );
123              
124 1         5 return $redirect;
125             }
126              
127              
128             sub slo_redirect_binding {
129 0     0 1 0 my ($self, $idp, $param) = @_;
130              
131             my $redirect = Net::SAML2::Binding::Redirect->new(
132             url => $idp->slo_url('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
133             cert => $idp->cert('signing'),
134             key => $self->key,
135             param => $param,
136             sls_force_lcase_url_encoding => $idp->{sls_force_lcase_url_encoding},
137             sls_double_encoded_response => $idp->{sls_double_encoded_response},
138 0         0 );
139 0         0 return $redirect;
140             }
141              
142              
143             sub soap_binding {
144 1     1 1 5231 my ($self, $ua, $idp_url, $idp_cert) = @_;
145              
146 1         39 my $soap = Net::SAML2::Binding::SOAP->new(
147             ua => $ua,
148             key => $self->key,
149             cert => $self->cert,
150             url => $idp_url,
151             idp_cert => $idp_cert,
152             cacert => $self->cacert,
153             );
154              
155 1         4 return $soap;
156             }
157              
158              
159             sub post_binding {
160 1     1 1 7 my ($self) = @_;
161              
162 1         27 my $post = Net::SAML2::Binding::POST->new(
163             cacert => $self->cacert,
164             );
165              
166 1         3 return $post;
167             }
168              
169              
170             sub metadata {
171 1     1 1 8 my ($self) = @_;
172              
173 13     13   149 use Net::SAML2::Util qw/generate_id/;
  13         39  
  13         5276  
174              
175 1         12 my $x = XML::Generator->new(':pretty', conformance => 'loose');
176 1         158 my $md = ['md' => 'urn:oasis:names:tc:SAML:2.0:metadata'];
177 1         6 my $ds = ['ds' => 'http://www.w3.org/2000/09/xmldsig#'];
178              
179 1 50       38 my $metadata = $x->EntityDescriptor(
    50          
    50          
180             $md,
181             {
182             entityID => $self->id },
183             $x->SPSSODescriptor(
184             $md,
185             { AuthnRequestsSigned => defined($self->authnreq_signed) ? $self->authnreq_signed : '1',
186             WantAssertionsSigned => defined($self->want_assertions_signed) ? $self->want_assertions_signed : '1',
187             errorURL => $self->url . $self->error_url,
188             protocolSupportEnumeration => 'urn:oasis:names:tc:SAML:2.0:protocol',
189             ID => generate_id()},
190             $x->KeyDescriptor(
191             $md,
192             {
193             use => 'signing' },
194             $x->KeyInfo(
195             $ds,
196             $x->X509Data(
197             $ds,
198             $x->X509Certificate(
199             $ds,
200             $self->_cert_text,
201             )
202             )
203             )
204             ),
205             $x->SingleLogoutService(
206             $md,
207             { Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP',
208             Location => $self->url . $self->slo_url_soap },
209             ),
210             $x->SingleLogoutService(
211             $md,
212             { Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
213             Location => $self->url . $self->slo_url_redirect },
214             ),
215             $x->SingleLogoutService(
216             $md,
217             { Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
218             Location => $self->url . $self->slo_url_post },
219             ),
220             $x->AssertionConsumerService(
221             $md,
222             { Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
223             Location => $self->url . $self->acs_url_post,
224             index => '1',
225             isDefault => 'true' },
226             ),
227             $x->AssertionConsumerService(
228             $md,
229             { Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
230             Location => $self->url . $self->acs_url_artifact,
231             index => '2',
232             isDefault => 'false' },
233             ),
234             ),
235             $x->Organization(
236             $md,
237             $x->OrganizationName(
238             $md,
239             {
240             'xml:lang' => 'en' },
241             $self->org_name,
242             ),
243             $x->OrganizationDisplayName(
244             $md,
245             {
246             'xml:lang' => 'en' },
247             $self->org_display_name,
248             ),
249             $x->OrganizationURL(
250             $md,
251             {
252             'xml:lang' => 'en' },
253             defined($self->org_url) ? $self->org_url :$self->url
254             )
255             ),
256             $x->ContactPerson(
257             $md,
258             {
259             contactType => 'other' },
260             $x->Company(
261             $md,
262             $self->org_display_name,
263             ),
264             $x->EmailAddress(
265             $md,
266             $self->org_contact,
267             ),
268             )
269             );
270              
271 13     13   133 use Net::SAML2::XML::Sig;
  13         32  
  13         168  
272              
273 1         1001 my $signer = Net::SAML2::XML::Sig->new({
274             key => $self->key,
275             cert => $self->cert,
276             sig_hash => 'sha256',
277             digest_hash => 'sha256',
278             x509 => 1,
279             });
280              
281             # create a signature
282 1         7 my $signed = $signer->sign($metadata);
283              
284 1         137 return $signed;
285             }
286              
287             __PACKAGE__->meta->make_immutable;
288              
289             __END__
290              
291             =pod
292              
293             =encoding UTF-8
294              
295             =head1 NAME
296              
297             Net::SAML2::SP - Net::SAML2::SP - SAML Service Provider object
298              
299             =head1 VERSION
300              
301             version 0.43
302              
303             =head1 SYNOPSIS
304              
305             my $sp = Net::SAML2::SP->new(
306             id => 'http://localhost:3000',
307             url => 'http://localhost:3000',
308             cert => 'sign-nopw-cert.pem',
309             key => 'sign-nopw-key.pem',
310             );
311              
312             =head1 NAME
313              
314             Net::SAML2::SP - SAML Service Provider object
315              
316             =head1 METHODS
317              
318             =head2 new( ... )
319              
320             Constructor. Create an SP object.
321              
322             Arguments:
323              
324             =over
325              
326             =item B<url>
327              
328             base for all SP service URLs
329              
330             =item B<id>
331              
332             SP's identity URI.
333              
334             =item B<cert>
335              
336             path to the signing certificate
337              
338             =item B<key>
339              
340             path to the private key for the signing certificate
341              
342             =item B<cacert>
343              
344             path to the CA certificate for verification
345              
346             =item B<org_name>
347              
348             SP organisation name
349              
350             =item B<org_display_name>
351              
352             SP organisation display name
353              
354             =item B<org_contact>
355              
356             SP contact email address
357              
358             =item B<org_url>
359              
360             SP organization url. This is optional and url will be used as in
361             previous versions if this is not provided.
362              
363             =item B<authnreq_signed>
364              
365             Specifies in the metadata whether the SP signs the AuthnRequest
366             Optional (0 or 1) defaults to 1 (TRUE) if not specified.
367              
368             =item B<want_assertions_signed>
369              
370             Specifies in the metadata whether the SP wants the Assertion from
371             the IdP to be signed
372             Optional (0 or 1) defaults to 1 (TRUE) if not specified.
373              
374             =back
375              
376             =head2 BUILD ( hashref of the parameters passed to the constructor )
377              
378             Called after the object is created to load the cert from a file
379              
380             =head2 authn_request( $destination, $nameid_format )
381              
382             Returns an AuthnRequest object created by this SP, intended for the
383             given destination, which should be the identity URI of the IdP.
384              
385             =head2 logout_request( $destination, $nameid, $nameid_format, $session )
386              
387             Returns a LogoutRequest object created by this SP, intended for the
388             given destination, which should be the identity URI of the IdP.
389              
390             Also requires the nameid (+format) and session to be logged out.
391              
392             =head2 logout_response( $destination, $status, $response_to )
393              
394             Returns a LogoutResponse object created by this SP, intended for the
395             given destination, which should be the identity URI of the IdP.
396              
397             Also requires the status and the ID of the corresponding
398             LogoutRequest.
399              
400             =head2 artifact_request( $destination, $artifact )
401              
402             Returns an ArtifactResolve request object created by this SP, intended
403             for the given destination, which should be the identity URI of the
404             IdP.
405              
406             =head2 sso_redirect_binding( $idp, $param )
407              
408             Returns a Redirect binding object for this SP, configured against the
409             given IDP for Single Sign On. $param specifies the name of the query
410             parameter involved - typically C<SAMLRequest>.
411              
412             =head2 slo_redirect_binding( $idp, $param )
413              
414             Returns a Redirect binding object for this SP, configured against the
415             given IDP for Single Log Out. $param specifies the name of the query
416             parameter involved - typically C<SAMLRequest> or C<SAMLResponse>.
417              
418             =head2 soap_binding( $ua, $idp_url, $idp_cert )
419              
420             Returns a SOAP binding object for this SP, with a destination of the
421             given URL and signing certificate.
422              
423             XXX UA
424              
425             =head2 post_binding( )
426              
427             Returns a POST binding object for this SP.
428              
429             =head2 metadata( )
430              
431             Returns the metadata XML document for this SP.
432              
433             =head1 AUTHOR
434              
435             Chris Andrews <chrisa@cpan.org>
436              
437             =head1 COPYRIGHT AND LICENSE
438              
439             This software is copyright (c) 2021 by Chris Andrews and Others, see the git log.
440              
441             This is free software; you can redistribute it and/or modify it under
442             the same terms as the Perl 5 programming language system itself.
443              
444             =cut