lemonldap-ng/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm

333 lines
9.8 KiB
Perl
Raw Normal View History

2019-11-08 18:31:50 +01:00
package Lemonldap::NG::Manager::Api::2F;
our $VERSION = '2.0.7';
package Lemonldap::NG::Manager::Api;
use 5.10.0;
use utf8;
use Mouse;
use JSON;
2019-12-20 11:05:16 +01:00
use MIME::Base64;
use Lemonldap::NG::Common::Session;
sub getSecondFactors {
my ( $self, $req ) = @_;
2019-12-20 11:13:15 +01:00
my ( $uid, $res );
$uid = $req->params('uid')
or return $self->sendError( $req, 'uid is missing', 400 );
$self->logger->debug("[API] 2F for $uid requested");
$res = $self->_get2F($uid);
2019-12-20 11:05:16 +01:00
return $self->sendError( $req, $res->{msg}, $res->{code} )
2019-12-20 11:13:15 +01:00
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, $res->{secondFactors} );
}
sub getSecondFactorsByType {
my ( $self, $req ) = @_;
2019-12-20 11:13:15 +01:00
my ( $uid, $type, $res );
$uid = $req->params('uid')
or return $self->sendError( $req, 'Uid is missing', 400 );
$type = $req->params('type')
or return $self->sendError( $req, 'Type is missing', 400 );
$self->logger->debug("[API] 2F for $uid with type $type requested");
2019-12-20 11:13:15 +01:00
$res = $self->_get2F( $uid, uc $type );
2019-12-20 11:05:16 +01:00
return $self->sendError( $req, $res->{msg}, $res->{code} )
2019-12-20 11:13:15 +01:00
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, $res->{secondFactors} );
}
sub getSecondFactorsById {
my ( $self, $req ) = @_;
2019-12-20 11:13:15 +01:00
my ( $uid, $id, $res );
$uid = $req->params('uid')
or return $self->sendError( $req, 'uid is missing', 400 );
$id = $req->params('id')
or return $self->sendError( $req, 'id is missing', 400 );
$self->logger->debug("[API] 2F for $uid with id $id requested");
2019-12-20 11:13:15 +01:00
$res = $self->_get2F( $uid, undef, $id );
2019-12-20 11:05:16 +01:00
return $self->sendError( $req, $res->{msg}, $res->{code} )
2019-12-20 11:13:15 +01:00
unless ( $res->{res} eq 'ok' );
2019-12-20 11:05:16 +01:00
2019-12-20 11:13:15 +01:00
return $self->sendError( $req, "2F id '$id' not found for user '$uid'",
404 )
unless ( scalar @{ $res->{secondFactors} } > 0 );
2019-12-20 11:05:16 +01:00
2019-12-20 11:13:15 +01:00
return $self->sendJSONresponse( $req, @{ $res->{secondFactors} }[0] );
}
sub deleteSecondFactors {
my ( $self, $req ) = @_;
2019-12-20 11:13:15 +01:00
my ( $uid, $res );
$uid = $req->params('uid')
or return $self->sendError( $req, 'uid is missing', 400 );
$self->logger->debug("[API] Delete all 2F for $uid requested");
$res = $self->_delete2F($uid);
2019-12-20 11:05:16 +01:00
return $self->sendError( $req, $res->{msg}, $res->{code} )
2019-12-20 11:13:15 +01:00
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, { message => $res->{msg} } );
}
sub deleteSecondFactorsById {
my ( $self, $req ) = @_;
2019-12-20 11:13:15 +01:00
my ( $uid, $id, $res );
$uid = $req->params('uid')
or return $self->sendError( $req, 'uid is missing', 400 );
$id = $req->params('id')
or return $self->sendError( $req, 'id is missing', 400 );
$self->logger->debug("[API] Delete 2F for $uid with id $id requested");
2019-12-20 11:13:15 +01:00
$res = $self->_delete2F( $uid, undef, $id );
2019-12-20 11:05:16 +01:00
return $self->sendError( $req, $res->{msg}, $res->{code} )
2019-12-20 11:13:15 +01:00
unless ( $res->{res} eq 'ok' );
2019-12-20 11:05:16 +01:00
2019-12-20 11:13:15 +01:00
return $self->sendError( $req, "2F id '$id' not found for user '$uid'",
404 )
unless ( $res->{removed} > 0 );
return $self->sendJSONresponse( $req, { message => $res->{msg} } );
}
sub deleteSecondFactorsByType {
my ( $self, $req ) = @_;
2019-12-20 11:13:15 +01:00
my ( $uid, $type, $res );
$uid = $req->params('uid')
or return $self->sendError( $req, 'uid is missing', 400 );
$type = $req->params('type')
or return $self->sendError( $req, 'type is missing', 400 );
2019-12-20 11:13:15 +01:00
$self->logger->debug(
"[API] Delete all 2F for $uid with type $type requested");
2019-12-20 11:13:15 +01:00
$res = $self->_delete2F( $uid, uc $type );
2019-12-20 11:05:16 +01:00
return $self->sendError( $req, $res->{msg}, $res->{code} )
2019-12-20 11:13:15 +01:00
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, { message => $res->{msg} } );
}
sub _get2F {
my ( $self, $uid, $type, $id ) = @_;
2019-12-20 11:13:15 +01:00
my ( $res, $psessions, @secondFactors );
2019-12-20 11:13:15 +01:00
if ( defined $type ) {
$res = $self->_checkType($type);
2019-12-20 11:13:15 +01:00
return $res if ( $res->{res} ne 'ok' );
}
$psessions = $self->_getSessions2F($self->_getPersistentMod, 'Persistent', '_session_uid', $uid);
2019-12-20 11:13:15 +01:00
foreach ( keys %{$psessions} ) {
my $devices =
from_json( $psessions->{$_}->{_2fDevices}, { allow_nonref => 1 } );
foreach my $device ( @{$devices} ) {
2019-12-20 11:13:15 +01:00
$self->logger->debug(
"Check device [epoch=$device->{epoch}, type=$device->{type}, name=$device->{name}]"
);
push @secondFactors,
{
id => $self->_genId2F($device),
type => $device->{type},
name => $device->{name}
}
unless ( ( defined $type and $type ne $device->{type} )
or ( defined $id and $id ne $self->_genId2F($device) ) );
}
}
2019-12-20 11:13:15 +01:00
$self->logger->debug(
"Found " . scalar @secondFactors . " 2F devices for uid $uid." );
return { res => 'ok', secondFactors => [@secondFactors] };
2019-12-20 11:05:16 +01:00
}
sub _genId2F {
2019-12-20 11:13:15 +01:00
my ( $self, $device ) = @_;
return encode_base64( "$device->{epoch}::$device->{type}::$device->{name}",
"" );
}
sub _getPersistentMod {
2019-12-20 11:13:15 +01:00
my ($self) = @_;
my $mod = $self->sessionTypes->{persistent};
$mod->{options}->{backend} = $mod->{module};
return $mod;
}
2019-12-20 11:13:15 +01:00
sub _getSSOMod {
my ($self) = @_;
my $mod = $self->sessionTypes->{global};
$mod->{options}->{backend} = $mod->{module};
return $mod;
}
sub _getSessions2F {
my ( $self, $mod, $kind, $key, $uid ) = @_;
$self->logger->debug("Looking for sessions for uid $uid ...");
my $sessions = Lemonldap::NG::Common::Apache::Session->searchOn(
$mod->{options}, $key, $uid,
2019-12-20 11:13:15 +01:00
( '_session_kind', '_session_uid', '_session_id', '_2fDevices' ) );
foreach ( keys %{$sessions} ) {
delete $sessions->{$_}
unless ( $sessions->{$_}->{_session_kind} eq $kind );
2019-12-20 11:05:16 +01:00
}
2019-12-20 11:13:15 +01:00
$self->logger->debug(
"Found " . scalar( keys %{$sessions} ) . " $kind sessions for uid $uid." );
return $sessions;
}
sub _getSession2F {
my ( $self, $sessionId, $mod ) = @_;
$self->logger->debug("Looking for session with sessionId $sessionId ...");
my $session = $self->getApacheSession( $mod, $sessionId );
2019-12-20 11:13:15 +01:00
$self->logger->debug(
defined $session
? "Session $sessionId found."
: " No session found for sessionId $sessionId" );
return $session;
}
sub _delete2FFromSessions {
my ( $self, $uid, $type, $id, $mod, $kind, $key ) = @_;
2019-12-20 11:13:15 +01:00
my (
$sessions, $session, $devices, @keep, $removed,
$total, $module, $localStorage
2019-12-20 11:13:15 +01:00
);
$sessions = $self->_getSessions2F($mod, $kind, $key, $uid);
foreach ( keys %{$sessions} ) {
$session = $self->_getSession2F($_, $mod)
2019-12-20 11:13:15 +01:00
or return { res => 'ko', code => 500, msg => $@ };
2019-12-20 11:13:15 +01:00
$self->logger->debug(
"Looking for 2F Device(s) attached to sessionId $_");
if ( $session->data->{_2fDevices} ) {
2019-12-20 11:13:15 +01:00
$devices =
from_json( $session->data->{_2fDevices}, { allow_nonref => 1 } );
$total = scalar @$devices;
2019-12-20 11:13:15 +01:00
$self->logger->debug(
"Found $total 2F devices attached to sessionId $_");
@keep = ();
while (@$devices) {
my $element = shift @$devices;
if (( defined $type or defined $id ) and (
( defined $type and $type ne $element->{type})
2019-12-20 11:13:15 +01:00
or
( defined $id and $id ne $self->_genId2F($element) )
)
) {
push @keep, $element;
} else {
$removed->{$self->_genId2F($element)} = "removed";
}
}
if ( ($total - scalar @keep) > 0 ) {
2019-12-20 11:13:15 +01:00
# Update session
2019-12-20 11:13:15 +01:00
$self->logger->debug(
"Removing " . ($total - scalar @keep) . " 2F device(s) attached to sessionId $_ ..."
2019-12-20 11:13:15 +01:00
);
$session->data->{_2fDevices} = to_json( \@keep );
$session->update( \%{ $session->data } );
# Delete from local cache
if ($session->{options}->{localStorage}) {
$module = $session->{options}->{localStorage};
eval "use $module;";
$localStorage = $module->new( $session->{options}->{localStorageOptions} );
if ($localStorage->get($_) ) {
$self->logger->debug("Delete local cache for session $_");
$localStorage->remove($_);
}
2019-12-20 11:13:15 +01:00
}
}
else {
$self->logger->debug(
"No matching 2F devices attached to sessionId $_ were selected for removal."
2019-12-20 11:13:15 +01:00
);
}
2019-12-20 11:13:15 +01:00
}
else {
$self->logger->debug(
"No 2F devices attached to sessionId $_ were found.");
}
}
return { res => 'ok', removed => $removed };
}
sub _delete2F {
my ( $self, $uid, $type, $id ) = @_;
my ( $res, $removed, $count);
if ( defined $type ) {
$res = $self->_checkType($type);
return $res if ( $res->{res} ne 'ok' );
}
$res = $self->_delete2FFromSessions($uid, $type, $id, $self->_getPersistentMod, 'Persistent', '_session_uid');
return $res if ( $res->{res} ne 'ok' );
$removed = $res->{removed};
$res = $self->_delete2FFromSessions($uid, $type, $id, $self->_getSSOMod, 'SSO', 'uid');
return $res if ( $res->{res} ne 'ok' );
$removed = ($removed, $res->{removed});
$count = scalar (keys %{$removed});
return {
2019-12-20 11:13:15 +01:00
res => 'ok',
removed => $count,
msg => $count > 0
? "Successful operation: " . $count . " 2F were removed"
2019-12-20 11:13:15 +01:00
: "No operation performed"
};
}
sub _checkType {
my ( $self, $type ) = @_;
2019-12-20 11:13:15 +01:00
return {
res => "ko",
code => 405,
msg =>
"Invalid input: Type \"$type\" does not exist. Allowed values for type are: \"U2F\", \"TOTP\" or \"UBK\""
}
unless ( $type =~ /\b(?:U2F|TOTP|UBK)\b/ );
return { res => "ok" };
2019-11-08 18:31:50 +01:00
}
1;