# -- # 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