# -- # 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::Ticket::TicketACL; use strict; use warnings; use Kernel::System::VariableCheck qw(:all); our $ObjectManagerDisabled = 1; =head1 NAME Kernel::System::Ticket::TicketACL - ticket ACL lib =head1 DESCRIPTION All ticket ACL functions. =head2 TicketAcl() Restricts the Data parameter sent to a subset of it, depending on a group of user defied rules called ACLs. The reduced subset can be access from TicketACLData() if ReturnType parameter is set to: Ticket, Process or ActivityDialog, or in TicketACLActionData(), if ReturnType Action is used. Each ACL can contain different restrictions for different objects the ReturnType parameter defines which object is considered for this restrictions, in the case of the Ticket object a second parameter called ReturnSubtype is needed, to specify the ticket attribute to be restricted, like: Queue, State, Owner, etc. While for the rest of the objects a "-" value must be set. The ReturnType and ReturnSubType must be set according to the Data parameter sent. The rest of the attributes define the matching options for the ACL rules. Example to restrict ticket actions: my $Success = $TicketObject->TicketAcl( Data => { # Values to restrict 1 => AgentTicketZoom, # ... }, Action => 'AgentTicketZoom', # Optional TicketID => 123, # Optional DynamicField => { # Optional DynamicField_NameX => 123, DynamicField_NameZ => 'some value', }, QueueID => 123, # Optional Queue => 'some queue name', # Optional NewQueueID => 123, # Optional, QueueID or NewQueueID can be # used and they both refers to QueueID ServiceID => 123, # Optional Service => 'some service name', # Optional TypeID => 123, Type => 'some ticket type name', # Optional PriorityID => 123, # Optional NewPriorityID => 123, # Optional, PriorityID or NewPriorityID can be # used and they both refers to PriorityID Priority => 'some priority name', # Optional SLAID => 123, SLA => 'some SLA name', # Optional StateID => 123, # Optional NextStateID => 123, # Optional, StateID or NextStateID can be # used and they both refers to StateID State => 'some ticket state name', # Optional OwnerID => 123, # Optional NewOwnerID => 123, # Optional, OwnerID or NewOwnerID can be # used and they both refers to OwnerID Owner => 'some user login' # Optional ResponsibleID => 123, # Optional NewResponsibleID => 123, # Optional, ResponsibleID or NewResposibleID # can be used and they both refers to # ResponsibleID Responsible => 'some user login' # Optional ReturnType => 'Action', # To match Possible, PossibleAdd or # PossibleNot key in ACL ReturnSubType => '-', # To match Possible, PossibleAdd or # PossibleNot sub-key in ACL UserID => 123, # UserID => 1 is not affected by this function CustomerUserID => 'customer login', # UserID or CustomerUserID are mandatory # Process Management Parameters ProcessEntityID => 123, # Optional ActivityEntityID => 123, # Optional ActivityDialogEntityID => 123, # Optional ); or to restrict ticket states: $Success = $TicketObject->TicketAcl( Data => { 1 => 'new', 2 => 'open', # ... }, ReturnType => 'Ticket', ReturnSubType => 'State', UserID => 123, ); returns: $Success = 1, # if an ACL matches, or false otherwise. If ACL modules are configured in the C config key, they are invoked during the call to C. The configuration of a module looks like this: $ConfigObject->{'Ticket::Acl::Module'}->{'TheName'} = { Module => 'Kernel::System::Ticket::Acl::TheAclModule', Checks => ['Owner', 'Queue', 'SLA', 'Ticket'], ReturnType => 'Ticket', ReturnSubType => ['State', 'Service'], }; Each time the C and one of the C entries is identical to the same arguments passed to C, the module of the name in C is loaded, the C method is called on it, and then the C method is called. The C array reference in the configuration controls what arguments are passed. to the C method. Valid keys are C, C, C, C, C, C, C, C, C, C, C, C and C. If any of those are present, the C argument passed to C contains an entry with the same name, and as a value the associated data. The C method can add entries to the C param hash, which are then evaluated along with all other ACL. It should only add entries whose conditionals can be checked with the data specified in the C configuration entry. The return value of the C method is ignored. =cut sub TicketAcl { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!', ); return; } # check needed stuff for my $Needed (qw(ReturnSubType ReturnType Data)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!" ); return; } } # do not execute ACLs if UserID 1 is used return if $Param{UserID} && $Param{UserID} == 1; # get config object my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $ACLs = $ConfigObject->Get('TicketAcl'); my $AclModules = $ConfigObject->Get('Ticket::Acl::Module'); # only execute ACLs if ACL or ACL module is configured if ( !$ACLs && !$AclModules ) { return; } # find out which data we actually need my %ApplicableAclModules; my %RequiredChecks; my $CheckAll = 0; MODULENAME: for my $ModuleName ( sort keys %{ $AclModules // {} } ) { my $Module = $AclModules->{$ModuleName}; if ( $Module->{ReturnType} && $Module->{ReturnType} ne $Param{ReturnType} ) { next MODULENAME; } if ( $Module->{ReturnSubType} ) { if ( ref( $Module->{ReturnSubType} ) eq 'HASH' ) { next MODULENAME if !grep { $Param{ReturnSubType} eq $_ } @{ $Module->{ReturnSubType} }; } else { # a scalar, we hope next MODULENAME if !$Module->{ReturnSubType} eq $Param{ReturnSubType}; } } # here only modules applicable to this ACL invocation remain $ApplicableAclModules{$ModuleName} = $Module; if ( $Module->{Checks} && ref( $Module->{Checks} ) eq 'ARRAY' ) { $RequiredChecks{$_} = 1 for @{ $Module->{Checks} }; } elsif ( $Module->{Checks} ) { $RequiredChecks{ $Module->{Checks} } = 1; } else { $CheckAll = 1; } } return if !%ApplicableAclModules && !$ACLs && !$CheckAll; for my $ACL ( values %{ $ACLs // {} } ) { for my $Source (qw/ Properties PropertiesDatabase/) { for my $Check ( sort keys %{ $ACL->{$Source} } ) { my $CleanedUp = $Check; $CleanedUp =~ s/(?:ID|Name|Login)$//; $CleanedUp =~ s/^(?:Next|New|Old)//; $RequiredChecks{$CleanedUp} = 1; if ( $Check eq 'Ticket' ) { if ( ref( $ACL->{Properties}{$Check} ) eq 'HASH' ) { for my $InnerCheck ( sort keys %{ $ACL->{$Source}{$Check} } ) { $InnerCheck =~ s/(?:ID|Name|Login)$//; $InnerCheck =~ s/^(?:Next|New|Old)//; $RequiredChecks{$InnerCheck} = 1; } } } } } } # gather all required data to be compared against the ACLs my $CheckResult = $Self->_GetChecks( %Param, CheckAll => $CheckAll, RequiredChecks => \%RequiredChecks, ); my %Checks = %{ $CheckResult->{Checks} || {} }; my %ChecksDatabase = %{ $CheckResult->{ChecksDatabase} || {} }; # check ACL configuration my %Acls; if ( $ConfigObject->Get('TicketAcl') ) { %Acls = %{ $ConfigObject->Get('TicketAcl') }; } # check ACL module MODULE: for my $ModuleName ( sort keys %ApplicableAclModules ) { my $Module = $ApplicableAclModules{$ModuleName}; next MODULE if !$Kernel::OM->Get('Kernel::System::Main')->Require( $Module->{Module} ); my $Generic = $Module->{Module}->new(); $Generic->Run( %Param, Acl => \%Acls, Checks => \%Checks, Config => $Module, ); } # get used data my %Data; if ( ref $Param{Data} ) { %Data = %{ $Param{Data} }; } my %NewData; my $UseNewMasterParams = 0; my %NewDefaultActionData; if ( $Param{ReturnType} eq 'Action' ) { if ( !IsHashRefWithData( $Param{Data} ) ) { # use Data if is a string and it is not '-' if ( IsStringWithData( $Param{Data} ) && $Param{Data} ne '-' ) { %Data = ( 1 => $Param{Data} ); } # otherwise use the param Action elsif ( IsStringWithData( $Param{Action} ) ) { %Data = ( 1 => $Param{Action} ); } } my %NewActionData = %Data; # calculate default ticket action ACL data my @ActionsToDelete; my $DefaultActionData = $ConfigObject->Get('TicketACL::Default::Action') || {}; if ( IsHashRefWithData($DefaultActionData) ) { for my $Index ( sort keys %NewActionData ) { my $Action = $NewActionData{$Index}; if ( !$DefaultActionData->{$Index} ) { push @ActionsToDelete, $Action; } } } $Self->{DefaultTicketAclActionData} = \%NewActionData; for my $Action (@ActionsToDelete) { delete $Self->{DefaultTicketAclActionData}->{$Action}; } } # set NewTmpData after Possible Data recalculation on ReturnType Action my %NewTmpData = %Data; # get the debug parameters $Self->{ACLDebug} = $ConfigObject->Get('TicketACL::Debug::Enabled') || 0; $Self->{ACLDebugLogPriority} = $ConfigObject->Get('TicketACL::Debug::LogPriority') || 'debug'; my $ACLDebugConfigFilters = $ConfigObject->Get('TicketACL::Debug::Filter') || {}; for my $FilterName ( sort keys %{$ACLDebugConfigFilters} ) { my %Filter = %{ $ACLDebugConfigFilters->{$FilterName} }; for my $FilterItem ( sort keys %Filter ) { $Self->{ACLDebugFilters}->{$FilterItem} = $Filter{$FilterItem}; } } # check if debug filters apply (ticket) if ( $Self->{ACLDebug} ) { DEBUGFILTER: for my $DebugFilter ( sort keys %{ $Self->{ACLDebugFilters} } ) { next DEBUGFILTER if $DebugFilter eq 'ACLName'; next DEBUGFILTER if !$Self->{ACLDebugFilters}->{$DebugFilter}; if ( $DebugFilter =~ m{]+)>}msx ) { my $TicketParam = $1; if ( defined $ChecksDatabase{Ticket}->{$TicketParam} && $ChecksDatabase{Ticket}->{$TicketParam} && $Self->{ACLDebugFilters}->{$DebugFilter} ne $ChecksDatabase{Ticket}->{$TicketParam} ) { $Self->{ACLDebug} = 0; last DEBUGFILTER; } } } } # remember last ACLDebug state (before ACLs loop) $Self->{ACLDebugRecovery} = $Self->{ACLDebug}; ACLRULES: for my $Acl ( sort keys %Acls ) { # check if debug filters apply (ACL) (only if ACLDebug is active) if ( $Self->{ACLDebugRecovery} && defined $Self->{ACLDebugFilters}->{'ACLName'} && $Self->{ACLDebugFilters}->{'ACLName'} ) { # if not match current ACL disable ACLDebug if ( $Self->{ACLDebugFilters}->{'ACLName'} ne $Acl ) { $Self->{ACLDebug} = 0; } # reenable otherwise (we are sure it was enabled before) else { $Self->{ACLDebug} = 1; } } my %Step = %{ $Acls{$Acl} }; # check force match my $ForceMatch; if ( !IsHashRefWithData( $Step{Properties} ) && !IsHashRefWithData( $Step{PropertiesDatabase} ) ) { $ForceMatch = 1; } my $PropertiesMatch; my $PropertiesMatchTry; my $PropertiesDatabaseMatch; my $PropertiesDatabaseMatchTry; my $UseNewParams = 0; for my $PropertiesHash (qw(Properties PropertiesDatabase)) { my %UsedChecks = %Checks; if ( $PropertiesHash eq 'PropertiesDatabase' ) { %UsedChecks = %ChecksDatabase; } # set match params my $Match = 1; my $MatchTry = 0; for my $Key ( sort keys %{ $Step{$PropertiesHash} } ) { for my $Data ( sort keys %{ $Step{$PropertiesHash}->{$Key} } ) { my $MatchProperty = 0; for my $Item ( @{ $Step{$PropertiesHash}->{$Key}->{$Data} } ) { if ( ref $UsedChecks{$Key}->{$Data} eq 'ARRAY' ) { my $MatchItem = 0; if ( substr( $Item, 0, length '[Not' ) eq '[Not' ) { $MatchItem = 1; } my $MatchedArrayDataItem; ARRAYDATAITEM: for my $ArrayDataItem ( @{ $UsedChecks{$Key}->{$Data} } ) { $MatchedArrayDataItem = $ArrayDataItem; my $LoopMatchResult = $Self->_CompareMatchWithData( Match => $Item, Data => $ArrayDataItem, SingleItem => 0, ); if ( !$LoopMatchResult->{Skip} ) { $MatchItem = $LoopMatchResult->{Match}; last ARRAYDATAITEM; } } if ($MatchItem) { $MatchProperty = 1; # debug log if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' $PropertiesHash:'$Key->$Data' MatchedARRAY ($Item eq $MatchedArrayDataItem)", ); } } } elsif ( defined $UsedChecks{$Key}->{$Data} ) { my $DataItem = $UsedChecks{$Key}->{$Data}; my $MatchResult = $Self->_CompareMatchWithData( Match => $Item, Data => $DataItem, SingleItem => 1 ); if ( $MatchResult->{Match} ) { $MatchProperty = 1; # debug if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' $PropertiesHash:'$Key->$Data' Matched ($Item eq $UsedChecks{$Key}->{$Data})", ); } } } } if ( !$MatchProperty ) { $Match = 0; } $MatchTry = 1; } } # check force option if ($ForceMatch) { $Match = 1; $MatchTry = 1; } if ( $PropertiesHash eq 'Properties' ) { $PropertiesMatch = $Match; $PropertiesMatchTry = $MatchTry; } else { $PropertiesDatabaseMatch = $Match; $PropertiesDatabaseMatchTry = $MatchTry; } # check if properties is missing if ( !IsHashRefWithData( $Step{Properties} ) ) { $PropertiesMatch = $PropertiesDatabaseMatch; $PropertiesMatchTry = $PropertiesDatabaseMatchTry; } # check if properties database is missing if ( !IsHashRefWithData( $Step{PropertiesDatabase} ) ) { $PropertiesDatabaseMatch = $PropertiesMatch; $PropertiesDatabaseMatchTry = $PropertiesMatchTry; } } # the following logic should be applied to calculate if an ACL matches: # if both Properties and PropertiesDatabase match => match # if Properties matches, and PropertiesDatabase does not match => no match # if PropertiesDatabase matches, but Properties does not match => no match # if PropertiesDatabase matches, and Properties is missing => match # if Properties matches, and PropertiesDatabase is missing => match. my $Match; if ( $PropertiesMatch && $PropertiesDatabaseMatch ) { $Match = 1; } my $MatchTry; if ( $PropertiesMatchTry && $PropertiesDatabaseMatchTry ) { $MatchTry = 1; } # debug log if ( $Match && $MatchTry ) { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Matched for return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } my %SpecialReturnTypes = ( Action => 1, Process => 1, ActivityDialog => 1, ); if ( $SpecialReturnTypes{ $Param{ReturnType} } ) { # build new Special ReturnType data hash (ProcessManagement) # for Special ReturnType Step{Possible} if ( ( %Checks || %ChecksDatabase ) && $Match && $MatchTry && $Step{Possible}->{ $Param{ReturnType} } && IsArrayRefWithData( $Step{Possible}->{ $Param{ReturnType} } ) ) { $UseNewParams = 1; # reset return data as it will be filled with just the Possible Items excluded the # ones that are not in the possible section, this is the same as remove all # missing items from the original data %NewTmpData = (); # debug log if ( $Self->{ADLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Used with Possible:'$Param{ReturnType}:$Param{ReturnSubType}'", ); $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Reset return data:'$Param{ReturnType}:$Param{ReturnSubType}''", ); } # possible list for my $ID ( sort keys %Data ) { for my $New ( @{ $Step{Possible}->{ $Param{ReturnType} } } ) { my $MatchResult = $Self->_CompareMatchWithData( Match => $New, Data => $Data{$ID}, SingleItem => 1 ); if ( $MatchResult->{Match} ) { $NewTmpData{$ID} = $Data{$ID}; if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Possible param '$Data{$ID}' added to return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } else { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Possible param '$Data{$ID}' skipped from return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } } # for Special ReturnType Step{PossibleAdd} if ( ( %Checks || %ChecksDatabase ) && $Match && $MatchTry && $Step{PossibleAdd}->{ $Param{ReturnType} } && IsArrayRefWithData( $Step{PossibleAdd}->{ $Param{ReturnType} } ) ) { $UseNewParams = 1; # debug log if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Used with PossibleAdd:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } # possible add list for my $ID ( sort keys %Data ) { for my $New ( @{ $Step{PossibleAdd}->{ $Param{ReturnType} } } ) { my $MatchResult = $Self->_CompareMatchWithData( Match => $New, Data => $Data{$ID}, SingleItem => 1 ); if ( $MatchResult->{Match} ) { $NewTmpData{$ID} = $Data{$ID}; if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleAdd param '$Data{$ID}' added to return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } else { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleAdd param '$Data{$ID}' skipped from return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } } # for Special Step{PossibleNot} if ( ( %Checks || %ChecksDatabase ) && $Match && $MatchTry && $Step{PossibleNot}->{ $Param{ReturnType} } && IsArrayRefWithData( $Step{PossibleNot}->{ $Param{ReturnType} } ) ) { $UseNewParams = 1; # debug log if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Used with PossibleNot:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } # not possible list for my $ID ( sort keys %Data ) { my $Match = 1; for my $New ( @{ $Step{PossibleNot}->{ $Param{ReturnType} } } ) { my $LoopMatchResult = $Self->_CompareMatchWithData( Match => $New, Data => $Data{$ID}, SingleItem => 1 ); if ( $LoopMatchResult->{Match} ) { $Match = 0; } } if ( !$Match ) { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleNot param '$Data{$ID}' removed from return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } if ( $NewTmpData{$ID} ) { delete $NewTmpData{$ID}; } } else { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleNot param '$Data{$ID}' leaved for return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } } elsif ( $Param{ReturnType} eq 'Ticket' ) { # build new ticket data hash # Step Ticket Possible (Resets White list) if ( ( %Checks || %ChecksDatabase ) && $Match && $MatchTry && $Step{Possible}->{Ticket}->{ $Param{ReturnSubType} } ) { $UseNewParams = 1; # reset return data as it will be filled with just the Possible Items excluded the ones # that are not in the possible section, this is the same as remove all missing items from # the original data %NewTmpData = (); # debug log if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Used with Possible:'$Param{ReturnType}:$Param{ReturnSubType}'", ); $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Reset return data:'$Param{ReturnType}:$Param{ReturnSubType}''", ); } # possible list for my $ID ( sort keys %Data ) { for my $New ( @{ $Step{Possible}->{Ticket}->{ $Param{ReturnSubType} } } ) { my $MatchResult = $Self->_CompareMatchWithData( Match => $New, Data => $Data{$ID}, SingleItem => 1 ); if ( $MatchResult->{Match} ) { $NewTmpData{$ID} = $Data{$ID}; if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Possible param '$Data{$ID}' added to return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } else { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Possible param '$Data{$ID}' skipped from return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } } # Step Ticket PossibleAdd (Add new options to the white list) if ( ( %Checks || %ChecksDatabase ) && $Match && $MatchTry && $Step{PossibleAdd}->{Ticket}->{ $Param{ReturnSubType} } ) { $UseNewParams = 1; # debug log if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Used with PossibleAdd:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } # possible add list for my $ID ( sort keys %Data ) { for my $New ( @{ $Step{PossibleAdd}->{Ticket}->{ $Param{ReturnSubType} } } ) { my $MatchResult = $Self->_CompareMatchWithData( Match => $New, Data => $Data{$ID}, SingleItem => 1 ); if ( $MatchResult->{Match} ) { $NewTmpData{$ID} = $Data{$ID}; if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleAdd param '$Data{$ID}' added to return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } else { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleAdd param '$Data{$ID}' skipped from return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } } # Step Ticket PossibleNot (removes options from white list) if ( ( %Checks || %ChecksDatabase ) && $Match && $MatchTry && $Step{PossibleNot}->{Ticket}->{ $Param{ReturnSubType} } ) { $UseNewParams = 1; # debug log if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' Used with PossibleNot:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } # not possible list for my $ID ( sort keys %Data ) { my $Match = 1; for my $New ( @{ $Step{PossibleNot}->{Ticket}->{ $Param{ReturnSubType} } } ) { my $LoopMatchResult = $Self->_CompareMatchWithData( Match => $New, Data => $Data{$ID}, SingleItem => 1 ); if ( $LoopMatchResult->{Match} ) { $Match = 0; } } if ( !$Match ) { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleNot param '$Data{$ID}' removed from return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } if ( $NewTmpData{$ID} ) { delete $NewTmpData{$ID}; } } else { if ( $Self->{ACLDebug} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => $Self->{ACLDebugLogPriority}, Message => "TicketACL '$Acl' PossibleNot param '$Data{$ID}' leaved for return data:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } } # remember to new params if given if ($UseNewParams) { %NewData = %NewTmpData; $UseNewMasterParams = 1; } # return new params if stop after this step if ( $UseNewParams && $Step{StopAfterMatch} ) { $Self->{TicketAclData} = \%NewData; # if we stop after the first match # exit the ACLRULES loop last ACLRULES; } } # return if no new param exists return if !$UseNewMasterParams; $Self->{TicketAclData} = \%NewData; return 1; } =head2 TicketAclData() return the current ACL data hash after TicketAcl() my %Acl = $TicketObject->TicketAclData(); =cut sub TicketAclData { my ( $Self, %Param ) = @_; return %{ $Self->{TicketAclData} || {} }; } =head2 TicketAclActionData() return the current ACL action data hash after TicketAcl() my %AclAction = $TicketObject->TicketAclActionData(); =cut sub TicketAclActionData { my ( $Self, %Param ) = @_; if ( $Self->{TicketAclData} ) { return %{ $Self->{TicketAclData} }; } return %{ $Self->{DefaultTicketActionData} || {} }; } =begin Internal: =cut =head2 _GetChecks() creates two check hashes (one for current data updatable via AJAX refreshes and another for static ticket data stored in the DB) with the required data to use as a basis to match the ACLs my $ChecskResult = $TicketObject->_GetChecks( CheckAll => '1', # Optional RequiredChecks => $RequiredCheckHashRef, # Optional a hash reference with the # attributes to gather: # e. g. User => 1, will fetch all user # information from the database, this data # will be tried to match with current ACLs Action => 'AgentTicketZoom', # Optional TicketID => 123, # Optional DynamicField => { # Optional DynamicField_NameX => 123, DynamicField_NameZ => 'some value', }, QueueID => 123, # Optional Queue => 'some queue name', # Optional ServiceID => 123, # Optional Service => 'some service name', # Optional TypeID => 123, Type => 'some ticket type name', # Optional PriorityID => 123, # Optional NewPriorityID => 123, # Optional, PriorityID or NewPriorityID can be # used and they both refers to PriorityID Priority => 'some priority name', # Optional SLAID => 123, SLA => 'some SLA name', # Optional StateID => 123, # Optional NextStateID => 123, # Optional, StateID or NextStateID can be # used and they both refers to StateID State => 'some ticket state name', # Optional OwnerID => 123, # Optional NewOwnerID => 123, # Optional, OwnerID or NewOwnerID can be # used and they both refers to OwnerID Owner => 'some user login' # Optional ResponsibleID => 123, # Optional NewResponsibleID => 123, # Optional, ResponsibleID or NewResposibleID # can be used and they both refers to # ResponsibleID Responsible => 'some user login' # Optional UserID => 123, # UserID => 1 is not affected by this function CustomerUserID => 'customer login', # UserID or CustomerUserID are mandatory # Process Management Parameters ProcessEntityID => 123, # Optional ActivityEntityID => 123, # Optional ActivityDialogEntityID => 123, # Optional ); returns: $ChecksResult = { Checks => { # ... Ticket => { TicketID => 123, # ... Queue => 'some queue name', QueueID => '123', # ... }, Queue => { Name => 'some queue name', # ... }, # ... }, ChecksDatabase => # ... Ticket => { TicketID => 123, # ... Queue => 'original queue name', QueueID => '456', # ... }, Queue => { Name => 'original queue name', # ... }, # ... }, }; =cut sub _GetChecks { my ( $Self, %Param ) = @_; my $CheckAll = $Param{CheckAll}; my %RequiredChecks = %{ $Param{RequiredChecks} }; # get used interface for process management checks my $Interface = 'AgentInterface'; if ( !$Param{UserID} ) { $Interface = 'CustomerInterface'; } my %Checks; my %ChecksDatabase; if ( $Param{Action} ) { $Checks{Frontend} = { Action => $Param{Action}, }; $ChecksDatabase{Frontend} = { Action => $Param{Action}, }; } # get config object my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # use ticket data if ticket id is given # do that always, even if $RequiredChecks{Ticket} is not that # (because too much stuff depends on it) if ( $Param{TicketID} ) { my %Ticket = $Self->TicketGet( %Param, DynamicFields => 1, ); $Checks{Ticket} = \%Ticket; # keep database ticket data separated since the reference is affected below my %TicketDatabase = %Ticket; $ChecksDatabase{Ticket} = \%TicketDatabase; # get used dynamic fields where Activity and Process Entities IDs are Stored # (ProcessManagement) my $ActivityEntityIDField = $ConfigObject->Get("Process::DynamicFieldProcessManagementActivityID"); my $ProcessEntityIDField = $ConfigObject->Get("Process::DynamicFieldProcessManagementProcessID"); # check for ActivityEntityID if ( $Ticket{ 'DynamicField_' . $ActivityEntityIDField } ) { $ChecksDatabase{Process}->{ActivityEntityID} = $Ticket{ 'DynamicField_' . $ActivityEntityIDField }; } # check for ProcessEntityID if ( $Ticket{ 'DynamicField_' . $ProcessEntityIDField } ) { $ChecksDatabase{Process}->{ProcessEntityID} = $Ticket{ 'DynamicField_' . $ProcessEntityIDField }; } # take over the ChecksDatabase to the Checks hash as basis if ( $ChecksDatabase{Process} && %{ $ChecksDatabase{Process} } ) { my %ProcessDatabase = %{ $ChecksDatabase{Process} }; $Checks{Process} = \%ProcessDatabase; } } # check for ProcessEntityID if set as parameter (ProcessManagement) if ( ( $CheckAll || $RequiredChecks{Process} ) && $Param{ProcessEntityID} ) { $Checks{Process}->{ProcessEntityID} = $Param{ProcessEntityID}; } # check for ActivityDialogEntityID if set as parameter (ProcessManagement) if ( ( $CheckAll || $RequiredChecks{Process} ) && $Param{ActivityDialogEntityID} ) { my $ActivityDialog = $Kernel::OM->Get('Kernel::System::ProcessManagement::ActivityDialog')->ActivityDialogGet( ActivityDialogEntityID => $Param{ActivityDialogEntityID}, Interface => $Interface, ); if ( IsHashRefWithData($ActivityDialog) ) { $Checks{Process}->{ActivityDialogEntityID} = $Param{ActivityDialogEntityID}; } } # check for dynamic fields if ( IsHashRefWithData( $Param{DynamicField} ) ) { $Checks{DynamicField} = $Param{DynamicField}; # update or add dynamic fields information to the ticket check for my $DynamicFieldName ( sort keys %{ $Param{DynamicField} } ) { $Checks{Ticket}->{$DynamicFieldName} = $Param{DynamicField}->{$DynamicFieldName}; } } # always get info from ticket too and set it to the Dynamic Field check hash if the info is # different. this can be done because in the previous step ticket info was updated. but maybe # ticket has more information stored than in the DynamicField parameter. TICKETATTRIBUTE: for my $TicketAttribute ( sort keys %{ $Checks{Ticket} // {} } ) { next TICKETATTRIBUTE if !$TicketAttribute; # check if is a dynamic field with data next TICKETATTRIBUTE if $TicketAttribute !~ m{ \A DynamicField_ }smx; next TICKETATTRIBUTE if !defined $Checks{Ticket}->{$TicketAttribute}; next TICKETATTRIBUTE if !length $Checks{Ticket}->{$TicketAttribute}; if ( ref $Checks{Ticket}->{$TicketAttribute} eq 'ARRAY' && !IsArrayRefWithData( $Checks{Ticket}->{$TicketAttribute} ) ) { next TICKETATTRIBUTE; } # compare if data is different and skip on same data if ( $Checks{DynamicField}->{$TicketAttribute} && !DataIsDifferent( Data1 => $Checks{Ticket}->{$TicketAttribute}, Data2 => $Checks{DynamicField}->{$TicketAttribute}, ) ) { next TICKETATTRIBUTE; } $Checks{DynamicField}->{$TicketAttribute} = $Checks{Ticket}->{$TicketAttribute}; } # also copy the database information to the appropriate hash TICKETATTRIBUTE: for my $TicketAttribute ( sort keys %{ $ChecksDatabase{Ticket} } ) { next TICKETATTRIBUTE if !$TicketAttribute; # check if is a dynamic field with data next TICKETATTRIBUTE if $TicketAttribute !~ m{ \A DynamicField_ }smx; next TICKETATTRIBUTE if !defined $ChecksDatabase{Ticket}->{$TicketAttribute}; next TICKETATTRIBUTE if !length $ChecksDatabase{Ticket}->{$TicketAttribute}; if ( ref $ChecksDatabase{Ticket}->{$TicketAttribute} eq 'ARRAY' && !IsArrayRefWithData( $ChecksDatabase{Ticket}->{$TicketAttribute} ) ) { next TICKETATTRIBUTE; } $ChecksDatabase{DynamicField}->{$TicketAttribute} = $ChecksDatabase{Ticket}->{$TicketAttribute}; } # use user data if ( ( $CheckAll || $RequiredChecks{User} ) && $Param{UserID} ) { my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData( UserID => $Param{UserID}, ); # get group object my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $Param{UserID}, Type => $Type, ); my @GroupNames = sort values %Groups; $User{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $Param{UserID}, ); my @RoleNames = sort values %RoleIDs; $User{Role} = \@RoleNames; $Checks{User} = \%User; $ChecksDatabase{User} = \%User; } # get customer user objects my $CustomerGroupObject = $Kernel::OM->Get('Kernel::System::CustomerGroup'); my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); # use customer user data if ( ( $CheckAll || $RequiredChecks{CustomerUser} ) && $Param{CustomerUserID} ) { my %CustomerUser = $CustomerUserObject->CustomerUserDataGet( User => $Param{CustomerUserID}, ); for my $Type ( @{ $ConfigObject->Get('System::Customer::Permission') } ) { my @Groups = $CustomerGroupObject->GroupMemberList( UserID => $Param{CustomerUserID}, Result => 'Name', Type => $Type, ); $CustomerUser{"Group_$Type"} = \@Groups; } $Checks{CustomerUser} = \%CustomerUser; # update or add customer information to the ticket check $Checks{Ticket}->{CustomerUserID} = $Checks{CustomerUser}->{UserLogin}; $Checks{Ticket}->{CustomerID} = $Checks{CustomerUser}->{UserCustomerID}; } else { if ( IsStringWithData( $Checks{Ticket}->{CustomerUserID} ) ) { # get customer data from the ticket my %CustomerUser = $CustomerUserObject->CustomerUserDataGet( User => $Checks{Ticket}->{CustomerUserID}, ); for my $Type ( @{ $ConfigObject->Get('System::Customer::Permission') } ) { my @Groups = $CustomerGroupObject->GroupMemberList( UserID => $Checks{Ticket}->{CustomerUserID}, Result => 'Name', Type => $Type, ); $CustomerUser{"Group_$Type"} = \@Groups; } $Checks{CustomerUser} = \%CustomerUser; } } # create hash with the ticket information stored in the database if ( ( $CheckAll || $RequiredChecks{CustomerUser} ) && IsStringWithData( $ChecksDatabase{Ticket}->{CustomerUserID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{CustomerUser}->{UserLogin} && $ChecksDatabase{Ticket}->{CustomerUserID} eq $Checks{CustomerUser}->{UserLogin} ) { $ChecksDatabase{CustomerUser} = $Checks{CustomerUser}; } # otherwise complete the data querying the database again else { my %CustomerUser = $CustomerUserObject->CustomerUserDataGet( User => $ChecksDatabase{Ticket}->{CustomerUserID}, ); for my $Type ( @{ $ConfigObject->Get('System::Customer::Permission') } ) { my @Groups = $CustomerGroupObject->GroupMemberList( UserID => $ChecksDatabase{Ticket}->{CustomerUserID}, Result => 'Name', Type => $Type, ); $CustomerUser{"Group_$Type"} = \@Groups; } $ChecksDatabase{CustomerUser} = \%CustomerUser; } } # use queue data (if given) if ( $CheckAll || $RequiredChecks{Queue} ) { # get queue object my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); if ( $Param{NewQueueID} && !$Param{QueueID} ) { $Param{QueueID} = $Param{NewQueueID}; } if ( $Param{QueueID} ) { my %Queue = $QueueObject->QueueGet( ID => $Param{QueueID} ); $Checks{Queue} = \%Queue; # update or add queue information to the ticket check $Checks{Ticket}->{Queue} = $Checks{Queue}->{Name}; $Checks{Ticket}->{QueueID} = $Checks{Queue}->{QueueID}; } elsif ( $Param{Queue} ) { my %Queue = $QueueObject->QueueGet( Name => $Param{Queue} ); $Checks{Queue} = \%Queue; # update or add queue information to the ticket check $Checks{Ticket}->{Queue} = $Checks{Queue}->{Name}; $Checks{Ticket}->{QueueID} = $Checks{Queue}->{QueueID}; } elsif ( !$Param{QueueID} && !$Param{Queue} ) { if ( IsPositiveInteger( $Checks{Ticket}->{QueueID} ) ) { # get queue data from the ticket my %Queue = $QueueObject->QueueGet( ID => $Checks{Ticket}->{QueueID} ); $Checks{Queue} = \%Queue; } } } # create hash with the ticket information stored in the database if ( ( $CheckAll || $RequiredChecks{Queue} ) && IsPositiveInteger( $ChecksDatabase{Ticket}->{QueueID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{Queue}->{QueueID} && $ChecksDatabase{Ticket}->{QueueID} eq $Checks{Queue}->{QueueID} ) { $ChecksDatabase{Queue} = $Checks{Queue}; } # otherwise complete the data querying the database again else { # get queue object my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); my %Queue = $QueueObject->QueueGet( ID => $ChecksDatabase{Ticket}->{QueueID}, ); $ChecksDatabase{Queue} = \%Queue; } } # use service data (if given) if ( $CheckAll || $RequiredChecks{Service} ) { # get service object my $ServiceObject = $Kernel::OM->Get('Kernel::System::Service'); if ( $Param{ServiceID} ) { my %Service = $ServiceObject->ServiceGet( ServiceID => $Param{ServiceID}, UserID => 1, ); $Checks{Service} = \%Service; # update or add service information to the ticket check $Checks{Ticket}->{Service} = $Checks{Service}->{Name}; $Checks{Ticket}->{ServiceID} = $Checks{Service}->{ServiceID}; } elsif ( $Param{Service} ) { my %Service = $ServiceObject->ServiceGet( Name => $Param{Service}, UserID => 1, ); $Checks{Service} = \%Service; # update or add service information to the ticket check $Checks{Ticket}->{Service} = $Checks{Service}->{Name}; $Checks{Ticket}->{ServiceID} = $Checks{Service}->{ServiceID}; } elsif ( !$Param{ServiceID} && !$Param{Service} ) { if ( IsPositiveInteger( $Checks{Ticket}->{ServiceID} ) ) { # get service data from the ticket my %Service = $ServiceObject->ServiceGet( ServiceID => $Checks{Ticket}->{ServiceID}, UserID => 1, ); $Checks{Service} = \%Service; } } # create hash with the ticket information stored in the database if ( IsPositiveInteger( $ChecksDatabase{Ticket}->{ServiceID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{Queue}->{QueueID} && $ChecksDatabase{Ticket}->{ServiceID} eq $Checks{Service}->{ServiceID} ) { $ChecksDatabase{Service} = $Checks{Service}; } # otherwise complete the data querying the database again else { my %Service = $ServiceObject->ServiceGet( ServiceID => $ChecksDatabase{Ticket}->{ServiceID}, UserID => 1, ); $ChecksDatabase{Service} = \%Service; } } } # use type data (if given) if ( $CheckAll || $RequiredChecks{Type} ) { # get type object my $TypeObject = $Kernel::OM->Get('Kernel::System::Type'); if ( $Param{TypeID} ) { my %Type = $TypeObject->TypeGet( ID => $Param{TypeID}, UserID => 1, ); $Checks{Type} = \%Type; # update or add ticket type information to the ticket check $Checks{Ticket}->{Type} = $Checks{Type}->{Name}; $Checks{Ticket}->{TypeID} = $Checks{Type}->{ID}; } elsif ( $Param{Type} ) { # TODO Attention! # # The parameter type can contain not only the wanted ticket type, because also # some other functions in Kernel/System/Ticket.pm use a type parameter, for example # MoveList() etc... These functions could be rewritten to not # use a Type parameter, or the functions that call TicketAcl() could be modified to # not just pass the complete Param-Hash, but instead a new parameter, like FrontEndParameter. # # As a workaround we lookup the TypeList first, and compare if the type parameter # is found in the list, so we can be more sure that it is the type that we want here. # lookup the type list (workaround for described problem) my %TypeList = reverse $TypeObject->TypeList(); # check if type is in the type list (workaround for described problem) if ( $TypeList{ $Param{Type} } ) { my %Type = $TypeObject->TypeGet( Name => $Param{Type}, UserID => 1, ); $Checks{Type} = \%Type; # update or add ticket type information to the ticket check $Checks{Ticket}->{Type} = $Checks{Type}->{Name}; $Checks{Ticket}->{TypeID} = $Checks{Type}->{ID}; } } elsif ( !$Param{TypeID} && !$Param{Type} ) { if ( IsPositiveInteger( $Checks{Ticket}->{TypeID} ) ) { # get type data from the ticket my %Type = $TypeObject->TypeGet( ID => $Checks{Ticket}->{TypeID}, UserID => 1, ); $Checks{Type} = \%Type; } } # create hash with the ticket information stored in the database if ( IsPositiveInteger( $ChecksDatabase{Ticket}->{TypeID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{Type}->{ID} && $ChecksDatabase{Ticket}->{TypeID} eq $Checks{Type}->{ID} ) { $ChecksDatabase{Type} = $Checks{Type}; } # otherwise complete the data querying the database again else { my %Type = $TypeObject->TypeGet( ID => $ChecksDatabase{Ticket}->{TypeID}, UserID => 1, ); $ChecksDatabase{Type} = \%Type; } } } if ( $CheckAll || $RequiredChecks{Priority} ) { # get priority object my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority'); # use priority data (if given) if ( $Param{NewPriorityID} && !$Param{PriorityID} ) { $Param{PriorityID} = $Param{NewPriorityID}; } if ( $Param{PriorityID} ) { my %Priority = $PriorityObject->PriorityGet( PriorityID => $Param{PriorityID}, UserID => 1, ); $Checks{Priority} = \%Priority; # update or add priority information to the ticket check $Checks{Ticket}->{Priority} = $Checks{Priority}->{Name}; $Checks{Ticket}->{PriorityID} = $Checks{Priority}->{ID}; } elsif ( $Param{Priority} ) { my $PriorityID = $PriorityObject->PriorityLookup( Priority => $Param{Priority}, ); my %Priority = $PriorityObject->PriorityGet( PriorityID => $PriorityID, UserID => 1, ); $Checks{Priority} = \%Priority; # update or add priority information to the ticket check $Checks{Ticket}->{Priority} = $Checks{Priority}->{Name}; $Checks{Ticket}->{PriorityID} = $Checks{Priority}->{ID}; } elsif ( !$Param{PriorityID} && !$Param{Priority} ) { if ( IsPositiveInteger( $Checks{Ticket}->{PriorityID} ) ) { # get priority data from the ticket my %Priority = $PriorityObject->PriorityGet( PriorityID => $Checks{Ticket}->{PriorityID}, UserID => 1, ); $Checks{Priority} = \%Priority; } } } # create hash with the ticket information stored in the database if ( IsPositiveInteger( $ChecksDatabase{Ticket}->{PriorityID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{Priority}->{ID} && $ChecksDatabase{Ticket}->{PriorityID} eq $Checks{Priority}->{ID} ) { $ChecksDatabase{Priority} = $Checks{Priority}; } # otherwise complete the data querying the database again else { # get priority object my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority'); # get priority data from the ticket my %Priority = $PriorityObject->PriorityGet( PriorityID => $ChecksDatabase{Ticket}->{PriorityID}, UserID => 1, ); $ChecksDatabase{Priority} = \%Priority; } } # use SLA data (if given) if ( $CheckAll || $RequiredChecks{SLA} ) { # get sla object my $SLAObject = $Kernel::OM->Get('Kernel::System::SLA'); if ( $Param{SLAID} ) { my %SLA = $SLAObject->SLAGet( SLAID => $Param{SLAID}, UserID => 1, ); $Checks{SLA} = \%SLA; # update or add SLA information to the ticket check $Checks{Ticket}->{SLA} = $Checks{SLA}->{Name}; $Checks{Ticket}->{SLAID} = $Checks{SLA}->{SLAID}; } elsif ( $Param{SLA} ) { my $SLAID = $SLAObject->SLALookup( Name => $Param{SLA}, ); my %SLA = $SLAObject->SLAGet( SLAID => $SLAID, UserID => 1, ); $Checks{SLA} = \%SLA; # update or add SLA information to the ticket check $Checks{Ticket}->{SLA} = $Checks{SLA}->{Name}; $Checks{Ticket}->{SLAID} = $Checks{SLA}->{SLAID}; } elsif ( !$Param{SLAID} && !$Param{SLA} ) { if ( IsPositiveInteger( $Checks{Ticket}->{SLAID} ) ) { # get SLA data from the ticket my %SLA = $SLAObject->SLAGet( SLAID => $Checks{Ticket}->{SLAID}, UserID => 1, ); $Checks{SLA} = \%SLA; } } } # create hash with the ticket information stored in the database if ( IsPositiveInteger( $ChecksDatabase{Ticket}->{SLAID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{SLA}->{SLAID} && $ChecksDatabase{Ticket}->{SLAID} eq $Checks{SLA}->{SLAID} ) { $ChecksDatabase{SLA} = $Checks{SLA}; } # otherwise complete the data querying the database again else { my %SLA = $Kernel::OM->Get('Kernel::System::SLA')->SLAGet( SLAID => $ChecksDatabase{Ticket}->{SLAID}, UserID => 1, ); $ChecksDatabase{SLA} = \%SLA; } } # get state object my $StateObject = $Kernel::OM->Get('Kernel::System::State'); # use state data (if given) if ( $CheckAll || $RequiredChecks{State} ) { if ( !$Param{StateID} ) { $Param{StateID} = $Param{NextStateID} || $Param{NewStateID}; } if ( $Param{StateID} ) { my %State = $StateObject->StateGet( ID => $Param{StateID}, UserID => 1, ); $Checks{State} = \%State; # update or add state information to the ticket check $Checks{Ticket}->{State} = $Checks{State}->{Name}; $Checks{Ticket}->{StateID} = $Checks{State}->{ID}; $Checks{Ticket}->{StateType} = $Checks{State}->{TypeName}; } elsif ( $Param{State} ) { my %State = $StateObject->StateGet( Name => $Param{State}, UserID => 1, ); $Checks{State} = \%State; # update or add state information to the ticket check $Checks{Ticket}->{State} = $Checks{State}->{Name}; $Checks{Ticket}->{StateID} = $Checks{State}->{ID}; $Checks{Ticket}->{StateType} = $Checks{State}->{TypeName}; } elsif ( !$Param{StateID} && !$Param{State} ) { if ( IsPositiveInteger( $Checks{Ticket}->{StateID} ) ) { # get state data from the ticket my %State = $StateObject->StateGet( ID => $Checks{Ticket}->{StateID}, UserID => 1, ); $Checks{State} = \%State; } } } # create hash with the ticket information stored in the database if ( IsPositiveInteger( $ChecksDatabase{Ticket}->{StateID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{State}->{ID} && $ChecksDatabase{Ticket}->{StateID} eq $Checks{State}->{ID} ) { $ChecksDatabase{State} = $Checks{State}; } # otherwise complete the data querying the database again else { my %State = $StateObject->StateGet( ID => $ChecksDatabase{Ticket}->{StateID}, UserID => 1, ); $ChecksDatabase{State} = \%State; } } # get needed objects my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); my $UserObject = $Kernel::OM->Get('Kernel::System::User'); # use owner data (if given) if ( $CheckAll || $RequiredChecks{Owner} ) { if ( $Param{NewOwnerID} && !$Param{OwnerID} && defined $Param{NewOwnerType} && $Param{NewOwnerType} eq 'New' ) { $Param{OwnerID} = $Param{NewOwnerID}; } elsif ( $Param{OldOwnerID} && !$Param{OwnerID} && defined $Param{NewOwnerType} && $Param{NewOwnerType} eq 'Old' ) { $Param{OwnerID} = $Param{OldOwnerID}; } if ( $Param{OwnerID} ) { my %Owner = $UserObject->GetUserData( UserID => $Param{OwnerID}, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $Param{OwnerID}, Type => $Type, ); my @GroupNames = sort values %Groups; $Owner{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $Param{OwnerID}, ); my @RoleNames = sort values %RoleIDs; $Owner{Role} = \@RoleNames; $Checks{Owner} = \%Owner; # update or add owner information to the ticket check $Checks{Ticket}->{Owner} = $Checks{Owner}->{UserLogin}; $Checks{Ticket}->{OwnerID} = $Checks{Owner}->{UserID}; } elsif ( $Param{Owner} ) { my $OwnerID = $UserObject->UserLookup( UserLogin => $Param{Owner}, ); my %Owner = $UserObject->GetUserData( UserID => $OwnerID, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $OwnerID, Type => $Type, ); my @GroupNames = sort values %Groups; $Owner{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $OwnerID, ); my @RoleNames = sort values %RoleIDs; $Owner{Role} = \@RoleNames; $Checks{Owner} = \%Owner; # update or add owner information to the ticket check $Checks{Ticket}->{Owner} = $Checks{Owner}->{UserLogin}; $Checks{Ticket}->{OwnerID} = $Checks{Owner}->{UserID}; } elsif ( !$Param{OwnerID} && !$Param{Owner} ) { if ( IsPositiveInteger( $Checks{Ticket}->{OwnerID} ) ) { # get responsible data from the ticket my %Owner = $UserObject->GetUserData( UserID => $Checks{Ticket}->{OwnerID}, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $Checks{Ticket}->{OwnerID}, Type => $Type, ); my @GroupNames = sort values %Groups; $Owner{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $Checks{Ticket}->{OwnerID}, ); my @RoleNames = sort values %RoleIDs; $Owner{Role} = \@RoleNames; $Checks{Owner} = \%Owner; } } } # create hash with the ticket information stored in the database if ( IsPositiveInteger( $ChecksDatabase{Ticket}->{OwnerID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{Owner}->{UserID} && $ChecksDatabase{Ticket}->{OwnerID} eq $Checks{Owner}->{UserID} ) { $ChecksDatabase{Owner} = $Checks{Owner}; } # otherwise complete the data querying the database again else { my %Owner = $UserObject->GetUserData( UserID => $ChecksDatabase{Ticket}->{OwnerID}, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $ChecksDatabase{Ticket}->{OwnerID}, Type => $Type, ); my @GroupNames = sort values %Groups; $Owner{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $ChecksDatabase{Ticket}->{OwnerID}, ); my @RoleNames = sort values %RoleIDs; $Owner{Role} = \@RoleNames; $ChecksDatabase{Owner} = \%Owner; } } # use responsible data (if given) $Param{ResponsibleID} ||= $Param{NewResponsibleID}; if ( $CheckAll || $RequiredChecks{Responsible} ) { if ( $Param{ResponsibleID} ) { my %Responsible = $UserObject->GetUserData( UserID => $Param{ResponsibleID}, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $Param{ResponsibleID}, Type => $Type, ); my @GroupNames = sort values %Groups; $Responsible{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $Param{ResponsibleID}, ); my @RoleNames = sort values %RoleIDs; $Responsible{Role} = \@RoleNames; $Checks{Responsible} = \%Responsible; # update or add responsible information to the ticket check $Checks{Ticket}->{Responsible} = $Checks{Responsible}->{UserLogin}; $Checks{Ticket}->{ResponsibleID} = $Checks{Responsible}->{UserID}; } elsif ( $Param{Responsible} ) { my $ResponsibleID = $UserObject->UserLookup( UserLogin => $Param{Responsible}, ); my %Responsible = $UserObject->GetUserData( UserID => $ResponsibleID, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $ResponsibleID, Type => $Type, ); my @GroupNames = sort values %Groups; $Responsible{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $ResponsibleID, ); my @RoleNames = sort values %RoleIDs; $Responsible{Role} = \@RoleNames; $Checks{Responsible} = \%Responsible; # update or add responsible information to the ticket check $Checks{Ticket}->{Responsible} = $Checks{Responsible}->{UserLogin}; $Checks{Ticket}->{ResponsibleID} = $Checks{Responsible}->{UserID}; } elsif ( !$Param{ResponsibleID} && !$Param{Responsible} ) { if ( IsPositiveInteger( $Checks{Ticket}->{ResponsibleID} ) ) { # get responsible data from the ticket my %Responsible = $UserObject->GetUserData( UserID => $Checks{Ticket}->{ResponsibleID}, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $Checks{Ticket}->{ResponsibleID}, Type => $Type, ); my @GroupNames = sort values %Groups; $Responsible{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $Checks{Ticket}->{ResponsibleID}, ); my @RoleNames = sort values %RoleIDs; $Responsible{Role} = \@RoleNames; $Checks{Responsible} = \%Responsible; } } } # create hash with the ticket information stored in the database if ( IsPositiveInteger( $ChecksDatabase{Ticket}->{ResponsibleID} ) ) { # check if database data matches current data (performance) if ( defined $Checks{Owner}->{UserID} && defined $Checks{Responsible}->{UserID} && $ChecksDatabase{Ticket}->{ResponsibleID} eq $Checks{Responsible}->{UserID} ) { $ChecksDatabase{Responsible} = $Checks{Responsible}; } # otherwise complete the data querying the database again else { my %Responsible = $UserObject->GetUserData( UserID => $ChecksDatabase{Ticket}->{ResponsibleID}, ); for my $Type ( @{ $ConfigObject->Get('System::Permission') } ) { my %Groups = $GroupObject->PermissionUserGet( UserID => $ChecksDatabase{Ticket}->{ResponsibleID}, Type => $Type, ); my @GroupNames = sort values %Groups; $Responsible{"Group_$Type"} = \@GroupNames; } my %RoleIDs = $GroupObject->PermissionUserRoleGet( UserID => $ChecksDatabase{Ticket}->{ResponsibleID}, ); my @RoleNames = sort values %RoleIDs; $Responsible{Role} = \@RoleNames; $ChecksDatabase{Responsible} = \%Responsible; } } # within this function %Param is modified by replacements like: # $Param{PriorityID} = $Param{NewPriorityID} # apparently this changes are not longer needed outside this function and it is not necessary # to return such replacements return { Checks => \%Checks, ChecksDatabase => \%ChecksDatabase, }; } =head2 _CompareMatchWithData() Compares a properties element with the data sent to the ACL, the compare results varies on how the ACL properties where defined including normal, negated, regular expression and negated regular expression comparisons. my $Result = $TicketObject->_CompareMatchWithData( Match => 'a value', # or '[Not]a value', or '[RegExp]val' or '[NotRegExp]val' # or '[Notregexp]val' or '[Notregexp]' Data => 'a value', SingleItem => 1, # or 0, optional, default 0 ); Returns: $Result = { Success => 1, # or false Match => 1, # or false Skip => 1, # or false (in certain cases where SingleItem is set) }; =cut sub _CompareMatchWithData { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(Match Data)) { if ( !defined $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return { Success => 0, }; } } my $Match = $Param{Match}; my $Data = $Param{Data}; # Negated matches requires a different logic. if ( substr( $Match, 0, length '[Not' ) eq '[Not' ) { # Not equal match if ( substr( $Match, 0, length '[Not]' ) eq '[Not]' ) { my $NotValue = substr $Match, length '[Not]'; if ( $NotValue eq $Data ) { return { Success => 1, Match => 0, }; } } # Not reg-exp match case-sensitive. elsif ( substr( $Match, 0, length '[NotRegExp]' ) eq '[NotRegExp]' ) { my $RegExp = substr $Match, length '[NotRegExp]'; if ( $Data =~ /$RegExp/ ) { return { Success => 1, Match => 0, }; } } # Not reg-exp match case-insensitive. elsif ( substr( $Match, 0, length '[Notregexp]' ) eq '[Notregexp]' ) { my $RegExp = substr $Match, length '[Notregexp]'; if ( $Data =~ /$RegExp/i ) { return { Success => 1, Match => 0, }; } } if ( $Param{SingleItem} ) { return { Success => 1, Match => 1, Skip => 0, }; } else { return { Success => 1, Match => 1, Skip => 1, }; } } else { # Equal match. if ( $Match eq $Data ) { return { Success => 1, Match => 1, }; } # Reg-exp match case-sensitive. elsif ( substr( $Match, 0, length '[RegExp]' ) eq '[RegExp]' ) { my $RegExp = substr $Match, length '[RegExp]'; if ( $Data =~ /$RegExp/ ) { return { Success => 1, Match => 1, }; } } # Reg-exp match case-insensitive. elsif ( substr( $Match, 0, length '[regexp]' ) eq '[regexp]' ) { my $RegExp = substr $Match, length '[regexp]'; if ( $Data =~ /$RegExp/i ) { return { Success => 1, Match => 1, }; } } if ( $Param{SingleItem} ) { return { Success => 1, Match => 0, Skip => 0, }; } else { return { Success => 1, Match => 0, Skip => 1, }; } } } 1; =end Internal: =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