# --
# 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::User;
use strict;
use warnings;
use Crypt::PasswdMD5 qw(unix_md5_crypt apache_md5_crypt);
use Digest::SHA;
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Cache',
'Kernel::System::CheckItem',
'Kernel::System::DB',
'Kernel::System::Encode',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::SearchProfile',
'Kernel::System::DateTime',
'Kernel::System::Valid',
);
=head1 NAME
Kernel::System::User - user lib
=head1 DESCRIPTION
All user functions. E. g. to add and updated user and other functions.
=head1 PUBLIC INTERFACE
=head2 new()
Don't use the constructor directly, use the ObjectManager instead:
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# get user table
$Self->{UserTable} = $ConfigObject->Get('DatabaseUserTable') || 'user';
$Self->{UserTableUserID} = $ConfigObject->Get('DatabaseUserTableUserID') || 'id';
$Self->{UserTableUserPW} = $ConfigObject->Get('DatabaseUserTableUserPW') || 'pw';
$Self->{UserTableUser} = $ConfigObject->Get('DatabaseUserTableUser') || 'login';
$Self->{CacheType} = 'User';
$Self->{CacheTTL} = 60 * 60 * 24 * 20;
# set lower if database is case sensitive
$Self->{Lower} = '';
if ( $Kernel::OM->Get('Kernel::System::DB')->GetDatabaseFunction('CaseSensitive') ) {
$Self->{Lower} = 'LOWER';
}
return $Self;
}
=head2 GetUserData()
get user data (UserLogin, UserFirstname, UserLastname, UserEmail, ...)
my %User = $UserObject->GetUserData(
UserID => 123,
);
or
my %User = $UserObject->GetUserData(
User => 'franz',
Valid => 1, # not required -> 0|1 (default 0)
# returns only data if user is valid
NoOutOfOffice => 1, # not required -> 0|1 (default 0)
# returns data without out of office infos
);
=cut
sub GetUserData {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{User} && !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need User or UserID!',
);
return;
}
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# get configuration for the full name order
my $FirstnameLastNameOrder = $ConfigObject->Get('FirstnameLastnameOrder') || 0;
# check if result is cached
if ( $Param{Valid} ) {
$Param{Valid} = 1;
}
else {
$Param{Valid} = 0;
}
if ( $Param{NoOutOfOffice} ) {
$Param{NoOutOfOffice} = 1;
}
else {
$Param{NoOutOfOffice} = 0;
}
my $CacheKey;
if ( $Param{User} ) {
$CacheKey = join '::', 'GetUserData', 'User',
$Param{User},
$Param{Valid},
$FirstnameLastNameOrder,
$Param{NoOutOfOffice};
}
else {
$CacheKey = join '::', 'GetUserData', 'UserID',
$Param{UserID},
$Param{Valid},
$FirstnameLastNameOrder,
$Param{NoOutOfOffice};
}
# check cache
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return %{$Cache} if $Cache;
# get initial data
my @Bind;
my $SQL = "SELECT $Self->{UserTableUserID}, $Self->{UserTableUser}, "
. " title, first_name, last_name, $Self->{UserTableUserPW}, valid_id, "
. " create_time, change_time FROM $Self->{UserTable} WHERE ";
if ( $Param{User} ) {
my $User = lc $Param{User};
$SQL .= " $Self->{Lower}($Self->{UserTableUser}) = ?";
push @Bind, \$User;
}
else {
$SQL .= " $Self->{UserTableUserID} = ?";
push @Bind, \$Param{UserID};
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
return if !$DBObject->Prepare(
SQL => $SQL,
Bind => \@Bind,
Limit => 1,
);
my %Data;
while ( my @Row = $DBObject->FetchrowArray() ) {
$Data{UserID} = $Row[0];
$Data{UserLogin} = $Row[1];
$Data{UserTitle} = $Row[2];
$Data{UserFirstname} = $Row[3];
$Data{UserLastname} = $Row[4];
$Data{UserPw} = $Row[5];
$Data{ValidID} = $Row[6];
$Data{CreateTime} = $Row[7];
$Data{ChangeTime} = $Row[8];
}
# check data
if ( !$Data{UserID} ) {
if ( $Param{User} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "No UserData for user: '$Param{User}'.",
);
return;
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "No UserData for user id: '$Param{UserID}'.",
);
return;
}
}
# Store CacheTTL locally so that we can reduce it for users that are out of office.
my $CacheTTL = $Self->{CacheTTL};
# check valid, return if there is locked for valid users
if ( $Param{Valid} ) {
my $Hit = 0;
for ( $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet() ) {
if ( $_ eq $Data{ValidID} ) {
$Hit = 1;
}
}
if ( !$Hit ) {
# set cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
TTL => $CacheTTL,
Key => $CacheKey,
Value => {},
);
return;
}
}
# generate the full name and save it in the hash
my $UserFullname = $Self->_UserFullname(
%Data,
NameOrder => $FirstnameLastNameOrder,
);
# save the generated fullname in the hash.
$Data{UserFullname} = $UserFullname;
# get preferences
my %Preferences = $Self->GetPreferences( UserID => $Data{UserID} );
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
# add last login timestamp
if ( $Preferences{UserLastLogin} ) {
my $UserLastLoginTimeObj = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
Epoch => $Preferences{UserLastLogin}
}
);
$Preferences{UserLastLoginTimestamp} = $UserLastLoginTimeObj->ToString();
}
# check compat stuff
if ( !$Preferences{UserEmail} ) {
$Preferences{UserEmail} = $Data{UserLogin};
}
# out of office check
if ( !$Param{NoOutOfOffice} ) {
if ( $Preferences{OutOfOffice} ) {
my $CurrentTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $CreateDTObject = sub {
my %Param = @_;
return $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
String => sprintf(
'%d-%02d-%02d %s',
$Param{Year},
$Param{Month},
$Param{Day},
$Param{Time}
),
},
);
};
my $TimeStartObj = $CreateDTObject->(
Year => $Preferences{OutOfOfficeStartYear},
Month => $Preferences{OutOfOfficeStartMonth},
Day => $Preferences{OutOfOfficeStartDay},
Time => '00:00:00',
);
my $TimeEndObj = $CreateDTObject->(
Year => $Preferences{OutOfOfficeEndYear},
Month => $Preferences{OutOfOfficeEndMonth},
Day => $Preferences{OutOfOfficeEndDay},
Time => '23:59:59',
);
if ( $TimeStartObj < $CurrentTimeObject && $TimeEndObj > $CurrentTimeObject ) {
my $OutOfOfficeMessageTemplate =
$ConfigObject->Get('OutOfOfficeMessageTemplate') || '*** out of office until %s (%s d left) ***';
my $TillDate = sprintf(
'%04d-%02d-%02d',
$Preferences{OutOfOfficeEndYear},
$Preferences{OutOfOfficeEndMonth},
$Preferences{OutOfOfficeEndDay}
);
my $Till = int( ( $TimeEndObj->ToEpoch() - $CurrentTimeObject->ToEpoch() ) / 60 / 60 / 24 );
$Preferences{OutOfOfficeMessage} = sprintf( $OutOfOfficeMessageTemplate, $TillDate, $Till );
$Data{UserFullname} .= ' ' . $Preferences{OutOfOfficeMessage};
}
# Reduce CacheTTL to one hour for users that are out of office to make sure the cache expires timely
# even if there is no update action.
$CacheTTL = 60 * 60 * 1;
}
}
# merge hash
%Data = ( %Data, %Preferences );
# add preferences defaults
my $Config = $ConfigObject->Get('PreferencesGroups');
if ( $Config && ref $Config eq 'HASH' ) {
KEY:
for my $Key ( sort keys %{$Config} ) {
next KEY if !defined $Config->{$Key}->{DataSelected};
# check if data is defined
next KEY if defined $Data{ $Config->{$Key}->{PrefKey} };
# set default data
$Data{ $Config->{$Key}->{PrefKey} } = $Config->{$Key}->{DataSelected};
}
}
# set cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
TTL => $CacheTTL,
Key => $CacheKey,
Value => \%Data,
);
return %Data;
}
=head2 UserAdd()
to add new users
my $UserID = $UserObject->UserAdd(
UserFirstname => 'Huber',
UserLastname => 'Manfred',
UserLogin => 'mhuber',
UserPw => 'some-pass', # not required
UserEmail => 'email@example.com',
UserMobile => '1234567890', # not required
ValidID => 1,
ChangeUserID => 123,
);
=cut
sub UserAdd {
my ( $Self, %Param ) = @_;
# check needed stuff
for (qw(UserFirstname UserLastname UserLogin UserEmail ValidID ChangeUserID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!",
);
return;
}
}
# check if a user with this login (username) already exits
if ( $Self->UserLoginExistsCheck( UserLogin => $Param{UserLogin} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "A user with the username '$Param{UserLogin}' already exists.",
);
return;
}
# check email address
if (
$Param{UserEmail}
&& !$Kernel::OM->Get('Kernel::System::CheckItem')->CheckEmail( Address => $Param{UserEmail} )
&& grep { $_ eq $Param{ValidID} } $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet()
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Email address ($Param{UserEmail}) not valid ("
. $Kernel::OM->Get('Kernel::System::CheckItem')->CheckError() . ")!",
);
return;
}
# check password
if ( !$Param{UserPw} ) {
$Param{UserPw} = $Self->GenerateRandomPassword();
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# Don't store the user's password in plaintext initially. It will be stored in a
# hashed version later with SetPassword().
my $RandomPassword = $Self->GenerateRandomPassword();
# sql
return if !$DBObject->Do(
SQL => "INSERT INTO $Self->{UserTable} "
. "(title, first_name, last_name, "
. " $Self->{UserTableUser}, $Self->{UserTableUserPW}, "
. " valid_id, create_time, create_by, change_time, change_by)"
. " VALUES "
. " (?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)",
Bind => [
\$Param{UserTitle}, \$Param{UserFirstname}, \$Param{UserLastname},
\$Param{UserLogin}, \$RandomPassword, \$Param{ValidID},
\$Param{ChangeUserID}, \$Param{ChangeUserID},
],
);
# get new user id
my $UserLogin = lc $Param{UserLogin};
return if !$DBObject->Prepare(
SQL => "SELECT $Self->{UserTableUserID} FROM $Self->{UserTable} "
. " WHERE $Self->{Lower}($Self->{UserTableUser}) = ?",
Bind => [ \$UserLogin ],
Limit => 1,
);
# fetch the result
my $UserID;
while ( my @Row = $DBObject->FetchrowArray() ) {
$UserID = $Row[0];
}
# check if user exists
if ( !$UserID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "Unable to create User: '$Param{UserLogin}' ($Param{ChangeUserID})!",
);
return;
}
# log notice
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message =>
"User: '$Param{UserLogin}' ID: '$UserID' created successfully ($Param{ChangeUserID})!",
);
# set password
$Self->SetPassword(
UserLogin => $Param{UserLogin},
PW => $Param{UserPw}
);
my @UserPreferences = grep { $_ =~ m{^User}xsi } sort keys %Param;
USERPREFERENCE:
for my $UserPreference (@UserPreferences) {
# UserEmail is required.
next USERPREFERENCE if $UserPreference eq 'UserEmail' && !$Param{UserEmail};
# Set user preferences.
# Native user data will not be overwriten (handeled by SetPreferences()).
$Self->SetPreferences(
UserID => $UserID,
Key => $UserPreference,
Value => $Param{$UserPreference} || '',
);
}
# delete cache
$Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
Type => $Self->{CacheType},
);
return $UserID;
}
=head2 UserUpdate()
to update users
$UserObject->UserUpdate(
UserID => 4321,
UserFirstname => 'Huber',
UserLastname => 'Manfred',
UserLogin => 'mhuber',
UserPw => 'some-pass', # not required
UserEmail => 'email@example.com',
UserMobile => '1234567890', # not required
ValidID => 1,
ChangeUserID => 123,
);
=cut
sub UserUpdate {
my ( $Self, %Param ) = @_;
# check needed stuff
for (qw(UserID UserFirstname UserLastname UserLogin ValidID ChangeUserID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!",
);
return;
}
}
# store old user login for later use
my $OldUserLogin = $Self->UserLookup(
UserID => $Param{UserID},
);
# check if a user with this login (username) already exists
if (
$Self->UserLoginExistsCheck(
UserLogin => $Param{UserLogin},
UserID => $Param{UserID}
)
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "A user with the username '$Param{UserLogin}' already exists.",
);
return;
}
# check email address
if (
$Param{UserEmail}
&& !$Kernel::OM->Get('Kernel::System::CheckItem')->CheckEmail( Address => $Param{UserEmail} )
&& grep { $_ eq $Param{ValidID} } $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet()
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Email address ($Param{UserEmail}) not valid ("
. $Kernel::OM->Get('Kernel::System::CheckItem')->CheckError() . ")!",
);
return;
}
# update db
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => "UPDATE $Self->{UserTable} SET title = ?, first_name = ?, last_name = ?, "
. " $Self->{UserTableUser} = ?, valid_id = ?, "
. " change_time = current_timestamp, change_by = ? "
. " WHERE $Self->{UserTableUserID} = ?",
Bind => [
\$Param{UserTitle}, \$Param{UserFirstname}, \$Param{UserLastname},
\$Param{UserLogin}, \$Param{ValidID}, \$Param{ChangeUserID}, \$Param{UserID},
],
);
# check pw
if ( $Param{UserPw} ) {
$Self->SetPassword(
UserLogin => $Param{UserLogin},
PW => $Param{UserPw}
);
}
my @UserPreferences = grep { $_ =~ m{^User}xsi } sort keys %Param;
USERPREFERENCE:
for my $UserPreference (@UserPreferences) {
# UserEmail is required.
next USERPREFERENCE if $UserPreference eq 'UserEmail' && !$Param{UserEmail};
# Set user preferences.
# Native user data will not be overwriten (handeled by SetPreferences()).
$Self->SetPreferences(
UserID => $Param{UserID},
Key => $UserPreference,
Value => $Param{$UserPreference} || '',
);
}
# update search profiles if the UserLogin changed
if ( lc $OldUserLogin ne lc $Param{UserLogin} ) {
$Kernel::OM->Get('Kernel::System::SearchProfile')->SearchProfileUpdateUserLogin(
Base => 'TicketSearch',
UserLogin => $OldUserLogin,
NewUserLogin => $Param{UserLogin},
);
}
$Self->_UserCacheClear( UserID => $Param{UserID} );
# TODO Not needed to delete the cache if ValidID or Name was not changed
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $SystemPermissionConfig = $Kernel::OM->Get('Kernel::Config')->Get('System::Permission') || [];
for my $Type ( @{$SystemPermissionConfig}, 'rw' ) {
$CacheObject->Delete(
Type => 'GroupPermissionUserGet',
Key => 'PermissionUserGet::' . $Param{UserID} . '::' . $Type,
);
}
$CacheObject->CleanUp(
Type => 'GroupPermissionGroupGet',
);
return 1;
}
=head2 UserSearch()
to search users
my %List = $UserObject->UserSearch(
Search => '*some*', # also 'hans+huber' possible
Valid => 1, # not required
);
my %List = $UserObject->UserSearch(
UserLogin => '*some*',
Limit => 50,
Valid => 1, # not required
);
my %List = $UserObject->UserSearch(
PostMasterSearch => 'email@example.com',
Valid => 1, # not required
);
Returns hash of UserID, Login pairs:
my %List = (
1 => 'root@locahost',
4 => 'admin',
9 => 'joe',
);
For PostMasterSearch, it returns hash of UserID, Email pairs:
my %List = (
4 => 'john@example.com',
9 => 'joe@example.com',
);
=cut
sub UserSearch {
my ( $Self, %Param ) = @_;
my %Users;
my $Valid = $Param{Valid} // 1;
# check needed stuff
if ( !$Param{Search} && !$Param{UserLogin} && !$Param{PostMasterSearch} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need Search, UserLogin or PostMasterSearch!',
);
return;
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# get like escape string needed for some databases (e.g. oracle)
my $LikeEscapeString = $DBObject->GetDatabaseFunction('LikeEscapeString');
# build SQL string
my $SQL = "SELECT $Self->{UserTableUserID}, login
FROM $Self->{UserTable} WHERE ";
my @Bind;
if ( $Param{Search} ) {
my %QueryCondition = $DBObject->QueryCondition(
Key => [qw(login first_name last_name)],
Value => $Param{Search},
BindMode => 1,
);
$SQL .= $QueryCondition{SQL} . ' ';
push @Bind, @{ $QueryCondition{Values} };
}
elsif ( $Param{PostMasterSearch} ) {
my %UserID = $Self->SearchPreferences(
Key => 'UserEmail',
Value => $Param{PostMasterSearch},
);
for ( sort keys %UserID ) {
my %User = $Self->GetUserData(
UserID => $_,
Valid => $Param{Valid},
);
if (%User) {
return %UserID;
}
}
return;
}
elsif ( $Param{UserLogin} ) {
my $UserLogin = lc $Param{UserLogin};
$SQL .= " $Self->{Lower}($Self->{UserTableUser}) LIKE ? $LikeEscapeString";
$UserLogin =~ s/\*/%/g;
$UserLogin = $DBObject->Quote( $UserLogin, 'Like' );
push @Bind, \$UserLogin;
}
# add valid option
if ($Valid) {
$SQL .= "AND valid_id IN ("
. join( ', ', $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet() ) . ")";
}
# get data
return if !$DBObject->Prepare(
SQL => $SQL,
Bind => \@Bind,
Limit => $Self->{UserSearchListLimit} || $Param{Limit},
);
# fetch the result
while ( my @Row = $DBObject->FetchrowArray() ) {
$Users{ $Row[0] } = $Row[1];
}
return %Users;
}
=head2 SetPassword()
to set users passwords
$UserObject->SetPassword(
UserLogin => 'some-login',
PW => 'some-new-password'
);
=cut
sub SetPassword {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{UserLogin} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserLogin!'
);
return;
}
# get old user data
my %User = $Self->GetUserData( User => $Param{UserLogin} );
if ( !$User{UserLogin} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'No such User!',
);
return;
}
my $Pw = $Param{PW} || '';
my $CryptedPw = '';
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $CryptType = $ConfigObject->Get('AuthModule::DB::CryptType') || 'sha2';
# crypt plain (no crypt at all)
if ( $CryptType eq 'plain' ) {
$CryptedPw = $Pw;
}
# crypt with UNIX crypt
elsif ( $CryptType eq 'crypt' ) {
# encode output, needed by crypt() only non utf8 signs
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Pw );
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Param{UserLogin} );
$CryptedPw = crypt( $Pw, $Param{UserLogin} );
}
# crypt with md5
elsif ( $CryptType eq 'md5' || !$CryptType ) {
# encode output, needed by unix_md5_crypt() only non utf8 signs
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Pw );
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Param{UserLogin} );
$CryptedPw = unix_md5_crypt( $Pw, $Param{UserLogin} );
}
# crypt with md5 (compatible with Apache's .htpasswd files)
elsif ( $CryptType eq 'apr1' ) {
# encode output, needed by unix_md5_crypt() only non utf8 signs
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Pw );
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Param{UserLogin} );
$CryptedPw = apache_md5_crypt( $Pw, $Param{UserLogin} );
}
# crypt with sha1
elsif ( $CryptType eq 'sha1' ) {
my $SHAObject = Digest::SHA->new('sha1');
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Pw );
$SHAObject->add($Pw);
$CryptedPw = $SHAObject->hexdigest();
}
# crypt with sha512
elsif ( $CryptType eq 'sha512' ) {
my $SHAObject = Digest::SHA->new('sha512');
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Pw );
$SHAObject->add($Pw);
$CryptedPw = $SHAObject->hexdigest();
}
# bcrypt
elsif ( $CryptType eq 'bcrypt' ) {
if ( !$Kernel::OM->Get('Kernel::System::Main')->Require('Crypt::Eksblowfish::Bcrypt') ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"User: '$User{UserLogin}' tried to store password with bcrypt but 'Crypt::Eksblowfish::Bcrypt' is not installed!",
);
return;
}
my $Cost = $ConfigObject->Get('AuthModule::DB::bcryptCost') // 12;
# Don't allow values smaller than 9 for security.
$Cost = 9 if $Cost < 9;
# Current Crypt::Eksblowfish::Bcrypt limit is 31.
$Cost = 31 if $Cost > 31;
my $Salt = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString( Length => 16 );
# remove UTF8 flag, required by Crypt::Eksblowfish::Bcrypt
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Pw );
# calculate password hash
my $Octets = Crypt::Eksblowfish::Bcrypt::bcrypt_hash(
{
key_nul => 1,
cost => $Cost,
salt => $Salt,
},
$Pw
);
# We will store cost and salt in the password string so that it can be decoded
# in future even if we use a higher cost by default.
$CryptedPw = "BCRYPT:$Cost:$Salt:" . Crypt::Eksblowfish::Bcrypt::en_base64($Octets);
}
# crypt with sha256 as fallback
else {
my $SHAObject = Digest::SHA->new('sha256');
# encode output, needed by sha256_hex() only non utf8 signs
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Pw );
$SHAObject->add($Pw);
$CryptedPw = $SHAObject->hexdigest();
}
# update db
my $UserLogin = lc $Param{UserLogin};
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => "UPDATE $Self->{UserTable} SET $Self->{UserTableUserPW} = ? "
. " WHERE $Self->{Lower}($Self->{UserTableUser}) = ?",
Bind => [ \$CryptedPw, \$UserLogin ],
);
# log notice
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "User: '$Param{UserLogin}' changed password successfully!",
);
return 1;
}
=head2 UserLookup()
user login or id lookup
my $UserLogin = $UserObject->UserLookup(
UserID => 1,
Silent => 1, # optional, don't generate log entry if user was not found
);
my $UserID = $UserObject->UserLookup(
UserLogin => 'some_user_login',
Silent => 1, # optional, don't generate log entry if user was not found
);
=cut
sub UserLookup {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{UserLogin} && !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserLogin or UserID!'
);
return;
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
if ( $Param{UserLogin} ) {
# check cache
my $CacheKey = 'UserLookup::ID::' . $Param{UserLogin};
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return $Cache if $Cache;
# build sql query
my $UserLogin = lc $Param{UserLogin};
return if !$DBObject->Prepare(
SQL => "SELECT $Self->{UserTableUserID} FROM $Self->{UserTable} "
. " WHERE $Self->{Lower}($Self->{UserTableUser}) = ?",
Bind => [ \$UserLogin ],
Limit => 1,
);
# fetch the result
my $ID;
while ( my @Row = $DBObject->FetchrowArray() ) {
$ID = $Row[0];
}
if ( !$ID ) {
if ( !$Param{Silent} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No UserID found for '$Param{UserLogin}'!",
);
}
return;
}
# set cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
TTL => $Self->{CacheTTL},
Key => $CacheKey,
Value => $ID,
);
return $ID;
}
else {
# check cache
my $CacheKey = 'UserLookup::Login::' . $Param{UserID};
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return $Cache if $Cache;
# build sql query
return if !$DBObject->Prepare(
SQL => "SELECT $Self->{UserTableUser} FROM $Self->{UserTable} "
. " WHERE $Self->{UserTableUserID} = ?",
Bind => [ \$Param{UserID} ],
Limit => 1,
);
# fetch the result
my $Login;
while ( my @Row = $DBObject->FetchrowArray() ) {
$Login = $Row[0];
}
if ( !$Login ) {
if ( !$Param{Silent} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No UserLogin found for '$Param{UserID}'!",
);
}
return;
}
# set cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
TTL => $Self->{CacheTTL},
Key => $CacheKey,
Value => $Login,
);
return $Login;
}
}
=head2 UserName()
get user name
my $Name = $UserObject->UserName(
User => 'some-login',
);
or
my $Name = $UserObject->UserName(
UserID => 123,
);
=cut
sub UserName {
my ( $Self, %Param ) = @_;
my %User = $Self->GetUserData(%Param);
return if !%User;
return $User{UserFullname};
}
=head2 UserList()
return a hash with all users
my %List = $UserObject->UserList(
Type => 'Short', # Short|Long, default Short
Valid => 1, # default 1
NoOutOfOffice => 1, # (optional) default 0
);
=cut
sub UserList {
my ( $Self, %Param ) = @_;
my $Type = $Param{Type} || 'Short';
# set valid option
my $Valid = $Param{Valid} // 1;
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# get configuration for the full name order
my $FirstnameLastNameOrder = $ConfigObject->Get('FirstnameLastnameOrder') || 0;
my $NoOutOfOffice = $Param{NoOutOfOffice} || 0;
# check cache
my $CacheKey = join '::', 'UserList', $Type, $Valid, $FirstnameLastNameOrder, $NoOutOfOffice;
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return %{$Cache} if $Cache;
my $SelectStr;
if ( $Type eq 'Short' ) {
$SelectStr = "$ConfigObject->{DatabaseUserTableUserID}, "
. " $ConfigObject->{DatabaseUserTableUser}";
}
else {
$SelectStr = "$ConfigObject->{DatabaseUserTableUserID}, "
. " last_name, first_name, "
. " $ConfigObject->{DatabaseUserTableUser}";
}
my $SQL = "SELECT $SelectStr FROM $ConfigObject->{DatabaseUserTable}";
# sql query
if ($Valid) {
$SQL
.= " WHERE valid_id IN ( ${\(join ', ', $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet())} )";
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
return if !$DBObject->Prepare( SQL => $SQL );
# fetch the result
my %UsersRaw;
my %Users;
while ( my @Row = $DBObject->FetchrowArray() ) {
$UsersRaw{ $Row[0] } = \@Row;
}
if ( $Type eq 'Short' ) {
for my $CurrentUserID ( sort keys %UsersRaw ) {
$Users{$CurrentUserID} = $UsersRaw{$CurrentUserID}->[1];
}
}
else {
for my $CurrentUserID ( sort keys %UsersRaw ) {
my @Data = @{ $UsersRaw{$CurrentUserID} };
my $UserFullname = $Self->_UserFullname(
UserFirstname => $Data[2],
UserLastname => $Data[1],
UserLogin => $Data[3],
NameOrder => $FirstnameLastNameOrder,
);
$Users{$CurrentUserID} = $UserFullname;
}
}
# check vacation option
if ( !$NoOutOfOffice ) {
USERID:
for my $UserID ( sort keys %Users ) {
next USERID if !$UserID;
my %User = $Self->GetUserData(
UserID => $UserID,
);
if ( $User{OutOfOfficeMessage} ) {
$Users{$UserID} .= ' ' . $User{OutOfOfficeMessage};
}
}
}
# set cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
TTL => $Self->{CacheTTL},
Key => $CacheKey,
Value => \%Users,
);
return %Users;
}
=head2 GenerateRandomPassword()
generate a random password
my $Password = $UserObject->GenerateRandomPassword();
or
my $Password = $UserObject->GenerateRandomPassword(
Size => 16,
);
=cut
sub GenerateRandomPassword {
my ( $Self, %Param ) = @_;
# generated passwords are eight characters long by default.
my $Size = $Param{Size} || 8;
my $Password = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString(
Length => $Size,
);
return $Password;
}
=head2 SetPreferences()
set user preferences
$UserObject->SetPreferences(
Key => 'UserComment',
Value => 'some comment',
UserID => 123,
);
=cut
sub SetPreferences {
my ( $Self, %Param ) = @_;
# check needed stuff
for (qw(Key UserID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
# Don't allow overwriting of native user data.
my %Blacklisted = (
UserID => 1,
UserLogin => 1,
UserPw => 1,
UserFirstname => 1,
UserLastname => 1,
UserFullname => 1,
UserTitle => 1,
ChangeTime => 1,
CreateTime => 1,
ValidID => 1,
);
return 0 if $Blacklisted{ $Param{Key} };
# get current setting
my %User = $Self->GetUserData(
UserID => $Param{UserID},
NoOutOfOffice => 1,
);
# no updated needed
return 1
if defined $User{ $Param{Key} }
&& defined $Param{Value}
&& $User{ $Param{Key} } eq $Param{Value};
$Self->_UserCacheClear( UserID => $Param{UserID} );
# get user preferences config
my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('User::PreferencesModule')
|| 'Kernel::System::User::Preferences::DB';
# get generator preferences module
my $PreferencesObject = $Kernel::OM->Get($GeneratorModule);
# set preferences
return $PreferencesObject->SetPreferences(%Param);
}
sub _UserCacheClear {
my ( $Self, %Param ) = @_;
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need UserID!"
);
return;
}
my $Login = $Self->UserLookup( UserID => $Param{UserID} );
my @CacheKeys;
# Delete cache for all possible FirstnameLastNameOrder settings as this might be overridden by users.
for my $FirstnameLastNameOrder ( 0 .. 8 ) {
for my $ActiveLevel1 ( 0 .. 1 ) {
for my $ActiveLevel2 ( 0 .. 1 ) {
push @CacheKeys, (
"GetUserData::User::${Login}::${ActiveLevel1}::${FirstnameLastNameOrder}::${ActiveLevel2}",
"GetUserData::UserID::$Param{UserID}::${ActiveLevel1}::${FirstnameLastNameOrder}::${ActiveLevel2}",
"UserList::Short::${ActiveLevel1}::${FirstnameLastNameOrder}::${ActiveLevel2}",
"UserList::Long::${ActiveLevel1}::${FirstnameLastNameOrder}::${ActiveLevel2}",
);
}
}
push @CacheKeys, (
'UserLookup::ID::' . $Login,
'UserLookup::Login::' . $Param{UserID},
);
}
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
for my $CacheKey (@CacheKeys) {
$CacheObject->Delete(
Type => $Self->{CacheType},
Key => $CacheKey,
);
}
return 1;
}
=head2 GetPreferences()
get user preferences
my %Preferences = $UserObject->GetPreferences(
UserID => 123,
);
=cut
sub GetPreferences {
my ( $Self, %Param ) = @_;
# get user preferences config
my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('User::PreferencesModule')
|| 'Kernel::System::User::Preferences::DB';
# get generator preferences module
my $PreferencesObject = $Kernel::OM->Get($GeneratorModule);
return $PreferencesObject->GetPreferences(%Param);
}
=head2 SearchPreferences()
search in user preferences
my %UserList = $UserObject->SearchPreferences(
Key => 'UserEmail',
Value => 'email@example.com', # optional, limit to a certain value/pattern
);
=cut
sub SearchPreferences {
my ( $Self, %Param ) = @_;
# get user preferences config
my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('User::PreferencesModule')
|| 'Kernel::System::User::Preferences::DB';
# get generator preferences module
my $PreferencesObject = $Kernel::OM->Get($GeneratorModule);
return $PreferencesObject->SearchPreferences(%Param);
}
=head2 TokenGenerate()
generate a random token
my $Token = $UserObject->TokenGenerate(
UserID => 123,
);
=cut
sub TokenGenerate {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need UserID!"
);
return;
}
my $Token = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString(
Length => 15,
);
# save token in preferences
$Self->SetPreferences(
Key => 'UserToken',
Value => $Token,
UserID => $Param{UserID},
);
return $Token;
}
=head2 TokenCheck()
check password token
my $Valid = $UserObject->TokenCheck(
Token => $Token,
UserID => 123,
);
=cut
sub TokenCheck {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{Token} || !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need Token and UserID!'
);
return;
}
# get preferences token
my %Preferences = $Self->GetPreferences(
UserID => $Param{UserID},
);
# check requested vs. stored token
if ( $Preferences{UserToken} && $Preferences{UserToken} eq $Param{Token} ) {
# reset password token
$Self->SetPreferences(
Key => 'UserToken',
Value => '',
UserID => $Param{UserID},
);
# return true if token is valid
return 1;
}
# return false if token is invalid
return;
}
=begin Internal:
=head2 _UserFullname()
Builds the user fullname based on firstname, lastname and login. The order
can be configured.
my $Fullname = $Object->_UserFullname(
UserFirstname => 'Test',
UserLastname => 'Person',
UserLogin => 'tp',
NameOrder => 0, # optional 0, 1, 2, 3, 4, 5
);
=cut
sub _UserFullname {
my ( $Self, %Param ) = @_;
for my $Needed (qw(UserFirstname UserLastname UserLogin)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $FirstnameLastNameOrder = $Param{NameOrder} || 0;
my $UserFullname;
if ( $FirstnameLastNameOrder eq '0' ) {
$UserFullname = $Param{UserFirstname} . ' '
. $Param{UserLastname};
}
elsif ( $FirstnameLastNameOrder eq '1' ) {
$UserFullname = $Param{UserLastname} . ', '
. $Param{UserFirstname};
}
elsif ( $FirstnameLastNameOrder eq '2' ) {
$UserFullname = $Param{UserFirstname} . ' '
. $Param{UserLastname} . ' ('
. $Param{UserLogin} . ')';
}
elsif ( $FirstnameLastNameOrder eq '3' ) {
$UserFullname = $Param{UserLastname} . ', '
. $Param{UserFirstname} . ' ('
. $Param{UserLogin} . ')';
}
elsif ( $FirstnameLastNameOrder eq '4' ) {
$UserFullname = '(' . $Param{UserLogin}
. ') ' . $Param{UserFirstname}
. ' ' . $Param{UserLastname};
}
elsif ( $FirstnameLastNameOrder eq '5' ) {
$UserFullname = '(' . $Param{UserLogin}
. ') ' . $Param{UserLastname}
. ', ' . $Param{UserFirstname};
}
elsif ( $FirstnameLastNameOrder eq '6' ) {
$UserFullname = $Param{UserLastname} . ' '
. $Param{UserFirstname};
}
elsif ( $FirstnameLastNameOrder eq '7' ) {
$UserFullname = $Param{UserLastname} . ' '
. $Param{UserFirstname} . ' ('
. $Param{UserLogin} . ')';
}
elsif ( $FirstnameLastNameOrder eq '8' ) {
$UserFullname = '(' . $Param{UserLogin}
. ') ' . $Param{UserLastname}
. ' ' . $Param{UserFirstname};
}
elsif ( $FirstnameLastNameOrder eq '9' ) {
$UserFullname = $Param{UserLastname} . $Param{UserFirstname};
}
return $UserFullname;
}
=end Internal:
=cut
=head2 UserLoginExistsCheck()
return 1 if another user with this login (username) already exists
$Exist = $UserObject->UserLoginExistsCheck(
UserLogin => 'Some::UserLogin',
UserID => 1, # optional
);
=cut
sub UserLoginExistsCheck {
my ( $Self, %Param ) = @_;
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
return if !$DBObject->Prepare(
SQL =>
"SELECT $Self->{UserTableUserID} FROM $Self->{UserTable} WHERE $Self->{UserTableUser} = ?",
Bind => [ \$Param{UserLogin} ],
);
# fetch the result
my $Flag;
while ( my @Row = $DBObject->FetchrowArray() ) {
if ( !$Param{UserID} || $Param{UserID} ne $Row[0] ) {
$Flag = 1;
}
}
if ($Flag) {
return 1;
}
return 0;
}
1;
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L).
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.
=cut