#!/usr/bin/perl

use warnings;
use strict;
use POSIX;

use Lemonldap::NG::Common::CliSessions;
use Lemonldap::NG::Common::Session 'id2storage';

use strict;
use Getopt::Long;
use Pod::Usage;

our $VERSION = "2.0.12";

# Options
my $opts = {};
my $help;
my $opt_user  = '__APACHEUSER__';
my $opt_group = '__APACHEGROUP__';
my $out;

GetOptions(
    'help|h'         => \$help,
    'select|s=s@'    => \$opts->{select},
    'where|w=s'      => \$opts->{where},
    'all|a'          => \$opts->{all},
    'backend|b=s'    => \$opts->{backend},
    'persistent|p'   => \$opts->{persistent},
    'refresh-tokens' => \$opts->{refreshTokens},
    'hash|cookie|c'  => \$opts->{hash},
    'id-only|i'      => \$opts->{idonly},
    'user|u=s'       => \$opt_user,
    'group|g=s'      => \$opt_group,
    'count'          => \$opts->{count},
    'out|o=s'        => \$out,
) or pod2usage( -exitcode => 1, -verbose => 0 );

pod2usage( -exitcode => 0, -verbose => 2 ) if $help;

eval {
    no warnings;
    POSIX::setgid( scalar( getgrnam($opt_group) ) );
    POSIX::setuid( scalar( getpwnam($opt_user) ) );
    my (undef, undef, undef, undef, undef, undef, undef, $homedir, undef) = getpwnam($opt_user);
    $ENV{HOME} = $homedir if $homedir;
};

my $action = shift @ARGV;

unless ($action) {
    pod2usage( -exitcode => 1, -verbose => 0 );
}

if ( $action eq 'count' ) {
    $action = 'search';
    $opts->{count} = 1;
}

if ( $action eq "get" ) {
    unless ( @ARGV >= 1 ) {
        pod2usage(
            -exitval  => 1,
            -verbose  => 99,
            -sections => "COMMANDS/Get"
        );
    }
}
elsif ( $action eq "delete" ) {
    unless ( @ARGV >= 1 or $opts->{where} ) {
        pod2usage(
            -exitval  => 1,
            -verbose  => 99,
            -sections => "COMMANDS/Delete"
        );
    }
}
elsif ( $action eq "delKey" ) {
    unless ( @ARGV >= 2 ) {
        pod2usage(
            -exitval  => 1,
            -verbose  => 99,
            -sections => "COMMANDS/Delete Key"
        );
    }
}
elsif ( $action eq "setKey" ) {
    unless ( @ARGV >= 3 ) {
        pod2usage(
            -exitval  => 1,
            -verbose  => 99,
            -sections => "COMMANDS/Set Key"
        );
    }
}

elsif ( $action eq "secondfactors" ) {
    unless ( @ARGV >= 1 ) {
        pod2usage(
            -exitval  => 1,
            -verbose  => 99,
            -sections => "COMMANDS/Second Factors"
        );
    }
}

elsif ( $action eq "consents" ) {
    unless ( @ARGV >= 2 ) {
        pod2usage(
            -exitval  => 1,
            -verbose  => 99,
            -sections => "COMMANDS/Consents"
        );
    }
}

elsif ( $action eq "history" ) {
    unless ( @ARGV >= 2 ) {
        pod2usage(
            -exitval  => 1,
            -verbose  => 99,
            -sections => "COMMANDS/History"
        );
    }
}

my $client;
if ( $out and $out ne '-' ) {
    open F, '>', $out or die "Unable to open file given by --out: $!";
    $client = Lemonldap::NG::Common::CliSessions->new( { stdout => *F } );
}
else {
    $client = Lemonldap::NG::Common::CliSessions->new;
}

exit $client->run( $action, $opts, @ARGV );

__END__

=encoding UTF-8

=head1 NAME

lemonldap-ng-sessions - Scripting CLI for LemonLDAP::NG sessions

=head1 SYNOPSIS

  lemonldap-ng-sessions [<options>] <command> [<arguments> ...]

=head1 DESCRIPTION

B<Commands>:

=over

=item B<L</get>>: get one or several session from known IDs

=item B<L</search>>: search for sessions

=item B<count>: alias for B<search --count>

=item B<L</delete>>: delete existing sessions

=item B<L</setKey>>: add/change key in existing session

=item B<L</delKey>>: delete key from existing session

=item B<L</secondfactors>>: manage second factors

=item B<L</consents>>: manage OIDC user consents

=item B<L</backup>>: dump all sessions

=item B<L</restore>>: restore sessions from a dump file

=item B<L</history>>: manage login history

=back

B<Options>:

=over

=item B<--help>: Show full help

=item B<--select>: Select which fields to print

=item B<--backend>: Specify session backend

=item B<--persistent>: Search in persistent sessions

=item B<--refresh-tokens>: Search into refresh_tokens only

=item B<--where>: Set search filter (search/delete only)

=item B<--id-only>: Only return IDs (search only)

=item B<--user>: Change user running the script

=item B<--group>: Change group running the script

=item B<--hash>: When "hashed session storage" is in use, indicates that the
given session ID is the original value (cookie value)

=item B<--count> I<(only with B<L</search>>)>: display the number of matching
sessions instead of their content

=item B<--out>: write result into given file

=back

=head2 COMMANDS

=head3 get

    lemonldap-ng-sessions get <id> [<id> ...]

This command lets you read the content of a session.

You must pass one or several session IDs as parameters.


Examples

	lemonldap-ng-sessions get 9684dd2a6489bf2be2fbdd799a8028e3

	lemonldap-ng-sessions get --persistent dwho

=head3 search

    lemonldap-ng-sessions search [<options>]

This command lets you search for sessions.

It can be used to find the session IDs that other commands need.

You can restrict the search with options. See L</OPTIONS>

Examples

	lemonldap-ng-sessions search

	lemonldap-ng-sessions search --backend persistent

	lemonldap-ng-sessions search --where uid=dwho

	lemonldap-ng-sessions search --where uid=dwho \
		--id-only

	lemonldap-ng-sessions search --backend persistent \
		--where _session_uid=dwho

	lemonldap-ng-sessions search --where uid=dwho \
		--select authenticationLevel

    lemonldap-ng-sessions search --where '_startTime>20240410063538'
    lemonldap-ng-sessions search --where '_startTime<20240410063538'

And you can just display the number of result using
B<lemonldap-ng-sessions search --count>

=head3 delete

    lemonldap-ng-sessions delete <id> [<id> ...]
    lemonldap-ng-sessions delete --where <filter>

This command lets you delete sessions.

You may give it one or several session IDs to remove.

Examples:
	
	lemonldap-ng-sessions delete 9684dd2a6489bf2be2fbdd799a8028e3

	lemonldap-ng-sessions delete --persistent dwho

Or you can give it a search expression.

Examples:

	lemonldap-ng-sessions delete --where uid=dwho

	lemonldap-ng-sessions delete --persistent --where _session_uid=dwho

=head3 setKey

    lemonldap-ng-sessions setKey <id> <key> <value> [<key> <value> ...]

This command allows you to modify one or several keys from an existing session.

Examples:

	lemonldap-ng-sessions setKey 9684dd2a6489bf2be2fbdd799a8028e3 \
		authenticationLevel 1


=head3 delKey

    lemonldap-ng-sessions delKey <id> <key> [<key> ...]

This command lets you remove a key from an existing session.

You must specify a session ID, and one of several session keys
to remove.

Examples:
	
	lemonldap-ng-sessions delKey --persistent dwho _oidcConsents
		

=head3 secondfactors

    lemonldap-ng-sessions secondfactors <command> <user> [<id> ... ]

B<Commands>:

=over

=item B<get> <user>

show all second factors for a user

=item B<delete> <user> <id> [<id> ...]

delete second factors for a user. The ID must match one of the
IDs returned by the "show" command.

=item B<delType> [<user>|--all] <type> [<type> ...]

delete all second factors of a given type for a user

=item B<migrateu2f> [<user>|--all]

migrate U2F device registrations to WebAuthn device registrations

=back

=head3 consents

    lemonldap-ng-sessions consents <command> <user> [<id> ... ]

Commands:
	
    get <user>
        show all OIDC consents for a user
    delete <user> <id> [<id> ...]
        delete OIDC consents for a user

=head3 backup

    lemonldap-ng-sessions backup > file
    # or
    lemonldap-ng-sessions backup --out file
    # or limited to persistent sessions
    lemonldap-ng-sessions backup --persistent --out file
    # or limited to OIDC refresh_tokens
    lemonldap-ng-sessions backup --refresh-tokens --out file

Simple command to dump all sessions or only persistent or refresh_tokens.

=head3 restore

    lemonldap-ng-sessions restore < file
    # or
    lemonldap-ng-sessions restore file

Command to restore sessions using a file generated by L</backup> command

=head3 history

    lemonldap-ng-sessions history <command> <user>

B<Commands>:

=over

=item B<lastsuccessdate> <user>

display last successful login time for a user

=item B<lastfailuredate> <user>

display last failed login time for a user,
along with the error message if available

=item B<get> <user>

display full login history for a user in JSON format,
including both successful and failed logins with timestamps and IP addresses

=back

All history commands require the History plugin to be enabled.

Examples:

	lemonldap-ng-sessions history lastsuccessdate dwho
	lemonldap-ng-sessions history lastfailuredate dwho
	lemonldap-ng-sessions history get dwho

Output example for lastsuccessdate:

	Thu Mar 12 10:30:45 2026

Output example for lastfailuredate:

	Thu Mar 12 09:15:22 2026 (5)

=head2 OPTIONS

=over

=item B<--select>,B<-s>

Lets you select which fields to output in the JSON result.

This option can be set multiple times


=item B<--where>,B<-w>

This option lets you filter your session search according to a filter.

For now, only one filter can be set.

Examples:

	--search uid=dwho
	--search _sessionType=OIDC
	--search '_startTime>20240410063538'
	--search '_startTime<20240410063538'

=item B<--backend>,B<-b>

This option lets you specify which session backend to use.

You only need it when you configured multiple session backends in your
LemonLDAP::NG installation (for Persistent, SAML, CAS or OIDC sessions)

Examples:

	--backend persistent
	--backend saml
	--backend oidc
	--backend cas


=item B<--persistent>,B<-p>

This option is a shortcut for specifying --backend persistent and using
the UID hash as a session ID

Example:

	lemonldap-ng-sessions --backend persistent \
		get 5efe8af397fc3577e05b483aca964f1b

is the same as

	lemonldap-ng-sessions get --persistent dwho


=item B<--id-only>,B<-i>

This option replaces the standard JSON output format with a simpler format of
one session ID per line.

This allows some interesting combos using xargs. For example, if you want to
remove all sessions started by "dwho"

	lemonldap-ng-sessions search --where uid=dwho --id-only | \
		xargs lemonldap-ng-sessions delete

=item B<--user>,B<-u>

This option forces the system user that runs the script.

=item B<--group>,B<-g>

This option forces the system group that runs the script.

=item B<--hash>, B<-c>

When the session storage is protected by
L<hashed session storage|https://lemonldap-ng.org/documentation/latest/security.html#configure-security-settings>,
this option indicates that the given session is the original value
I<(cookie value)>

=back

=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 COPYRIGHT AND LICENSE

=over

=item Copyright (C) 2016 by Xavier Guimard, E<lt>x.guimard@free.frE<gt>

=item Copyright (C) 2016 by Clément Oudot, E<lt>clem.oudot@gmail.comE<gt>

=back

This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see L<http://www.gnu.org/licenses/>.

=cut
