lemonldap-ng/lemonldap-ng-common/lib/Lemonldap/NG/Common/CliSessions.pm

400 lines
9.8 KiB
Perl

package Lemonldap::NG::Common::CliSessions;
use strict;
use Mouse;
use JSON;
use Lemonldap::NG::Common::Conf;
use Lemonldap::NG::Common::Logger::Std;
use Lemonldap::NG::Common::Apache::Session;
use Lemonldap::NG::Common::Session;
use Lemonldap::NG::Common::Util qw/getPSessionID genId2F/;
our $VERSION = '2.1.0';
has opts => ( is => 'rw' );
has stdout => (
is => 'ro',
default => *STDOUT,
);
has stderr => (
is => 'ro',
default => *STDERR,
);
has conf => (
is => 'ro',
default => sub {
my $res = Lemonldap::NG::Common::Conf->new( { (
ref $_[0] && $_[0]->{iniFile}
? ( confFile => $_[0]->{iniFile} )
: ()
)
}
);
die $Lemonldap::NG::Common::Conf::msg unless ($res);
return $res->getConf();
},
);
sub _to_json {
my $self = shift;
my $obj = shift;
return to_json( $obj, { pretty => 1, canonical => 1 } );
}
sub _search {
my ($self) = shift;
my $backendStorage =
( lc( $self->opts->{backend} || 'global' ) ) . "Storage";
# Handle --persistent
if ( $self->opts->{persistent} ) {
$backendStorage = "persistentStorage";
}
$backendStorage = "globalStorage" unless $self->conf->{$backendStorage};
my $args = $self->conf->{"${backendStorage}Options"};
$args->{backend} = $self->conf->{$backendStorage};
my @fields = @{ $self->opts->{select} || [] };
if ( $self->opts->{idonly} ) {
@fields = ('_session_id');
}
my $res;
if ( $self->opts->{where} ) {
# TODO fix regexp?
if ( $self->opts->{where} =~ /^(\w+)\s*=\s*(.*)/ ) {
my ( $selectField, $value ) = ( $1, $2 );
$res = Lemonldap::NG::Common::Apache::Session->searchOn( $args,
$selectField, $value, @fields );
}
else {
die "Invalid --where option : " . $self->opts->{where};
}
}
else {
$res =
Lemonldap::NG::Common::Apache::Session->get_key_from_all_sessions(
$args, ( @fields ? [@fields] : () ) );
}
return $res;
}
sub search {
my ($self) = shift;
my $res = $self->_search();
my $o = $self->stdout;
if ( $self->opts->{idonly} ) {
print $o map { $res->{$_}->{_session_id} . "\n" } keys %{$res};
}
else {
print $o $self->_to_json( [ values %{$res} ] );
}
return 0;
}
# Returns the session object, so we can modify it
sub _get_one_session {
my ( $self, $id, $backend ) = @_;
# Lookup backend storage from CLI options
my $backendStorage =
( lc( $self->opts->{backend} || 'global' ) ) . "Storage";
# allow argument to overwrite the backend
if ($backend) {
$backendStorage = $backend . "Storage";
}
# Handle --persistent
elsif ( $self->opts->{persistent} ) {
$backendStorage = "persistentStorage";
$id = getPSessionID($id);
}
# In any case, fall back to global storage if we couldn't find the backend
$backendStorage = "globalStorage" unless $self->conf->{$backendStorage};
my $as = Lemonldap::NG::Common::Session->new( {
storageModule => $self->conf->{$backendStorage},
storageModuleOptions => $self->conf->{"${backendStorage}Options"},
id => $id,
}
);
if ( $as->error ) {
my $e = $self->stderr;
print $e $as->error;
return undef;
}
else {
return $as;
}
}
# Returns only session data
sub _get_one_data {
my ( $self, $id ) = @_;
my $as = $self->_get_one_session($id);
if ($as) {
my $new;
# Filter
if ( ( ref( $self->opts->{select} ) eq 'ARRAY' )
and ( scalar @{ $self->opts->{select} } > 0 ) )
{
for ( @{ $self->opts->{select} } ) {
$new->{$_} = $as->data->{$_} if defined $as->data->{$_};
}
}
else {
$new = $as->data;
}
return $new;
}
return undef;
}
sub delete {
my ($self) = shift;
my $result = 0;
my @sessions;
# Run search if a where option was provided
if ( $self->opts->{where} ) {
my $res = $self->_search();
@sessions = keys %{$res};
}
else {
@sessions = @_;
}
my @result;
for my $id (@sessions) {
my $as = $self->_get_one_session($id);
if ($as) {
unless ( $as->remove ) {
my $e = $self->stderr;
print $e $as->error;
$result = 1;
}
}
}
return $result;
}
sub get {
my $self = shift;
my $o = $self->stdout;
my @result;
for my $id (@_) {
my $new = $self->_get_one_data($id);
push @result, $new if $new;
}
print $o $self->_to_json( \@result );
return 0;
}
sub _get_psession {
my ( $self, $uid ) = @_;
my $psession_id = getPSessionID($uid);
my $res = $self->_get_one_session( $psession_id, 'persistent' );
die "Could not get psession for user $uid" unless $res;
return $res;
}
sub _get_psession_data {
my ( $self, $uid ) = @_;
my $ps = $self->_get_psession($uid);
return $ps->data;
}
# This method takes a special psession key (oidcConsents, 2fDevices..)
# and returns the expected JSON object
# idBuilder is a sub that gets applied to every object in the array, yielding the key of
# this object in the resulting hash
sub _get_psession_special {
my ( $self, $target, $keyName, $idBuilder ) = @_;
my $psession = $self->_get_psession_data($target);
my $res = {};
my $special = $psession->{$keyName} || "[]";
$special = from_json($special);
die "Expecting JSON array in $keyName" unless ref($special) eq "ARRAY";
for my $item ( @{$special} ) {
my $id = $idBuilder->($item);
$res->{$id} = $item;
}
return $res;
}
# This method deletes all matching items from an array psession key (oidcConsents, 2fDevices..)
# keyBuilder is a sub that gets applied to every object in the array, yielding the value to be
# compared against
sub _del_psession_special {
my ( $self, $target, $specialKeyName, $itemKeyBuilder, @todelete ) = @_;
my $psession = $self->_get_psession($target);
my $data = $psession->data;
my $deleted = 0;
my $special = $data->{$specialKeyName} || "[]";
$special = from_json($special);
die "Expecting JSON array in $specialKeyName"
unless ref($special) eq "ARRAY";
my @new;
for my $item ( @{$special} ) {
my $id = $itemKeyBuilder->($item);
if ( $id and grep { $_ eq $id } @todelete ) {
$deleted = $deleted + 1;
}
else {
push @new, $item;
}
}
if ($deleted) {
$data->{$specialKeyName} = to_json( [@new] );
}
$psession->update($data);
}
sub consents_get {
my $self = shift;
my $target = shift;
my $o = $self->stdout;
my $consents = $self->_get_psession_special( $target, '_oidcConsents',
sub { $_[0]->{rp} } );
print $o $self->_to_json($consents);
return 0;
}
sub secondfactors_get {
my $self = shift;
my $target = shift;
my $o = $self->stdout;
my $consents = $self->_get_psession_special( $target, '_2fDevices',
sub { genId2F( $_[0] ) } );
print $o $self->_to_json($consents);
return 0;
}
sub consents_delete {
my $self = shift;
my $target = shift;
my @ids = @_;
return unless @ids;
$self->_del_psession_special( $target, '_oidcConsents',
sub { $_[0]->{rp} }, @ids );
return 0;
}
sub secondfactors_delete {
my $self = shift;
my $target = shift;
my @ids = @_;
return unless @ids;
$self->_del_psession_special( $target, '_2fDevices',
sub { genId2F( $_[0] ) }, @ids );
return 0;
}
sub secondfactors_delType {
my $self = shift;
my $target = shift;
my @types = @_;
return unless @types;
$self->_del_psession_special( $target, '_2fDevices', sub { $_[0]->{type} },
@types );
return 0;
}
sub setKey {
my $self = shift;
my $id = shift;
die "Unever number of arguments" unless ( @_ % 2 ) == 0;
my %newvalues = (@_);
die "Session ID and new values not provided" unless $id and %newvalues;
my $as = $self->_get_one_session($id);
die unless $as;
my $data = $as->data;
for ( keys %newvalues ) {
$data->{$_} = $newvalues{$_};
}
$as->update($data);
return 0;
}
sub delKey {
my $self = shift;
my $id = shift;
my @todelete = @_;
die "Session ID and key names not provided" unless $id and @todelete;
my $as = $self->_get_one_session($id);
die unless $as;
my $data = $as->data;
for (@todelete) {
# Weird, I know. But that's how
# Lemonldap::NG::Common::Session::update works
$data->{$_} = undef;
}
$as->update($data);
return 0;
}
sub run {
my $self = shift;
my $action = shift;
my $opts = shift;
unless ( ref $self ) {
$self = $self->new;
}
unless ($action) {
die 'nothing to do, aborting';
}
$self->opts($opts);
# Simple commands
if ( $action =~ /^(?:get|search|delete|setKey|delKey)$/ ) {
return $self->$action(@_);
}
# Subcommands and target
elsif ( $action =~ /^(?:secondfactors|consents)$/ ) {
my $subcommand = shift;
unless ( $subcommand and @_ ) {
die "Missing subcommand and target for $action";
}
my $func = "${action}_${subcommand}";
if ( $self->can($func) ) {
return $self->$func(@_);
}
else {
die "Unknown subcommand $subcommand for action $action";
}
}
else {
die "unknown action $action. Only get or search are allowed";
}
}
1;