Files
2024-10-14 00:08:40 +02:00

383 lines
10 KiB
Perl

# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::Auth;
use strict;
use warnings;
use Kernel::Language qw(Translatable);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DateTime',
'Kernel::System::Group',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::SystemMaintenance',
'Kernel::System::User',
'Kernel::System::Valid',
);
=head1 NAME
Kernel::System::Auth - agent authentication module.
=head1 DESCRIPTION
The authentication module for the agent interface.
=head1 PUBLIC INTERFACE
=head2 new()
Don't use the constructor directly, use the ObjectManager instead:
my $AuthObject = $Kernel::OM->Get('Kernel::System::Auth');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# get needed objects
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# load auth modules
COUNT:
for my $Count ( '', 1 .. 10 ) {
my $GenericModule = $ConfigObject->Get("AuthModule$Count");
next COUNT if !$GenericModule;
if ( !$MainObject->Require($GenericModule) ) {
$MainObject->Die("Can't load backend module $GenericModule! $@");
}
$Self->{"AuthBackend$Count"} = $GenericModule->new( Count => $Count );
}
# load 2factor auth modules
COUNT:
for my $Count ( '', 1 .. 10 ) {
my $GenericModule = $ConfigObject->Get("AuthTwoFactorModule$Count");
next COUNT if !$GenericModule;
if ( !$MainObject->Require($GenericModule) ) {
$MainObject->Die("Can't load backend module $GenericModule! $@");
}
$Self->{"AuthTwoFactorBackend$Count"} = $GenericModule->new( %{$Self}, Count => $Count );
}
# load sync modules
COUNT:
for my $Count ( '', 1 .. 10 ) {
my $GenericModule = $ConfigObject->Get("AuthSyncModule$Count");
next COUNT if !$GenericModule;
if ( !$MainObject->Require($GenericModule) ) {
$MainObject->Die("Can't load backend module $GenericModule! $@");
}
$Self->{"AuthSyncBackend$Count"} = $GenericModule->new( %{$Self}, Count => $Count );
}
# Initialize last error message
$Self->{LastErrorMessage} = '';
return $Self;
}
=head2 GetOption()
Get module options. Currently there is just one option, "PreAuth".
if ( $AuthObject->GetOption( What => 'PreAuth' ) ) {
print "No login screen is needed. Authentication is based on some other options. E. g. $ENV{REMOTE_USER}\n";
}
=cut
sub GetOption {
my ( $Self, %Param ) = @_;
return $Self->{AuthBackend}->GetOption(%Param);
}
=head2 Auth()
The authentication function.
if ( $AuthObject->Auth( User => $User, Pw => $Pw ) ) {
print "Auth ok!\n";
}
else {
print "Auth invalid!\n";
}
=cut
sub Auth {
my ( $Self, %Param ) = @_;
# get needed objects
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# use all 11 auth backends and return on first true
my $User;
COUNT:
for my $Count ( '', 1 .. 10 ) {
# return on no config setting
next COUNT if !$Self->{"AuthBackend$Count"};
# check auth backend
$User = $Self->{"AuthBackend$Count"}->Auth(%Param);
# next on no success
next COUNT if !$User;
# Sync will happen before two factor authentication (if configured)
# because user might not exist before being created in sync (see bug #11966).
# A failed two factor auth after successful sync will result
# in a new or updated user but no information or permission leak.
# configured auth sync backend
my $AuthSyncBackend = $ConfigObject->Get("AuthModule::UseSyncBackend$Count");
if ( !defined $AuthSyncBackend ) {
$AuthSyncBackend = $ConfigObject->Get("AuthModule{$Count}::UseSyncBackend");
}
# for backwards compatibility, OTRS 3.1.1, 3.1.2 and 3.1.3 used this wrong format (see bug#8387)
# sync with configured auth backend
if ( defined $AuthSyncBackend ) {
# if $AuthSyncBackend is defined but empty, don't sync with any backend
if ($AuthSyncBackend) {
# sync configured backend
$Self->{$AuthSyncBackend}->Sync( %Param, User => $User );
}
}
# use all 11 sync backends
else {
SOURCE:
for my $Count ( '', 1 .. 10 ) {
# return on no config setting
next SOURCE if !$Self->{"AuthSyncBackend$Count"};
# sync backend
$Self->{"AuthSyncBackend$Count"}->Sync( %Param, User => $User );
}
}
# If we have no UserID at this point
# it means auth was ok but user didn't exist before
# and wasn't created in sync module.
# We will skip two factor authentication even if configured
# because we don't have user data to compare the otp anyway.
# This will not count as a failed login.
my $UserID = $UserObject->UserLookup(
UserLogin => $User,
);
last COUNT if !$UserID;
# check 2factor auth backends
my $TwoFactorAuth;
TWOFACTORSOURCE:
for my $Count ( '', 1 .. 10 ) {
# return on no config setting
next TWOFACTORSOURCE if !$Self->{"AuthTwoFactorBackend$Count"};
# 2factor backend
my $AuthOk = $Self->{"AuthTwoFactorBackend$Count"}->Auth(
TwoFactorToken => $Param{TwoFactorToken},
User => $User,
UserID => $UserID,
);
$TwoFactorAuth = $AuthOk ? 'passed' : 'failed';
last TWOFACTORSOURCE if $AuthOk;
}
# if at least one 2factor auth backend was checked but none was successful,
# it counts as a failed login
if ( $TwoFactorAuth && $TwoFactorAuth ne 'passed' ) {
$User = undef;
last COUNT;
}
# remember auth backend
$UserObject->SetPreferences(
Key => 'UserAuthBackend',
Value => $Count,
UserID => $UserID,
);
last COUNT;
}
# return if no auth user
if ( !$User ) {
# remember failed logins
my $UserID = $UserObject->UserLookup(
UserLogin => $Param{User},
);
return if !$UserID;
my %User = $UserObject->GetUserData(
UserID => $UserID,
Valid => 1,
);
my $Count = $User{UserLoginFailed} || 0;
$Count++;
$UserObject->SetPreferences(
Key => 'UserLoginFailed',
Value => $Count,
UserID => $UserID,
);
# set agent to invalid-temporarily if max failed logins reached
my $Config = $ConfigObject->Get('PreferencesGroups');
my $PasswordMaxLoginFailed;
if ( $Config && $Config->{Password} && $Config->{Password}->{PasswordMaxLoginFailed} ) {
$PasswordMaxLoginFailed = $Config->{Password}->{PasswordMaxLoginFailed};
}
return if !%User;
return if !$PasswordMaxLoginFailed;
return if $Count < $PasswordMaxLoginFailed;
my $ValidID = $Kernel::OM->Get('Kernel::System::Valid')->ValidLookup(
Valid => 'invalid-temporarily',
);
# Make sure not to accidentially overwrite the password.
delete $User{UserPw};
my $Update = $UserObject->UserUpdate(
%User,
ValidID => $ValidID,
ChangeUserID => 1,
);
return if !$Update;
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "Login failed $Count times. Set $User{UserLogin} to "
. "'invalid-temporarily'.",
);
return;
}
# remember login attributes
my $UserID = $UserObject->UserLookup(
UserLogin => $User,
);
return $User if !$UserID;
# on system maintenance just admin users
# should be allowed to get into the system
my $ActiveMaintenance = $Kernel::OM->Get('Kernel::System::SystemMaintenance')->SystemMaintenanceIsActive();
# reset failed logins
$UserObject->SetPreferences(
Key => 'UserLoginFailed',
Value => 0,
UserID => $UserID,
);
# check if system maintenance is active
if ($ActiveMaintenance) {
# check if user is allow to login
# get current user groups
my %Groups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
UserID => $UserID,
Type => 'move_into',
);
# reverse groups hash for easy look up
%Groups = reverse %Groups;
# check if the user is in the Admin group
# if that is not the case return
if ( !$Groups{admin} ) {
$Self->{LastErrorMessage} =
$ConfigObject->Get('SystemMaintenance::IsActiveDefaultLoginErrorMessage')
|| Translatable("It is currently not possible to login due to a scheduled system maintenance.");
return;
}
}
# last login preferences update
$UserObject->SetPreferences(
Key => 'UserLastLogin',
Value => $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch(),
UserID => $UserID,
);
return $User;
}
=head2 GetLastErrorMessage()
Retrieve $Self->{LastErrorMessage} content.
my $AuthErrorMessage = $AuthObject->GetLastErrorMessage();
Result:
$AuthErrorMessage = "An error string message.";
=cut
sub GetLastErrorMessage {
my ( $Self, %Param ) = @_;
return $Self->{LastErrorMessage};
}
1;
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L<https://otrs.org/>).
This software comes with ABSOLUTELY NO WARRANTY. For details, see
the enclosed file COPYING for license information (GPL). If you
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
=cut