The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

#!/usr/bin/perl
use strict;
use Getopt::Long qw/GetOptionsFromArray/;
# Get configuration from CLI opts
sub get_config {
my ($array) = @_;
my %opts;
# Get command line options
my $result =
GetOptionsFromArray( $array, \%opts, 'output|o=s', 'metadata|m=s',
'verbose|v', 'dry-run|n', 'help|h', );
pod2usage(1) if $opts{help};
_check_arguments( \%opts );
return \%opts;
}
our $verbose = 0;
sub printlog {
if ($verbose) {
print STDERR @_, "\n";
}
}
sub printfatal {
print STDERR @_, "\n";
}
sub _check_arguments {
my ($opts) = @_;
my @missing;
push @missing, "metadata URL (-m)" if !$opts->{metadata};
push @missing, "output path (-o)" if !$opts->{output};
pod2usage(
-message => ( "Missing " . join( ' and ', @missing ) ),
-exitval => 2
) if @missing;
}
sub _check_xml {
my ($tmpfile) = @_;
# Check XML syntax
my $reader = XML::LibXML::Reader->new( location => $tmpfile );
if ( !$reader ) {
unlink($tmpfile);
printfatal "Cannot read $tmpfile: $!";
return 0;
}
my $valid = 0;
eval {
while ( $reader->read ) {
if ( $reader->depth == 0
and $reader->nodeType == XML_READER_TYPE_ELEMENT )
{
if ( $reader->localName eq "EntitiesDescriptor"
and $reader->namespaceURI eq
"urn:oasis:names:tc:SAML:2.0:metadata" )
{
$valid = 1;
}
}
}
};
if ( my $error = $@ ) {
unlink($tmpfile);
printfatal "Cannot parse $tmpfile as XML: $@";
return 0;
}
if ( $valid == 1 ) {
return 1;
}
else {
unlink($tmpfile);
printfatal "XML does not contain an EntitiesDescriptor";
return 0;
}
}
sub downloadSamlMetadata {
my ($opts) = @_;
# Get merged config
$verbose = $opts->{verbose};
# Download metadata file
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->show_progress(1) if $verbose;
$ua->env_proxy;
my $metadata_file = $opts->{metadata};
my $output = $opts->{output};
printlog("Try to download metadata file at $metadata_file");
my $tmpfile = mktemp("$output.XXXXXXXX");
printlog("Using temporary file $tmpfile");
my $response = $ua->get( $metadata_file, ':content_file' => $tmpfile );
if ( $response->is_success ) {
printlog("Metadata successfully downloaded");
}
else {
printlog("Removing $tmpfile");
unlink($tmpfile);
printfatal "Error downloading metadata ", $response->status_line;
return 1;
}
return 1 unless _check_xml($tmpfile);
if ( $opts->{'dry-run'} ) {
printlog("Removing $tmpfile");
unlink($tmpfile);
}
else {
# Move file
if ( !rename( $tmpfile, $output ) ) {
unlink($tmpfile);
printfatal "Could not atomically replace $output";
return 1;
}
}
return 0;
}
sub main {
my $config = get_config( \@ARGV );
exit( downloadSamlMetadata($config) );
}
# If run as a script
unless (caller) {
main();
}
__END__
Script to download SAML federation metadata\n\n";
Usage: $0 -m <metadata file URL> -o <output file>\n\n";
Options:\n";
=encoding UTF-8
=head1 NAME
downloadSamlMetadata - Script to download SAML federation metadata
=head1 SYNOPSIS
downloadSamlMetadata -m <metadata URL> -o <output file> [options]
Options:
-m, --metadata URL of metadata document
-o, --output Destination file
-n, --dry-run do not replace the existing file
-v, --verbose increase verbosity of output
-h, --help print full documentation
=head1 OPTIONS
=over
=item B<-m I<URL>>, B<--metadata=I<URL>>
Specifies the <URL> of the metadata document to import
=item B<-o I<FILE>>, B<--output=I<FILE>>
Specifies the <FILE> to which the metadata will be downloaded. If the
destination file already exists, it will be atomically replaced.
=item B<-n>, B<--dry-run>
This option prevents the destination file from being replaced. It can be used for testing.
=item B<-v>, B<--verbose>
Increase verbosity during script execution
=item B<-h>, B<--help>
Displays the script's documentation
=back
=head1 SEE ALSO
=head1 AUTHORS
=over
=item Clement Oudot, E<lt>clement@oodo.netE<gt>
=back
=head1 BUG REPORT
Use OW2 system to report bug or ask for features:
=head1 DOWNLOAD
Lemonldap::NG is available at