From 384b96e8068bb3b11b18a05a9eaeff483ae742ea Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 14 Jun 2021 14:13:59 +0200 Subject: [PATCH 01/11] Issue #1054: fix logic for empty string check This also avoids some warnings in the error log. --- bin/cgi-bin/customer.pl | 2 +- bin/cgi-bin/index.pl | 2 +- bin/cgi-bin/installer.pl | 2 +- bin/cgi-bin/migration.pl | 2 +- bin/cgi-bin/nph-genericinterface.pl | 2 +- bin/cgi-bin/public.pl | 2 +- bin/cgi-bin/rpc.pl | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/cgi-bin/customer.pl b/bin/cgi-bin/customer.pl index d7e1e9a700..05571ae09c 100755 --- a/bin/cgi-bin/customer.pl +++ b/bin/cgi-bin/customer.pl @@ -32,7 +32,7 @@ #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work # otobo.psgi looks primarily in $ENV{PATH_INFO} -local $ENV{PATH_INFO} = join '/', grep { defined $_ || $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; +local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; my $CgiBinDir = dirname(__FILE__); diff --git a/bin/cgi-bin/index.pl b/bin/cgi-bin/index.pl index d7e1e9a700..05571ae09c 100755 --- a/bin/cgi-bin/index.pl +++ b/bin/cgi-bin/index.pl @@ -32,7 +32,7 @@ #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work # otobo.psgi looks primarily in $ENV{PATH_INFO} -local $ENV{PATH_INFO} = join '/', grep { defined $_ || $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; +local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; my $CgiBinDir = dirname(__FILE__); diff --git a/bin/cgi-bin/installer.pl b/bin/cgi-bin/installer.pl index d7e1e9a700..05571ae09c 100755 --- a/bin/cgi-bin/installer.pl +++ b/bin/cgi-bin/installer.pl @@ -32,7 +32,7 @@ #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work # otobo.psgi looks primarily in $ENV{PATH_INFO} -local $ENV{PATH_INFO} = join '/', grep { defined $_ || $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; +local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; my $CgiBinDir = dirname(__FILE__); diff --git a/bin/cgi-bin/migration.pl b/bin/cgi-bin/migration.pl index d7e1e9a700..05571ae09c 100755 --- a/bin/cgi-bin/migration.pl +++ b/bin/cgi-bin/migration.pl @@ -32,7 +32,7 @@ #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work # otobo.psgi looks primarily in $ENV{PATH_INFO} -local $ENV{PATH_INFO} = join '/', grep { defined $_ || $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; +local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; my $CgiBinDir = dirname(__FILE__); diff --git a/bin/cgi-bin/nph-genericinterface.pl b/bin/cgi-bin/nph-genericinterface.pl index d7e1e9a700..05571ae09c 100755 --- a/bin/cgi-bin/nph-genericinterface.pl +++ b/bin/cgi-bin/nph-genericinterface.pl @@ -32,7 +32,7 @@ #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work # otobo.psgi looks primarily in $ENV{PATH_INFO} -local $ENV{PATH_INFO} = join '/', grep { defined $_ || $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; +local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; my $CgiBinDir = dirname(__FILE__); diff --git a/bin/cgi-bin/public.pl b/bin/cgi-bin/public.pl index d7e1e9a700..05571ae09c 100755 --- a/bin/cgi-bin/public.pl +++ b/bin/cgi-bin/public.pl @@ -32,7 +32,7 @@ #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work # otobo.psgi looks primarily in $ENV{PATH_INFO} -local $ENV{PATH_INFO} = join '/', grep { defined $_ || $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; +local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; my $CgiBinDir = dirname(__FILE__); diff --git a/bin/cgi-bin/rpc.pl b/bin/cgi-bin/rpc.pl index d7e1e9a700..05571ae09c 100755 --- a/bin/cgi-bin/rpc.pl +++ b/bin/cgi-bin/rpc.pl @@ -32,7 +32,7 @@ #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work # otobo.psgi looks primarily in $ENV{PATH_INFO} -local $ENV{PATH_INFO} = join '/', grep { defined $_ || $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; +local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; my $CgiBinDir = dirname(__FILE__); From 3ea11e7c9b5253f4774907de785ead802dd25426 Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 14 Jun 2021 14:18:45 +0200 Subject: [PATCH 02/11] Issue #1054: add some unicode to the DumpEnvApp --- bin/psgi-bin/otobo.psgi | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/bin/psgi-bin/otobo.psgi b/bin/psgi-bin/otobo.psgi index f8227a208d..28af70d97c 100755 --- a/bin/psgi-bin/otobo.psgi +++ b/bin/psgi-bin/otobo.psgi @@ -439,11 +439,17 @@ my $HelloApp = sub { my $DumpEnvApp = sub { my $Env = shift; + # collect some useful info local $Data::Dumper::Sortkeys = 1; my $Message = Data::Dumper->Dump( - [ "DumpEnvApp:", scalar localtime, $Env, \%ENV, \@INC, \%INC ], - [qw(Title Time Env ENV INC_array INC_hash)], + [ "DumpEnvApp:", scalar localtime, $Env, \%ENV, \@INC, \%INC, '🦦' ], + [qw(Title Time Env ENV INC_array INC_hash otter)], ); + + # add some unicode + $Message .= "unicode: 🦦 ⛄ 🥨\n"; + + # emit the content as UTF-8 utf8::encode($Message); return [ @@ -612,6 +618,8 @@ my $OTOBOApp = builder { $ResponseObject->Code(200); # TODO: is it always 200 ? $ResponseObject->Content($Content); + # for debugging: warn Dumper( { Response => $ResponseObject, is_utf8 => utf8::is_utf8( $ResponseObject->{Response}->{body} ) } ); + # return the funnny unblessed array reference return $ResponseObject->Finalize(); } @@ -659,7 +667,7 @@ builder { # Server the static files in var/httpd/httpd. mount '/otobo-web' => $StaticApp; - # uncomment for trouble shouting + # uncomment for trouble shooting #mount '/hello' => $HelloApp; #mount '/dump_env' => $DumpEnvApp; #mount '/otobo/hello' => $HelloApp; @@ -683,5 +691,5 @@ builder { mount "/index.html" => Plack::App::File->new( file => "$FindBin::Bin/../../var/httpd/htdocs/index.html" )->to_app(); }; -# for debugging: dump the PSGI environment for any request +# enable for debugging: dump debugging info, including the PSGI environment, for any request #$DumpEnvApp; From 78fed7a6d0226f89577a61e53a93334dcc5fead2 Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 14 Jun 2021 14:21:26 +0200 Subject: [PATCH 03/11] Issue #1054: add config for the locations otobo and otobo-web This is similar to the old apache2-httpd.include.conf --- scripts/apache2-httpd-cgi.include.conf | 104 ++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/scripts/apache2-httpd-cgi.include.conf b/scripts/apache2-httpd-cgi.include.conf index bcdcca8e45..f47260304f 100644 --- a/scripts/apache2-httpd-cgi.include.conf +++ b/scripts/apache2-httpd-cgi.include.conf @@ -3,27 +3,25 @@ # added for OTOBO (http://otobo.de/) # -- -# For running OTOBO as a PSGI app under Apache2 link to this file from -# /etc/apache2/sites-enables/zzz_otobo.conf. -# (cd /etc/apache2/sites-enabled && sudo ln -sf /opt/otobo/scripts/apache2-httpd-psgi.include.conf zzz_otobo.conf) +# This configuration is an example for running OTOBO via scripts in bin/cgi-bin. +# The scripts themselves are using the Plack app otobo.psgi internally. +# Note that the recommended way of running OTOBO under Apache is to use the config +# from scripts/apache2-httpd-cgi.include.conf. -# And then restart the webserver. -# sudo systemctl restart apache2.service +ScriptAlias /otobo/ "/opt/otobo/bin/cgi-bin/" +Alias /otobo-web/ "/opt/otobo/var/httpd/htdocs/" -# mod_perl is required - # Setup environment and preload modules + # each site should have it's own Perl interpreter PerlOptions +Parent - Perlrequire /opt/otobo/scripts/apache2-perl-startup.pl - # Reload Perl modules when changed on disk - PerlModule Apache2::Reload - PerlInitHandler Apache2::Reload + # @INC is also set on otobo.psgi, but this has not been reliable + PerlSwitches -I /opt/otobo -I /opt/otobo/Kernel/cpan-lib -I /opt/otobo/Custom # general mod_perl2 options - #ErrorDocument 403 /otobo/customer.pl +# ErrorDocument 403 /otobo/customer.pl ErrorDocument 403 /otobo/index.pl SetHandler perl-script PerlResponseHandler ModPerl::Registry @@ -43,3 +41,85 @@ + + + AllowOverride None + Options +ExecCGI -Includes + + # Require supported starting with Apache 2.4 + # No authentication and all requests are allowed. + Require all granted + + + + AddOutputFilterByType DEFLATE text/html text/javascript application/javascript text/css text/xml application/json text/json + + + + + + + AllowOverride None + + # Require supported starting with Apache 2.4 + # No authentication and all requests are allowed. + Require all granted + + + + AddOutputFilterByType DEFLATE text/html text/javascript application/javascript text/css text/xml application/json text/json + + + + # Make sure CSS and JS files are read as UTF8 by the browsers. + AddCharset UTF-8 .css + AddCharset UTF-8 .js + + # Set explicit mime type for woff fonts since it is relatively new and apache may not know about it. + AddType application/font-woff .woff + + + +# Allow access to public interface for unauthenticated requests on systems with set-up authentication. +# Will work only for RegistrationUpdate, since page resources are still not be loaded. +# +# +# # Require supported starting with Apache 2.4 +# # No authentication and all requests are allowed. +# Require all granted +# +# + + + # Cache css-cache for 30 days + + + Header set Cache-Control "max-age=2592000 must-revalidate" + + + + # Cache css thirdparty for 4 hours, including icon fonts + + + Header set Cache-Control "max-age=14400 must-revalidate" + + + + # Cache js-cache for 30 days + + + Header set Cache-Control "max-age=2592000 must-revalidate" + + + + # Cache js thirdparty for 4 hours + + + Header set Cache-Control "max-age=14400 must-revalidate" + + + + +# Limit the number of requests per child to avoid excessive memory usage. +# 1000 is the same value as the default value used by Gazelle. +MaxRequestsPerChild 1000 From e34f021606ea7361c54f7ffa22ccfbc60bda47ba Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 14 Jun 2021 14:36:27 +0200 Subject: [PATCH 04/11] Issue #1054: eliminate scripts/apache2-perl-startup.pl Currently not used, so remove it for decreasing the level of confusion. --- .gitignore | 1 - scripts/apache2-perl-startup.pl | 75 --------------------------------- scripts/test/Archive.t | 3 +- scripts/test/Compile.t | 1 - 4 files changed, 2 insertions(+), 78 deletions(-) delete mode 100755 scripts/apache2-perl-startup.pl diff --git a/.gitignore b/.gitignore index 3f09eb35ff..f98462adc5 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ Kernel/Config/GenericAgent.pm Kernel/Config.pm Kernel/Language/*.old bin/cgi-bin/.htaccess -scripts/apache2-perl-startup2.pl var/virtualfs var/run var/log/*.log diff --git a/scripts/apache2-perl-startup.pl b/scripts/apache2-perl-startup.pl deleted file mode 100755 index db0410bb62..0000000000 --- a/scripts/apache2-perl-startup.pl +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env perl -# -- -# OTOBO is a web-based ticketing system for service organisations. -# -- -# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ -# Copyright (C) 2019-2021 Rother OSS GmbH, https://otobo.de/ -# -- -# This program is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later version. -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -- - -# TODO: maybe eliminate this file, see https://github.com/RotherOSS/otobo/issues/1054 - -use strict; -use warnings; - -# Make sure we are in a sane environment. -$ENV{MOD_PERL} =~ m/mod_perl/ || die 'mod_perl is not used!'; - -BEGIN { - - # switch to unload_package_xs, the PP version is broken in Perl 5.10.1. - # see http://rt.perl.org/rt3//Public/Bug/Display.html?id=72866 - $ModPerl::Util::DEFAULT_UNLOAD_METHOD = 'unload_package_xs'; - - # set $0 to index.pl if it is not an existing file: - # on Fedora, $0 is not a path which would break OTOBO. - # see bug # 8533 - if ( !-e $0 || -d $0 ) { - $0 = '/opt/otobo/bin/cgi-bin/index.pl'; ## no critic qw(Variables::RequireLocalizedPunctuationVars) - } -} - -use Apache2::RequestRec (); -use ModPerl::Util (); - -use lib "/opt/otobo/"; -use lib "/opt/otobo/Kernel/cpan-lib"; -use lib "/opt/otobo/Custom"; - -# Preload frequently used modules to speed up client spawning. -use CGI (); -CGI->compile(':cgi'); -use CGI::Carp (); - -# enable this if you use mysql -#use DBD::mysql (); -#use Kernel::System::DB::mysql; - -# enable this if you use postgresql -#use DBD::Pg (); -#use Kernel::System::DB::postgresql; - -# enable this if you use oracle -#use DBD::Oracle (); -#use Kernel::System::DB::oracle; - -# Preload Net::DNS if it is installed. It is important to preload Net::DNS because otherwise loading -# could take more than 30 seconds. -eval { require Net::DNS }; - -# Preload DateTime, an expensive external dependency. -use DateTime (); - -# Preload dependencies that are always used. -use Template (); -use Encode qw(:all); - -1; diff --git a/scripts/test/Archive.t b/scripts/test/Archive.t index 3e1381bfd5..f3d4be2740 100644 --- a/scripts/test/Archive.t +++ b/scripts/test/Archive.t @@ -84,7 +84,8 @@ else { } # Skip files with expected changes. - next LINE if $Filename =~ m/Cron|CHANGES|apache2-perl-startup/; + next LINE if $Filename eq 'Cron'; + next LINE if $Filename eq 'CHANGES'; # ignore output files of unittest runs next LINE if $Filename =~ m/unittest_.*\.out/; diff --git a/scripts/test/Compile.t b/scripts/test/Compile.t index d75c2fbee7..c8bd97dd0a 100644 --- a/scripts/test/Compile.t +++ b/scripts/test/Compile.t @@ -46,7 +46,6 @@ my %FailureIsAccepted = ( 'Kernel/cpan-lib/PDF/API2/Win32.pm' => 'Win32::TieRegistry is not available, but never mind as Win32 is not supported', 'Kernel/cpan-lib/SOAP/Lite.pm' => 'some strangeness concerning SOAP::Constants', 'Kernel/cpan-lib/URI/urn/isbn.pm' => 'Business::ISBN is not required', - 'scripts/apache2-perl-startup.pl' => 'mod_perl not neccessarily available', ); # object for doing the actual check From f9c4c424252f84f44b7f655e4ec596da41cc69c5 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 24 Jun 2021 16:43:50 +0200 Subject: [PATCH 05/11] Issue #1054: recommend the correct config file --- scripts/apache2-httpd-cgi.include.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/apache2-httpd-cgi.include.conf b/scripts/apache2-httpd-cgi.include.conf index f47260304f..85803f7ea6 100644 --- a/scripts/apache2-httpd-cgi.include.conf +++ b/scripts/apache2-httpd-cgi.include.conf @@ -4,9 +4,9 @@ # -- # This configuration is an example for running OTOBO via scripts in bin/cgi-bin. -# The scripts themselves are using the Plack app otobo.psgi internally. -# Note that the recommended way of running OTOBO under Apache is to use the config -# from scripts/apache2-httpd-cgi.include.conf. +# The scripts themselves are using the PSGI app otobo.psgi internally. +# Note that this is not the recommended way of running OTOBO under Apache. +# The recommended way is to use the config provided in scripts/apache2-httpd.include.conf. ScriptAlias /otobo/ "/opt/otobo/bin/cgi-bin/" Alias /otobo-web/ "/opt/otobo/var/httpd/htdocs/" @@ -21,7 +21,7 @@ Alias /otobo-web/ "/opt/otobo/var/httpd/htdocs/" # general mod_perl2 options -# ErrorDocument 403 /otobo/customer.pl + # ErrorDocument 403 /otobo/customer.pl ErrorDocument 403 /otobo/index.pl SetHandler perl-script PerlResponseHandler ModPerl::Registry From 51125da9744b7f12ec3aae7339a82b5b87643d80 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 24 Jun 2021 16:44:57 +0200 Subject: [PATCH 06/11] Issue #1054: no need to special case /otobo/nph-genericinterface.pl Looks like Plack::Handler::Apache2 already handles the headers --- scripts/apache2-httpd-cgi.include.conf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/apache2-httpd-cgi.include.conf b/scripts/apache2-httpd-cgi.include.conf index 85803f7ea6..6cb13c8ff8 100644 --- a/scripts/apache2-httpd-cgi.include.conf +++ b/scripts/apache2-httpd-cgi.include.conf @@ -35,11 +35,6 @@ Alias /otobo-web/ "/opt/otobo/var/httpd/htdocs/" - # mod_perl2 options for GenericInterface - - PerlOptions -ParseHeaders - - From dffa78aa3dddd2f8a15404f97a2bde782c3f7c02 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 24 Jun 2021 18:55:53 +0200 Subject: [PATCH 07/11] Issue #1054: switch to Test2::V0 in order to get a more readable report --- .../Operation/Ticket/TicketGet.t | 371 +++++++++--------- 1 file changed, 188 insertions(+), 183 deletions(-) diff --git a/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t b/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t index dc5e5aaa12..32d4e2ec90 100644 --- a/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t +++ b/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t @@ -18,19 +18,21 @@ use strict; use warnings; use utf8; -# Set up the test driver $Self when we are running as a standalone script. -use Kernel::System::UnitTest::RegisterDriver; - -use vars (qw($Self)); - +# core modules use MIME::Base64; +# CPAN modules +use Test2::V0; + +# OTOBO modules +use Kernel::System::UnitTest::RegisterDriver; # Set up $Self and $Kernel::OM use Kernel::GenericInterface::Debugger; use Kernel::GenericInterface::Operation::Session::SessionCreate; use Kernel::GenericInterface::Operation::Ticket::TicketGet; - use Kernel::System::VariableCheck qw(:all); +our $Self; + my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # disable SessionCheckRemoteIP setting @@ -45,6 +47,7 @@ $Kernel::OM->ObjectParamAdd( SkipSSLVerify => 1, }, ); + my $Helper = $Kernel::OM->Get('Kernel::System::UnitTest::Helper'); # get a random number @@ -1867,134 +1870,82 @@ $Self->Is( for my $Test (@Tests) { - # create local object - my $LocalObject = "Kernel::GenericInterface::Operation::Ticket::$Test->{Operation}"->new( - DebuggerObject => $DebuggerObject, - WebserviceID => $WebserviceID, - ); - - $Self->Is( - "Kernel::GenericInterface::Operation::Ticket::$Test->{Operation}", - ref $LocalObject, - "$Test->{Name} - Create local object", - ); - - my %Auth = ( - UserLogin => $UserLogin, - Password => $Password, - ); - if ( IsHashRefWithData( $Test->{Auth} ) ) { - %Auth = %{ $Test->{Auth} }; - } + subtest $Test->{Name} => sub { - # start requester with our web-service - my $LocalResult = $LocalObject->Run( - WebserviceID => $WebserviceID, - Invoker => $Test->{Operation}, - Data => { - %Auth, - %{ $Test->{RequestData} }, - }, - ); - - # check result - $Self->Is( - 'HASH', - ref $LocalResult, - "$Test->{Name} - Local result structure is valid", - ); + # create local object + my $LocalObject = "Kernel::GenericInterface::Operation::Ticket::$Test->{Operation}"->new( + DebuggerObject => $DebuggerObject, + WebserviceID => $WebserviceID, + ); - # create requester object - my $RequesterObject = $Kernel::OM->Get('Kernel::GenericInterface::Requester'); - $Self->Is( - 'Kernel::GenericInterface::Requester', - ref $RequesterObject, - "$Test->{Name} - Create requester object", - ); + $Self->Is( + "Kernel::GenericInterface::Operation::Ticket::$Test->{Operation}", + ref $LocalObject, + "Create local object", + ); - # start requester with our web-service - my $RequesterResult = $RequesterObject->Run( - WebserviceID => $WebserviceID, - Invoker => $Test->{Operation}, - Data => { - %Auth, - %{ $Test->{RequestData} }, - }, - ); + my %Auth = ( + UserLogin => $UserLogin, + Password => $Password, + ); + if ( IsHashRefWithData( $Test->{Auth} ) ) { + %Auth = %{ $Test->{Auth} }; + } - # check result - $Self->Is( - 'HASH', - ref $RequesterResult, - "$Test->{Name} - Requester result structure is valid", - ); + # start requester with our web-service + my $LocalResult = $LocalObject->Run( + WebserviceID => $WebserviceID, + Invoker => $Test->{Operation}, + Data => { + %Auth, + %{ $Test->{RequestData} }, + }, + ); - $Self->Is( - $RequesterResult->{Success}, - $Test->{SuccessRequest}, - "$Test->{Name} - Requester successful result", - ); + # check result + $Self->Is( + 'HASH', + ref $LocalResult, + "Local result structure is valid", + ); - # workaround because results from direct call and - # from SOAP call are a little bit different - if ( $Test->{Operation} eq 'TicketGet' ) { + # create requester object + my $RequesterObject = $Kernel::OM->Get('Kernel::GenericInterface::Requester'); + $Self->Is( + 'Kernel::GenericInterface::Requester', + ref $RequesterObject, + "Create requester object", + ); - if ( ref $LocalResult->{Data}->{Ticket} eq 'ARRAY' ) { - for my $Item ( @{ $LocalResult->{Data}->{Ticket} } ) { - for my $Key ( sort keys %{$Item} ) { - if ( !defined $Item->{$Key} ) { - $Item->{$Key} = ''; - } - if ( $SkipFields{$Key} ) { - delete $Item->{$Key}; - } - if ( $Key eq 'DynamicField' ) { - for my $DF ( @{ $Item->{$Key} } ) { - if ( !defined $DF->{Value} ) { - $DF->{Value} = ''; - } - } - } - } + # start requester with our web-service + my $RequesterResult = $RequesterObject->Run( + WebserviceID => $WebserviceID, + Invoker => $Test->{Operation}, + Data => { + %Auth, + %{ $Test->{RequestData} }, + }, + ); - # Articles - if ( defined $Item->{Article} ) { - for my $Article ( @{ $Item->{Article} } ) { - for my $Key ( sort keys %{$Article} ) { - if ( !defined $Article->{$Key} ) { - $Article->{$Key} = ''; - } - if ( $SkipFields{$Key} ) { - delete $Article->{$Key}; - } + # check result + $Self->Is( + 'HASH', + ref $RequesterResult, + "Requester result structure is valid", + ); - if ( $Key eq 'Attachment' ) { - for my $Atm ( @{ $Article->{$Key} } ) { - $Atm->{ContentID} = ''; - $Atm->{ContentAlternative} = ''; - } - } + $Self->Is( + $RequesterResult->{Success}, + $Test->{SuccessRequest}, + "Requester successful result", + ); - if ( $Key eq 'DynamicField' ) { - for my $DF ( @{ $Article->{$Key} } ) { - if ( !defined $DF->{Value} ) { - $DF->{Value} = ''; - } - } - } - } - } - } - } - } + # workaround because results from direct call and + # from SOAP call are a little bit different + if ( $Test->{Operation} eq 'TicketGet' ) { - if ( - defined $RequesterResult->{Data} - && defined $RequesterResult->{Data}->{Ticket} - ) - { - if ( ref $RequesterResult->{Data}->{Ticket} eq 'ARRAY' ) { - for my $Item ( @{ $RequesterResult->{Data}->{Ticket} } ) { + if ( ref $LocalResult->{Data}->{Ticket} eq 'ARRAY' ) { + for my $Item ( @{ $LocalResult->{Data}->{Ticket} } ) { for my $Key ( sort keys %{$Item} ) { if ( !defined $Item->{$Key} ) { $Item->{$Key} = ''; @@ -2010,29 +1961,10 @@ for my $Test (@Tests) { } } } - } - } - elsif ( ref $RequesterResult->{Data}->{Ticket} eq 'HASH' ) { - for my $Key ( sort keys %{ $RequesterResult->{Data}->{Ticket} } ) { - if ( !defined $RequesterResult->{Data}->{Ticket}->{$Key} ) { - $RequesterResult->{Data}->{Ticket}->{$Key} = ''; - } - if ( $SkipFields{$Key} ) { - delete $RequesterResult->{Data}->{Ticket}->{$Key}; - } - if ( $Key eq 'DynamicField' ) { - for my $DF ( @{ $RequesterResult->{Data}->{Ticket}->{$Key} } ) { - if ( !defined $DF->{Value} ) { - $DF->{Value} = ''; - } - } - } - } - # Articles - if ( defined $RequesterResult->{Data}->{Ticket}->{Article} ) { - if ( ref $RequesterResult->{Data}->{Ticket}->{Article} eq 'ARRAY' ) { - for my $Article ( @{ $RequesterResult->{Data}->{Ticket}->{Article} } ) { + # Articles + if ( defined $Item->{Article} ) { + for my $Article ( @{ $Item->{Article} } ) { for my $Key ( sort keys %{$Article} ) { if ( !defined $Article->{$Key} ) { $Article->{$Key} = ''; @@ -2040,12 +1972,14 @@ for my $Test (@Tests) { if ( $SkipFields{$Key} ) { delete $Article->{$Key}; } + if ( $Key eq 'Attachment' ) { for my $Atm ( @{ $Article->{$Key} } ) { $Atm->{ContentID} = ''; $Atm->{ContentAlternative} = ''; } } + if ( $Key eq 'DynamicField' ) { for my $DF ( @{ $Article->{$Key} } ) { if ( !defined $DF->{Value} ) { @@ -2056,22 +1990,25 @@ for my $Test (@Tests) { } } } - elsif ( ref $RequesterResult->{Data}->{Ticket}->{Article} eq 'HASH' ) { - for my $Key ( sort keys %{ $RequesterResult->{Data}->{Ticket}->{Article} } ) { - if ( !defined $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} ) { - $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} = ''; + } + } + + if ( + defined $RequesterResult->{Data} + && defined $RequesterResult->{Data}->{Ticket} + ) + { + if ( ref $RequesterResult->{Data}->{Ticket} eq 'ARRAY' ) { + for my $Item ( @{ $RequesterResult->{Data}->{Ticket} } ) { + for my $Key ( sort keys %{$Item} ) { + if ( !defined $Item->{$Key} ) { + $Item->{$Key} = ''; } if ( $SkipFields{$Key} ) { - delete $RequesterResult->{Data}->{Ticket}->{Article}->{$Key}; - } - if ( $Key eq 'Attachment' ) { - for my $Atm ( @{ $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} } ) { - $Atm->{ContentID} = ''; - $Atm->{ContentAlternative} = ''; - } + delete $Item->{$Key}; } if ( $Key eq 'DynamicField' ) { - for my $DF ( @{ $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} } ) { + for my $DF ( @{ $Item->{$Key} } ) { if ( !defined $DF->{Value} ) { $DF->{Value} = ''; } @@ -2080,38 +2017,106 @@ for my $Test (@Tests) { } } } + elsif ( ref $RequesterResult->{Data}->{Ticket} eq 'HASH' ) { + for my $Key ( sort keys %{ $RequesterResult->{Data}->{Ticket} } ) { + if ( !defined $RequesterResult->{Data}->{Ticket}->{$Key} ) { + $RequesterResult->{Data}->{Ticket}->{$Key} = ''; + } + if ( $SkipFields{$Key} ) { + delete $RequesterResult->{Data}->{Ticket}->{$Key}; + } + if ( $Key eq 'DynamicField' ) { + for my $DF ( @{ $RequesterResult->{Data}->{Ticket}->{$Key} } ) { + if ( !defined $DF->{Value} ) { + $DF->{Value} = ''; + } + } + } + } + + # Articles + if ( defined $RequesterResult->{Data}->{Ticket}->{Article} ) { + if ( ref $RequesterResult->{Data}->{Ticket}->{Article} eq 'ARRAY' ) { + for my $Article ( @{ $RequesterResult->{Data}->{Ticket}->{Article} } ) { + for my $Key ( sort keys %{$Article} ) { + if ( !defined $Article->{$Key} ) { + $Article->{$Key} = ''; + } + if ( $SkipFields{$Key} ) { + delete $Article->{$Key}; + } + if ( $Key eq 'Attachment' ) { + for my $Atm ( @{ $Article->{$Key} } ) { + $Atm->{ContentID} = ''; + $Atm->{ContentAlternative} = ''; + } + } + if ( $Key eq 'DynamicField' ) { + for my $DF ( @{ $Article->{$Key} } ) { + if ( !defined $DF->{Value} ) { + $DF->{Value} = ''; + } + } + } + } + } + } + elsif ( ref $RequesterResult->{Data}->{Ticket}->{Article} eq 'HASH' ) { + for my $Key ( sort keys %{ $RequesterResult->{Data}->{Ticket}->{Article} } ) { + if ( !defined $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} ) { + $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} = ''; + } + if ( $SkipFields{$Key} ) { + delete $RequesterResult->{Data}->{Ticket}->{Article}->{$Key}; + } + if ( $Key eq 'Attachment' ) { + for my $Atm ( @{ $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} } ) { + $Atm->{ContentID} = ''; + $Atm->{ContentAlternative} = ''; + } + } + if ( $Key eq 'DynamicField' ) { + for my $DF ( @{ $RequesterResult->{Data}->{Ticket}->{Article}->{$Key} } ) { + if ( !defined $DF->{Value} ) { + $DF->{Value} = ''; + } + } + } + } + } + } + } } } - } - - # remove ErrorMessage parameter from direct call - # result to be consistent with SOAP call result - if ( $LocalResult->{ErrorMessage} ) { - delete $LocalResult->{ErrorMessage}; - } - $Self->IsDeeply( - $RequesterResult, - $Test->{ExpectedReturnRemoteData}, - "$Test->{Name} - Requester success status (needs configured and running web server)", - ); + # remove ErrorMessage parameter from direct call + # result to be consistent with SOAP call result + if ( $LocalResult->{ErrorMessage} ) { + delete $LocalResult->{ErrorMessage}; + } - if ( $Test->{ExpectedReturnLocalData} ) { - $Self->IsDeeply( - $LocalResult, - $Test->{ExpectedReturnLocalData}, - "$Test->{Name} - Local result matched with expected local call result.", - ); - } - else { - $Self->IsDeeply( - $LocalResult, + is( + $RequesterResult, $Test->{ExpectedReturnRemoteData}, - "$Test->{Name} - Local result matched with remote result.", + "Requester success status (needs configured and running web server)", ); - } -} #end loop + if ( $Test->{ExpectedReturnLocalData} ) { + is( + $LocalResult, + $Test->{ExpectedReturnLocalData}, + "Local result matched with expected local call result.", + ); + } + else { + is( + $LocalResult, + $Test->{ExpectedReturnRemoteData}, + "Local result matched with remote result.", + ); + } + }; +} # cleanup @@ -2159,4 +2164,4 @@ for my $TestFieldConfigItem (@TestFieldConfig) { # cleanup cache $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(); -$Self->DoneTesting(); +done_testing(); From 34ebec1f267a7c18e63ab377006f1ae07a991462 Mon Sep 17 00:00:00 2001 From: bernhard Date: Sat, 26 Jun 2021 14:15:16 +0200 Subject: [PATCH 08/11] Issue #1054: added a check of the new session id --- .../test/GenericInterface/Operation/Ticket/TicketGet.t | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t b/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t index 32d4e2ec90..96b4f245c3 100644 --- a/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t +++ b/scripts/test/GenericInterface/Operation/Ticket/TicketGet.t @@ -1070,7 +1070,7 @@ my $WebserviceConfig = { 'Test for Ticket Connector using SOAP transport backend.', Debugger => { DebugThreshold => 'debug', - TestMode => 1, + TestMode => 1, # write no debug info in the table gi_debugger_entry_content }, Provider => { Transport => { @@ -1145,7 +1145,12 @@ my $RequesterSessionResult = $RequesterSessionObject->Run( }, ); +# sanity check of the request for a new session +# e.g. 'wewB0FscgcXFLYDmoSgmAEcEP8n5wMAT' my $NewSessionID = $RequesterSessionResult->{Data}->{SessionID}; +note "got the new session ID: $NewSessionID"; +ok( $NewSessionID, 'received a new session id' ); +like( $NewSessionID, qr{^\w{32}$}, 'new session id looks sane, is 32 characters long' ); my @Tests = ( { @@ -1857,7 +1862,7 @@ my @Tests = ( my $DebuggerObject = Kernel::GenericInterface::Debugger->new( DebuggerConfig => { DebugThreshold => 'debug', - TestMode => 1, + TestMode => 1, # write no debug info in the table gi_debugger_entry_content }, WebserviceID => $WebserviceID, CommunicationType => 'Provider', From cd1f6e5816685fb95fa9ba3a311ea2fe5332da75 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 29 Jun 2021 17:59:31 +0200 Subject: [PATCH 09/11] Issue #1054: make nph-genericinterface.pl like a nph-script --- .../GenericInterface/Transport/HTTP/SOAP.pm | 2 +- bin/cgi-bin/nph-genericinterface.pl | 102 +++++++++++++++++- scripts/apache2-httpd-cgi.include.conf | 5 + 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/Kernel/GenericInterface/Transport/HTTP/SOAP.pm b/Kernel/GenericInterface/Transport/HTTP/SOAP.pm index faac5eac95..25fd255b84 100644 --- a/Kernel/GenericInterface/Transport/HTTP/SOAP.pm +++ b/Kernel/GenericInterface/Transport/HTTP/SOAP.pm @@ -27,8 +27,8 @@ use PerlIO; # CPAN modules use HTTP::Status; -use SOAP::Lite; use Plack::Response; +use SOAP::Lite; # for enabling debugging import +trace => 'all' # OTOBO modules use Kernel::System::VariableCheck qw(:all); diff --git a/bin/cgi-bin/nph-genericinterface.pl b/bin/cgi-bin/nph-genericinterface.pl index 05571ae09c..f1dd065e05 100755 --- a/bin/cgi-bin/nph-genericinterface.pl +++ b/bin/cgi-bin/nph-genericinterface.pl @@ -27,6 +27,8 @@ use Plack::Util qw(); use Plack::Handler::CGI qw(); +#use Plack::Middleware::DebugLogging; + # OTOBO modules #local $ENV{PLACK_URLMAP_DEBUG} = 1; # enable when the URL mapping does not work @@ -35,6 +37,104 @@ local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; +#$Plack::Middleware::DebugLogging::module_map->{'text/xml; charset=utf-8'} = 'XML::Simple'; +#$Plack::Middleware::DebugLogging::module_map->{'text/xml; charset=UTF-8'} = 'XML::Simple'; + my $CgiBinDir = dirname(__FILE__); + +#state $App = Plack::Middleware::DebugLogging->wrap( +# Plack::Util::load_psgi("$CgiBinDir/../psgi-bin/otobo.psgi"), +# debug => 1, +# response => 1, +# request => 1 +#); state $App = Plack::Util::load_psgi("$CgiBinDir/../psgi-bin/otobo.psgi"); -Plack::Handler::CGI->new()->run($App); + +# set up a PSGI environment from %ENV +my $PSGIEnv = Plack::Handler::CGI->new()->setup_env(); + +# run the PSGI-application +my $Res = $App->($PSGIEnv); + +# sanity check +die "Bad response from the PSGI app" unless ref $Res eq 'ARRAY'; +die "Bad response from the PSGI app" unless ref $Res->[2] eq 'ARRAY'; + +# turn the PSGI response into non parsed header HTTP response + +# copied from HTTP::Status +my %Code2Message = ( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', # RFC 2518 (WebDAV) + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', # RFC 2518 (WebDAV) + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Request Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', # RFC 2518 (WebDAV) + 423 => 'Locked', # RFC 2518 (WebDAV) + 424 => 'Failed Dependency', # RFC 2518 (WebDAV) + 425 => 'No code', # WebDAV Advanced Collections + 426 => 'Upgrade Required', # RFC 2817 + 449 => 'Retry with', # unofficial Microsoft + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', # RFC 2295 + 507 => 'Insufficient Storage', # RFC 2518 (WebDAV) + 509 => 'Bandwidth Limit Exceeded', # unofficial + 510 => 'Not Extended', # RFC 2774 +); + +*STDOUT->autoflush(1); +binmode STDOUT; + +# first the status line +my $StatusMessage = $Code2Message{ $Res->[0] } // 'UNKNOWN'; +my $Headers = "HTTP/1.1 $Res->[0] $StatusMessage\015\012"; + +# add the headers +while ( my ( $Key, $Val ) = splice $Res->[1]->@*, 0, 2 ) { + $Headers .= "$Key: $Val\015\012"; +} +$Headers .= "\015\012"; + +print STDOUT $Headers; + +for my $Line ( $Res->[2]->@* ) { + utf8::encode($Line); + print STDOUT $Line; +} diff --git a/scripts/apache2-httpd-cgi.include.conf b/scripts/apache2-httpd-cgi.include.conf index 6cb13c8ff8..85803f7ea6 100644 --- a/scripts/apache2-httpd-cgi.include.conf +++ b/scripts/apache2-httpd-cgi.include.conf @@ -35,6 +35,11 @@ Alias /otobo-web/ "/opt/otobo/var/httpd/htdocs/" + # mod_perl2 options for GenericInterface + + PerlOptions -ParseHeaders + + From 89abd889712626c42c2afea89e4915c5a44a9d6e Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 1 Jul 2021 16:52:55 +0200 Subject: [PATCH 10/11] Issue #1054: accept suggestion from Perl::Tidy --- Kernel/GenericInterface/Transport/HTTP/SOAP.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kernel/GenericInterface/Transport/HTTP/SOAP.pm b/Kernel/GenericInterface/Transport/HTTP/SOAP.pm index 25fd255b84..76ed6ba0ee 100644 --- a/Kernel/GenericInterface/Transport/HTTP/SOAP.pm +++ b/Kernel/GenericInterface/Transport/HTTP/SOAP.pm @@ -28,7 +28,7 @@ use PerlIO; # CPAN modules use HTTP::Status; use Plack::Response; -use SOAP::Lite; # for enabling debugging import +trace => 'all' +use SOAP::Lite; # for enabling debugging import +trace => 'all' # OTOBO modules use Kernel::System::VariableCheck qw(:all); From 14b2be746908cc63d6f078aead245af7b3de7770 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 1 Jul 2021 19:34:17 +0200 Subject: [PATCH 11/11] Issue #1054: use the neate builder syntax for middlewares --- bin/cgi-bin/nph-genericinterface.pl | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/bin/cgi-bin/nph-genericinterface.pl b/bin/cgi-bin/nph-genericinterface.pl index f1dd065e05..2382e4c43d 100755 --- a/bin/cgi-bin/nph-genericinterface.pl +++ b/bin/cgi-bin/nph-genericinterface.pl @@ -24,10 +24,9 @@ use File::Basename qw(dirname); # CPAN modules -use Plack::Util qw(); +use Plack::Builder; use Plack::Handler::CGI qw(); - -#use Plack::Middleware::DebugLogging; +use Plack::Util qw(); # OTOBO modules @@ -37,18 +36,20 @@ local $ENV{PATH_INFO} = join '/', grep { defined $_ && $_ ne '' } @ENV{qw(SCRIPT_NAME PATH_INFO)}; local $ENV{SCRIPT_NAME} = ''; -#$Plack::Middleware::DebugLogging::module_map->{'text/xml; charset=utf-8'} = 'XML::Simple'; -#$Plack::Middleware::DebugLogging::module_map->{'text/xml; charset=UTF-8'} = 'XML::Simple'; - my $CgiBinDir = dirname(__FILE__); -#state $App = Plack::Middleware::DebugLogging->wrap( -# Plack::Util::load_psgi("$CgiBinDir/../psgi-bin/otobo.psgi"), -# debug => 1, -# response => 1, -# request => 1 -#); -state $App = Plack::Util::load_psgi("$CgiBinDir/../psgi-bin/otobo.psgi"); +state $App = builder { + + # enable 'Plack::Middleware::DebugLogging', + # debug => 1, + # response => 1, + # request => 1; + + Plack::Util::load_psgi("$CgiBinDir/../psgi-bin/otobo.psgi"); +}; + +#$Plack::Middleware::DebugLogging::module_map->{'text/xml; charset=utf-8'} = 'XML::Simple'; +#$Plack::Middleware::DebugLogging::module_map->{'text/xml; charset=UTF-8'} = 'XML::Simple'; # set up a PSGI environment from %ENV my $PSGIEnv = Plack::Handler::CGI->new()->setup_env();