1707 lines
51 KiB
Perl
1707 lines
51 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::CustomerUser::LDAP;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Net::LDAP;
|
|
use Net::LDAP::Util qw(escape_filter_value);
|
|
|
|
use Kernel::System::VariableCheck qw(:all);
|
|
|
|
our @ObjectDependencies = (
|
|
'Kernel::Config',
|
|
'Kernel::System::Cache',
|
|
'Kernel::System::DB',
|
|
'Kernel::System::DynamicField',
|
|
'Kernel::System::DynamicField::Backend',
|
|
'Kernel::System::Encode',
|
|
'Kernel::System::Log',
|
|
'Kernel::System::DateTime',
|
|
);
|
|
|
|
sub new {
|
|
my ( $Type, %Param ) = @_;
|
|
|
|
# allocate new hash for object
|
|
my $Self = {};
|
|
bless( $Self, $Type );
|
|
|
|
# check needed data
|
|
for my $Needed (qw( PreferencesObject CustomerUserMap )) {
|
|
$Self->{$Needed} = $Param{$Needed} || die "Got no $Needed!";
|
|
}
|
|
|
|
# max shown user a search list
|
|
$Self->{UserSearchListLimit} = $Self->{CustomerUserMap}->{CustomerUserSearchListLimit} || 200;
|
|
|
|
# get ldap preferences
|
|
$Self->{Die} = 0;
|
|
if ( defined $Self->{CustomerUserMap}->{Params}->{Die} ) {
|
|
$Self->{Die} = $Self->{CustomerUserMap}->{Params}->{Die};
|
|
}
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# params
|
|
if ( $Self->{CustomerUserMap}->{Params}->{Params} ) {
|
|
$Self->{Params} = $Self->{CustomerUserMap}->{Params}->{Params};
|
|
}
|
|
|
|
# Net::LDAP new params
|
|
elsif ( $ConfigObject->Get( 'AuthModule::LDAP::Params' . $Param{Count} ) ) {
|
|
$Self->{Params} = $ConfigObject->Get( 'AuthModule::LDAP::Params' . $Param{Count} );
|
|
}
|
|
else {
|
|
$Self->{Params} = {};
|
|
}
|
|
|
|
# host
|
|
if ( $Self->{CustomerUserMap}->{Params}->{Host} ) {
|
|
$Self->{Host} = $Self->{CustomerUserMap}->{Params}->{Host};
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need CustomerUser->Params->Host in Kernel/Config.pm',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# base dn
|
|
if ( defined $Self->{CustomerUserMap}->{Params}->{BaseDN} ) {
|
|
$Self->{BaseDN} = $Self->{CustomerUserMap}->{Params}->{BaseDN};
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need CustomerUser->Params->BaseDN in Kernel/Config.pm',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# scope
|
|
if ( $Self->{CustomerUserMap}->{Params}->{SSCOPE} ) {
|
|
$Self->{SScope} = $Self->{CustomerUserMap}->{Params}->{SSCOPE};
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need CustomerUser->Params->SSCOPE in Kernel/Config.pm',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# search user
|
|
$Self->{SearchUserDN} = $Self->{CustomerUserMap}->{Params}->{UserDN} || '';
|
|
$Self->{SearchUserPw} = $Self->{CustomerUserMap}->{Params}->{UserPw} || '';
|
|
|
|
# group dn
|
|
$Self->{GroupDN} = $Self->{CustomerUserMap}->{Params}->{GroupDN} || '';
|
|
|
|
# customer key
|
|
if ( $Self->{CustomerUserMap}->{CustomerKey} ) {
|
|
$Self->{CustomerKey} = $Self->{CustomerUserMap}->{CustomerKey};
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need CustomerUser->CustomerKey in Kernel/Config.pm',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# customer id
|
|
if ( $Self->{CustomerUserMap}->{CustomerID} ) {
|
|
$Self->{CustomerID} = $Self->{CustomerUserMap}->{CustomerID};
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need CustomerUser->CustomerID in Kernel/Config.pm',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# ldap filter always used
|
|
$Self->{AlwaysFilter} = $Self->{CustomerUserMap}->{Params}->{AlwaysFilter} || '';
|
|
|
|
$Self->{ExcludePrimaryCustomerID} = $Self->{CustomerUserMap}->{CustomerUserExcludePrimaryCustomerID} || 0;
|
|
$Self->{SearchPrefix} = $Self->{CustomerUserMap}->{CustomerUserSearchPrefix};
|
|
if ( !defined $Self->{SearchPrefix} ) {
|
|
$Self->{SearchPrefix} = '';
|
|
}
|
|
$Self->{SearchSuffix} = $Self->{CustomerUserMap}->{CustomerUserSearchSuffix};
|
|
if ( !defined $Self->{SearchSuffix} ) {
|
|
$Self->{SearchSuffix} = '*';
|
|
}
|
|
|
|
# charset settings
|
|
$Self->{SourceCharset} = $Self->{CustomerUserMap}->{Params}->{SourceCharset} || '';
|
|
|
|
# set cache type
|
|
$Self->{CacheType} = 'CustomerUser' . $Param{Count};
|
|
|
|
# create cache object, but only if CacheTTL is set in customer config
|
|
if ( $Self->{CustomerUserMap}->{CacheTTL} ) {
|
|
$Self->{CacheObject} = $Kernel::OM->Get('Kernel::System::Cache');
|
|
}
|
|
|
|
# get valid filter if used
|
|
$Self->{ValidFilter} = $Self->{CustomerUserMap}->{CustomerUserValidFilter} || '';
|
|
|
|
# connect first if Die is enabled, make sure that connection is possible, else die
|
|
if ( $Self->{Die} ) {
|
|
return if !$Self->_Connect();
|
|
}
|
|
|
|
# fetch names of configured dynamic fields
|
|
my @DynamicFieldMapEntries = grep { $_->[5] eq 'dynamic_field' } @{ $Self->{CustomerUserMap}->{Map} };
|
|
$Self->{ConfiguredDynamicFieldNames} = { map { $_->[2] => 1 } @DynamicFieldMapEntries };
|
|
|
|
return $Self;
|
|
}
|
|
|
|
sub _Connect {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# return if connection is already open
|
|
return 1 if $Self->{LDAP};
|
|
|
|
# ldap connect and bind (maybe with SearchUserDN and SearchUserPw)
|
|
$Self->{LDAP} = Net::LDAP->new( $Self->{Host}, %{ $Self->{Params} } );
|
|
|
|
if ( !$Self->{LDAP} ) {
|
|
if ( $Self->{Die} ) {
|
|
die "Can't connect to $Self->{Host}: $@";
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can't connect to $Self->{Host}: $@",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
my $Result;
|
|
if ( $Self->{SearchUserDN} && $Self->{SearchUserPw} ) {
|
|
$Result = $Self->{LDAP}->bind(
|
|
dn => $Self->{SearchUserDN},
|
|
password => $Self->{SearchUserPw},
|
|
);
|
|
}
|
|
else {
|
|
$Result = $Self->{LDAP}->bind();
|
|
}
|
|
|
|
if ( $Result->code() ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'First bind failed! ' . $Result->error(),
|
|
);
|
|
$Self->{LDAP}->disconnect();
|
|
return;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub CustomerName {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{UserLogin} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserLogin!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
# build filter
|
|
my $Filter = "($Self->{CustomerKey}=" . escape_filter_value( $Param{UserLogin} ) . ')';
|
|
|
|
# prepare filter
|
|
if ( $Self->{AlwaysFilter} ) {
|
|
$Filter = "(&$Filter$Self->{AlwaysFilter})";
|
|
}
|
|
|
|
# check cache
|
|
if ( $Self->{CacheObject} ) {
|
|
my $Name = $Self->{CacheObject}->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => 'CustomerName::' . $Param{UserLogin},
|
|
);
|
|
return $Name if defined $Name;
|
|
}
|
|
|
|
# create ldap connect
|
|
return if !$Self->_Connect();
|
|
|
|
# perform user search
|
|
my $Result = $Self->{LDAP}->search(
|
|
base => $Self->{BaseDN},
|
|
scope => $Self->{SScope},
|
|
filter => $Filter,
|
|
sizelimit => $Self->{UserSearchListLimit},
|
|
attrs => $Self->{CustomerUserMap}->{CustomerUserNameFields},
|
|
);
|
|
|
|
if ( $Result->code() ) {
|
|
if ( $Result->code() == 4 ) {
|
|
|
|
# Result code 4 (LDAP_SIZELIMIT_EXCEEDED) is normal if there
|
|
# are more items in LDAP than search limit defined in OTRS or
|
|
# in LDAP server. Avoid spamming logs with such errors.
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'debug',
|
|
Message => 'LDAP size limit exceeded (' . $Result->error() . ').',
|
|
);
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Search failed! ' . $Result->error(),
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
my %NameParts;
|
|
|
|
for my $Entry ( $Result->all_entries() ) {
|
|
|
|
for my $Field ( @{ $Self->{CustomerUserMap}->{CustomerUserNameFields} } ) {
|
|
|
|
if ( defined $Entry->get_value($Field) ) {
|
|
$NameParts{$Field} = $Self->_ConvertFrom( $Entry->get_value($Field) );
|
|
}
|
|
}
|
|
}
|
|
|
|
# fetch dynamic field values, if configured
|
|
my @DynamicFieldCustomerUserNameFields = grep { exists $Self->{ConfiguredDynamicFieldNames}->{$_} }
|
|
@{ $Self->{CustomerUserMap}->{CustomerUserNameFields} };
|
|
if (@DynamicFieldCustomerUserNameFields) {
|
|
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
|
|
|
|
DYNAMICFIELDNAME:
|
|
for my $DynamicFieldName (@DynamicFieldCustomerUserNameFields) {
|
|
my $DynamicFieldConfig = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldGet(
|
|
Name => $DynamicFieldName,
|
|
);
|
|
next DYNAMICFIELDNAME if !IsHashRefWithData($DynamicFieldConfig);
|
|
|
|
my $Value = $DynamicFieldBackendObject->ValueGet(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
ObjectName => $Param{UserLogin},
|
|
);
|
|
|
|
next DYNAMICFIELDNAME if !defined $Value;
|
|
|
|
if ( !IsArrayRefWithData($Value) ) {
|
|
$Value = [$Value];
|
|
}
|
|
|
|
my @RenderedValues;
|
|
|
|
VALUE:
|
|
for my $CurrentValue ( @{$Value} ) {
|
|
next VALUE if !defined $CurrentValue || !length $CurrentValue;
|
|
|
|
my $RenderedValue = $DynamicFieldBackendObject->ReadableValueRender(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
Value => $CurrentValue,
|
|
);
|
|
|
|
next VALUE if !IsHashRefWithData($RenderedValue) || !defined $RenderedValue->{Value};
|
|
|
|
push @RenderedValues, $RenderedValue->{Value};
|
|
}
|
|
|
|
$NameParts{$DynamicFieldName} = join ' ', @RenderedValues;
|
|
}
|
|
}
|
|
|
|
# assemble name
|
|
my @NameParts;
|
|
CUSTOMERUSERNAMEFIELD:
|
|
for my $CustomerUserNameField ( @{ $Self->{CustomerUserMap}->{CustomerUserNameFields} } ) {
|
|
next CUSTOMERUSERNAMEFIELD
|
|
if !exists $NameParts{$CustomerUserNameField}
|
|
|| !defined $NameParts{$CustomerUserNameField}
|
|
|| !length $NameParts{$CustomerUserNameField};
|
|
push @NameParts, $NameParts{$CustomerUserNameField};
|
|
}
|
|
|
|
my $JoinCharacter = $Self->{CustomerUserMap}->{CustomerUserNameFieldsJoin} // ' ';
|
|
my $Name = join $JoinCharacter, @NameParts;
|
|
|
|
# cache request
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => 'CustomerName::' . $Param{UserLogin},
|
|
Value => $Name,
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
return $Name;
|
|
}
|
|
|
|
sub CustomerSearch {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
if ( $Param{CustomerIDRaw} ) {
|
|
$Param{CustomerID} = $Param{CustomerIDRaw};
|
|
}
|
|
|
|
# check needed stuff
|
|
if ( !$Param{Search} && !$Param{UserLogin} && !$Param{PostMasterSearch} && !$Param{CustomerID} )
|
|
{
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Search, UserLogin, PostMasterSearch or CustomerID!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
# build filter
|
|
my $Filter = '';
|
|
if ( $Param{Search} ) {
|
|
|
|
my $Count = 0;
|
|
my @Parts = split( /\+/, $Param{Search}, 6 );
|
|
for my $Part (@Parts) {
|
|
|
|
$Part = $Self->{SearchPrefix} . $Part . $Self->{SearchSuffix};
|
|
$Part =~ s/(\%+)/\%/g;
|
|
$Part =~ s/(\*+)\*/*/g;
|
|
$Count++;
|
|
|
|
# remove dynamic field names that are configured in CustomerUserSearchFields
|
|
# as they cannot be retrieved here
|
|
my @CustomerUserSearchFields = grep { !exists $Self->{ConfiguredDynamicFieldNames}->{$_} }
|
|
@{ $Self->{CustomerUserMap}->{CustomerUserSearchFields} };
|
|
|
|
if (@CustomerUserSearchFields) {
|
|
|
|
# quote LDAP filter value but keep asterisks unescaped (wildcard)
|
|
$Part =~ s/\*/encodedasterisk20160930/g;
|
|
$Part = escape_filter_value( $Self->_ConvertTo($Part) );
|
|
$Part =~ s/encodedasterisk20160930/*/g;
|
|
|
|
$Filter .= '(|';
|
|
for my $Field (@CustomerUserSearchFields) {
|
|
$Filter .= "($Field=" . $Part . ')';
|
|
}
|
|
$Filter .= ')';
|
|
}
|
|
else {
|
|
|
|
# quote LDAP filter value but keep asterisks unescaped (wildcard)
|
|
$Part =~ s/\*/encodedasterisk20160930/g;
|
|
$Part = escape_filter_value($Part);
|
|
$Part =~ s/encodedasterisk20160930/*/g;
|
|
|
|
$Filter .= "($Self->{CustomerKey}=" . $Part . ')';
|
|
}
|
|
}
|
|
|
|
if ( $Count > 1 ) {
|
|
$Filter = "(&$Filter)";
|
|
}
|
|
}
|
|
elsif ( $Param{PostMasterSearch} ) {
|
|
|
|
# remove dynamic field names that are configured in CustomerUserPostMasterSearchFields
|
|
# as they cannot be retrieved here
|
|
my @CustomerUserPostMasterSearchFields = grep { !exists $Self->{ConfiguredDynamicFieldNames}->{$_} }
|
|
@{ $Self->{CustomerUserMap}->{CustomerUserPostMasterSearchFields} };
|
|
|
|
if (@CustomerUserPostMasterSearchFields) {
|
|
|
|
# quote LDAP filter value but keep asterisks unescaped (wildcard)
|
|
$Param{PostMasterSearch} =~ s/\*/encodedasterisk20160930/g;
|
|
$Param{PostMasterSearch} = escape_filter_value( $Param{PostMasterSearch} );
|
|
$Param{PostMasterSearch} =~ s/encodedasterisk20160930/*/g;
|
|
|
|
$Filter = '(|';
|
|
for my $Field (@CustomerUserPostMasterSearchFields) {
|
|
$Filter .= "($Field=$Param{PostMasterSearch})";
|
|
}
|
|
$Filter .= ')';
|
|
}
|
|
}
|
|
elsif ( $Param{UserLogin} ) {
|
|
$Filter = "($Self->{CustomerKey}=" . escape_filter_value( $Param{UserLogin} ) . ')';
|
|
}
|
|
elsif ( $Param{CustomerID} ) {
|
|
$Filter = "($Self->{CustomerID}=" . escape_filter_value( $Param{CustomerID} ) . ')';
|
|
}
|
|
|
|
# prepare filter
|
|
if ( $Self->{AlwaysFilter} ) {
|
|
$Filter = "(&$Filter$Self->{AlwaysFilter})";
|
|
}
|
|
|
|
# add valid filter
|
|
if ( $Self->{ValidFilter} ) {
|
|
$Filter = "(&$Filter$Self->{ValidFilter})";
|
|
}
|
|
|
|
# check cache
|
|
my $CacheKey = join '::', map { $_ . '=' . $Param{$_} } sort keys %Param;
|
|
|
|
if ( $Self->{CacheObject} ) {
|
|
my $Users = $Self->{CacheObject}->Get(
|
|
Type => $Self->{CacheType} . '_CustomerSearch',
|
|
Key => $CacheKey,
|
|
);
|
|
return %{$Users} if ref $Users eq 'HASH';
|
|
}
|
|
|
|
# create ldap connect
|
|
return if !$Self->_Connect();
|
|
|
|
my $CustomerUserListFields = $Self->{CustomerUserMap}->{CustomerUserListFields};
|
|
|
|
# remove dynamic field names that are configured in CustomerUserListFields
|
|
# as they cannot be handled here
|
|
my @CustomerUserListFieldsWithoutDynamicFields
|
|
= grep { !exists $Self->{ConfiguredDynamicFieldNames}->{$_} } @{$CustomerUserListFields};
|
|
|
|
# combine needed attrs
|
|
my @Attributes = ( @CustomerUserListFieldsWithoutDynamicFields, $Self->{CustomerKey} );
|
|
|
|
# perform user search
|
|
my $Result = $Self->{LDAP}->search(
|
|
base => $Self->{BaseDN},
|
|
scope => $Self->{SScope},
|
|
filter => $Filter,
|
|
sizelimit => $Param{Limit} || $Self->{UserSearchListLimit},
|
|
attrs => \@Attributes,
|
|
);
|
|
|
|
# log ldap errors
|
|
if ( $Result->code() ) {
|
|
if ( $Result->code() == 4 ) {
|
|
|
|
# Result code 4 (LDAP_SIZELIMIT_EXCEEDED) is normal if there
|
|
# are more items in LDAP than search limit defined in OTRS or
|
|
# in LDAP server. Avoid spamming logs with such errors.
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'debug',
|
|
Message => 'LDAP size limit exceeded (' . $Result->error() . ').',
|
|
);
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Search failed! ' . $Result->error(),
|
|
);
|
|
}
|
|
}
|
|
|
|
# dynamic field handling
|
|
my @CustomerUserListFieldsDynamicFields
|
|
= grep { exists $Self->{ConfiguredDynamicFieldNames}->{$_} } @{$CustomerUserListFields};
|
|
my %CustomerUserListFieldsDynamicFields = map { $_ => 1 } @CustomerUserListFieldsDynamicFields;
|
|
|
|
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
|
|
|
|
my $DynamicFieldConfigs = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
|
|
ObjectType => 'CustomerUser',
|
|
Valid => 1,
|
|
);
|
|
my %DynamicFieldConfigsByName = map { $_->{Name} => $_ } @{$DynamicFieldConfigs};
|
|
|
|
my %Users;
|
|
for my $Entry ( $Result->all_entries() ) {
|
|
|
|
my $CustomerString = '';
|
|
|
|
my $CustomerKey;
|
|
if ( defined $Entry->get_value( $Self->{CustomerKey} ) ) {
|
|
$CustomerKey = $Self->_ConvertFrom( $Entry->get_value( $Self->{CustomerKey} ) );
|
|
}
|
|
|
|
FIELD:
|
|
for my $Field ( @{$CustomerUserListFields} ) {
|
|
|
|
# dynamic field value
|
|
if ( $CustomerUserListFieldsDynamicFields{$Field} ) {
|
|
next FIELD if !defined $CustomerKey;
|
|
next FIELD if !exists $DynamicFieldConfigsByName{$Field};
|
|
|
|
my $Value = $DynamicFieldBackendObject->ValueGet(
|
|
DynamicFieldConfig => $DynamicFieldConfigsByName{$Field},
|
|
ObjectName => $CustomerKey,
|
|
);
|
|
|
|
next FIELD if !defined $Value;
|
|
|
|
if ( !IsArrayRefWithData($Value) ) {
|
|
$Value = [$Value];
|
|
}
|
|
|
|
my @Values;
|
|
|
|
VALUE:
|
|
for my $CurrentValue ( @{$Value} ) {
|
|
next VALUE if !defined $CurrentValue || !length $CurrentValue;
|
|
|
|
my $ReadableValue = $DynamicFieldBackendObject->ReadableValueRender(
|
|
DynamicFieldConfig => $DynamicFieldConfigsByName{$Field},
|
|
Value => $CurrentValue,
|
|
);
|
|
|
|
next VALUE if !IsHashRefWithData($ReadableValue) || !defined $ReadableValue->{Value};
|
|
|
|
my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
|
|
DynamicFieldConfig => $DynamicFieldConfigsByName{$Field},
|
|
Behavior => 'IsACLReducible',
|
|
);
|
|
if ($IsACLReducible) {
|
|
my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
|
|
DynamicFieldConfig => $DynamicFieldConfigsByName{$Field},
|
|
);
|
|
|
|
if (
|
|
IsHashRefWithData($PossibleValues)
|
|
&& defined $PossibleValues->{ $ReadableValue->{Value} }
|
|
)
|
|
{
|
|
$ReadableValue->{Value} = $PossibleValues->{ $ReadableValue->{Value} };
|
|
}
|
|
}
|
|
|
|
push @Values, $ReadableValue->{Value};
|
|
}
|
|
|
|
$CustomerString .= ( join ' ', @Values ) . ' ';
|
|
|
|
next FIELD;
|
|
}
|
|
|
|
my $Value = $Self->_ConvertFrom( $Entry->get_value($Field) );
|
|
|
|
if ($Value) {
|
|
if ( $Field =~ /^targetaddress$/i ) {
|
|
$Value =~ s/SMTP:(.*)/$1/;
|
|
}
|
|
$CustomerString .= $Value . ' ';
|
|
}
|
|
}
|
|
|
|
$CustomerString =~ s/^(.*)\s(.+?\@.+?\..+?)(\s|)$/"$1" <$2>/;
|
|
|
|
if ( defined $CustomerKey ) {
|
|
$Users{$CustomerKey} = $CustomerString;
|
|
}
|
|
}
|
|
|
|
# check if user need to be in a group!
|
|
if ( $Self->{GroupDN} ) {
|
|
|
|
for my $Filter2 ( sort keys %Users ) {
|
|
|
|
my $Result2 = $Self->{LDAP}->search(
|
|
base => $Self->{GroupDN},
|
|
scope => $Self->{SScope},
|
|
filter => 'memberUid=' . escape_filter_value($Filter2),
|
|
sizelimit => $Param{Limit} || $Self->{UserSearchListLimit},
|
|
attrs => ['1.1'],
|
|
);
|
|
|
|
if ( !$Result2->all_entries() ) {
|
|
delete $Users{$Filter2};
|
|
}
|
|
}
|
|
}
|
|
|
|
# cache request
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType} . '_CustomerSearch',
|
|
Key => $CacheKey,
|
|
Value => \%Users,
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
return %Users;
|
|
}
|
|
|
|
sub CustomerSearchDetail {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
if ( ref $Param{SearchFields} ne 'ARRAY' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "SearchFields must be an array reference!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
my $Valid = defined $Param{Valid} ? $Param{Valid} : 1;
|
|
|
|
$Param{Limit} //= '';
|
|
|
|
# Split the search fields in scalar and array fields, before the diffrent handling.
|
|
my @ScalarSearchFields = grep { 'Input' eq $_->{Type} } @{ $Param{SearchFields} };
|
|
my @ArraySearchFields = grep { 'Selection' eq $_->{Type} } @{ $Param{SearchFields} };
|
|
|
|
# Verify that all passed array parameters contain an arrayref.
|
|
ARGUMENT:
|
|
for my $Argument (@ArraySearchFields) {
|
|
if ( !defined $Param{ $Argument->{Name} } ) {
|
|
$Param{ $Argument->{Name} } ||= [];
|
|
|
|
next ARGUMENT;
|
|
}
|
|
|
|
if ( ref $Param{ $Argument->{Name} } ne 'ARRAY' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "$Argument->{Name} must be an array reference!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# Set the default behaviour for the return type.
|
|
my $Result = $Param{Result} || 'ARRAY';
|
|
|
|
# Special handling if the result type is 'COUNT'.
|
|
if ( $Result eq 'COUNT' ) {
|
|
|
|
# Ignore the parameter 'Limit' when result type is 'COUNT'.
|
|
$Param{Limit} = '';
|
|
|
|
# Delete the OrderBy parameter when the result type is 'COUNT'.
|
|
$Param{OrderBy} = [];
|
|
}
|
|
|
|
# Define order table from the search fields.
|
|
my %OrderByTable = map { $_->{Name} => $_->{DatabaseField} } @{ $Param{SearchFields} };
|
|
|
|
for my $Field (@ArraySearchFields) {
|
|
|
|
my $SelectionsData = $Field->{SelectionsData};
|
|
|
|
for my $SelectedValue ( @{ $Param{ $Field->{Name} } } ) {
|
|
|
|
# Check if the selected value for the current field is valid.
|
|
if ( !$SelectionsData->{$SelectedValue} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The selected value $Field->{Name} is not valid!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Build the ldap filter for the diffrent search params.
|
|
my $Filter = '';
|
|
|
|
for my $Field (@ScalarSearchFields) {
|
|
|
|
# Search for scalar fields (wildcards are allowed).
|
|
if ( $Param{ $Field->{Name} } ) {
|
|
|
|
$Param{ $Field->{Name} } =~ s/(\%+)/\%/g;
|
|
$Param{ $Field->{Name} } =~ s/(\*+)\*/*/g;
|
|
|
|
$Filter .= "($Field->{DatabaseField}=" . $Self->_ConvertTo( $Param{ $Field->{Name} } ) . ")";
|
|
}
|
|
}
|
|
|
|
if ($Filter) {
|
|
$Filter = "(&$Filter)";
|
|
}
|
|
|
|
my $ArrayFilter = '';
|
|
|
|
# Special parameter for CustomerIDs from a customer company search result.
|
|
if ( IsArrayRefWithData( $Param{CustomerCompanySearchCustomerIDs} ) ) {
|
|
$ArrayFilter .= '(|';
|
|
for my $OneParam ( @{ $Param{CustomerCompanySearchCustomerIDs} } ) {
|
|
$ArrayFilter .= "($Self->{CustomerID}=" . $Self->_ConvertTo($OneParam) . ")";
|
|
}
|
|
$ArrayFilter .= ')';
|
|
}
|
|
|
|
FIELD:
|
|
for my $Field (@ArraySearchFields) {
|
|
|
|
# Ignore empty lists.
|
|
next FIELD if !@{ $Param{ $Field->{Name} } };
|
|
|
|
$ArrayFilter .= '(|';
|
|
for my $OneParam ( @{ $Param{ $Field->{Name} } } ) {
|
|
$ArrayFilter .= "($Field->{DatabaseField}=" . $Self->_ConvertTo($OneParam) . ")";
|
|
}
|
|
$ArrayFilter .= ')';
|
|
}
|
|
|
|
# Add the array filter fields to the ldap filter.
|
|
if ($ArrayFilter) {
|
|
$Filter = "(&$Filter$ArrayFilter)";
|
|
}
|
|
|
|
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
|
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
|
|
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
|
|
|
|
# Check all configured change dynamic fields, build lookup hash by name.
|
|
my %CustomerUserDynamicFieldName2Config;
|
|
my $CustomerUserDynamicFields = $DynamicFieldObject->DynamicFieldListGet(
|
|
ObjectType => 'CustomerUser',
|
|
);
|
|
for my $DynamicField ( @{$CustomerUserDynamicFields} ) {
|
|
$CustomerUserDynamicFieldName2Config{ $DynamicField->{Name} } = $DynamicField;
|
|
}
|
|
|
|
my $SQLDynamicFieldFrom = '';
|
|
my $SQLDynamicFieldWhere = '';
|
|
my $DynamicFieldJoinCounter = 1;
|
|
|
|
DYNAMICFIELD:
|
|
for my $DynamicField ( @{$CustomerUserDynamicFields} ) {
|
|
|
|
my $SearchParam = $Param{ "DynamicField_" . $DynamicField->{Name} };
|
|
|
|
next DYNAMICFIELD if ( !$SearchParam );
|
|
next DYNAMICFIELD if ( ref $SearchParam ne 'HASH' );
|
|
|
|
my $NeedJoin;
|
|
|
|
for my $Operator ( sort keys %{$SearchParam} ) {
|
|
|
|
my @SearchParams = ( ref $SearchParam->{$Operator} eq 'ARRAY' )
|
|
? @{ $SearchParam->{$Operator} }
|
|
: ( $SearchParam->{$Operator} );
|
|
|
|
my $SQLDynamicFieldWhereSub = '';
|
|
if ($SQLDynamicFieldWhere) {
|
|
$SQLDynamicFieldWhereSub = ' AND (';
|
|
}
|
|
else {
|
|
$SQLDynamicFieldWhereSub = ' (';
|
|
}
|
|
|
|
my $Counter = 0;
|
|
TEXT:
|
|
for my $Text (@SearchParams) {
|
|
next TEXT if ( !defined $Text || $Text eq '' );
|
|
|
|
$Text =~ s/\*/%/gi;
|
|
|
|
# Check search attribute, we do not need to search for '*'.
|
|
next TEXT if $Text =~ /^\%{1,3}$/;
|
|
|
|
my $ValidateSuccess = $DynamicFieldBackendObject->ValueValidate(
|
|
DynamicFieldConfig => $DynamicField,
|
|
Value => $Text,
|
|
UserID => $Param{UserID} || 1,
|
|
);
|
|
if ( !$ValidateSuccess ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Search not executed due to invalid value '"
|
|
. $Text
|
|
. "' on field '"
|
|
. $DynamicField->{Name} . "'!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ($Counter) {
|
|
$SQLDynamicFieldWhereSub .= ' OR ';
|
|
}
|
|
$SQLDynamicFieldWhereSub .= $DynamicFieldBackendObject->SearchSQLGet(
|
|
DynamicFieldConfig => $DynamicField,
|
|
TableAlias => "dfv$DynamicFieldJoinCounter",
|
|
Operator => $Operator,
|
|
SearchTerm => $Text,
|
|
);
|
|
|
|
$Counter++;
|
|
}
|
|
$SQLDynamicFieldWhereSub .= ') ';
|
|
|
|
if ($Counter) {
|
|
$SQLDynamicFieldWhere .= $SQLDynamicFieldWhereSub;
|
|
$NeedJoin = 1;
|
|
}
|
|
}
|
|
|
|
if ($NeedJoin) {
|
|
$SQLDynamicFieldFrom .= "
|
|
INNER JOIN dynamic_field_value dfv$DynamicFieldJoinCounter
|
|
ON (df_obj_id_name.object_id = dfv$DynamicFieldJoinCounter.object_id
|
|
AND dfv$DynamicFieldJoinCounter.field_id = "
|
|
. $DBObject->Quote( $DynamicField->{ID}, 'Integer' ) . ")
|
|
";
|
|
|
|
$DynamicFieldJoinCounter++;
|
|
}
|
|
}
|
|
|
|
# Execute a dynamic field search, if a dynamic field where statement exists.
|
|
if ($SQLDynamicFieldWhere) {
|
|
|
|
my @DynamicFieldUserLogins;
|
|
|
|
# sql uery for the dynamic fields
|
|
my $SQLDynamicField
|
|
= "SELECT DISTINCT(df_obj_id_name.object_name) FROM dynamic_field_obj_id_name df_obj_id_name "
|
|
. $SQLDynamicFieldFrom
|
|
. " WHERE "
|
|
. $SQLDynamicFieldWhere;
|
|
|
|
my $UsedCache;
|
|
|
|
if ( $Self->{CacheObject} ) {
|
|
|
|
my $DynamicFieldSearchCacheData = $Self->{CacheObject}->Get(
|
|
Type => $Self->{CacheType} . '_CustomerSearchDetailDynamicFields',
|
|
Key => $SQLDynamicField,
|
|
);
|
|
|
|
if ( defined $DynamicFieldSearchCacheData ) {
|
|
if ( ref $DynamicFieldSearchCacheData eq 'ARRAY' ) {
|
|
@DynamicFieldUserLogins = @{$DynamicFieldSearchCacheData};
|
|
|
|
# set the used cache flag
|
|
$UsedCache = 1;
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Invalid ref ' . ref($DynamicFieldSearchCacheData) . '!'
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Get the data only from database, if no cache entry exists.
|
|
if ( !$UsedCache ) {
|
|
|
|
return if !$DBObject->Prepare(
|
|
SQL => $SQLDynamicField,
|
|
);
|
|
|
|
while ( my @Row = $DBObject->FetchrowArray() ) {
|
|
push @DynamicFieldUserLogins, $Row[0];
|
|
}
|
|
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType} . '_CustomerSearchDetailDynamicFields',
|
|
Key => $SQLDynamicField,
|
|
Value => \@DynamicFieldUserLogins,
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
}
|
|
|
|
# Add the user logins from the dynamic fields, if a search result exists from the dynamic field search
|
|
# or skip the search and return a emptry array ref, if no user logins exists from the dynamic field search.
|
|
if (@DynamicFieldUserLogins) {
|
|
|
|
my $DynamicFieldUserLoginsFilter = '(|';
|
|
for my $OneParam (@DynamicFieldUserLogins) {
|
|
$DynamicFieldUserLoginsFilter .= "($Self->{CustomerKey}=" . $Self->_ConvertTo($OneParam) . ")";
|
|
}
|
|
$DynamicFieldUserLoginsFilter .= ')';
|
|
|
|
# Add the dynamic field user logins filter to the ldap filter.
|
|
$Filter = "(&$Filter$DynamicFieldUserLoginsFilter)";
|
|
}
|
|
else {
|
|
return $Result eq 'COUNT' ? 0 : [];
|
|
}
|
|
}
|
|
|
|
# Special parameter to exclude some user logins from the search result.
|
|
if ( IsArrayRefWithData( $Param{ExcludeUserLogins} ) ) {
|
|
my $ExcludeUserLoginsFilter = '(&';
|
|
for my $OneParam ( @{ $Param{ExcludeUserLogins} } ) {
|
|
$ExcludeUserLoginsFilter .= "(!($Self->{CustomerKey}=" . $Self->_ConvertTo($OneParam) . "))";
|
|
}
|
|
$ExcludeUserLoginsFilter .= ')';
|
|
|
|
# Add the exclude user logins filter to the ldap filter.
|
|
$Filter = "(&$Filter$ExcludeUserLoginsFilter)";
|
|
}
|
|
|
|
if ( $Self->{AlwaysFilter} ) {
|
|
$Filter = "(&$Filter$Self->{AlwaysFilter})";
|
|
}
|
|
|
|
if ( $Self->{ValidFilter} && $Valid ) {
|
|
$Filter = "(&$Filter$Self->{ValidFilter})";
|
|
}
|
|
|
|
# Default filter for the search, if no filter exists.
|
|
if ( !$Filter ) {
|
|
$Filter = "($Self->{CustomerKey}=*)";
|
|
}
|
|
|
|
# Check if OrderBy contains only unique valid values.
|
|
my %OrderBySeen;
|
|
for my $OrderBy ( @{ $Param{OrderBy} } ) {
|
|
|
|
if ( !$OrderBy || $OrderBySeen{$OrderBy} ) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "OrderBy contains invalid value '$OrderBy' or the value is used more than once!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# Remember the value to check if it appears more than once.
|
|
$OrderBySeen{$OrderBy} = 1;
|
|
}
|
|
|
|
# Check if OrderByDirection array contains only 'Up' or 'Down'.
|
|
DIRECTION:
|
|
for my $Direction ( @{ $Param{OrderByDirection} } ) {
|
|
|
|
# Only 'Up' or 'Down' allowed.
|
|
next DIRECTION if $Direction eq 'Up';
|
|
next DIRECTION if $Direction eq 'Down';
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "OrderByDirection can only contain 'Up' or 'Down'!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# Assemble the ORDER BY clause.
|
|
my @OrderByFields;
|
|
my $OrderByString = '';
|
|
my $Count = 0;
|
|
|
|
ORDERBY:
|
|
for my $OrderBy ( @{ $Param{OrderBy} } ) {
|
|
next ORDERBY if !$OrderByTable{$OrderBy};
|
|
|
|
my $Direction = $Param{OrderByDirection}->[$Count] || 'Down';
|
|
|
|
$OrderByString .= $OrderByTable{$OrderBy} . $Direction;
|
|
|
|
push @OrderByFields, {
|
|
Name => $OrderBy,
|
|
DatabaseField => $OrderByTable{$OrderBy},
|
|
Direction => $Direction,
|
|
};
|
|
}
|
|
continue {
|
|
$Count++;
|
|
}
|
|
|
|
# If there is a possibility that the ordering is not determined
|
|
# we add an descending ordering by id.
|
|
if ( !grep { $_ eq 'UserLogin' } ( @{ $Param{OrderBy} } ) ) {
|
|
push @OrderByFields, {
|
|
Name => 'UserLogin',
|
|
DatabaseField => "$Self->{CustomerKey}",
|
|
Direction => 'Down',
|
|
};
|
|
}
|
|
|
|
my $CacheKey = 'CustomerSearchDetail::' . $Result . $Filter . $Param{Limit} . $OrderByString;
|
|
|
|
if ( $Self->{CacheObject} ) {
|
|
my $CacheData = $Self->{CacheObject}->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
|
|
if ( defined $CacheData ) {
|
|
if ( ref $CacheData eq 'ARRAY' ) {
|
|
return $CacheData;
|
|
}
|
|
elsif ( ref $CacheData eq '' ) {
|
|
return $CacheData;
|
|
}
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Invalid ref ' . ref($CacheData) . '!'
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
return if !$Self->_Connect();
|
|
|
|
# cCmbine needed attributes.
|
|
my @Attributes = map { $_->{DatabaseField} } @OrderByFields;
|
|
|
|
# Perform the ldap user search.
|
|
my $ResultSearch = $Self->{LDAP}->search(
|
|
base => $Self->{BaseDN},
|
|
scope => $Self->{SScope},
|
|
filter => $Filter,
|
|
sizelimit => $Self->{UserSearchListLimit},
|
|
attrs => \@Attributes,
|
|
);
|
|
|
|
if ( $ResultSearch->code() ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => $ResultSearch->error(),
|
|
);
|
|
}
|
|
|
|
my @TmpCustomerUsers;
|
|
for my $Entry ( $ResultSearch->all_entries() ) {
|
|
|
|
my %Data;
|
|
for my $OrderBy (@OrderByFields) {
|
|
|
|
my $FieldValue = $Entry->get_value( $OrderBy->{DatabaseField} );
|
|
$FieldValue = $Self->_ConvertFrom($FieldValue);
|
|
|
|
$Data{ $OrderBy->{Name} } = $FieldValue;
|
|
}
|
|
|
|
push @TmpCustomerUsers, \%Data;
|
|
}
|
|
|
|
# Sort the customer users.
|
|
@TmpCustomerUsers = sort { $Self->_SearchResultSort(@OrderByFields) } @TmpCustomerUsers;
|
|
|
|
my @IDs;
|
|
|
|
# Check if user need to be in a group!
|
|
if ( $Self->{GroupDN} ) {
|
|
|
|
FILTERID:
|
|
for my $Data (@TmpCustomerUsers) {
|
|
|
|
my $ResultGroupDN = $Self->{LDAP}->search(
|
|
base => $Self->{GroupDN},
|
|
scope => $Self->{SScope},
|
|
filter => 'memberUid=' . $Data->{UserLogin},
|
|
sizelimit => $Self->{UserSearchListLimit},
|
|
attrs => ['1.1'],
|
|
);
|
|
|
|
next FILTERID if !$ResultGroupDN->all_entries();
|
|
|
|
push @IDs, $Data->{UserLogin};
|
|
}
|
|
}
|
|
else {
|
|
@IDs = map { $_->{UserLogin} } @TmpCustomerUsers;
|
|
}
|
|
|
|
if ( $Param{Limit} ) {
|
|
splice @IDs, $Param{Limit};
|
|
}
|
|
|
|
if ( $Result eq 'COUNT' ) {
|
|
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => scalar @IDs,
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
return scalar @IDs;
|
|
}
|
|
else {
|
|
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \@IDs,
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
return \@IDs;
|
|
}
|
|
}
|
|
|
|
sub CustomerIDList {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Valid = defined $Param{Valid} ? $Param{Valid} : 1;
|
|
my $SearchTerm = $Param{SearchTerm} || '';
|
|
|
|
my $CacheKey = "CustomerIDList::${Valid}::$SearchTerm";
|
|
|
|
# check cache
|
|
if ( $Self->{CacheObject} ) {
|
|
my $Result = $Self->{CacheObject}->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
return @{$Result} if ref $Result eq 'ARRAY';
|
|
}
|
|
|
|
# prepare filter
|
|
my $Filter = "($Self->{CustomerID}=*)";
|
|
if ($SearchTerm) {
|
|
|
|
my $SearchFilter = $Self->{SearchPrefix} . $SearchTerm . $Self->{SearchSuffix};
|
|
$SearchFilter =~ s/(\%+)/\%/g;
|
|
$SearchFilter =~ s/(\*+)\*/*/g;
|
|
|
|
# quote LDAP filter value but keep asterisks unescaped (wildcard)
|
|
$SearchFilter =~ s/\*/encodedasterisk20160930/g;
|
|
$SearchFilter = escape_filter_value($SearchFilter);
|
|
$SearchFilter =~ s/encodedasterisk20160930/*/g;
|
|
|
|
$Filter = "($Self->{CustomerID}=$SearchFilter)";
|
|
|
|
}
|
|
|
|
if ( $Self->{AlwaysFilter} ) {
|
|
$Filter = "(&$Filter$Self->{AlwaysFilter})";
|
|
}
|
|
|
|
# add valid filter
|
|
if ( $Self->{ValidFilter} && $Valid ) {
|
|
$Filter = "(&$Filter$Self->{ValidFilter})";
|
|
}
|
|
|
|
# create ldap connect
|
|
return if !$Self->_Connect();
|
|
|
|
# combine needed attrs
|
|
my @Attributes = ( $Self->{CustomerKey}, $Self->{CustomerID} );
|
|
|
|
# perform user search
|
|
my $Result = $Self->{LDAP}->search(
|
|
base => $Self->{BaseDN},
|
|
scope => $Self->{SScope},
|
|
filter => $Filter,
|
|
sizelimit => $Self->{UserSearchListLimit},
|
|
attrs => \@Attributes,
|
|
);
|
|
|
|
# log ldap errors
|
|
if ( $Result->code() ) {
|
|
|
|
if ( $Result->code() == 4 ) {
|
|
|
|
# Result code 4 (LDAP_SIZELIMIT_EXCEEDED) is normal if there
|
|
# are more items in LDAP than search limit defined in OTRS or
|
|
# in LDAP server. Avoid spamming logs with such errors.
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'debug',
|
|
Message => 'LDAP size limit exceeded (' . $Result->error() . ').',
|
|
);
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Search failed! ' . $Result->error(),
|
|
);
|
|
}
|
|
}
|
|
|
|
my %Users;
|
|
for my $Entry ( $Result->all_entries() ) {
|
|
|
|
my $FieldValue = $Entry->get_value( $Self->{CustomerID} );
|
|
$FieldValue = defined $FieldValue ? $FieldValue : '';
|
|
|
|
my $KeyValue = $Entry->get_value( $Self->{CustomerKey} );
|
|
$KeyValue = defined $KeyValue ? $KeyValue : '';
|
|
$Users{ $Self->_ConvertFrom($KeyValue) } = $Self->_ConvertFrom($FieldValue);
|
|
}
|
|
|
|
# check if user need to be in a group!
|
|
if ( $Self->{GroupDN} ) {
|
|
for my $Filter2 ( sort keys %Users ) {
|
|
my $Result2 = $Self->{LDAP}->search(
|
|
base => $Self->{GroupDN},
|
|
scope => $Self->{SScope},
|
|
filter => 'memberUid=' . escape_filter_value($Filter2),
|
|
sizelimit => $Self->{UserSearchListLimit},
|
|
attrs => ['1.1'],
|
|
);
|
|
if ( !$Result2->all_entries() ) {
|
|
delete $Users{$Filter2};
|
|
}
|
|
}
|
|
}
|
|
|
|
# make CustomerIDs unique
|
|
my %Tmp;
|
|
@Tmp{ values %Users } = undef;
|
|
my @Result = keys %Tmp;
|
|
|
|
# cache request
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \@Result,
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
return @Result;
|
|
}
|
|
|
|
sub CustomerIDs {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{User} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need User!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check cache
|
|
if ( $Self->{CacheObject} ) {
|
|
my $CustomerIDs = $Self->{CacheObject}->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => "CustomerIDs::$Param{User}",
|
|
);
|
|
return @{$CustomerIDs} if ref $CustomerIDs eq 'ARRAY';
|
|
}
|
|
|
|
# get customer data
|
|
my %Data = $Self->CustomerUserDataGet(
|
|
User => $Param{User},
|
|
);
|
|
|
|
# there are multi customer ids
|
|
my @CustomerIDs;
|
|
if ( $Data{UserCustomerIDs} ) {
|
|
|
|
# used separators
|
|
SEPARATOR:
|
|
for my $Separator ( ';', ',', '|' ) {
|
|
|
|
next SEPARATOR if $Data{UserCustomerIDs} !~ /\Q$Separator\E/;
|
|
|
|
# split it
|
|
my @IDs = split /\Q$Separator\E/, $Data{UserCustomerIDs};
|
|
|
|
for my $ID (@IDs) {
|
|
$ID =~ s/^\s+//g;
|
|
$ID =~ s/\s+$//g;
|
|
push @CustomerIDs, $ID;
|
|
}
|
|
|
|
last SEPARATOR;
|
|
}
|
|
|
|
# fallback if no separator got found
|
|
if ( !@CustomerIDs ) {
|
|
$Data{UserCustomerIDs} =~ s/^\s+//g;
|
|
$Data{UserCustomerIDs} =~ s/\s+$//g;
|
|
push @CustomerIDs, $Data{UserCustomerIDs};
|
|
}
|
|
}
|
|
|
|
# use also the primary customer id
|
|
if ( $Data{UserCustomerID} && !$Self->{ExcludePrimaryCustomerID} ) {
|
|
push @CustomerIDs, $Data{UserCustomerID};
|
|
}
|
|
|
|
# cache request
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => 'CustomerIDs::' . $Param{User},
|
|
Value => \@CustomerIDs,
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
return @CustomerIDs;
|
|
}
|
|
|
|
sub CustomerUserDataGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{User} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need User!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
# perform user search
|
|
my @Attributes;
|
|
ENTRY:
|
|
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
|
|
next ENTRY if $Entry->[5] eq 'dynamic_field';
|
|
push( @Attributes, $Entry->[2] );
|
|
}
|
|
my $Filter = "($Self->{CustomerKey}=" . escape_filter_value( $Param{User} ) . ')';
|
|
|
|
# prepare filter
|
|
if ( $Self->{AlwaysFilter} ) {
|
|
$Filter = "(&$Filter$Self->{AlwaysFilter})";
|
|
}
|
|
|
|
# check cache
|
|
if ( $Self->{CacheObject} ) {
|
|
my $Data = $Self->{CacheObject}->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => 'CustomerUserDataGet::' . $Param{User},
|
|
);
|
|
return %{$Data} if ref $Data eq 'HASH';
|
|
}
|
|
|
|
# create ldap connect
|
|
return if !$Self->_Connect();
|
|
|
|
# perform search
|
|
my $Result = $Self->{LDAP}->search(
|
|
base => $Self->{BaseDN},
|
|
scope => $Self->{SScope},
|
|
filter => $Filter,
|
|
attrs => \@Attributes,
|
|
);
|
|
|
|
# log ldap errors
|
|
if ( $Result->code() ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => $Result->error(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get first entry
|
|
my $Result2 = $Result->entry(0);
|
|
if ( !$Result2 ) {
|
|
return;
|
|
}
|
|
|
|
# get customer user info
|
|
my %Data;
|
|
ENTRY:
|
|
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
|
|
next ENTRY if $Entry->[5] eq 'dynamic_field';
|
|
|
|
my $Value = $Self->_ConvertFrom( $Result2->get_value( $Entry->[2] ) ) || '';
|
|
|
|
if ( $Value && $Entry->[2] =~ /^targetaddress$/i ) {
|
|
$Value =~ s/SMTP:(.*)/$1/;
|
|
}
|
|
|
|
$Data{ $Entry->[0] } = $Value;
|
|
}
|
|
|
|
return if !$Data{UserLogin};
|
|
|
|
# to build the UserMailString
|
|
my $UserMailString = '';
|
|
my @UserMailStringParts;
|
|
|
|
my $CustomerUserListFieldsMap = $Self->{CustomerUserMap}->{CustomerUserListFields};
|
|
if ( !IsArrayRefWithData($CustomerUserListFieldsMap) ) {
|
|
$CustomerUserListFieldsMap = [ 'first_name', 'last_name', 'email', ];
|
|
}
|
|
|
|
for my $Field ( @{$CustomerUserListFieldsMap} ) {
|
|
|
|
my $Value = $Self->_ConvertFrom( $Result2->get_value($Field) ) || '';
|
|
|
|
if ($Value) {
|
|
if ( $Field =~ /^targetaddress$/i ) {
|
|
$Value =~ s/SMTP:(.*)/$1/;
|
|
}
|
|
push @UserMailStringParts, $Value;
|
|
}
|
|
}
|
|
$UserMailString = join ' ', @UserMailStringParts;
|
|
$UserMailString =~ s/^(.*)\s(.+?\@.+?\..+?)(\s|)$/"$1" <$2>/;
|
|
|
|
# add the UserMailString to the data hash
|
|
$Data{UserMailString} = $UserMailString;
|
|
|
|
# compat!
|
|
$Data{UserID} = $Data{UserLogin};
|
|
|
|
# get preferences
|
|
my %Preferences = $Self->GetPreferences( UserID => $Data{UserLogin} );
|
|
|
|
# add last login timestamp
|
|
if ( $Preferences{UserLastLogin} ) {
|
|
|
|
my $DateTimeObject = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
Epoch => $Preferences{UserLastLogin},
|
|
},
|
|
);
|
|
|
|
$Preferences{UserLastLoginTimestamp} = $DateTimeObject->ToString();
|
|
|
|
}
|
|
|
|
# cache request
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => 'CustomerUserDataGet::' . $Param{User},
|
|
Value => { %Data, %Preferences },
|
|
TTL => $Self->{CustomerUserMap}->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
return ( %Data, %Preferences );
|
|
}
|
|
|
|
sub CustomerUserAdd {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check ro/rw
|
|
if ( $Self->{ReadOnly} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Customer backend is read only!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Not supported for this module!'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
sub CustomerUserUpdate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check ro/rw
|
|
if ( $Self->{ReadOnly} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Customer backend is read only!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Not supported for this module!'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
sub SetPassword {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Pw = $Param{PW} || '';
|
|
|
|
# check ro/rw
|
|
if ( $Self->{ReadOnly} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Customer backend is read only!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Not supported for this module!'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
sub GenerateRandomPassword {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# generated passwords are eight characters long by default.
|
|
my $Size = $Param{Size} || 8;
|
|
|
|
# The list of characters that can appear in a randomly generated password.
|
|
# Note that users can put any character into a password they choose themselves.
|
|
my @PwChars = ( 0 .. 9, 'A' .. 'Z', 'a' .. 'z', '-', '_', '!', '@', '#', '$', '%', '^', '&', '*' );
|
|
|
|
# number of characters in the list.
|
|
my $PwCharsLen = scalar(@PwChars);
|
|
|
|
# generate the password.
|
|
my $Password = '';
|
|
for ( my $i = 0; $i < $Size; $i++ ) {
|
|
$Password .= $PwChars[ rand $PwCharsLen ];
|
|
}
|
|
|
|
return $Password;
|
|
}
|
|
|
|
sub SetPreferences {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed params
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserID!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
# cache reset
|
|
if ( $Self->{CacheObject} ) {
|
|
$Self->{CacheObject}->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => "CustomerUserDataGet::$Param{UserID}",
|
|
);
|
|
}
|
|
return $Self->{PreferencesObject}->SetPreferences(%Param);
|
|
}
|
|
|
|
sub GetPreferences {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed params
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserID!'
|
|
);
|
|
return;
|
|
}
|
|
|
|
return $Self->{PreferencesObject}->GetPreferences(%Param);
|
|
}
|
|
|
|
sub SearchPreferences {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
return $Self->{PreferencesObject}->SearchPreferences(%Param);
|
|
}
|
|
|
|
sub _ConvertFrom {
|
|
my ( $Self, $Text ) = @_;
|
|
|
|
return if !defined $Text;
|
|
|
|
if ( !$Self->{SourceCharset} ) {
|
|
return $Text;
|
|
}
|
|
|
|
return $Kernel::OM->Get('Kernel::System::Encode')->Convert(
|
|
Text => $Text,
|
|
From => $Self->{SourceCharset},
|
|
To => 'utf-8',
|
|
);
|
|
}
|
|
|
|
sub _ConvertTo {
|
|
my ( $Self, $Text ) = @_;
|
|
|
|
return if !defined $Text;
|
|
|
|
# get encode object
|
|
my $EncodeObject = $Kernel::OM->Get('Kernel::System::Encode');
|
|
|
|
if ( !$Self->{SourceCharset} ) {
|
|
$EncodeObject->EncodeInput( \$Text );
|
|
return $Text;
|
|
}
|
|
|
|
return $EncodeObject->Convert(
|
|
Text => $Text,
|
|
To => $Self->{SourceCharset},
|
|
From => 'utf-8',
|
|
);
|
|
}
|
|
|
|
sub _CustomerUserCacheClear {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
return if !$Self->{CacheObject};
|
|
|
|
if ( !$Param{UserLogin} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserLogin!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
$Self->{CacheObject}->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => "CustomerUserDataGet::$Param{UserLogin}",
|
|
);
|
|
$Self->{CacheObject}->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => "CustomerName::$Param{UserLogin}",
|
|
);
|
|
|
|
$Self->{CacheObject}->CleanUp(
|
|
Type => $Self->{CacheType} . '_CustomerSearch',
|
|
);
|
|
|
|
$Self->{CacheObject}->CleanUp(
|
|
Type => 'CustomerGroup',
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub _SearchResultSort {
|
|
my ( $Self, @OrderByFields ) = @_;
|
|
|
|
for my $OrderBy (@OrderByFields) {
|
|
my $Compare;
|
|
|
|
if ( $OrderBy->{Direction} && $OrderBy->{Direction} eq 'Up' ) {
|
|
$Compare = lc( $a->{ $OrderBy->{Name} } ) cmp lc( $b->{ $OrderBy->{Name} } );
|
|
}
|
|
else {
|
|
$Compare = lc( $b->{ $OrderBy->{Name} } ) cmp lc( $a->{ $OrderBy->{Name} } );
|
|
}
|
|
return $Compare if $Compare;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub DESTROY {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# take down session
|
|
if ( $Self->{LDAP} ) {
|
|
$Self->{LDAP}->unbind();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
1;
|