init III
This commit is contained in:
591
Perl OTRS/Kernel/GenericInterface/Provider.pm
Normal file
591
Perl OTRS/Kernel/GenericInterface/Provider.pm
Normal file
@@ -0,0 +1,591 @@
|
||||
# --
|
||||
# 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::GenericInterface::Provider;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use URI::Escape;
|
||||
use Storable;
|
||||
|
||||
use Kernel::GenericInterface::Debugger;
|
||||
use Kernel::GenericInterface::Transport;
|
||||
use Kernel::GenericInterface::Mapping;
|
||||
use Kernel::GenericInterface::Operation;
|
||||
use Kernel::System::GenericInterface::Webservice;
|
||||
use Kernel::System::VariableCheck qw(IsHashRefWithData);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::System::Log',
|
||||
'Kernel::System::GenericInterface::Webservice',
|
||||
'Kernel::GenericInterface::ErrorHandling',
|
||||
);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::GenericInterface::Provider - handler for incoming web service requests.
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 new()
|
||||
|
||||
Don't use the constructor directly, use the ObjectManager instead:
|
||||
|
||||
my $ProviderObject = $Kernel::OM->Get('Kernel::GenericInterface::Provider');
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $Type, %Param ) = @_;
|
||||
|
||||
# Allocate new hash for object.
|
||||
my $Self = {};
|
||||
bless( $Self, $Type );
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
=head2 Run()
|
||||
|
||||
Receives the current incoming web service request, handles it,
|
||||
and returns an appropriate answer based on the requested web service.
|
||||
|
||||
# put this in the handler script
|
||||
$ProviderObject->Run();
|
||||
|
||||
=cut
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# On Microsoft IIS 7.0, $ENV{REQUEST_URI} is not set. See bug#9172.
|
||||
my $RequestURI = $ENV{REQUEST_URI} || $ENV{PATH_INFO};
|
||||
|
||||
#
|
||||
# Locate and verify the desired web service based on the request URI and load its configuration data.
|
||||
#
|
||||
|
||||
# Check RequestURI for a web service by id or name.
|
||||
my %WebserviceGetData;
|
||||
if (
|
||||
$RequestURI
|
||||
&& $RequestURI
|
||||
=~ m{ nph-genericinterface[.]pl/ (?: WebserviceID/ (?<ID> \d+ ) | Webservice/ (?<Name> [^/?]+ ) ) }smx
|
||||
)
|
||||
{
|
||||
%WebserviceGetData = (
|
||||
ID => $+{ID},
|
||||
Name => $+{Name} ? URI::Escape::uri_unescape( $+{Name} ) : undef,
|
||||
);
|
||||
}
|
||||
|
||||
# URI is empty or invalid.
|
||||
if ( !%WebserviceGetData ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Could not determine WebserviceID or Webservice from query string '$RequestURI'",
|
||||
);
|
||||
return; # bail out without Transport, Apache will generate 500 Error
|
||||
}
|
||||
|
||||
# Check if requested web service exists and is valid.
|
||||
my $WebserviceObject = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice');
|
||||
my $WebserviceList = $WebserviceObject->WebserviceList();
|
||||
my %WebserviceListReverse = reverse %{$WebserviceList};
|
||||
if (
|
||||
$WebserviceGetData{Name} && !$WebserviceListReverse{ $WebserviceGetData{Name} }
|
||||
|| $WebserviceGetData{ID} && !$WebserviceList->{ $WebserviceGetData{ID} }
|
||||
)
|
||||
{
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Could not find valid web service for query string '$RequestURI'",
|
||||
);
|
||||
return; # bail out without Transport, Apache will generate 500 Error
|
||||
}
|
||||
|
||||
my $Webservice = $WebserviceObject->WebserviceGet(%WebserviceGetData);
|
||||
if ( !IsHashRefWithData($Webservice) ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message =>
|
||||
"Could not load web service configuration for query string '$RequestURI'",
|
||||
);
|
||||
return; # bail out without Transport, Apache will generate 500 Error
|
||||
}
|
||||
|
||||
# Create a debugger instance which will log the details of this communication entry.
|
||||
my $DebuggerObject = Kernel::GenericInterface::Debugger->new(
|
||||
DebuggerConfig => $Webservice->{Config}->{Debugger},
|
||||
WebserviceID => $Webservice->{ID},
|
||||
CommunicationType => 'Provider',
|
||||
RemoteIP => $ENV{REMOTE_ADDR},
|
||||
);
|
||||
|
||||
if ( ref $DebuggerObject ne 'Kernel::GenericInterface::Debugger' ) {
|
||||
|
||||
return; # bail out without Transport, Apache will generate 500 Error
|
||||
}
|
||||
|
||||
$DebuggerObject->Debug(
|
||||
Summary => 'Communication sequence started',
|
||||
Data => \%ENV,
|
||||
);
|
||||
|
||||
#
|
||||
# Create the network transport backend and read the network request.
|
||||
#
|
||||
|
||||
my $ProviderConfig = $Webservice->{Config}->{Provider};
|
||||
|
||||
$Self->{TransportObject} = Kernel::GenericInterface::Transport->new(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
TransportConfig => $ProviderConfig->{Transport},
|
||||
);
|
||||
|
||||
# Bail out if transport initialization failed.
|
||||
if ( ref $Self->{TransportObject} ne 'Kernel::GenericInterface::Transport' ) {
|
||||
|
||||
return $DebuggerObject->Error(
|
||||
Summary => 'TransportObject could not be initialized',
|
||||
Data => $Self->{TransportObject},
|
||||
);
|
||||
}
|
||||
|
||||
# Combine all data for error handler we got so far.
|
||||
my %HandleErrorData = (
|
||||
DebuggerObject => $DebuggerObject,
|
||||
WebserviceID => $Webservice->{ID},
|
||||
WebserviceConfig => $Webservice->{Config},
|
||||
);
|
||||
|
||||
# Read request content.
|
||||
my $FunctionResult = $Self->{TransportObject}->ProviderProcessRequest();
|
||||
|
||||
# If the request was not processed correctly, send error to client.
|
||||
if ( !$FunctionResult->{Success} ) {
|
||||
|
||||
my $Summary = $FunctionResult->{ErrorMessage} // 'TransportObject returned an error, cancelling Request';
|
||||
return $Self->_HandleError(
|
||||
%HandleErrorData,
|
||||
DataInclude => {},
|
||||
ErrorStage => 'ProviderRequestReceive',
|
||||
Summary => $Summary,
|
||||
Data => $FunctionResult->{Data} // $Summary,
|
||||
);
|
||||
}
|
||||
|
||||
# prepare the data include configuration and payload
|
||||
my %DataInclude = (
|
||||
ProviderRequestInput => $FunctionResult->{Data},
|
||||
);
|
||||
|
||||
my $Operation = $FunctionResult->{Operation};
|
||||
|
||||
$DebuggerObject->Debug(
|
||||
Summary => "Detected operation '$Operation'",
|
||||
);
|
||||
|
||||
#
|
||||
# Map the incoming data based on the configured mapping.
|
||||
#
|
||||
|
||||
my $DataIn = $FunctionResult->{Data};
|
||||
|
||||
$DebuggerObject->Debug(
|
||||
Summary => "Incoming data before mapping",
|
||||
Data => $DataIn,
|
||||
);
|
||||
|
||||
# Decide if mapping needs to be used or not.
|
||||
if (
|
||||
IsHashRefWithData( $ProviderConfig->{Operation}->{$Operation}->{MappingInbound} )
|
||||
)
|
||||
{
|
||||
my $MappingInObject = Kernel::GenericInterface::Mapping->new(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
Operation => $Operation,
|
||||
OperationType => $ProviderConfig->{Operation}->{$Operation}->{Type},
|
||||
MappingConfig =>
|
||||
$ProviderConfig->{Operation}->{$Operation}->{MappingInbound},
|
||||
);
|
||||
|
||||
# If mapping initialization failed, bail out.
|
||||
if ( ref $MappingInObject ne 'Kernel::GenericInterface::Mapping' ) {
|
||||
$DebuggerObject->Error(
|
||||
Summary => 'MappingIn could not be initialized',
|
||||
Data => $MappingInObject,
|
||||
);
|
||||
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
ErrorMessage => $FunctionResult->{ErrorMessage},
|
||||
);
|
||||
}
|
||||
|
||||
# add operation to data for error handler
|
||||
$HandleErrorData{Operation} = $Operation;
|
||||
|
||||
$FunctionResult = $MappingInObject->Map(
|
||||
Data => $DataIn,
|
||||
);
|
||||
|
||||
if ( !$FunctionResult->{Success} ) {
|
||||
|
||||
my $Summary = $FunctionResult->{ErrorMessage} // 'MappingInObject returned an error, cancelling Request';
|
||||
return $Self->_HandleError(
|
||||
%HandleErrorData,
|
||||
DataInclude => \%DataInclude,
|
||||
ErrorStage => 'ProviderRequestMap',
|
||||
Summary => $Summary,
|
||||
Data => $FunctionResult->{Data} // $Summary,
|
||||
);
|
||||
}
|
||||
|
||||
# extend the data include payload
|
||||
$DataInclude{ProviderRequestMapOutput} = $FunctionResult->{Data};
|
||||
|
||||
$DataIn = $FunctionResult->{Data};
|
||||
|
||||
$DebuggerObject->Debug(
|
||||
Summary => "Incoming data after mapping",
|
||||
Data => $DataIn,
|
||||
);
|
||||
}
|
||||
|
||||
#
|
||||
# Execute actual operation.
|
||||
#
|
||||
|
||||
my $OperationObject = Kernel::GenericInterface::Operation->new(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
Operation => $Operation,
|
||||
OperationType => $ProviderConfig->{Operation}->{$Operation}->{Type},
|
||||
WebserviceID => $Webservice->{ID},
|
||||
);
|
||||
|
||||
# If operation initialization failed, bail out.
|
||||
if ( ref $OperationObject ne 'Kernel::GenericInterface::Operation' ) {
|
||||
$DebuggerObject->Error(
|
||||
Summary => 'Operation could not be initialized',
|
||||
Data => $OperationObject,
|
||||
);
|
||||
|
||||
# Set default error message.
|
||||
my $ErrorMessage = 'Unknown error in Operation initialization';
|
||||
|
||||
# Check if we got an error message from the operation and overwrite it.
|
||||
if ( IsHashRefWithData($OperationObject) && $OperationObject->{ErrorMessage} ) {
|
||||
$ErrorMessage = $OperationObject->{ErrorMessage};
|
||||
}
|
||||
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
ErrorMessage => $ErrorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
# add operation object to data for error handler
|
||||
$HandleErrorData{OperationObject} = $OperationObject;
|
||||
|
||||
$FunctionResult = $OperationObject->Run(
|
||||
Data => $DataIn,
|
||||
);
|
||||
|
||||
if ( !$FunctionResult->{Success} ) {
|
||||
|
||||
my $Summary = $FunctionResult->{ErrorMessage} // 'OperationObject returned an error, cancelling Request';
|
||||
return $Self->_HandleError(
|
||||
%HandleErrorData,
|
||||
DataInclude => \%DataInclude,
|
||||
ErrorStage => 'ProviderRequestProcess',
|
||||
Summary => $Summary,
|
||||
Data => $FunctionResult->{Data} // $Summary,
|
||||
);
|
||||
}
|
||||
|
||||
# extend the data include payload
|
||||
$DataInclude{ProviderResponseInput} = $FunctionResult->{Data};
|
||||
|
||||
#
|
||||
# Map the outgoing data based on configured mapping.
|
||||
#
|
||||
|
||||
my $DataOut = $FunctionResult->{Data};
|
||||
|
||||
$DebuggerObject->Debug(
|
||||
Summary => "Outgoing data before mapping",
|
||||
Data => $DataOut,
|
||||
);
|
||||
|
||||
# Decide if mapping needs to be used or not.
|
||||
if (
|
||||
IsHashRefWithData(
|
||||
$ProviderConfig->{Operation}->{$Operation}->{MappingOutbound}
|
||||
)
|
||||
)
|
||||
{
|
||||
my $MappingOutObject = Kernel::GenericInterface::Mapping->new(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
Operation => $Operation,
|
||||
OperationType => $ProviderConfig->{Operation}->{$Operation}->{Type},
|
||||
MappingConfig =>
|
||||
$ProviderConfig->{Operation}->{$Operation}->{MappingOutbound},
|
||||
);
|
||||
|
||||
# If mapping initialization failed, bail out
|
||||
if ( ref $MappingOutObject ne 'Kernel::GenericInterface::Mapping' ) {
|
||||
$DebuggerObject->Error(
|
||||
Summary => 'MappingOut could not be initialized',
|
||||
Data => $MappingOutObject,
|
||||
);
|
||||
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
ErrorMessage => $FunctionResult->{ErrorMessage},
|
||||
);
|
||||
}
|
||||
|
||||
$FunctionResult = $MappingOutObject->Map(
|
||||
Data => $DataOut,
|
||||
DataInclude => \%DataInclude,
|
||||
);
|
||||
|
||||
if ( !$FunctionResult->{Success} ) {
|
||||
|
||||
my $Summary = $FunctionResult->{ErrorMessage} // 'MappingOutObject returned an error, cancelling Request';
|
||||
return $Self->_HandleError(
|
||||
%HandleErrorData,
|
||||
DataInclude => \%DataInclude,
|
||||
ErrorStage => 'ProviderResponseMap',
|
||||
Summary => $Summary,
|
||||
Data => $FunctionResult->{Data} // $Summary,
|
||||
);
|
||||
}
|
||||
|
||||
# extend the data include payload
|
||||
$DataInclude{ProviderResponseMapOutput} = $FunctionResult->{Data};
|
||||
|
||||
$DataOut = $FunctionResult->{Data};
|
||||
|
||||
$DebuggerObject->Debug(
|
||||
Summary => "Outgoing data after mapping",
|
||||
Data => $DataOut,
|
||||
);
|
||||
}
|
||||
|
||||
#
|
||||
# Generate the actual response.
|
||||
#
|
||||
|
||||
$FunctionResult = $Self->{TransportObject}->ProviderGenerateResponse(
|
||||
Success => 1,
|
||||
Data => $DataOut,
|
||||
);
|
||||
|
||||
if ( !$FunctionResult->{Success} ) {
|
||||
|
||||
my $Summary = $FunctionResult->{ErrorMessage} // 'TransportObject returned an error, cancelling Request';
|
||||
$Self->_HandleError(
|
||||
%HandleErrorData,
|
||||
DataInclude => \%DataInclude,
|
||||
ErrorStage => 'ProviderResponseTransmit',
|
||||
Summary => $Summary,
|
||||
Data => $FunctionResult->{Data} // $Summary,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=begin Internal:
|
||||
|
||||
=head2 _GenerateErrorResponse()
|
||||
|
||||
returns an error message to the client.
|
||||
|
||||
$ProviderObject->_GenerateErrorResponse(
|
||||
ErrorMessage => $ErrorMessage,
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub _GenerateErrorResponse {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $FunctionResult = $Self->{TransportObject}->ProviderGenerateResponse(
|
||||
Success => 0,
|
||||
ErrorMessage => $Param{ErrorMessage},
|
||||
);
|
||||
|
||||
if ( !$FunctionResult->{Success} ) {
|
||||
$Param{DebuggerObject}->Error(
|
||||
Summary => 'Error response could not be sent',
|
||||
Data => $FunctionResult->{ErrorMessage},
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 _HandleError()
|
||||
|
||||
handles errors by
|
||||
- informing operation about it (if supported)
|
||||
- calling an error handling layer
|
||||
|
||||
my $ReturnData = $RequesterObject->_HandleError(
|
||||
DebuggerObject => $DebuggerObject,
|
||||
WebserviceID => 1,
|
||||
WebserviceConfig => $WebserviceConfig,
|
||||
DataInclude => $DataIncludeStructure,
|
||||
ErrorStage => 'PrepareRequest', # at what point did the error occur?
|
||||
Summary => 'an error occurred',
|
||||
Data => $ErrorDataStructure,
|
||||
OperationObject => $OperationObject, # optional
|
||||
Operation => 'OperationName', # optional
|
||||
);
|
||||
|
||||
my $ReturnData = {
|
||||
Success => 0,
|
||||
ErrorMessage => $Param{Summary},
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub _HandleError {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
NEEDED:
|
||||
for my $Needed (qw(DebuggerObject WebserviceID WebserviceConfig DataInclude ErrorStage Summary Data)) {
|
||||
next NEEDED if $Param{$Needed};
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Got no $Needed!",
|
||||
);
|
||||
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $Param{DebuggerObject},
|
||||
ErrorMessage => "Got no $Needed!",
|
||||
);
|
||||
}
|
||||
|
||||
my $ErrorHandlingResult = $Kernel::OM->Get('Kernel::GenericInterface::ErrorHandling')->HandleError(
|
||||
WebserviceID => $Param{WebserviceID},
|
||||
WebserviceConfig => $Param{WebserviceConfig},
|
||||
CommunicationID => $Param{DebuggerObject}->{CommunicationID},
|
||||
CommunicationType => 'Provider',
|
||||
CommunicationName => $Param{Operation},
|
||||
ErrorStage => $Param{ErrorStage},
|
||||
Summary => $Param{Summary},
|
||||
Data => $Param{Data},
|
||||
);
|
||||
|
||||
if (
|
||||
!$Param{Operation}
|
||||
|| !$Param{OperationObject}
|
||||
|| !$Param{OperationObject}->{BackendObject}->can('HandleError')
|
||||
)
|
||||
{
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $Param{DebuggerObject},
|
||||
ErrorMessage => $Param{Summary},
|
||||
);
|
||||
}
|
||||
|
||||
my $HandleErrorData;
|
||||
if ( !defined $Param{Data} || IsString( $Param{Data} ) ) {
|
||||
$HandleErrorData = $Param{Data} // '';
|
||||
}
|
||||
else {
|
||||
$HandleErrorData = Storable::dclone( $Param{Data} );
|
||||
}
|
||||
$Param{DebuggerObject}->Debug(
|
||||
Summary => 'Error data before mapping',
|
||||
Data => $HandleErrorData,
|
||||
);
|
||||
|
||||
my $OperationConfig = $Param{WebserviceConfig}->{Provider}->{Operation}->{ $Param{Operation} };
|
||||
|
||||
# TODO: use separate mapping config for errors.
|
||||
if ( IsHashRefWithData( $OperationConfig->{MappingInbound} ) ) {
|
||||
my $MappingErrorObject = Kernel::GenericInterface::Mapping->new(
|
||||
DebuggerObject => $Param{DebuggerObject},
|
||||
Operation => $Param{Operation},
|
||||
OperationType => $OperationConfig->{Type},
|
||||
MappingConfig => $OperationConfig->{MappingInbound},
|
||||
);
|
||||
|
||||
# If mapping init failed, bail out.
|
||||
if ( ref $MappingErrorObject ne 'Kernel::GenericInterface::Mapping' ) {
|
||||
$Param{DebuggerObject}->Error(
|
||||
Summary => 'MappingErr could not be initialized',
|
||||
Data => $MappingErrorObject,
|
||||
);
|
||||
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $Param{DebuggerObject},
|
||||
ErrorMessage => 'MappingErr could not be initialized',
|
||||
);
|
||||
}
|
||||
|
||||
# Map error data.
|
||||
my $MappingErrorResult = $MappingErrorObject->Map(
|
||||
Data => {
|
||||
Fault => $HandleErrorData,
|
||||
},
|
||||
DataInclude => {
|
||||
%{ $Param{DataInclude} },
|
||||
ProviderErrorHandlingOutput => $ErrorHandlingResult->{Data},
|
||||
},
|
||||
);
|
||||
if ( !$MappingErrorResult->{Success} ) {
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $Param{DebuggerObject},
|
||||
ErrorMessage => $MappingErrorResult->{ErrorMessage},
|
||||
);
|
||||
}
|
||||
|
||||
$HandleErrorData = $MappingErrorResult->{Data};
|
||||
|
||||
$Param{DebuggerObject}->Debug(
|
||||
Summary => 'Error data after mapping',
|
||||
Data => $HandleErrorData,
|
||||
);
|
||||
}
|
||||
|
||||
my $OperationHandleErrorOutput = $Param{OperationObject}->HandleError(
|
||||
Data => $HandleErrorData,
|
||||
);
|
||||
if ( !$OperationHandleErrorOutput->{Success} ) {
|
||||
$Param{DebuggerObject}->Error(
|
||||
Summary => 'Error handling error data in Operation',
|
||||
Data => $OperationHandleErrorOutput->{ErrorMessage},
|
||||
);
|
||||
}
|
||||
|
||||
return $Self->_GenerateErrorResponse(
|
||||
DebuggerObject => $Param{DebuggerObject},
|
||||
ErrorMessage => $Param{Summary},
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end Internal:
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
the enclosed file COPYING for license information (GPL). If you
|
||||
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user