#!/usr/bin/perl
#=============================================================================
# LemonLDAP::NG session conversion tool
#
# This script lets an administrator migrate existing sessions from one backend
# to another. It is mostly useful when run on persistant sessions, but it can be
# useful in some other cases too, such as OIDC Offline sessions
#
# This is part of LemonLDAP::NG product, released under GPL
#=============================================================================

use Lemonldap::NG::Common::Apache::Session;
use Lemonldap::NG::Common::Session;
use Config::IniFiles;
use strict;
use Getopt::Std;
$Getopt::Std::STANDARD_HELP_VERSION = 1;

our $VERSION = "2.0.6";

# Options
# -d: debug mode
# -c: configuration file
# -i: ignore errors
my $opts = {};
getopts( 'dic:', $opts );

my $debug         = $opts->{d};
my $config_file   = $opts->{c};
my $ignore_errors = $opts->{i};
my $nb_converted  = 0;
my $nb_error      = 0;

sub HELP_MESSAGE {
    my $OUT = shift;
    print $OUT <<END_MESSAGE;

	$0 [-di] -c config_file.ini

	-d	Debug mode
	-i	Ignore errors

This script converts sessions in between the two backends specified in the configuration file
The configuration file must contain the following (adjust to your environment):

[sessions_from]
storageModule = Apache::Session::File
storageModuleOptions = {	\\
      'Directory' => '/var/lib/lemonldap-ng/sessions', 	\\
      'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock', \\
}
# Only convert some session types
# sessionKind = Persistent, SSO

[sessions_to]
storageModule = Apache::Session::Browseable::Postgres
storageModuleOptions = {	\\
	'DataSource' => 'DBI:Pg:database=lemonldapdb;host=pg.example.com', \\
	'UserName' => 'lemonldaplogin', \\
	'Password' => 'lemonldappw', \\
	'Commit' => 1, \\
	'Index' => 'ipAddr _whatToTrace user', \\
	'TableName' => 'sessions', \\
}

END_MESSAGE
}

unless ($config_file) {
    HELP_MESSAGE( \*STDERR );
    die "You must provide the -c option";
}

my $inicfg =
  Config::IniFiles->new( -file => $config_file, -allowcontinue => 1 );
my $cfg = {};

die "Could not read configuration file" unless $inicfg;

for my $section (qw/sessions_from sessions_to/) {
    die "Could not find section $section in configuration file $config_file"
      unless $inicfg->SectionExists($section);

    # Load section parameters
    my $r;
    foreach ( $inicfg->Parameters($section) ) {
        $r->{$_} = $inicfg->val( $section, $_ );

        # Remove spaces before and after value (#1488)
        $r->{$_} =~ s/^\s*(.+?)\s*/$1/;
        if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) {
            eval "\$r->{$_} = $r->{$_}";
            if ($@) {
                print $@;
                return $r;
            }
        }
    }
    $cfg->{$section} = $r;
}

my $backendFrom;
my $backendTo;
my @sessionKindOnly;
if ( $cfg->{sessions_from}->{sessionKind} ) {
    @sessionKindOnly = split /\W+/, $cfg->{sessions_from}->{sessionKind};
}

if ( $cfg->{sessions_from}->{storageModule} ) {
    $backendFrom = $cfg->{sessions_from}->{storageModuleOptions};
    $backendFrom->{backend} = $cfg->{sessions_from}->{storageModule};
}
else {
    die
      "[sessions_from] configuration section does not declare a storageModule";
}

if ( $cfg->{sessions_to}->{storageModule} ) {
    $backendTo = $cfg->{sessions_to}->{storageModuleOptions};
    $backendTo->{backend} = $cfg->{sessions_to}->{storageModule};
}
else {
    die "[sessions_to] configuration section does not declare a storageModule";
}

Lemonldap::NG::Common::Apache::Session->get_key_from_all_sessions(
    $backendFrom,
    sub {
        my $entry = shift;
        my $id    = shift;

        print "Processing session $id\n" if $debug;
        my $s = Lemonldap::NG::Common::Session->new( {
                storageModule        => $backendTo->{backend},
                storageModuleOptions => $backendTo,
                id                   => $id,
                info                 => $entry,
                force                => 1,
            }
        );

        # If filtering sessionKind
        if (@sessionKindOnly) {

            unless ( grep { $_ eq $entry->{_session_kind} } @sessionKindOnly ) {
                return undef;
            }
        }

        if ( $s->error ) {
            die "Error encountered on session $id" unless $ignore_errors;
            $nb_error += 1;
            print "Error converting session $id : " . $s->error . "\n";
        }
        else {
            print "Session $id successfully converted\n" if $debug;
            $nb_converted += 1;
        }
    }
);

print "$nb_converted sessions have been converted\n";

print "$nb_error errors encountered during conversion\n" if $nb_error;

my $exit = $nb_error ? 1 : 0;
exit $exit;

__END__

=head1 NAME

=encoding utf8

convertSessions - A tool to convert Lemonldap::NG sessions between storage backends.

=head1 SYNOPSIS

  convertSession [-di] -c parameters.ini


=head1 DESCRIPTION

convertConfig is a command line tool to migrate all sessions stored
in a source backend (sessions_from), into a new backend (sessions_to).

It requires a special configuration file in which you must list the source
and destination backend modules and parameters.

Sessions will not be deleted from the source backend. Existing sessions in the
destination backend will be kept, unless they have the same session ID as a
session in the source backend. In that case, the source will overwrite the
destination.


=head1 CONFIGURATION FILE FORMAT

The configuration file needs two sections to describe the source and destination backends

Here is an example

    [sessions_from]
    storageModule = Apache::Session::File
    storageModuleOptions = {	\
          'Directory' => '/var/lib/lemonldap-ng/sessions', 	\
          'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock', \
    }
    # Only migrate some session types
    # sessionKind = Persistent, SSO
    
    [sessions_to]
    storageModule = Apache::Session::Browseable::Postgres
    storageModuleOptions = {	\
    	'DataSource' => 'DBI:Pg:database=lemonldapdb;host=pg.example.com', \
    	'UserName' => 'lemonldaplogin', \
    	'Password' => 'lemonldappw', \
    	'Commit' => 1, \
    	'Index' => 'ipAddr _whatToTrace user', \
    	'TableName' => 'sessions', \
    }


The C<sessionKind> parameter may be used to filter only some session types.

Thanks to this, you can use this script to migrate from one database holding 
all your sessions to separate tables from each session type.


=head1 SEE ALSO

L<http://lemonldap-ng.org/>

=head1 AUTHORS

=over

=item Maxime Besson, E<lt>maxime.besson@worteks.comE<gt>

=back

=head1 BUG REPORT

Use OW2 system to report bug or ask for features:
L<https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues>

=head1 DOWNLOAD

Lemonldap::NG is available at
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>

