From 6df945da5900da86203e0527816690cb1d52c574 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Tue, 3 Apr 2018 23:05:04 -0400 Subject: Bug 1455495 - Replace apache with Mojolicious --- Bugzilla.pm | 2 +- Bugzilla/Attachment/PatchReader.pm | 15 +- Bugzilla/CGI.pm | 32 +++-- Bugzilla/DaemonControl.pm | 26 ++-- Bugzilla/Error.pm | 4 +- Bugzilla/Install/Filesystem.pm | 127 +---------------- Bugzilla/ModPerl.pm | 118 --------------- Bugzilla/ModPerl/BasicAuth.pm | 65 --------- Bugzilla/ModPerl/BlockIP.pm | 65 --------- Bugzilla/ModPerl/Hostage.pm | 71 --------- Bugzilla/ModPerl/StartupFix.pm | 51 ------- Bugzilla/Quantum.pm | 214 ++++++++++++++++++++++++++++ Bugzilla/Quantum/CGI.pm | 162 +++++++++++++++++++++ Bugzilla/Quantum/Plugin/BasicAuth.pm | 40 ++++++ Bugzilla/Quantum/Plugin/BlockIP.pm | 43 ++++++ Bugzilla/Quantum/Plugin/Glue.pm | 114 +++++++++++++++ Bugzilla/Quantum/Plugin/Hostage.pm | 80 +++++++++++ Bugzilla/Quantum/SES.pm | 203 ++++++++++++++++++++++++++ Bugzilla/Quantum/Static.pm | 30 ++++ Bugzilla/Quantum/Stdout.pm | 59 ++++++++ Bugzilla/Quantum/Template.pm | 39 +++++ Bugzilla/Template.pm | 1 + Bugzilla/WebService/Server/XMLRPC.pm | 6 +- Bugzilla/WebService/Util.pm | 8 +- Dockerfile | 9 +- Log/Log4perl/Layout/Mozilla.pm | 3 + Makefile.PL | 15 +- README.rst | 35 ----- buglist.cgi | 26 ++-- bugzilla.pl | 20 +++ checksetup.pl | 3 +- colchange.cgi | 16 +-- conf/log4perl-test.conf | 4 +- docker-compose.yml | 1 + heartbeat.cgi | 10 +- jobqueue-worker.pl | 0 qa/t/lib/QA/Util.pm | 28 ++++ qa/t/test_bug_edit.t | 117 ++++++++------- qa/t/test_shutdown.t | 72 ---------- scripts/block-ip.pl | 10 +- scripts/entrypoint.pl | 1 + scripts/undo.pl | 0 ses/index.cgi | 214 ---------------------------- t/001compile.t | 14 +- t/002goodperl.t | 7 +- template/en/default/global/header.html.tmpl | 2 + vagrant_support/apache.yml | 8 +- vagrant_support/hypnotoad | 122 ++++++++++++++++ vagrant_support/hypnotoad.yml | 27 ++++ vagrant_support/playbook.yml | 6 +- vagrant_support/update.yml | 5 +- 51 files changed, 1350 insertions(+), 1000 deletions(-) delete mode 100644 Bugzilla/ModPerl.pm delete mode 100644 Bugzilla/ModPerl/BasicAuth.pm delete mode 100644 Bugzilla/ModPerl/BlockIP.pm delete mode 100644 Bugzilla/ModPerl/Hostage.pm delete mode 100644 Bugzilla/ModPerl/StartupFix.pm create mode 100644 Bugzilla/Quantum.pm create mode 100644 Bugzilla/Quantum/CGI.pm create mode 100644 Bugzilla/Quantum/Plugin/BasicAuth.pm create mode 100644 Bugzilla/Quantum/Plugin/BlockIP.pm create mode 100644 Bugzilla/Quantum/Plugin/Glue.pm create mode 100644 Bugzilla/Quantum/Plugin/Hostage.pm create mode 100644 Bugzilla/Quantum/SES.pm create mode 100644 Bugzilla/Quantum/Static.pm create mode 100644 Bugzilla/Quantum/Stdout.pm create mode 100644 Bugzilla/Quantum/Template.pm create mode 100755 bugzilla.pl mode change 100644 => 100755 jobqueue-worker.pl delete mode 100644 qa/t/test_shutdown.t mode change 100644 => 100755 scripts/undo.pl delete mode 100755 ses/index.cgi create mode 100755 vagrant_support/hypnotoad create mode 100644 vagrant_support/hypnotoad.yml diff --git a/Bugzilla.pm b/Bugzilla.pm index 73d080395..4795a3f3a 100644 --- a/Bugzilla.pm +++ b/Bugzilla.pm @@ -801,7 +801,7 @@ sub check_rate_limit { my $limit = join("/", @$limit); Bugzilla->audit("[rate_limit] action=$action, ip=$ip, limit=$limit, name=$name"); if ($action eq 'block') { - Bugzilla::ModPerl::BlockIP->block_ip($ip); + $Bugzilla::Quantum::CGI::C->block_ip($ip); ThrowUserError("rate_limit"); } } diff --git a/Bugzilla/Attachment/PatchReader.pm b/Bugzilla/Attachment/PatchReader.pm index 2cfbf2c6b..8025f5b82 100644 --- a/Bugzilla/Attachment/PatchReader.pm +++ b/Bugzilla/Attachment/PatchReader.pm @@ -116,18 +116,9 @@ sub process_interdiff { $ENV{'PATH'} = $lc->{diffpath}; my ($pid, $interdiff_stdout, $interdiff_stderr); - if ($ENV{MOD_PERL}) { - require Apache2::RequestUtil; - require Apache2::SubProcess; - my $request = Apache2::RequestUtil->request; - (undef, $interdiff_stdout, $interdiff_stderr) = $request->spawn_proc_prog( - $lc->{interdiffbin}, [$old_filename, $new_filename] - ); - } else { - $interdiff_stderr = gensym; - my $pid = open3(gensym, $interdiff_stdout, $interdiff_stderr, - $lc->{interdiffbin}, $old_filename, $new_filename); - } + $interdiff_stderr = gensym; + $pid = open3(gensym, $interdiff_stdout, $interdiff_stderr, + $lc->{interdiffbin}, $old_filename, $new_filename); binmode $interdiff_stdout; # Check for errors diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm index dbcb3ef68..01d610db1 100644 --- a/Bugzilla/CGI.pm +++ b/Bugzilla/CGI.pm @@ -123,7 +123,7 @@ sub new { # Under mod_perl, CGI's global variables get reset on each request, # so we need to set them up again every time. - $class->_init_bz_cgi_globals() if $ENV{MOD_PERL}; + $class->_init_bz_cgi_globals(); my $self = $class->SUPER::new(@args); @@ -481,11 +481,6 @@ sub _prevent_unsafe_response { print $self->SUPER::header(-type => 'text/html', -status => '403 Forbidden'); if ($content_type ne 'text/html') { print "Untrusted Referer Header\n"; - if ($ENV{MOD_PERL}) { - my $r = $self->r; - $r->rflush; - $r->status(200); - } } exit; } @@ -603,8 +598,25 @@ sub header { $headers{'-link'} .= ', ; rel="preconnect"; crossorigin'; } } - - return $self->SUPER::header(%headers) || ""; + my $headers = $self->SUPER::header(%headers) || ''; + if ($self->server_software eq 'Bugzilla::Quantum::CGI') { + my $c = $Bugzilla::Quantum::CGI::C; + $c->res->headers->parse($headers); + my $status = $c->res->headers->status; + if ($status && $status =~ /^([0-9]+)/) { + $c->res->code($1); + } + elsif ($c->res->headers->location) { + $c->res->code(302); + } + else { + $c->res->code(200); + } + return ''; + } + else { + LOGDIE("Bugzilla::CGI->header() should only be called from inside Bugzilla::Quantum::CGI!"); + } } sub param { @@ -721,6 +733,7 @@ sub redirect { return $self->SUPER::redirect(@_); } +use Bugzilla::Logging; # This helps implement Bugzilla::Search::Recent, and also shortens search # URLs that get POSTed to buglist.cgi. sub redirect_search_url { @@ -791,9 +804,6 @@ sub redirect_to_https { # and do not work with 302. Our redirect really is permanent anyhow, so # it doesn't hurt to make it a 301. print $self->redirect(-location => $url, -status => 301); - - # When using XML-RPC with mod_perl, we need the headers sent immediately. - $self->r->rflush if $ENV{MOD_PERL}; exit; } diff --git a/Bugzilla/DaemonControl.pm b/Bugzilla/DaemonControl.pm index 6ff883af0..5cb32973f 100644 --- a/Bugzilla/DaemonControl.pm +++ b/Bugzilla/DaemonControl.pm @@ -23,7 +23,8 @@ use IO::Async::Protocol::LineStream; use IO::Async::Signal; use IO::Socket; use LWP::Simple qw(get); -use POSIX qw(setsid WEXITSTATUS); +use JSON::MaybeXS qw(encode_json); +use POSIX qw(WEXITSTATUS); use base qw(Exporter); @@ -43,8 +44,14 @@ our %EXPORT_TAGS = ( my $BUGZILLA_DIR = bz_locations->{cgi_path}; my $JOBQUEUE_BIN = catfile( $BUGZILLA_DIR, 'jobqueue.pl' ); my $CEREAL_BIN = catfile( $BUGZILLA_DIR, 'scripts', 'cereal.pl' ); -my $HTTPD_BIN = '/usr/sbin/httpd'; -my $HTTPD_CONFIG = catfile( bz_locations->{confdir}, 'httpd.conf' ); +my $BUGZILLA_BIN = catfile( $BUGZILLA_DIR, 'bugzilla.pl' ); +my $HYPNOTOAD_BIN = catfile( $BUGZILLA_DIR, 'local', 'bin', 'hypnotoad' ); +my @PERL5LIB = ( $BUGZILLA_DIR, catdir($BUGZILLA_DIR, 'lib'), catdir($BUGZILLA_DIR, 'local', 'lib', 'perl5') ); + +my %HTTP_BACKENDS = ( + hypnotoad => [ $HYPNOTOAD_BIN, $BUGZILLA_BIN, '-f' ], + simple => [ $BUGZILLA_BIN, 'daemon' ], +); sub catch_signal { my ($name, @done) = @_; @@ -98,13 +105,12 @@ sub run_httpd { my $exit_f = $loop->new_future; my $httpd = IO::Async::Process->new( code => sub { - - # we have to setsid() to make a new process group - # or else apache will kill its parent. - setsid(); - my @command = ( $HTTPD_BIN, '-DFOREGROUND', '-f' => $HTTPD_CONFIG, @args ); - exec @command - or die "failed to exec $command[0] $!"; + $ENV{BUGZILLA_HTTPD_ARGS} = encode_json(\@args); + $ENV{PERL5LIB} = join(':', @PERL5LIB); + my $backend = $ENV{HTTP_BACKEND} // 'hypnotoad'; + my $command = $HTTP_BACKENDS{ $backend }; + exec @$command + or die "failed to exec $command->[0] $!"; }, on_finish => on_finish($exit_f), on_exception => on_exception( 'httpd', $exit_f ), diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm index 9fcd16386..f932294b0 100644 --- a/Bugzilla/Error.pm +++ b/Bugzilla/Error.pm @@ -31,7 +31,7 @@ use Scalar::Util qw(blessed); sub _in_eval { my $in_eval = 0; for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) { - last if $sub =~ /^ModPerl/; + last if $sub =~ /^Bugzilla::Quantum::CGI::try/; $in_eval = 1 if $sub =~ /^\(eval\)/; } return $in_eval; @@ -196,7 +196,7 @@ sub ThrowTemplateError { # mod_perl overrides exit to call die with this string # we never want to display this to the user - exit if $template_err =~ /\bModPerl::Util::exit\b/; + die $template_err if ref($template_err) eq 'ARRAY' && $template_err->[0] eq "EXIT\n"; state $logger = Log::Log4perl->get_logger('Bugzilla.Error.Template'); $logger->error($template_err); diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 003be22e4..f520d3d56 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -41,56 +41,11 @@ use English qw(-no_match_vars $OSNAME); use base qw(Exporter); our @EXPORT = qw( update_filesystem - create_htaccess fix_all_file_permissions fix_dir_permissions fix_file_permissions ); -use constant HT_DEFAULT_DENY => <<'EOT'; -# nothing in this directory is retrievable unless overridden by an .htaccess -# in a subdirectory -deny from all -EOT - -use constant HT_GRAPHS_DIR => <<'EOT'; -# Allow access to .png and .gif files. - - Allow from all - - -# And no directory listings, either. -Deny from all -EOT - -use constant HT_WEBDOT_DIR => <<'EOT'; -# Restrict access to .dot files to the public webdot server at research.att.com -# if research.att.com ever changes their IP, or if you use a different -# webdot server, you'll need to edit this - - Allow from 192.20.225.0/24 - Deny from all - - -# Allow access to .png files created by a local copy of 'dot' - - Allow from all - - -# And no directory listings, either. -Deny from all -EOT - -use constant HT_ASSETS_DIR => <<'EOT'; -# Allow access to .css and js files - - Allow from all - - -# And no directory listings, either. -Deny from all -EOT - use constant INDEX_HTML => <<'EOT'; @@ -230,13 +185,13 @@ sub FILESYSTEM { 'jobqueue-worker.pl' => { perms => OWNER_EXECUTE }, 'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE }, + 'bugzilla.pl' => { perms => OWNER_EXECUTE }, 'Bugzilla.pm' => { perms => CGI_READ }, "$localconfig*" => { perms => CGI_READ }, 'META.*' => { perms => CGI_READ }, 'MYMETA.*' => { perms => CGI_READ }, 'bugzilla.dtd' => { perms => WS_SERVE }, 'mod_perl.pl' => { perms => WS_SERVE }, - '.htaccess' => { perms => WS_SERVE }, 'cvs-update.log' => { perms => WS_SERVE }, 'scripts/sendunsentbugmail.pl' => { perms => WS_EXECUTE }, 'docs/bugzilla.ent' => { perms => OWNER_WRITE }, @@ -438,54 +393,15 @@ sub FILESYSTEM { 'index.html' => { perms => WS_SERVE, contents => INDEX_HTML } ); - # Because checksetup controls the .htaccess creation separately - # by a localconfig variable, these go in a separate variable from - # %create_files. - # - # Note that these get WS_SERVE as their permission - # because they're *read* by the webserver, even though they're not - # actually, themselves, served. - my %htaccess = ( - "$attachdir/.htaccess" => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - "$libdir/Bugzilla/.htaccess" => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - "$extlib/.htaccess" => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - "$templatedir/.htaccess" => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - 'contrib/.htaccess' => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - 'scripts/.htaccess' => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - 't/.htaccess' => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - 'xt/.htaccess' => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - '.circleci/.htaccess' => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - "$confdir/.htaccess" => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - "$datadir/.htaccess" => { perms => WS_SERVE, - contents => HT_DEFAULT_DENY }, - "$graphsdir/.htaccess" => { perms => WS_SERVE, - contents => HT_GRAPHS_DIR }, - "$webdotdir/.htaccess" => { perms => WS_SERVE, - contents => HT_WEBDOT_DIR }, - "$assetsdir/.htaccess" => { perms => WS_SERVE, - contents => HT_ASSETS_DIR }, - ); - Bugzilla::Hook::process('install_filesystem', { files => \%files, create_dirs => \%create_dirs, non_recurse_dirs => \%non_recurse_dirs, recurse_dirs => \%recurse_dirs, create_files => \%create_files, - htaccess => \%htaccess, }); - my %all_files = (%create_files, %htaccess, %index_html, %files); + my %all_files = (%create_files, %index_html, %files); my %all_dirs = (%create_dirs, %non_recurse_dirs); return { @@ -494,7 +410,6 @@ sub FILESYSTEM { all_dirs => \%all_dirs, create_files => \%create_files, - htaccess => \%htaccess, index_html => \%index_html, all_files => \%all_files, }; @@ -542,13 +457,6 @@ sub update_filesystem { _rename_file($oldparamsfile, "$datadir/$oldparamsfile"); } - # Remove old assets htaccess file to force recreation with correct values. - if (-e "$assetsdir/.htaccess") { - if (read_file("$assetsdir/.htaccess") =~ //) { - unlink("$assetsdir/.htaccess"); - } - } - _create_files(%files); if ($params->{index_html}) { _create_files(%{$fs->{index_html}}); @@ -653,27 +561,6 @@ sub _convert_single_file_skins { } } -sub create_htaccess { - _create_files(%{FILESYSTEM()->{htaccess}}); - - # Repair old .htaccess files - - my $webdot_dir = bz_locations()->{'webdotdir'}; - # The public webdot IP address changed. - my $webdot = new IO::File("$webdot_dir/.htaccess", 'r') - || die "$webdot_dir/.htaccess: $!"; - my $webdot_data; - { local $/; $webdot_data = <$webdot>; } - $webdot->close; - if ($webdot_data =~ /192\.20\.225\.10/) { - print "Repairing $webdot_dir/.htaccess...\n"; - $webdot_data =~ s/192\.20\.225\.10/192.20.225.0\/24/g; - $webdot = new IO::File("$webdot_dir/.htaccess", 'w') || die $!; - print $webdot $webdot_data; - $webdot->close; - } -} - sub _rename_file { my ($from, $to) = @_; print install_string('file_rename', { from => $from, to => $to }), "\n"; @@ -984,16 +871,6 @@ Params: C - Whether or not we should create Returns: nothing -=item C - -Description: Creates all of the .htaccess files for Apache, - in the various Bugzilla directories. Also updates - the .htaccess files if they need updating. - -Params: none - -Returns: nothing - =item C Description: Sets all the file permissions on all of Bugzilla's files diff --git a/Bugzilla/ModPerl.pm b/Bugzilla/ModPerl.pm deleted file mode 100644 index 19cd1128f..000000000 --- a/Bugzilla/ModPerl.pm +++ /dev/null @@ -1,118 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. -package Bugzilla::ModPerl; - -use 5.10.1; -use strict; -use warnings; - -use File::Find (); -use Cwd (); -use Carp (); - -# We don't need (or want) to use Bugzilla's template subclass. -# it is easier to reason with the code without all the extra things Bugzilla::Template adds -# (and there might be side-effects, since this code is loaded very early in the httpd startup) -use Template (); - -use Bugzilla::ModPerl::BlockIP; -use Bugzilla::ModPerl::Hostage; - -sub apache_config { - my ($class, $cgi_path) = @_; - - Carp::croak "\$cgi_path is required" unless $cgi_path; - - my %htaccess; - $cgi_path = Cwd::realpath($cgi_path); - my $wanted = sub { - package File::Find; - our ($name, $dir); - - if ($name =~ m#/\.htaccess$#) { - open my $fh, '<', $name or die "cannot open $name $!"; - my $contents = do { - local $/ = undef; - <$fh>; - }; - close $fh; - $htaccess{$dir} = { file => $name, contents => $contents, dir => $dir }; - } - }; - - File::Find::find( { wanted => $wanted, no_chdir => 1 }, $cgi_path ); - my $template = Template->new; - my $conf; - my %vars = ( - root_htaccess => delete $htaccess{$cgi_path}, - htaccess_files => [ map { $htaccess{$_} } sort { length $a <=> length $b } keys %htaccess ], - cgi_path => $cgi_path, - ); - $template->process(\*DATA, \%vars, \$conf); - my $apache_version = Apache2::ServerUtil::get_server_version(); - if ($apache_version =~ m!Apache/(\d+)\.(\d+)\.(\d+)!) { - my ($major, $minor, $patch) = ($1, $2, $3); - if ($major > 2 || $major == 2 && $minor >= 4) { - $conf =~ s{^\s+deny\s+from\s+all.*$}{Require all denied}gmi; - $conf =~ s{^\s+allow\s+from\s+all.*$}{Require all granted}gmi; - $conf =~ s{^\s+allow\s+from\s+(\S+).*$}{Require host $1}gmi; - } - } - - return $conf; -} - -1; - -__DATA__ -# Make sure each httpd child receives a different random seed (bug 476622). -# Bugzilla::RNG has one srand that needs to be called for -# every process, and Perl has another. (Various Perl modules still use -# the built-in rand(), even though we never use it in Bugzilla itself, -# so we need to srand() both of them.) -PerlChildInitHandler "sub { Bugzilla::RNG::srand(); srand(); eval { Bugzilla->dbh->ping } }" -PerlInitHandler Bugzilla::ModPerl::Hostage -PerlAccessHandler Bugzilla::ModPerl::BlockIP - -# It is important to specify ErrorDocuments outside of all directories. -# These used to be in .htaccess, but then things like "AllowEncodedSlashes no" -# mean that urls containing %2f are unstyled. -ErrorDocument 401 /errors/401.html -ErrorDocument 403 /errors/403.html -ErrorDocument 404 /errors/404.html -ErrorDocument 500 /errors/500.html - - - AddHandler perl-script .cgi - # No need to PerlModule these because they're already defined in mod_perl.pl - PerlResponseHandler Bugzilla::ModPerl::ResponseHandler - PerlCleanupHandler Bugzilla::ModPerl::CleanupHandler Apache2::SizeLimit - PerlOptions +ParseHeaders - Options +ExecCGI +FollowSymLinks - DirectoryIndex index.cgi index.html - AllowOverride none - # from [% root_htaccess.file %] - [% root_htaccess.contents FILTER indent %] - - -# AWS SES endpoint for handling mail bounces/complaints - - PerlSetEnv AUTH_VAR_NAME ses_username - PerlSetEnv AUTH_VAR_PASS ses_password - PerlAuthenHandler Bugzilla::ModPerl::BasicAuth - AuthName SES - AuthType Basic - require valid-user - - -# directory rules for all the other places we have .htaccess files -[% FOREACH htaccess IN htaccess_files %] -# from [% htaccess.file %] - - [% htaccess.contents FILTER indent %] - -[% END %] diff --git a/Bugzilla/ModPerl/BasicAuth.pm b/Bugzilla/ModPerl/BasicAuth.pm deleted file mode 100644 index 7248a19f3..000000000 --- a/Bugzilla/ModPerl/BasicAuth.pm +++ /dev/null @@ -1,65 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. -package Bugzilla::ModPerl::BasicAuth; -use 5.10.1; -use strict; -use warnings; - -# Protects a mod_perl with Basic HTTP authentication. -# -# Example use: -# -# -# PerlAuthenHandler Bugzilla::ModPerl::BasicAuth -# PerlSetEnv AUTH_VAR_NAME ses_username -# PerlSetEnv AUTH_VAR_PASS ses_password -# AuthName SES -# AuthType Basic -# require valid-user -# -# -# AUTH_VAR_NAME and AUTH_VAR_PASS are the names of variables defined in -# `localconfig` which hold the authentication credentials. - -use Apache2::Const -compile => qw(OK HTTP_UNAUTHORIZED); ## no critic (Freenode::ModPerl) -use Bugzilla::Logging; -use Bugzilla (); - -sub handler { - my $r = shift; - my ($status, $password) = $r->get_basic_auth_pw; - if ($status != Apache2::Const::OK) { - WARN("Got non-OK status: $status when trying to get password"); - return $status - } - - my $auth_var_name = $ENV{AUTH_VAR_NAME}; - my $auth_var_pass = $ENV{AUTH_VAR_PASS}; - unless ($auth_var_name && $auth_var_pass) { - ERROR('AUTH_VAR_NAME and AUTH_VAR_PASS environmental vars not set'); - $r->note_basic_auth_failure; - return Apache2::Const::HTTP_UNAUTHORIZED; - } - - my $auth_user = Bugzilla->localconfig->{$auth_var_name}; - my $auth_pass = Bugzilla->localconfig->{$auth_var_pass}; - unless ($auth_user && $auth_pass) { - ERROR("$auth_var_name and $auth_var_pass not configured"); - $r->note_basic_auth_failure; - return Apache2::Const::HTTP_UNAUTHORIZED; - } - - unless ($r->user eq $auth_user && $password eq $auth_pass) { - $r->note_basic_auth_failure; - WARN('username and password do not match'); - return Apache2::Const::HTTP_UNAUTHORIZED; - } - - return Apache2::Const::OK; -} - -1; diff --git a/Bugzilla/ModPerl/BlockIP.pm b/Bugzilla/ModPerl/BlockIP.pm deleted file mode 100644 index 4e9a4be5c..000000000 --- a/Bugzilla/ModPerl/BlockIP.pm +++ /dev/null @@ -1,65 +0,0 @@ -package Bugzilla::ModPerl::BlockIP; -use 5.10.1; -use strict; -use warnings; - -use Apache2::RequestRec (); -use Apache2::Connection (); - -use Apache2::Const -compile => qw(OK); -use Cache::Memcached::Fast; - -use constant BLOCK_TIMEOUT => 60*60; - -my $MEMCACHED = Bugzilla::Memcached->_new()->{memcached}; -my $STATIC_URI = qr{ - ^/ - (?: extensions/[^/]+/web - | robots\.txt - | __heartbeat__ - | __lbheartbeat__ - | __version__ - | images - | skins - | js - | errors - ) -}xms; - -sub block_ip { - my ($class, $ip) = @_; - $MEMCACHED->set("block_ip:$ip" => 1, BLOCK_TIMEOUT) if $MEMCACHED; -} - -sub unblock_ip { - my ($class, $ip) = @_; - $MEMCACHED->delete("block_ip:$ip") if $MEMCACHED; -} - -sub handler { - my $r = shift; - return Apache2::Const::OK if $r->uri =~ $STATIC_URI; - - my $ip = $r->headers_in->{'X-Forwarded-For'}; - if ($ip) { - $ip = (split(/\s*,\s*/ms, $ip))[-1]; - } - else { - $ip = $r->connection->remote_ip; - } - - if ($MEMCACHED && $MEMCACHED->get("block_ip:$ip")) { - __PACKAGE__->block_ip($ip); - $r->status_line("429 Too Many Requests"); - # 500 is used here because apache 2.2 doesn't understand 429. - # the above line and the return value together mean we produce 429. - # Any other variation doesn't work. - $r->custom_response(500, "Too Many Requests"); - return 429; - } - else { - return Apache2::Const::OK; - } -} - -1; diff --git a/Bugzilla/ModPerl/Hostage.pm b/Bugzilla/ModPerl/Hostage.pm deleted file mode 100644 index a3bdfac58..000000000 --- a/Bugzilla/ModPerl/Hostage.pm +++ /dev/null @@ -1,71 +0,0 @@ -package Bugzilla::ModPerl::Hostage; -use 5.10.1; -use strict; -use warnings; - -use Apache2::Const qw(:common); ## no critic (Freenode::ModPerl) - -sub _attachment_root { - my ($base) = @_; - return undef unless $base; - return $base =~ m{^https?://(?:bug)?\%bugid\%\.([a-zA-Z\.-]+)} - ? $1 - : undef; -} - -sub _attachment_host_regex { - my ($base) = @_; - return undef unless $base; - my $val = $base; - $val =~ s{^https?://}{}s; - $val =~ s{/$}{}s; - my $regex = quotemeta $val; - $regex =~ s/\\\%bugid\\\%/\\d+/g; - return qr/^$regex$/s; -} - -sub handler { - my $r = shift; - state $urlbase = Bugzilla->localconfig->{urlbase}; - state $urlbase_uri = URI->new($urlbase); - state $urlbase_host = $urlbase_uri->host; - state $urlbase_host_regex = qr/^bug(\d+)\.\Q$urlbase_host\E$/; - state $attachment_base = Bugzilla->localconfig->{attachment_base}; - state $attachment_root = _attachment_root($attachment_base); - state $attachment_host_regex = _attachment_host_regex($attachment_base); - - my $hostname = $r->hostname; - return OK if $hostname eq $urlbase_host; - - my $path = $r->uri; - return OK if $path eq '/__lbheartbeat__'; - - if ($attachment_base && $hostname eq $attachment_root) { - $r->headers_out->set(Location => $urlbase); - return REDIRECT; - } - elsif ($attachment_base && $hostname =~ $attachment_host_regex) { - if ($path =~ m{^/attachment\.cgi}s) { - return OK; - } else { - my $new_uri = URI->new($r->unparsed_uri); - $new_uri->scheme($urlbase_uri->scheme); - $new_uri->host($urlbase_host); - $r->headers_out->set(Location => $new_uri); - return REDIRECT; - } - } - elsif (my ($id) = $hostname =~ $urlbase_host_regex) { - my $new_uri = $urlbase_uri->clone; - $new_uri->path('/show_bug.cgi'); - $new_uri->query_form(id => $id); - $r->headers_out->set(Location => $new_uri); - return REDIRECT; - } - else { - $r->headers_out->set(Location => $urlbase); - return REDIRECT; - } -} - -1; \ No newline at end of file diff --git a/Bugzilla/ModPerl/StartupFix.pm b/Bugzilla/ModPerl/StartupFix.pm deleted file mode 100644 index bcc467e9f..000000000 --- a/Bugzilla/ModPerl/StartupFix.pm +++ /dev/null @@ -1,51 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. -package Bugzilla::ModPerl::StartupFix; -use 5.10.1; -use strict; -use warnings; - -use Filter::Util::Call; -use Apache2::ServerUtil (); - -# This module is a source filter that removes every subsequent line -# if this is the first time apache has started, -# as reported by Apache2::ServerUtil::restart_count(), which is 1 -# on the first start. - -my $FIRST_STARTUP = <<'CODE'; -warn "Bugzilla::ModPerl::StartupFix: Skipping first startup using source filter\n"; -1; -CODE - -sub import { - my ($class) = @_; - my ($ref) = {}; - filter_add( bless $ref, $class ); -} - -# this will be called for each line. -# For the first line replaced, we insert $FIRST_STARTUP. -# Every subsequent line is replaced with an empty string. -sub filter { - my ($self) = @_; - my ($status); - if ($status = filter_read() > 0) { - if (Apache2::ServerUtil::restart_count() < 2) { - if (!$self->{did_it}) { - $self->{did_it} = 1; - $_ = $FIRST_STARTUP; - } - else { - $_ = ""; - } - } - } - return $status; -} - -1; \ No newline at end of file diff --git a/Bugzilla/Quantum.pm b/Bugzilla/Quantum.pm new file mode 100644 index 000000000..e4c75726c --- /dev/null +++ b/Bugzilla/Quantum.pm @@ -0,0 +1,214 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum; +use Mojo::Base 'Mojolicious'; + +use CGI::Compile; # Needed for its exit() overload +use Bugzilla::Logging; +use Bugzilla::Quantum::Template; +use Bugzilla::Quantum::CGI; +use Bugzilla::Quantum::SES; +use Bugzilla::Quantum::Static; + +use Bugzilla (); +use Bugzilla::Constants qw(bz_locations); +use Bugzilla::BugMail (); +use Bugzilla::CGI (); +use Bugzilla::Extension (); +use Bugzilla::Install::Requirements (); +use Bugzilla::Util (); +use Cwd qw(realpath); + +use MojoX::Log::Log4perl::Tiny; + +has 'static' => sub { Bugzilla::Quantum::Static->new }; + +sub startup { + my ($self) = @_; + + DEBUG("Starting up"); + $self->plugin('Bugzilla::Quantum::Plugin::Glue'); + $self->plugin('Bugzilla::Quantum::Plugin::Hostage'); + $self->plugin('Bugzilla::Quantum::Plugin::BlockIP'); + $self->plugin('Bugzilla::Quantum::Plugin::BasicAuth'); + + if ( $self->mode ne 'development' ) { + $self->hook( + after_static => sub { + my ($c) = @_; + $c->res->headers->cache_control('public, max-age=31536000'); + } + ); + } + + my $r = $self->routes; + Bugzilla::Quantum::CGI->load_all($r); + Bugzilla::Quantum::CGI->load_one('bzapi_cgi', 'extensions/BzAPI/bin/rest.cgi'); + + $r->any('/')->to('CGI#index_cgi'); + $r->any('/rest')->to('CGI#rest_cgi'); + $r->any('/rest.cgi/*PATH_INFP')->to('CGI#rest_cgi' => { PATH_INFO => '' }); + $r->any('/rest/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' }); + $r->any('/bug/:id')->to('CGI#show_bug_cgi'); + $r->any('/extensions/BzAPI/bin/rest.cgi/*PATH_INFO')->to('CGI#bzapi_cgi'); + + $r->get( + '/__lbheartbeat__' => sub { + my $c = shift; + $c->reply->file( $c->app->home->child('__lbheartbeat__') ); + }, + ); + + $r->get('/__heartbeat__')->to( 'CGI#heartbeat_cgi'); + $r->get('/robots.txt')->to( 'CGI#robots_cgi' ); + + $r->any('/review')->to( 'CGI#page_cgi' => {'id' => 'splinter.html'}); + $r->any('/user_profile')->to( 'CGI#page_cgi' => {'id' => 'user_profile.html'}); + $r->any('/userprofile')->to( 'CGI#page_cgi' => {'id' => 'user_profile.html'}); + $r->any('/request_defer')->to( 'CGI#page_cgi' => {'id' => 'request_defer.html'}); + $r->any('/login')->to( 'CGI#index_cgi' => { 'GoAheadAndLogIn' => '1' }); + + $r->any('/:new_bug' => [new_bug => qr{new[-_]bug}])->to( 'CGI#new_bug_cgi'); + + my $ses_auth = $r->under( + '/ses' => sub { + my ($c) = @_; + my $lc = Bugzilla->localconfig; + + return $c->basic_auth( 'SES', $lc->{ses_username}, $lc->{ses_password} ); + } + ); + $ses_auth->any('/index.cgi')->to('SES#main'); + + $r->any('/:REWRITE_itrequest' => [REWRITE_itrequest => qr{form[\.:]itrequest}])->to( + 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'itrequest' } + ); + $r->any('/:REWRITE_mozlist' => [REWRITE_mozlist => qr{form[\.:]mozlist}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'mozlist'} + ); + $r->any('/:REWRITE_poweredby' => [REWRITE_poweredby => qr{form[\.:]poweredby}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'poweredby'} + ); + $r->any('/:REWRITE_presentation' => [REWRITE_presentation => qr{form[\.:]presentation}])->to( + 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'presentation'} + ); + $r->any('/:REWRITE_trademark' => [REWRITE_trademark => qr{form[\.:]trademark}])->to( + 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'trademark'} + ); + $r->any('/:REWRITE_recoverykey' => [REWRITE_recoverykey => qr{form[\.:]recoverykey}])->to( + 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'recoverykey'} + ); + $r->any('/:REWRITE_legal' => [REWRITE_legal => qr{form[\.:]legal}])->to( + 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'legal' }, + ); + $r->any('/:REWRITE_recruiting' => [REWRITE_recruiting => qr{form[\.:]recruiting}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'recruiting'} + ); + $r->any('/:REWRITE_intern' => [REWRITE_intern => qr{form[\.:]intern}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'intern'} + ); + $r->any('/:REWRITE_mozpr' => [REWRITE_mozpr => qr{form[\.:]mozpr}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla PR', 'format' => 'mozpr' }, + ); + $r->any('/:REWRITE_reps_mentorship' => [REWRITE_reps_mentorship => qr{form[\.:]reps[\.:]mentorship}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'mozreps' }, + ); + $r->any('/:REWRITE_reps_budget' => [REWRITE_reps_budget => qr{form[\.:]reps[\.:]budget}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-budget'} + ); + $r->any('/:REWRITE_reps_swag' => [REWRITE_reps_swag => qr{form[\.:]reps[\.:]swag}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-swag'} + ); + $r->any('/:REWRITE_reps_it' => [REWRITE_reps_it => qr{form[\.:]reps[\.:]it}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-it'} + ); + $r->any('/:REWRITE_reps_payment' => [REWRITE_reps_payment => qr{form[\.:]reps[\.:]payment}])->to( + 'CGI#page_cgi' => {'id' => 'remo-form-payment.html'} + ); + $r->any('/:REWRITE_csa_discourse' => [REWRITE_csa_discourse => qr{form[\.:]csa[\.:]discourse}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Infrastructure & Operations', 'format' => 'csa-discourse'} + ); + $r->any('/:REWRITE_employee_incident' => [REWRITE_employee_incident => qr{form[\.:]employee[\.\-:]incident}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'employee-incident'} + ); + $r->any('/:REWRITE_brownbag' => [REWRITE_brownbag => qr{form[\.:]brownbag}])->to( + 'CGI#https_air_mozilla_org_requests' => {} + ); + $r->any('/:REWRITE_finance' => [REWRITE_finance => qr{form[\.:]finance}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Finance','format' => 'finance'} + ); + $r->any('/:REWRITE_moz_project_review' => [REWRITE_moz_project_review => qr{form[\.:]moz[\.\-:]project[\.\-:]review}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org','format' => 'moz-project-review'} + ); + $r->any('/:REWRITE_docs' => [REWRITE_docs => qr{form[\.:]docs?}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Developer Documentation','format' => 'doc'} + ); + $r->any('/:REWRITE_mdn' => [REWRITE_mdn => qr{form[\.:]mdn?}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'mdn','product' => 'developer.mozilla.org'} + ); + $r->any('/:REWRITE_swag_gear' => [REWRITE_swag_gear => qr{form[\.:](swag|gear)}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'swag','product' => 'Marketing'} + ); + $r->any('/:REWRITE_costume' => [REWRITE_costume => qr{form[\.:]costume}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Marketing','format' => 'costume'} + ); + $r->any('/:REWRITE_ipp' => [REWRITE_ipp => qr{form[\.:]ipp}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Internet Public Policy','format' => 'ipp'} + ); + $r->any('/:REWRITE_creative' => [REWRITE_creative => qr{form[\.:]creative}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'creative','product' => 'Marketing'} + ); + $r->any('/:REWRITE_user_engagement' => [REWRITE_user_engagement => qr{form[\.:]user[\.\-:]engagement}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'user-engagement','product' => 'Marketing'} + ); + $r->any('/:REWRITE_dev_engagement_event' => [REWRITE_dev_engagement_event => qr{form[\.:]dev[\.\-:]engagement[\.\-\:]event}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Developer Engagement','format' => 'dev-engagement-event'} + ); + $r->any('/:REWRITE_mobile_compat' => [REWRITE_mobile_compat => qr{form[\.:]mobile[\.\-:]compat}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Tech Evangelism','format' => 'mobile-compat'} + ); + $r->any('/:REWRITE_web_bounty' => [REWRITE_web_bounty => qr{form[\.:]web[\.:]bounty}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'web-bounty','product' => 'mozilla.org'} + ); + $r->any('/:REWRITE_automative' => [REWRITE_automative => qr{form[\.:]automative}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Testing','format' => 'automative'} + ); + $r->any('/:REWRITE_comm_newsletter' => [REWRITE_comm_newsletter => qr{form[\.:]comm[\.:]newsletter}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'comm-newsletter','product' => 'Marketing'} + ); + $r->any('/:REWRITE_screen_share_whitelist' => [REWRITE_screen_share_whitelist => qr{form[\.:]screen[\.:]share[\.:]whitelist}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'screen-share-whitelist','product' => 'Firefox'} + ); + $r->any('/:REWRITE_data_compliance' => [REWRITE_data_compliance => qr{form[\.:]data[\.\-:]compliance}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Data Compliance','format' => 'data-compliance'} + ); + $r->any('/:REWRITE_fsa_budget' => [REWRITE_fsa_budget => qr{form[\.:]fsa[\.:]budget}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'FSA','format' => 'fsa-budget'} + ); + $r->any('/:REWRITE_triage_request' => [REWRITE_triage_request => qr{form[\.:]triage[\.\-]request}])->to( + 'CGI#page_cgi' => {'id' => 'triage_request.html'} + ); + $r->any('/:REWRITE_crm_CRM' => [REWRITE_crm_CRM => qr{form[\.:](crm|CRM)}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'crm','product' => 'Marketing'} + ); + $r->any('/:REWRITE_nda' => [REWRITE_nda => qr{form[\.:]nda}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Legal','format' => 'nda'} + ); + $r->any('/:REWRITE_name_clearance' => [REWRITE_name_clearance => qr{form[\.:]name[\.:]clearance}])->to( + 'CGI#enter_bug_cgi' => {'format' => 'name-clearance','product' => 'Legal'} + ); + $r->any('/:REWRITE_shield_studies' => [REWRITE_shield_studies => qr{form[\.:]shield[\.:]studies}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Shield','format' => 'shield-studies'} + ); + $r->any('/:REWRITE_client_bounty' => [REWRITE_client_bounty => qr{form[\.:]client[\.:]bounty}])->to( + 'CGI#enter_bug_cgi' => {'product' => 'Firefox','format' => 'client-bounty'} + ); + +} + +1; diff --git a/Bugzilla/Quantum/CGI.pm b/Bugzilla/Quantum/CGI.pm new file mode 100644 index 000000000..85f14cf83 --- /dev/null +++ b/Bugzilla/Quantum/CGI.pm @@ -0,0 +1,162 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::CGI; +use Mojo::Base 'Mojolicious::Controller'; + +use CGI::Compile; +use Bugzilla::Constants qw(bz_locations); +use Bugzilla::Quantum::Stdout; +use File::Slurper qw(read_text); +use File::Spec::Functions qw(catfile); +use Sub::Name; +use Sub::Quote 2.005000; +use Try::Tiny; +use Taint::Util qw(untaint); +use Socket qw(AF_INET inet_aton); +use Sys::Hostname; +use English qw(-no_match_vars); + +our $C; +my %SEEN; + +sub load_all { + my ($class, $r) = @_; + + foreach my $file (glob '*.cgi') { + my $name = _file_to_method($file); + $class->load_one($name, $file); + $r->any("/$file")->to("CGI#$name"); + } +} + +sub load_one { + my ($class, $name, $file) = @_; + my $package = __PACKAGE__ . "::$name", + my $inner_name = "_$name"; + my $content = read_text( catfile( bz_locations->{cgi_path}, $file ) ); + $content = "package $package; $content"; + untaint($content); + my %options = ( + package => $package, + file => $file, + line => 1, + no_defer => 1, + ); + die "Tried to load $file more than once" if $SEEN{$file}++; + my $inner = quote_sub $inner_name, $content, {}, \%options; + my $wrapper = sub { + my ($c) = @_; + my $stdin = $c->_STDIN; + my $stdout = ''; + local $C = $c; + local %ENV = $c->_ENV($file); + local *STDIN; ## no critic (local) + local $CGI::Compile::USE_REAL_EXIT = 0; + local $PROGRAM_NAME = $file; + open STDIN, '<', $stdin->path or die "STDIN @{[$stdin->path]}: $!" if -s $stdin->path; + tie *STDOUT, 'Bugzilla::Quantum::Stdout', controller => $c; ## no critic (tie) + try { + Bugzilla->init_page(); + $inner->(); + } + catch { + die $_ unless ref $_ eq 'ARRAY' && $_->[0] eq "EXIT\n"; + } + finally { + untie *STDOUT; + $c->finish; + Bugzilla->_cleanup; ## no critic (private) + CGI::initialize_globals(); + }; + }; + + no strict 'refs'; ## no critic (strict) + *{$name} = subname($name, $wrapper); + return 1; +} + +sub _ENV { + my ($c, $script_name) = @_; + my $tx = $c->tx; + my $req = $tx->req; + my $headers = $req->headers; + my $content_length = $req->content->is_multipart ? $req->body_size : $headers->content_length; + my %env_headers = ( HTTP_COOKIE => '', HTTP_REFERER => '' ); + + for my $name ( @{ $headers->names } ) { + my $key = uc "http_$name"; + $key =~ s!\W!_!g; + $env_headers{$key} = $headers->header($name); + } + + my $remote_user; + if ( my $userinfo = $c->req->url->to_abs->userinfo ) { + $remote_user = $userinfo =~ /([^:]+)/ ? $1 : ''; + } + elsif ( my $authenticate = $headers->authorization ) { + $remote_user = $authenticate =~ /Basic\s+(.*)/ ? b64_decode $1 : ''; + $remote_user = $remote_user =~ /([^:]+)/ ? $1 : ''; + } + my $path_info = $c->param('PATH_INFO'); + my %captures = %{ $c->stash->{'mojo.captures'} // {} }; + foreach my $key (keys %captures) { + if ($key eq 'controller' || $key eq 'action' || $key eq 'PATH_INFO' || $key =~ /^REWRITE_/) { + delete $captures{$key}; + } + } + my $cgi_query = Mojo::Parameters->new(%captures); + $cgi_query->append($req->url->query); + my $prefix = $c->stash->{bmo_prefix} ? '/bmo/' : '/'; + + return ( + %ENV, + CONTENT_LENGTH => $content_length || 0, + CONTENT_TYPE => $headers->content_type || '', + GATEWAY_INTERFACE => 'CGI/1.1', + HTTPS => $req->is_secure ? 'YES' : 'NO', + %env_headers, + QUERY_STRING => $cgi_query->to_string, + PATH_INFO => $path_info ? "/$path_info" : '', + REMOTE_ADDR => $tx->original_remote_address, + REMOTE_HOST => $tx->original_remote_address, + REMOTE_PORT => $tx->remote_port, + REMOTE_USER => $remote_user || '', + REQUEST_METHOD => $req->method, + SCRIPT_NAME => "$prefix$script_name", + SERVER_NAME => hostname, + SERVER_PORT => $tx->local_port, + SERVER_PROTOCOL => $req->is_secure ? 'HTTPS' : 'HTTP', # TODO: Version is missing + SERVER_SOFTWARE => __PACKAGE__, + ); +} + +sub _STDIN { + my $c = shift; + my $stdin; + + if ( $c->req->content->is_multipart ) { + $stdin = Mojo::Asset::File->new; + $stdin->add_chunk( $c->req->build_body ); + } + else { + $stdin = $c->req->content->asset; + } + + return $stdin if $stdin->isa('Mojo::Asset::File'); + return Mojo::Asset::File->new->add_chunk( $stdin->slurp ); +} + +sub _file_to_method { + my ($name) = @_; + $name =~ s/\./_/s; + $name =~ s/\W+/_/gs; + return $name; +} + + +1; diff --git a/Bugzilla/Quantum/Plugin/BasicAuth.pm b/Bugzilla/Quantum/Plugin/BasicAuth.pm new file mode 100644 index 000000000..bc79c89ef --- /dev/null +++ b/Bugzilla/Quantum/Plugin/BasicAuth.pm @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. +package Bugzilla::Quantum::Plugin::BasicAuth; +use 5.10.1; +use Mojo::Base qw(Mojolicious::Plugin); + +use Bugzilla::Logging; +use Carp; + +sub register { + my ( $self, $app, $conf ) = @_; + + $app->renderer->add_helper( + basic_auth => sub { + my ($c, $realm, $auth_user, $auth_pass) = @_; + my $req = $c->req; + my ($user, $password) = $req->url->to_abs->userinfo =~ /^([^:]+):(.*)/; + + unless ($realm && $auth_user && $auth_pass) { + croak 'basic_auth() called with missing parameters.'; + } + + unless ($user eq $auth_user && $password eq $auth_pass) { + WARN('username and password do not match'); + $c->res->headers->www_authenticate("Basic realm=\"$realm\""); + $c->res->code(401); + $c->rendered; + return 0; + } + + return 1; + } + ); +} + +1; diff --git a/Bugzilla/Quantum/Plugin/BlockIP.pm b/Bugzilla/Quantum/Plugin/BlockIP.pm new file mode 100644 index 000000000..fbfffad66 --- /dev/null +++ b/Bugzilla/Quantum/Plugin/BlockIP.pm @@ -0,0 +1,43 @@ +package Bugzilla::Quantum::Plugin::BlockIP; +use 5.10.1; +use Mojo::Base 'Mojolicious::Plugin'; + +use Bugzilla::Memcached; + +use constant BLOCK_TIMEOUT => 60*60; + +my $MEMCACHED = Bugzilla::Memcached->_new()->{memcached}; + +sub register { + my ( $self, $app, $conf ) = @_; + + $app->hook(before_routes => \&_before_routes); + $app->helper(block_ip => \&_block_ip); + $app->helper(unblock_ip => \&_unblock_ip); +} + +sub _block_ip { + my ($class, $ip) = @_; + $MEMCACHED->set("block_ip:$ip" => 1, BLOCK_TIMEOUT) if $MEMCACHED; +} + +sub _unblock_ip { + my ($class, $ip) = @_; + $MEMCACHED->delete("block_ip:$ip") if $MEMCACHED; +} + +sub _before_routes { + my ( $c ) = @_; + return if $c->stash->{'mojo.static'}; + + my $ip = $c->tx->remote_address; + if ($MEMCACHED && $MEMCACHED->get("block_ip:$ip")) { + $c->block_ip($ip); + $c->res->code(429); + $c->res->message("Too Many Requests"); + $c->res->body("Too Many Requests"); + $c->finish; + } +} + +1; diff --git a/Bugzilla/Quantum/Plugin/Glue.pm b/Bugzilla/Quantum/Plugin/Glue.pm new file mode 100644 index 000000000..4261d6729 --- /dev/null +++ b/Bugzilla/Quantum/Plugin/Glue.pm @@ -0,0 +1,114 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::Plugin::Glue; +use 5.10.1; +use Mojo::Base 'Mojolicious::Plugin'; + +use Try::Tiny; +use Bugzilla::Constants; +use Bugzilla::Quantum::Template; +use Bugzilla::Logging; +use Bugzilla::RNG (); +use JSON::MaybeXS qw(decode_json); + +sub register { + my ( $self, $app, $conf ) = @_; + + my %D; + if ($ENV{BUGZILLA_HTTPD_ARGS}) { + my $args = decode_json($ENV{BUGZILLA_HTTPD_ARGS}); + foreach my $arg (@$args) { + if ($arg =~ /^-D(\w+)$/) { + $D{$1} = 1; + } + else { + die "Unknown httpd arg: $arg"; + } + } + } + + # hypnotoad is weird and doesn't look for MOJO_LISTEN itself. + $app->config( + hypnotoad => { + listen => [ $ENV{MOJO_LISTEN} ], + }, + ); + + # Make sure each httpd child receives a different random seed (bug 476622). + # Bugzilla::RNG has one srand that needs to be called for + # every process, and Perl has another. (Various Perl modules still use + # the built-in rand(), even though we never use it in Bugzilla itself, + # so we need to srand() both of them.) + # Also, ping the dbh to force a reconnection. + Mojo::IOLoop->next_tick( + sub { + Bugzilla::RNG::srand(); + srand(); + eval { Bugzilla->dbh->ping }; + } + ); + + $app->hook( + before_dispatch => sub { + my ($c) = @_; + if ($D{HTTPD_IN_SUBDIR}) { + my $path = $c->req->url->path; + if ($path =~ s{^/bmo}{}s) { + $c->stash->{bmo_prefix} = 1; + $c->req->url->path($path); + } + } + Log::Log4perl::MDC->put(request_id => $c->req->request_id); + } + ); + + Bugzilla::Extension->load_all(); + if ($app->mode ne 'development') { + Bugzilla->preload_features(); + DEBUG("preloading templates"); + Bugzilla->preload_templates(); + DEBUG("done preloading templates"); + } + $app->secrets([Bugzilla->localconfig->{side_wide_secret}]); + + $app->renderer->add_handler( + 'bugzilla' => sub { + my ( $renderer, $c, $output, $options ) = @_; + my $vars = delete $c->stash->{vars}; + + # Helpers + my %helper; + foreach my $method ( grep {m/^\w+\z/} keys %{ $renderer->helpers } ) { + my $sub = $renderer->helpers->{$method}; + $helper{$method} = sub { $c->$sub(@_) }; + } + $vars->{helper} = \%helper; + + # The controller + $vars->{c} = $c; + my $name = $options->{template}; + unless ($name =~ /\./) { + $name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format}; + } + my $template = Bugzilla->template; + $template->process( $name, $vars, $output ) + or die $template->error; + } + ); + + $app->log( + MojoX::Log::Log4perl::Tiny->new( + logger => Log::Log4perl->get_logger(ref $app) + ) + ); +} + + + + +1; diff --git a/Bugzilla/Quantum/Plugin/Hostage.pm b/Bugzilla/Quantum/Plugin/Hostage.pm new file mode 100644 index 000000000..42a05a910 --- /dev/null +++ b/Bugzilla/Quantum/Plugin/Hostage.pm @@ -0,0 +1,80 @@ +package Bugzilla::Quantum::Plugin::Hostage; +use 5.10.1; +use Mojo::Base 'Mojolicious::Plugin'; + +sub _attachment_root { + my ($base) = @_; + return undef unless $base; + return $base =~ m{^https?://(?:bug)?\%bugid\%\.([a-zA-Z\.-]+)} + ? $1 + : undef; +} + +sub _attachment_host_regex { + my ($base) = @_; + return undef unless $base; + my $val = $base; + $val =~ s{^https?://}{}s; + $val =~ s{/$}{}s; + my $regex = quotemeta $val; + $regex =~ s/\\\%bugid\\\%/\\d+/g; + return qr/^$regex$/s; +} + +sub register { + my ( $self, $app, $conf ) = @_; + + $app->hook(before_routes => \&_before_routes); +} + +sub _before_routes { + my ( $c ) = @_; + state $urlbase = Bugzilla->localconfig->{urlbase}; + state $urlbase_uri = URI->new($urlbase); + state $urlbase_host = $urlbase_uri->host; + state $urlbase_host_regex = qr/^bug(\d+)\.\Q$urlbase_host\E$/; + state $attachment_base = Bugzilla->localconfig->{attachment_base}; + state $attachment_root = _attachment_root($attachment_base); + state $attachment_host_regex = _attachment_host_regex($attachment_base); + + my $stash = $c->stash; + my $req = $c->req; + my $url = $req->url->to_abs; + + return if $stash->{'mojo.static'}; + + my $hostname = $url->host; + return if $hostname eq $urlbase_host; + + my $path = $url->path; + return if $path eq '/__lbheartbeat__'; + + if ($attachment_base && $hostname eq $attachment_root) { + $c->redirect_to($urlbase); + return; + } + elsif ($attachment_base && $hostname =~ $attachment_host_regex) { + if ($path =~ m{^/attachment\.cgi}s) { + return; + } else { + my $new_uri = $url->clone; + $new_uri->scheme($urlbase_uri->scheme); + $new_uri->host($urlbase_host); + $c->redirect_to($new_uri); + return; + } + } + elsif (my ($id) = $hostname =~ $urlbase_host_regex) { + my $new_uri = $urlbase_uri->clone; + $new_uri->path('/show_bug.cgi'); + $new_uri->query_form(id => $id); + $c->redirect_to($new_uri); + return; + } + else { + $c->redirect_to($urlbase); + return; + } +} + +1; \ No newline at end of file diff --git a/Bugzilla/Quantum/SES.pm b/Bugzilla/Quantum/SES.pm new file mode 100644 index 000000000..47c591fb5 --- /dev/null +++ b/Bugzilla/Quantum/SES.pm @@ -0,0 +1,203 @@ +package Bugzilla::Quantum::SES; +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use 5.10.1; +use Mojo::Base qw( Mojolicious::Controller ); + +use Bugzilla::Constants qw(ERROR_MODE_DIE); +use Bugzilla::Logging; +use Bugzilla::Mailer qw(MessageToMTA); +use Bugzilla::User (); +use Bugzilla::Util qw(html_quote remote_ip); +use JSON::MaybeXS qw(decode_json); +use LWP::UserAgent (); +use Try::Tiny qw(catch try); + +sub main { + my ($self) = @_; + Bugzilla->error_mode(ERROR_MODE_DIE); + my $message = $self->_decode_json_wrapper( $self->req->body ) // return; + my $message_type = $self->req->headers->header('X-Amz-SNS-Message-Type') // '(missing)'; + + if ( $message_type eq 'SubscriptionConfirmation' ) { + $self->_confirm_subscription($message); + } + + elsif ( $message_type eq 'Notification' ) { + my $notification = $self->_decode_json_wrapper( $message->{Message} ) // return; + unless ( + # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html + $self->_handle_notification( $notification, 'eventType' ) + + # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html + || $self->_handle_notification( $notification, 'notificationType' ) + ) + { + WARN('Failed to find notification type'); + $self->_respond( 400 => 'Bad Request' ); + } + } + + else { + WARN("Unsupported message-type: $message_type"); + $self->_respond( 200 => 'OK' ); + } +} + +sub _confirm_subscription { + my ($self, $message) = @_; + + my $subscribe_url = $message->{SubscribeURL}; + if ( !$subscribe_url ) { + WARN('Bad SubscriptionConfirmation request: missing SubscribeURL'); + $self->_respond( 400 => 'Bad Request' ); + return; + } + + my $ua = ua(); + my $res = $ua->get( $message->{SubscribeURL} ); + if ( !$res->is_success ) { + WARN( 'Bad response from SubscribeURL: ' . $res->status_line ); + $self->_respond( 400 => 'Bad Request' ); + return; + } + + $self->_respond( 200 => 'OK' ); +} + +sub _handle_notification { + my ( $self, $notification, $type_field ) = @_; + + if ( !exists $notification->{$type_field} ) { + return 0; + } + my $type = $notification->{$type_field}; + + if ( $type eq 'Bounce' ) { + $self->_process_bounce($notification); + } + elsif ( $type eq 'Complaint' ) { + $self->_process_complaint($notification); + } + else { + WARN("Unsupported notification-type: $type"); + $self->_respond( 200 => 'OK' ); + } + return 1; +} + +sub _process_bounce { + my ($self, $notification) = @_; + + # disable each account that is bouncing + foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) { + my $address = $recipient->{emailAddress}; + my $reason = sprintf '(%s) %s', $recipient->{action} // 'error', $recipient->{diagnosticCode} // 'unknown'; + + my $user = Bugzilla::User->new( { name => $address, cache => 1 } ); + if ($user) { + + # never auto-disable admin accounts + if ( $user->in_group('admin') ) { + Bugzilla->audit("ignoring bounce for admin <$address>: $reason"); + } + + else { + my $template = Bugzilla->template_inner(); + my $vars = { + mta => $notification->{bounce}->{reportingMTA} // 'unknown', + reason => $reason, + }; + my $disable_text; + $template->process( 'admin/users/bounce-disabled.txt.tmpl', $vars, \$disable_text ) + || die $template->error(); + + $user->set_disabledtext($disable_text); + $user->set_disable_mail(1); + $user->update(); + Bugzilla->audit( "bounce for <$address> disabled userid-" . $user->id . ": $reason" ); + } + } + + else { + Bugzilla->audit("bounce for <$address> has no user: $reason"); + } + } + + $self->_respond( 200 => 'OK' ); +} + +sub _process_complaint { + my ($self) = @_; + + # email notification to bugzilla admin + my ($notification) = @_; + my $template = Bugzilla->template_inner(); + my $json = JSON::MaybeXS->new( + pretty => 1, + utf8 => 1, + canonical => 1, + ); + + foreach my $recipient ( @{ $notification->{complaint}->{complainedRecipients} } ) { + my $reason = $notification->{complaint}->{complaintFeedbackType} // 'unknown'; + my $address = $recipient->{emailAddress}; + Bugzilla->audit("complaint for <$address> for '$reason'"); + my $vars = { + email => $address, + user => Bugzilla::User->new( { name => $address, cache => 1 } ), + reason => $reason, + notification => $json->encode($notification), + }; + my $message; + $template->process( 'email/ses-complaint.txt.tmpl', $vars, \$message ) + || die $template->error(); + MessageToMTA($message); + } + + $self->_respond( 200 => 'OK' ); +} + +sub _respond { + my ( $self, $code, $message ) = @_; + $self->render(text => "$message\n", status => $code); +} + +sub _decode_json_wrapper { + my ($self, $json) = @_; + my $result; + if ( !defined $json ) { + WARN( 'Missing JSON from ' . $self->tx->remote_address ); + $self->_respond( 400 => 'Bad Request' ); + return undef; + } + my $ok = try { + $result = decode_json($json); + } + catch { + WARN( 'Malformed JSON from ' . $self->tx->remote_address ); + $self->_respond( 400 => 'Bad Request' ); + return undef; + }; + return $ok ? $result : undef; +} + +sub ua { + my $ua = LWP::UserAgent->new(); + $ua->timeout(10); + $ua->protocols_allowed( [ 'http', 'https' ] ); + if ( my $proxy_url = Bugzilla->params->{'proxy_url'} ) { + $ua->proxy( [ 'http', 'https' ], $proxy_url ); + } + else { + $ua->env_proxy; + } + return $ua; +} + +1; \ No newline at end of file diff --git a/Bugzilla/Quantum/Static.pm b/Bugzilla/Quantum/Static.pm new file mode 100644 index 000000000..2bb54990e --- /dev/null +++ b/Bugzilla/Quantum/Static.pm @@ -0,0 +1,30 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::Static; +use Mojo::Base 'Mojolicious::Static'; +use Bugzilla::Constants qw(bz_locations); + +my $LEGACY_RE = qr{ + ^ (?:static/v[0-9]+\.[0-9]+/) ? + ( (?:extensions/[^/]+/web|(?:image|skin|j)s)/.+) + $ +}xs; + +sub file { + my ($self, $rel) = @_; + + if (my ($legacy_rel) = $rel =~ $LEGACY_RE) { + local $self->{paths} = [ bz_locations->{cgi_path} ]; + return $self->SUPER::file($legacy_rel); + } + else { + return $self->SUPER::file($rel); + } +} + +1; diff --git a/Bugzilla/Quantum/Stdout.pm b/Bugzilla/Quantum/Stdout.pm new file mode 100644 index 000000000..2cdba9160 --- /dev/null +++ b/Bugzilla/Quantum/Stdout.pm @@ -0,0 +1,59 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::Stdout; +use 5.10.1; +use Moo; + +use Bugzilla::Logging; +use Encode; + +has 'controller' => ( + is => 'ro', + required => 1, +); + +has '_encoding' => ( + is => 'rw', + default => '', +); + +sub TIEHANDLE { ## no critic (unpack) + my $class = shift; + + return $class->new(@_); +} + +sub PRINTF { ## no critic (unpack) + my $self = shift; + $self->PRINT(sprintf @_); +} + +sub PRINT { ## no critic (unpack) + my $self = shift; + my $c = $self->controller; + my $bytes = join '', @_; + return unless $bytes; + if ($self->_encoding) { + $bytes = encode($self->_encoding, $bytes); + } + $c->write($bytes); +} + +sub BINMODE { + my ($self, $mode) = @_; + if ($mode) { + if ($mode eq ':bytes' or $mode eq ':raw') { + $self->_encoding(''); + } + elsif ($mode eq ':utf8') { + $self->_encoding('utf8'); + } + } +} + +1; diff --git a/Bugzilla/Quantum/Template.pm b/Bugzilla/Quantum/Template.pm new file mode 100644 index 000000000..2442f1134 --- /dev/null +++ b/Bugzilla/Quantum/Template.pm @@ -0,0 +1,39 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::Template; +use 5.10.1; +use Moo; + +has 'controller' => ( + is => 'ro', + required => 1, +); + +has 'template' => ( + is => 'ro', + required => 1, + handles => ['error', 'get_format'], +); + +sub process { + my ($self, $file, $vars, $output) = @_; + + if (@_ < 4) { + $self->controller->stash->{vars} = $vars; + $self->controller->render(template => $file, handler => 'bugzilla'); + return 1; + } + elsif (@_ == 4) { + return $self->template->process($file, $vars, $output); + } + else { + die __PACKAGE__ . '->process() called with too many arguments'; + } +} + +1; \ No newline at end of file diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index 299734d64..39272a538 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -12,6 +12,7 @@ use 5.10.1; use strict; use warnings; +use Bugzilla::Logging; use Bugzilla::Template::PreloadProvider; use Bugzilla::Bug; use Bugzilla::Constants; diff --git a/Bugzilla/WebService/Server/XMLRPC.pm b/Bugzilla/WebService/Server/XMLRPC.pm index 6bb73af01..5ad50e91c 100644 --- a/Bugzilla/WebService/Server/XMLRPC.pm +++ b/Bugzilla/WebService/Server/XMLRPC.pm @@ -14,11 +14,7 @@ use warnings; use Bugzilla::Logging; use XMLRPC::Transport::HTTP; use Bugzilla::WebService::Server; -if ($ENV{MOD_PERL}) { - our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server); -} else { - our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server); -} +our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server); use Bugzilla::WebService::Constants; use Bugzilla::Error; diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm index 29ff05448..d462c884a 100644 --- a/Bugzilla/WebService/Util.pm +++ b/Bugzilla/WebService/Util.pm @@ -23,7 +23,7 @@ use base qw(Exporter); # We have to "require", not "use" this, because otherwise it tries to # use features of Test::More during import(). -require Test::Taint; +require Test::Taint if ${^TAINT}; our @EXPORT_OK = qw( extract_flags @@ -193,8 +193,10 @@ sub taint_data { # Though this is a private function, it hasn't changed since 2004 and # should be safe to use, and prevents us from having to write it ourselves # or require another module to do it. - Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params); - Test::Taint::taint_deeply(\@params); + if (${^TAINT}) { + Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params); + Test::Taint::taint_deeply(\@params); + } } sub _delete_bad_keys { diff --git a/Dockerfile b/Dockerfile index c86a92b0f..f9baf009d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,12 +9,6 @@ ENV CIRCLE_BUILD_URL=${CIRCLE_BUILD_URL} ENV CIRCLE_SHA1=${CIRCLE_SHA1} ENV LOG4PERL_CONFIG_FILE=log4perl-json.conf -ENV HTTPD_StartServers=8 -ENV HTTPD_MinSpareServers=5 -ENV HTTPD_MaxSpareServers=20 -ENV HTTPD_ServerLimit=256 -ENV HTTPD_MaxClients=256 -ENV HTTPD_MaxRequestsPerChild=4000 ENV PORT=8000 @@ -28,7 +22,8 @@ RUN mv /opt/bmo/local /app && \ chown -R app:app /app && \ perl -I/app -I/app/local/lib/perl5 -c -E 'use Bugzilla; BEGIN { Bugzilla->extensions }' && \ perl -c /app/scripts/entrypoint.pl && \ - setcap 'cap_net_bind_service=+ep' /usr/sbin/httpd + setcap 'cap_net_bind_service=+ep' /usr/sbin/httpd && \ + setcap 'cap_net_bind_service=+ep' /usr/bin/perl USER app diff --git a/Log/Log4perl/Layout/Mozilla.pm b/Log/Log4perl/Layout/Mozilla.pm index b625c54f4..4aedd9843 100644 --- a/Log/Log4perl/Layout/Mozilla.pm +++ b/Log/Log4perl/Layout/Mozilla.pm @@ -53,6 +53,9 @@ sub render { my $mdc = Log::Log4perl::MDC->get_context; my $fields = $mdc->{fields} // {}; + if ($mdc->{request_id}) { + $fields->{request_id} = $mdc->{request_id} + } my %out = ( EnvVersion => LOGGING_FORMAT_VERSION, Hostname => $HOSTNAME, diff --git a/Makefile.PL b/Makefile.PL index 46228ab56..51a2507e1 100755 --- a/Makefile.PL +++ b/Makefile.PL @@ -69,6 +69,7 @@ my %requires = ( 'Module::Metadata' => '1.000033', 'Module::Runtime' => '0.014', 'Mojolicious' => '7.71', + 'MojoX::Log::Log4perl::Tiny' => '0.01', 'Moo' => '2.002004', 'MooX::StrictConstructor' => '0.008', 'Mozilla::CA' => '20160104', @@ -79,6 +80,7 @@ my %requires = ( 'Template' => '2.24', 'Text::CSV_XS' => '1.26', 'Throwable' => '0.200013', + 'Sub::Quote' => '2.005000', 'Type::Tiny' => '1.000005', 'URI' => '1.55', 'URI::Escape::XS' => '0.14', @@ -282,18 +284,6 @@ my %optional_features = ( } }, }, - mod_perl => { - description => 'mod_perl support under Apache', - prereqs => { - runtime => { - requires => { - 'mod_perl2' => '1.999022', - 'Apache2::SizeLimit' => '0.96', - 'Plack::Handler::Apache2' => 0, - } - } - } - }, inbound_email => { description => 'Inbound Email', prereqs => { @@ -449,7 +439,6 @@ sub is_bmo_feature { ^ (?: pg | oracle - | mod_perl | sqlite | auth_ldap | auth_radius diff --git a/README.rst b/README.rst index 985be314e..0d8d03f38 100644 --- a/README.rst +++ b/README.rst @@ -330,41 +330,6 @@ BMO_use_mailer_queue Usually configured on the MTA section of the admin interface, you may change this here for testing purposes. Should be 1 or 0. If 1, the job queue will be used. For testing, only set to 0 if the BMO_mail_delivery_method is None or Test. -HTTPD_StartServers - Sets the number of child server processes created on startup. - As the number of processes is dynamically controlled depending on the load, - there is usually little reason to adjust this parameter. - Default: 8 - -HTTPD_MinSpareServers - Sets the desired minimum number of idle child server processes. An idle - process is one which is not handling a request. If there are fewer than - MinSpareServers idle, then the parent process creates new children at a - maximum rate of 1 per second. - Default: 5 - -HTTPD_MaxSpareServers - Sets the desired maximum number of idle child server processes. An idle - process is one which is not handling a request. If there are more than - MaxSpareServers idle, then the parent process will kill off the excess - processes. - Default: 20 - -HTTPD_MaxClients - Sets the maximum number of child processes that will be launched to serve requests. - Default: 256 - -HTTPD_ServerLimit - Sets the maximum configured value for MaxClients for the lifetime of the - Apache process. - Default: 256 - -HTTPD_MaxRequestsPerChild - Sets the limit on the number of requests that an individual child server - process will handle. After MaxRequestsPerChild requests, the child process - will die. If MaxRequestsPerChild is 0, then the process will never expire. - Default: 4000 - USE_NYTPROF Write `Devel::NYTProf`_ profiles out for each requests. These will be named /app/data/nytprof.$host.$script.$n.$pid, where $host is diff --git a/buglist.cgi b/buglist.cgi index 4d3ad1a86..fee259a2b 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -41,6 +41,8 @@ my $vars = {}; my $user = Bugzilla->login(); $cgi->redirect_search_url(); +use Bugzilla::Logging; +DEBUG("After the redirect."); my $buffer = $cgi->query_string(); if (length($buffer) == 0) { @@ -114,15 +116,15 @@ my $format = $template->get_format("list/list", scalar $cgi->param('format'), # Safari (WebKit) does not support it, despite a UA that says otherwise (bug 188712) # MSIE 5+ supports it on Mac (but not on Windows) (bug 190370) # -my $serverpush = - $format->{'extension'} eq "html" - && exists $ENV{'HTTP_USER_AGENT'} - && $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/ - && (($ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/) || ($ENV{'HTTP_USER_AGENT'} =~ /MSIE 5.*Mac_PowerPC/)) - && $ENV{'HTTP_USER_AGENT'} !~ /(?:WebKit|Trident|KHTML)/ - && !$agent - && !defined($cgi->param('serverpush')) - || $cgi->param('serverpush'); +my $serverpush = 0; + # $format->{'extension'} eq "html" + # && exists $ENV{'HTTP_USER_AGENT'} + # && $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/ + # && (($ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/) || ($ENV{'HTTP_USER_AGENT'} =~ /MSIE 5.*Mac_PowerPC/)) + # && $ENV{'HTTP_USER_AGENT'} !~ /(?:WebKit|Trident|KHTML)/ + # && !$agent + # && !defined($cgi->param('serverpush')) + # || $cgi->param('serverpush'); my $order = $cgi->param('order') || ""; @@ -752,12 +754,6 @@ if ($serverpush) { $template->process("list/server-push.html.tmpl", $vars) || ThrowTemplateError($template->error()); - # Under mod_perl, flush stdout so that the page actually shows up. - if ($ENV{MOD_PERL}) { - require Apache2::RequestUtil; - Apache2::RequestUtil->request->rflush(); - } - # Don't do multipart_end() until we're ready to display the replacement # page, otherwise any errors that happen before then (like SQL errors) # will result in a blank page being shown to the user instead of the error. diff --git a/bugzilla.pl b/bugzilla.pl new file mode 100755 index 000000000..efcea293b --- /dev/null +++ b/bugzilla.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl +use 5.10.1; +use strict; +use warnings; + +use File::Basename qw(basename dirname); +use File::Spec::Functions qw(catdir); +use Cwd qw(realpath); + +BEGIN { + require lib; + my $dir = realpath( dirname(__FILE__) ); + lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) ); +} +use Mojolicious::Commands; + +$ENV{MOJO_LISTEN} ||= $ENV{PORT} ? "http://*:$ENV{PORT}" : "http://*:3001"; + +# Start command line interface for application +Mojolicious::Commands->start_app('Bugzilla::Quantum'); diff --git a/checksetup.pl b/checksetup.pl index d3f08e024..7c9826ee3 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -135,7 +135,7 @@ require Bugzilla::Install::Localconfig; import Bugzilla::Install::Localconfig qw(update_localconfig); require Bugzilla::Install::Filesystem; -import Bugzilla::Install::Filesystem qw(update_filesystem create_htaccess +import Bugzilla::Install::Filesystem qw(update_filesystem fix_all_file_permissions); require Bugzilla::Install::DB; require Bugzilla::DB; @@ -201,7 +201,6 @@ unless ($switch{'no-database'}) { ########################################################################### update_filesystem({ index_html => $lc_hash->{'index_html'} }); -create_htaccess() if $lc_hash->{'create_htaccess'}; # Remove parameters from the params file that no longer exist in Bugzilla, # and set the defaults for new ones diff --git a/colchange.cgi b/colchange.cgi index d77782bec..46d25ecdf 100755 --- a/colchange.cgi +++ b/colchange.cgi @@ -136,26 +136,12 @@ if (defined $cgi->param('rememberedquery')) { $params->param('columnlist', join(",", @collist)); $vars->{'redirect_url'} = "buglist.cgi?".$params->query_string(); - # If we're running on Microsoft IIS, $cgi->redirect discards # the Set-Cookie lines. In mod_perl, $cgi->redirect with cookies # causes the page to be rendered as text/plain. # Workaround is to use the old-fashioned redirection mechanism. # See bug 214466 and bug 376044 for details. - if ($ENV{'MOD_PERL'} - || $ENV{'SERVER_SOFTWARE'} =~ /Microsoft-IIS/ - || $ENV{'SERVER_SOFTWARE'} =~ /Sun ONE Web/) - { - print $cgi->header(-type => "text/html", - -refresh => "0; URL=$vars->{'redirect_url'}"); - } - else { - print $cgi->redirect($vars->{'redirect_url'}); - exit; - } - - $template->process("global/message.html.tmpl", $vars) - || ThrowTemplateError($template->error()); + print $cgi->redirect($vars->{'redirect_url'}); exit; } diff --git a/conf/log4perl-test.conf b/conf/log4perl-test.conf index 77fc00af8..1760279db 100644 --- a/conf/log4perl-test.conf +++ b/conf/log4perl-test.conf @@ -4,14 +4,14 @@ log4perl.appender.Cereal.PeerAddr=127.0.0.1 log4perl.appender.Cereal.PeerPort=5880 log4perl.appender.Cereal.defer_connection=1 log4perl.appender.Cereal.layout = Log::Log4perl::Layout::PatternLayout -log4perl.appender.Cereal.layout.ConversionPattern = %d %6p | %c | %m{chomp}%n +log4perl.appender.Cereal.layout.ConversionPattern = %X{request_id} %d %6p | %c | %m{chomp}%n log4perl.filter.LOG_TO_STDERR = sub { not $ENV{LOG4PERL_STDERR_DISABLE} } log4perl.appender.Screen = Log::Log4perl::Appender::Screen log4perl.appender.Screen.Filter = LOG_TO_STDERR log4perl.appender.Screen.stderr = 1 log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout -log4perl.appender.Screen.layout.ConversionPattern = %d %6p | %c | %m{chomp}%n +log4perl.appender.Screen.layout.ConversionPattern = STDERR: %X{request_id} %d %6p | %c | %m{chomp}%n log4perl.appender.File = Log::Log4perl::Appender::File log4perl.appender.File.layout = Log::Log4perl::Layout::Mozilla diff --git a/docker-compose.yml b/docker-compose.yml index 1ca6f5c90..b00c6bc03 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,7 @@ services: - LOCALCONFIG_ENV=1 - LOG4PERL_CONFIG_FILE=log4perl-docker.conf - BUGZILLA_UNSAFE_AUTH_DELEGATION=1 + - HTTP_BACKEND=simple - PORT=80 - BMO_db_host=bmo-db.vm - BMO_db_name=bugs diff --git a/heartbeat.cgi b/heartbeat.cgi index 3edbed371..493674c16 100755 --- a/heartbeat.cgi +++ b/heartbeat.cgi @@ -29,7 +29,6 @@ my $ok = eval { die "database not available" unless $database_ok; die "memcached server(s) not available" unless $memcached_ok; - die "mod_perl not configured?" unless $ENV{MOD_PERL}; if ($dbh->isa('Bugzilla::DB::Mysql') && Bugzilla->params->{utf8} eq 'utf8mb4') { my $mysql_var = $dbh->selectall_hashref(q{SHOW VARIABLES LIKE 'character_set%'}, 'Variable_name'); foreach my $name (qw( character_set_client character_set_connection character_set_database )) { @@ -45,11 +44,4 @@ FATAL("heartbeat error: $@") if !$ok && $@; my $cgi = Bugzilla->cgi; print $cgi->header(-type => 'text/plain', -status => $ok ? '200 OK' : '500 Internal Server Error'); -print $ok ? "Bugzilla OK\n" : "Bugzilla NOT OK\n"; - -if ($ENV{MOD_PERL}) { - my $r = $cgi->r; - # doing this supresses the error document, but does not change the http response code. - $r->rflush; - $r->status(200); -} +print $ok ? "Bugzilla OK\n" : "Bugzilla NOT OK\n"; \ No newline at end of file diff --git a/jobqueue-worker.pl b/jobqueue-worker.pl old mode 100644 new mode 100755 diff --git a/qa/t/lib/QA/Util.pm b/qa/t/lib/QA/Util.pm index 5d041d560..bf9151fee 100644 --- a/qa/t/lib/QA/Util.pm +++ b/qa/t/lib/QA/Util.pm @@ -18,6 +18,7 @@ use Sys::Hostname qw(hostname); use Socket qw(inet_ntoa); use WWW::Selenium::Util qw(server_is_running); use URI; +use URI::QueryParam; # Fixes wide character warnings BEGIN { @@ -50,6 +51,7 @@ use base qw(Exporter); get_selenium get_rpc_clients + check_page_load WAIT_TIME CHROME_MODE @@ -396,6 +398,32 @@ sub set_parameters { } } +my @ANY_KEYS = qw( t token ); + +sub check_page_load { + my ($sel, $wait, $expected) = @_; + my $expected_uri = URI->new($expected); + $sel->wait_for_page_to_load_ok($wait); + my $uri = URI->new($sel->get_location); + + foreach my $u ($expected_uri, $uri) { + $u->host('HOSTNAME'); + foreach my $any_key (@ANY_KEYS) { + if ($u->query_param($any_key)) { + $u->query_param($any_key => '__ANYTHING__'); + } + } + } + + if ($expected_uri->query_param('id')) { + if ($expected_uri->query_param('id') eq '__BUG_ID__') { + $uri->query_param('id' => '__BUG_ID__'); + } + } + my ($pkg, $file, $line) = caller; + is($uri, $expected_uri, "checking location on $file line $line"); +} + 1; __END__ diff --git a/qa/t/test_bug_edit.t b/qa/t/test_bug_edit.t index 07a64876b..01037aa1e 100644 --- a/qa/t/test_bug_edit.t +++ b/qa/t/test_bug_edit.t @@ -32,10 +32,10 @@ if ($sel->is_text_present("My bugs from QA_Selenium")) { # Just in case the test failed before completion previously, reset the CANEDIT bit. go_to_admin($sel); $sel->click_ok("link=Groups"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editgroups.cgi}); $sel->title_is("Edit Groups"); $sel->click_ok("link=Master"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editgroups.cgi?action=changeform&group=26}); $sel->title_is("Change Group: Master"); my $group_url = $sel->get_location(); $group_url =~ /group=(\d+)$/; @@ -52,7 +52,7 @@ $sel->select_ok("bug_severity", "label=critical"); $sel->type_ok("short_desc", "Test bug editing"); $sel->type_ok("comment", "ploc"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=__BUG_ID__}); my $bug1_id = $sel->get_value('//input[@name="id" and @type="hidden"]'); $sel->is_text_present_ok('has been added to the database', "Bug $bug1_id created"); @@ -67,28 +67,28 @@ $sel->type_ok("status_whiteboard", "[Selenium was here]"); $sel->type_ok("comment", "new comment from me :)"); $sel->select_ok("bug_status", "label=RESOLVED"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); # Now move the bug into another product, which has a mandatory group. $sel->click_ok("link=bug $bug1_id"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/^$bug1_id /); $sel->select_ok("product", "label=QA-Selenium-TEST"); $sel->type_ok("comment", "moving to QA-Selenium-TEST"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Verify New Product Details..."); $sel->select_ok("component", "label=QA-Selenium-TEST"); $sel->is_element_present_ok('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]'); ok(!$sel->is_editable('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]'), "QA-Selenium-TEST group not editable"); $sel->is_checked_ok('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]', "QA-Selenium-TEST group is selected"); $sel->click_ok("change_product"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); $sel->click_ok("link=bug $bug1_id"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/^$bug1_id /); $sel->select_ok("bug_severity", "label=normal"); $sel->select_ok("priority", "label=High"); @@ -101,14 +101,14 @@ $sel->type_ok("comment", "Unchecking the reporter_accessible checkbox"); $sel->click_ok("reporter_accessible"); $sel->select_ok("bug_status", "label=VERIFIED"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); $sel->click_ok("link=bug $bug1_id"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/^$bug1_id /); $sel->type_ok("comment", "I am the reporter, but I can see the bug anyway as I belong to the mandatory group"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); logout($sel); @@ -125,16 +125,16 @@ $sel->click_ok("bz_assignee_edit_action"); $sel->type_ok("assigned_to", $config->{admin_user_login}); $sel->type_ok("comment", "I have editbugs privs. Taking!"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); $sel->click_ok("link=bug $bug1_id"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/^$bug1_id /); $sel->click_ok("cc_edit_area_showhide"); $sel->type_ok("newcc", $config->{unprivileged_user_login}); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); logout($sel); @@ -153,7 +153,7 @@ go_to_bug($sel, $bug1_id); $sel->click_ok("cclist_accessible"); $sel->type_ok("comment", "I am allowed to turn off cclist_accessible despite not being in the mandatory group"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); logout($sel); @@ -162,7 +162,7 @@ logout($sel); log_in($sel, $config, 'unprivileged'); $sel->type_ok("quicksearch_top", $bug1_id); $sel->submit("header-search"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_is("Access Denied"); $sel->is_text_present_ok("You are not authorized to access bug $bug1_id"); logout($sel); @@ -178,7 +178,7 @@ $sel->click_ok("set_default_assignee"); $sel->uncheck_ok("set_default_assignee"); $sel->type_ok("comment", "-> Moving back to Testproduct."); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Verify New Product Details..."); $sel->select_ok("component", "label=TestComponent"); $sel->is_text_present_ok("These groups are not legal for the 'TestProduct' product or you are not allowed to restrict bugs to these groups"); @@ -189,15 +189,15 @@ $sel->is_element_present_ok('//input[@type="checkbox" and @name="groups" and @va $sel->is_editable_ok('//input[@type="checkbox" and @name="groups" and @value="Master"]', "Master group is editable"); ok(!$sel->is_checked('//input[@type="checkbox" and @name="groups" and @value="Master"]'), "Master group not selected by default"); $sel->click_ok("change_product"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); $sel->click_ok("link=bug $bug1_id"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/^$bug1_id /); $sel->click_ok("cclist_accessible"); $sel->type_ok("comment", "I am allowed to turn off cclist_accessible despite not being in the mandatory group"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); logout($sel); @@ -216,7 +216,7 @@ $sel->click_ok("cc_edit_area_showhide"); $sel->add_selection_ok("cc", "label=" . $config->{admin_user_login}); $sel->click_ok("removecc"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); logout($sel); @@ -225,11 +225,11 @@ logout($sel); log_in($sel, $config, 'admin'); edit_product($sel, "TestProduct"); $sel->click_ok("link=Edit Group Access Controls:"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi?action=editgroupcontrols&product=TestProduct}); $sel->title_is("Edit Group Controls for TestProduct"); $sel->check_ok("canedit_$master_gid"); $sel->click_ok("submit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi}); $sel->title_is("Update group access controls for TestProduct"); # The user is in the master group, so he can comment. @@ -237,7 +237,7 @@ $sel->title_is("Update group access controls for TestProduct"); go_to_bug($sel, $bug1_id); $sel->type_ok("comment", "Do nothing except adding a comment..."); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); logout($sel); @@ -247,7 +247,7 @@ log_in($sel, $config, 'QA_Selenium_TEST'); go_to_bug($sel, $bug1_id); $sel->type_ok("comment", "Just a comment too..."); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Product Edit Access Denied"); $sel->is_text_present_ok("You are not permitted to edit bugs in product TestProduct."); logout($sel); @@ -256,10 +256,12 @@ logout($sel); log_in($sel, $config, 'admin'); open_advanced_search_page($sel); +screenshot_page($sel, '/app/artifacts/line259.png'); $sel->remove_all_selections_ok("product"); $sel->add_selection_ok("product", "TestProduct"); $sel->remove_all_selections_ok("bug_status"); $sel->remove_all_selections_ok("resolution"); +screenshot_page($sel, '/app/artifacts/line264.png'); $sel->is_checked_ok("emailassigned_to1"); $sel->select_ok("emailtype1", "label=is"); $sel->type_ok("email1", $config->{admin_user_login}); @@ -268,21 +270,22 @@ $sel->check_ok("emailqa_contact2"); $sel->check_ok("emailcc2"); $sel->select_ok("emailtype2", "label=is"); $sel->type_ok("email2", $config->{QA_Selenium_TEST_user_login}); +screenshot_page($sel, '/app/artifacts/line271.png'); $sel->click_ok("Search"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?emailreporter2=1&emailtype2=exact&order=Importance&list_id=15&emailtype1=exact&emailcc2=1&query_format=advanced&emailassigned_to1=1&emailqa_contact2=1&email2=QA-Selenium-TEST%40mozilla.test&email1=admin%40mozilla.test&emailassigned_to2=1&product=TestProduct}); $sel->title_is("Bug List"); - +screenshot_page($sel, '/app/artifacts/line275.png'); $sel->is_text_present_ok("One bug found."); $sel->type_ok("save_newqueryname", "My bugs from QA_Selenium"); $sel->click_ok("remember"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?newquery=email1%3Dadmin%2540mozilla.test%26email2%3DQA-Selenium-TEST%2540mozilla.test%26emailassigned_to1%3D1%26emailassigned_to2%3D1%26emailcc2%3D1%26emailqa_contact2%3D1%26emailreporter2%3D1%26emailtype1%3Dexact%26emailtype2%3Dexact%26list_id%3D15%26product%3DTestProduct%26query_format%3Dadvanced%26order%3Dpriority%252Cbug_severity&cmdtype=doit&remtype=asnamed&token=1531926552-dc69995d79c786af046436ec6717000b&newqueryname=My%20bugs%20from%20QA_Selenium&list_id=16}); $sel->title_is("Search created"); $sel->is_text_present_ok("OK, you have a new search named My bugs from QA_Selenium."); $sel->click_ok("link=My bugs from QA_Selenium"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=17}); $sel->title_is("Bug List: My bugs from QA_Selenium"); $sel->click_ok("long_format"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/show_bug.cgi}); $sel->title_is("Full Text Bug Listing"); $sel->is_text_present_ok("Bug $bug1_id"); $sel->is_text_present_ok("Status: CONFIRMED"); @@ -303,25 +306,25 @@ $sel->type_ok("short_desc", "New bug from me"); # We turned on the CANEDIT bit for TestProduct. $sel->type_ok("comment", "I can enter a new bug, but not edit it, right?"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=__BUG_ID__}); my $bug2_id = $sel->get_value('//input[@name="id" and @type="hidden"]'); $sel->is_text_present_ok('has been added to the database', "Bug $bug2_id created"); # Clicking the "Back" button and resubmitting the form again should trigger a suspicous action error. $sel->go_back_ok(); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/enter_bug.cgi?product=TestProduct&format=__default__}); $sel->title_is("Enter Bug: TestProduct"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/post_bug.cgi}); $sel->title_is("Suspicious Action"); $sel->is_text_present_ok("you have no valid token for the create_bug action"); $sel->click_ok('//input[@value="Confirm Changes"]'); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/show_bug.cgi?id=15}); $sel->is_text_present_ok('has been added to the database', 'Bug created'); $sel->type_ok("comment", "New comment not allowed"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Product Edit Access Denied"); $sel->is_text_present_ok("You are not permitted to edit bugs in product TestProduct."); logout($sel); @@ -334,42 +337,46 @@ $sel->click_ok("bz_assignee_edit_action"); $sel->type_ok("assigned_to", $config->{admin_user_login}); $sel->type_ok("comment", "Taking!"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug2_id}); $sel->is_text_present_ok("Changes submitted for bug $bug2_id"); # Test mass-change. $sel->click_ok("link=My bugs from QA_Selenium"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +screenshot_page($sel, '/app/artifacts/line344.png'); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=19}); +screenshot_page($sel, '/app/artifacts/line346.png'); $sel->title_is("Bug List: My bugs from QA_Selenium"); +screenshot_page($sel, '/app/artifacts/line348.png'); $sel->is_text_present_ok("2 bugs found"); +screenshot_page($sel, '/app/artifacts/line350.png'); $sel->click_ok("link=Change Several Bugs at Once"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?email1=admin%40mozilla.test&email2=QA-Selenium-TEST%40mozilla.test&emailassigned_to1=1&emailassigned_to2=1&emailcc2=1&emailqa_contact2=1&emailreporter2=1&emailtype1=exact&emailtype2=exact&product=TestProduct&query_format=advanced&order=priority%2Cbug_severity&tweak=1&list_id=20}); $sel->title_is("Bug List"); $sel->click_ok("check_all"); $sel->type_ok("comment", 'Mass change"'); $sel->select_ok("bug_status", "label=RESOLVED"); $sel->select_ok("resolution", "label=WORKSFORME"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Bugs processed"); $sel->click_ok("link=bug $bug1_id"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/$bug1_id /); $sel->selected_label_is("resolution", "WORKSFORME"); $sel->select_ok("resolution", "label=INVALID"); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); $sel->click_ok("link=bug $bug1_id"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/$bug1_id /); $sel->selected_label_is("resolution", "INVALID"); $sel->click_ok("link=History"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_activity.cgi?id=$bug1_id}); $sel->title_is("Changes made to bug $bug1_id"); $sel->is_text_present_ok("URL foo.cgi?action=bar"); $sel->is_text_present_ok("Severity critical blocker"); @@ -431,10 +438,10 @@ foreach my $params (["no_token_single_bug", ""], ["invalid_token_single_bug", "& $sel->title_is("Suspicious Action"); $sel->is_text_present_ok($token ? "an invalid token" : "web browser directly"); $sel->click_ok("confirm"); - $sel->wait_for_page_to_load_ok(WAIT_TIME); + check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->is_text_present_ok("Changes submitted for bug $bug1_id"); $sel->click_ok("link=bug $bug1_id"); - $sel->wait_for_page_to_load_ok(WAIT_TIME); + check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id}); $sel->title_like(qr/^$bug1_id /); $sel->is_text_present_ok($comment); } @@ -446,16 +453,16 @@ foreach my $params (["no_token_mass_change", ""], ["invalid_token_mass_change", $sel->title_is("Suspicious Action"); $sel->is_text_present_ok("no valid token for the buglist_mass_change action"); $sel->click_ok("confirm"); - $sel->wait_for_page_to_load_ok(WAIT_TIME); + check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Bugs processed"); foreach my $bug_id ($bug1_id, $bug2_id) { $sel->click_ok("link=bug $bug_id"); - $sel->wait_for_page_to_load_ok(WAIT_TIME); + check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug_id}); $sel->title_like(qr/^$bug_id /); $sel->is_text_present_ok($comment); next if $bug_id == $bug2_id; $sel->go_back_ok(); - $sel->wait_for_page_to_load_ok(WAIT_TIME); + check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Bugs processed"); } } @@ -463,26 +470,26 @@ foreach my $params (["no_token_mass_change", ""], ["invalid_token_mass_change", # Now move these bugs out of our radar. $sel->click_ok("link=My bugs from QA_Selenium"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=21}); $sel->title_is("Bug List: My bugs from QA_Selenium"); $sel->is_text_present_ok("2 bugs found"); $sel->click_ok("link=Change Several Bugs at Once"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?email1=admin%40mozilla.test&email2=QA-Selenium-TEST%40mozilla.test&emailassigned_to1=1&emailassigned_to2=1&emailcc2=1&emailqa_contact2=1&emailreporter2=1&emailtype1=exact&emailtype2=exact&product=TestProduct&query_format=advanced&order=priority%2Cbug_severity&tweak=1&list_id=22}); $sel->title_is("Bug List"); $sel->click_ok("check_all"); $sel->type_ok("comment", "Reassigning to the reporter"); $sel->type_ok("assigned_to", $config->{QA_Selenium_TEST_user_login}); $sel->click_ok("commit"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi}); $sel->title_is("Bugs processed"); # Now delete the saved search. $sel->click_ok("link=My bugs from QA_Selenium"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=23}); $sel->title_is("Bug List: My bugs from QA_Selenium"); $sel->click_ok("link=Forget Search 'My bugs from QA_Selenium'"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=My%20bugs%20from%20QA_Selenium&token=1531926582-f228fa8ebc2f2b3970f2a791e54534ec&list_id=24}); $sel->title_is("Search is gone"); $sel->is_text_present_ok("OK, the My bugs from QA_Selenium search is gone"); @@ -495,10 +502,10 @@ sub clear_canedit_on_testproduct { edit_product($sel, "TestProduct"); $sel->click_ok("link=Edit Group Access Controls:"); - $sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi?action=editgroupcontrols&product=TestProduct}); $sel->title_is("Edit Group Controls for TestProduct"); $sel->uncheck_ok("canedit_$master_gid"); $sel->click_ok("submit"); - $sel->wait_for_page_to_load_ok(WAIT_TIME); +check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi}); $sel->title_is("Update group access controls for TestProduct"); } diff --git a/qa/t/test_shutdown.t b/qa/t/test_shutdown.t deleted file mode 100644 index dc5cabd4a..000000000 --- a/qa/t/test_shutdown.t +++ /dev/null @@ -1,72 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. - -use strict; -use warnings; -use lib qw(lib ../../lib ../../local/lib/perl5); - -use Test::More "no_plan"; - -use QA::Util; - -my ($sel, $config) = get_selenium(); - -log_in($sel, $config, 'admin'); -set_parameters($sel, { "General" => {shutdownhtml => {type => "text", - value => "I'm down (set by test_shutdown.t)" } - } }); - -# None of the following pages should be accessible when Bugzilla is down. - -my @pages = qw(admin attachment buglist chart colchange config createaccount - describecomponents describekeywords duplicates - editclassifications editcomponents editfields editflagtypes - editgroups editkeywords editmilestones editproducts editsettings - editusers editvalues editversions editwhines editworkflow - enter_bug index long_list page post_bug process_bug query quips - relogin report reports request sanitycheck search_plugin - show_activity show_bug showattachment showdependencygraph - showdependencytree sidebar summarize_time token userprefs votes - xml xmlrpc); - -foreach my $page (@pages) { - $sel->open_ok("/$config->{bugzilla_installation}/${page}.cgi"); - $sel->title_is("Bugzilla is Down"); -} - -# Those have parameters passed to the page, so we put them here separately. - -@pages = ("query.cgi?format=report-table", "query.cgi?format=report-graph", - "votes.cgi?action=show_user", "votes.cgi?action=show_bug"); - -foreach my $page (@pages) { - $sel->open_ok("/$config->{bugzilla_installation}/$page"); - $sel->title_is("Bugzilla is Down"); -} - -# Clear 'shutdownhtml', to re-enable Bugzilla. -# At this point, the admin has been logged out. We cannot use log_in(), -# nor set_parameters(), due to shutdownhtml being active. - -$sel->open_ok("/$config->{bugzilla_installation}/editparams.cgi"); -$sel->title_is("Log in to Bugzilla"); -$sel->type_ok("Bugzilla_login", $config->{admin_user_login}, "Enter admin login name"); -$sel->type_ok("Bugzilla_password", $config->{admin_user_passwd}, "Enter admin password"); -$sel->click_ok("log_in"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); -$sel->title_is("Configuration: General"); -$sel->type_ok("shutdownhtml", ""); -$sel->click_ok('//input[@type="submit" and @value="Save Changes"]', undef, "Save Changes"); -$sel->wait_for_page_to_load_ok(WAIT_TIME); -$sel->title_is("Parameters Updated"); - -# Accessing index.cgi should work again now. - -$sel->click_ok('//*[@id="header-title"]//a'); -$sel->wait_for_page_to_load_ok(WAIT_TIME); -$sel->title_is("Bugzilla Main Page"); -logout($sel); diff --git a/scripts/block-ip.pl b/scripts/block-ip.pl index b767a1fd5..3fa66d336 100755 --- a/scripts/block-ip.pl +++ b/scripts/block-ip.pl @@ -12,8 +12,8 @@ use warnings; use lib qw(. lib local/lib/perl5); use Bugzilla; +use Bugzilla::Quantum; use Bugzilla::Constants; -use Bugzilla::ModPerl::BlockIP; use Getopt::Long; Bugzilla->usage_mode(USAGE_MODE_CMDLINE); @@ -23,10 +23,12 @@ GetOptions('unblock' => \$unblock); pod2usage("No IPs given") unless @ARGV; +my $app = Bugzilla::Quantum->new; + if ($unblock) { - Bugzilla::ModPerl::BlockIP->unblock_ip($_) for @ARGV; + $app->unblock_ip($_) for @ARGV; } else { - Bugzilla::ModPerl::BlockIP->block_ip($_) for @ARGV; + $app->block_ip($_) for @ARGV; } =head1 NAME @@ -52,4 +54,4 @@ If passed, the IPs will be unblocked instead of blocked. Use this to remove IPs =head1 DESCRIPTION -This is just a simple CLI inteface to L. +This is just a simple CLI inteface to L. diff --git a/scripts/entrypoint.pl b/scripts/entrypoint.pl index ce1cc795b..e5ecc4324 100755 --- a/scripts/entrypoint.pl +++ b/scripts/entrypoint.pl @@ -156,6 +156,7 @@ sub cmd_test_webservices { sub cmd_test_selenium { my $conf = require $ENV{BZ_QA_CONF_FILE}; + $ENV{HTTP_BACKEND} = 'simple'; check_data_dir(); copy_qa_extension(); diff --git a/scripts/undo.pl b/scripts/undo.pl old mode 100644 new mode 100755 diff --git a/ses/index.cgi b/ses/index.cgi deleted file mode 100755 index e36956b1d..000000000 --- a/ses/index.cgi +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/perl -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. - -use 5.10.1; -use strict; -use warnings; - -use lib qw(.. ../lib ../local/lib/perl5); - -use Bugzilla (); -use Bugzilla::Constants qw(ERROR_MODE_DIE); -use Bugzilla::Logging; -use Bugzilla::Mailer qw(MessageToMTA); -use Bugzilla::User (); -use Bugzilla::Util qw(html_quote remote_ip); -use JSON::MaybeXS qw(decode_json); -use LWP::UserAgent (); -use Try::Tiny qw(catch try); - -Bugzilla->error_mode(ERROR_MODE_DIE); -try { - main(); -} -catch { - FATAL("Fatal error: $_"); - respond( 500 => 'Internal Server Error' ); -}; - -sub main { - my $message = decode_json_wrapper( Bugzilla->cgi->param('POSTDATA') ) // return; - my $message_type = $ENV{HTTP_X_AMZ_SNS_MESSAGE_TYPE} // '(missing)'; - - if ( $message_type eq 'SubscriptionConfirmation' ) { - confirm_subscription($message); - } - - elsif ( $message_type eq 'Notification' ) { - my $notification = decode_json_wrapper( $message->{Message} ) // return; - unless ( - # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html - handle_notification( $notification, 'eventType' ) - - # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html - || handle_notification( $notification, 'notificationType' ) - ) - { - WARN('Failed to find notification type'); - respond( 400 => 'Bad Request' ); - } - } - - else { - WARN("Unsupported message-type: $message_type"); - respond( 200 => 'OK' ); - } -} - -sub confirm_subscription { - my ($message) = @_; - - my $subscribe_url = $message->{SubscribeURL}; - if ( !$subscribe_url ) { - WARN('Bad SubscriptionConfirmation request: missing SubscribeURL'); - respond( 400 => 'Bad Request' ); - return; - } - - my $ua = ua(); - my $res = $ua->get( $message->{SubscribeURL} ); - if ( !$res->is_success ) { - WARN( 'Bad response from SubscribeURL: ' . $res->status_line ); - respond( 400 => 'Bad Request' ); - return; - } - - respond( 200 => 'OK' ); -} - -sub handle_notification { - my ( $notification, $type_field ) = @_; - - if ( !exists $notification->{$type_field} ) { - return 0; - } - my $type = $notification->{$type_field}; - - if ( $type eq 'Bounce' ) { - process_bounce($notification); - } - elsif ( $type eq 'Complaint' ) { - process_complaint($notification); - } - else { - WARN("Unsupported notification-type: $type"); - respond( 200 => 'OK' ); - } - return 1; -} - -sub process_bounce { - my ($notification) = @_; - - # disable each account that is bouncing - foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) { - my $address = $recipient->{emailAddress}; - my $reason = sprintf '(%s) %s', $recipient->{action} // 'error', $recipient->{diagnosticCode} // 'unknown'; - - my $user = Bugzilla::User->new( { name => $address, cache => 1 } ); - if ($user) { - - # never auto-disable admin accounts - if ( $user->in_group('admin') ) { - Bugzilla->audit("ignoring bounce for admin <$address>: $reason"); - } - - else { - my $template = Bugzilla->template_inner(); - my $vars = { - mta => $notification->{bounce}->{reportingMTA} // 'unknown', - reason => $reason, - }; - my $disable_text; - $template->process( 'admin/users/bounce-disabled.txt.tmpl', $vars, \$disable_text ) - || die $template->error(); - - $user->set_disabledtext($disable_text); - $user->set_disable_mail(1); - $user->update(); - Bugzilla->audit( "bounce for <$address> disabled userid-" . $user->id . ": $reason" ); - } - } - - else { - Bugzilla->audit("bounce for <$address> has no user: $reason"); - } - } - - respond( 200 => 'OK' ); -} - -sub process_complaint { - - # email notification to bugzilla admin - my ($notification) = @_; - my $template = Bugzilla->template_inner(); - my $json = JSON::MaybeXS->new( - pretty => 1, - utf8 => 1, - canonical => 1, - ); - - foreach my $recipient ( @{ $notification->{complaint}->{complainedRecipients} } ) { - my $reason = $notification->{complaint}->{complaintFeedbackType} // 'unknown'; - my $address = $recipient->{emailAddress}; - Bugzilla->audit("complaint for <$address> for '$reason'"); - my $vars = { - email => $address, - user => Bugzilla::User->new( { name => $address, cache => 1 } ), - reason => $reason, - notification => $json->encode($notification), - }; - my $message; - $template->process( 'email/ses-complaint.txt.tmpl', $vars, \$message ) - || die $template->error(); - MessageToMTA($message); - } - - respond( 200 => 'OK' ); -} - -sub respond { - my ( $code, $message ) = @_; - print Bugzilla->cgi->header( -status => "$code $message" ); - - # apache will generate non-200 response pages for us - say html_quote($message) if $code == 200; -} - -sub decode_json_wrapper { - my ($json) = @_; - my $result; - if ( !defined $json ) { - WARN( 'Missing JSON from ' . remote_ip() ); - respond( 400 => 'Bad Request' ); - return undef; - } - my $ok = try { - $result = decode_json($json); - } - catch { - WARN( 'Malformed JSON from ' . remote_ip() ); - respond( 400 => 'Bad Request' ); - return undef; - }; - return $ok ? $result : undef; -} - -sub ua { - my $ua = LWP::UserAgent->new(); - $ua->timeout(10); - $ua->protocols_allowed( [ 'http', 'https' ] ); - if ( my $proxy_url = Bugzilla->params->{'proxy_url'} ) { - $ua->proxy( [ 'http', 'https' ], $proxy_url ); - } - else { - $ua->env_proxy; - } - return $ua; -} diff --git a/t/001compile.t b/t/001compile.t index e81162056..3d8d82bf4 100644 --- a/t/001compile.t +++ b/t/001compile.t @@ -35,7 +35,7 @@ sub compile_file { my ($file) = @_; # Don't allow CPAN.pm to modify the global @INC, which the version - # shipped with Perl 5.8.8 does. (It gets loaded by + # shipped with Perl 5.8.8 does. (It gets loaded by # Bugzilla::Install::CPAN.) local @INC = @INC; @@ -43,10 +43,6 @@ sub compile_file { skip "$file: extensions not tested", 1; return; } - if ($file =~ /ModPerl/) { - skip "$file: ModPerl stuff not tested", 1; - return; - } if ($file =~ s/\.pm$//) { $file =~ s{/}{::}g; @@ -82,7 +78,7 @@ my $file_features = map_files_to_features(); # Test the scripts by compiling them foreach my $file (@testitems) { # These were already compiled, above. - next if ($file eq 'Bugzilla.pm' + next if ($file eq 'Bugzilla.pm' or $file eq 'Bugzilla/Constants.pm' or $file eq 'Bugzilla/Install/Requirements.pm'); SKIP: { @@ -94,10 +90,10 @@ foreach my $file (@testitems) { skip "$file: $feature not enabled", 1; } - # Check that we have a DBI module to support the DB, if this + # Check that we have a DBI module to support the DB, if this # is a database module (but not Schema) if ($file =~ m{Bugzilla/DB/([^/]+)\.pm$} - and $file ne "Bugzilla/DB/Schema.pm") + and $file ne "Bugzilla/DB/Schema.pm") { my $module = lc($1); Bugzilla->feature($module) or skip "$file: Driver for $module not installed", 1; @@ -105,4 +101,4 @@ foreach my $file (@testitems) { compile_file($file); } -} +} diff --git a/t/002goodperl.t b/t/002goodperl.t index 5f201160b..80d7cf2b9 100644 --- a/t/002goodperl.t +++ b/t/002goodperl.t @@ -83,6 +83,7 @@ foreach my $file (@testitems) { my $found_use_strict = 0; my $found_use_warnings = 0; my $found_modern_perl = 0; + my $found_mojo = 0; $file =~ s/\s.*$//; # nuke everything after the first space (#comment) next if (!$file); # skip null entries @@ -92,13 +93,17 @@ foreach my $file (@testitems) { } while (my $file_line = ) { $found_modern_perl = 1 if $file_line =~ m/^use\s*(?:Moo|Role::Tiny)/; + $found_mojo = 1 if $file_line =~ m/^use\s(?:Mojo(?:licious::Lite|::Base)\b)/; $found_use_perl = 1 if $file_line =~ m/^\s*use 5.10.1/; $found_use_strict = 1 if $file_line =~ m/^\s*use strict/; $found_use_warnings = 1 if $file_line =~ m/^\s*use warnings/; - if ($found_modern_perl) { + if ($found_modern_perl || $found_mojo) { $found_use_strict = 1; $found_use_warnings = 1; } + if ($found_mojo) { + $found_use_perl = 1; + } last if ($found_use_perl && $found_use_strict && $found_use_warnings); } close (FILE); diff --git a/template/en/default/global/header.html.tmpl b/template/en/default/global/header.html.tmpl index 6a19eaf39..efb8d4407 100644 --- a/template/en/default/global/header.html.tmpl +++ b/template/en/default/global/header.html.tmpl @@ -97,6 +97,8 @@ [% IF Param('utf8') %] [% END %] + [% USE Bugzilla %] + [% IF Bugzilla.cgi.should_block_referrer %] diff --git a/vagrant_support/apache.yml b/vagrant_support/apache.yml index 5031187e3..2d65965ab 100644 --- a/vagrant_support/apache.yml +++ b/vagrant_support/apache.yml @@ -6,8 +6,8 @@ mode: 0644 when: LAZY == 0 -- name: enable httpd - service: name=httpd enabled=yes +- name: disable httpd + service: name=httpd enabled=no when: LAZY == 0 - name: ensure bugzilla.log has right permissions @@ -16,5 +16,5 @@ - name: ensure bugzilla-json.log has right permissions file: path=/vagrant/logs/bugzilla-json.log state=touch owner=vagrant group=apache mode=0660 -- name: restart httpd - service: name=httpd state=restarted \ No newline at end of file +- name: stop httpd + service: name=httpd state=stopped diff --git a/vagrant_support/hypnotoad b/vagrant_support/hypnotoad new file mode 100755 index 000000000..5e8dc910e --- /dev/null +++ b/vagrant_support/hypnotoad @@ -0,0 +1,122 @@ +#!/bin/bash +# +# bugzilla-push This starts, stops, and restarts the Bugzilla push +# daemon, which manages syncronising bugzilla.mozilla.org and +# third party bugzilla installs. +# +# chkconfig: 345 85 15 +# description: Bugzilla push daemon +# +### BEGIN INIT INFO +# Provides: bugzilla-push +# Required-Start: $local_fs $syslog MTA mysqld +# Required-Stop: $local_fs $syslog MTA mysqld +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: Start and stop the Bugzilla push daemon. +# Description: The Bugzilla push daemon (bugzilla-pushd.pl) +# +# +# +### END INIT INFO + +NAME=`basename $0` + +################# +# Configuration # +################# + +# This should be the path to your Bugzilla +BUGZILLA=/vagrant +# Who owns the Bugzilla directory and files? +USER=vagrant + +PERL5LIB="$BUGZILLA:$BUGZILLA/lib:$BUGZILLA/local/lib/perl5" +export PERL5LIB +export MOJO_MODE + +if [[ x$PORT == x ]]; then + PORT=80 + export PORT +fi + +# If you want to pass any options to the daemon (like -d for debugging) +# specify it here. +OPTIONS="" + +# You can also override the configuration by creating a +# /etc/sysconfig/bugzilla-queue file so that you don't +# have to edit this script. +if [ -r /etc/sysconfig/$NAME ]; then + . /etc/sysconfig/$NAME +fi + +########## +# Script # +########## + +RETVAL=0 +BIN="$BUGZILLA/local/bin/hypnotoad" +PIDFILE="$BUGZILLA/hypnotoad.pid" + +# Source function library. +. /etc/rc.d/init.d/functions + +usage () +{ + echo "Usage: service $NAME {start|stop|status|restart|condrestart}" + RETVAL=1 +} + + +start () +{ + if [[ -f $PIDFILE ]]; then + checkpid "$(cat $PIDFILE)" && return 0 + fi + echo -n "Starting $NAME: " + daemon --pidfile=$PIDFILE --user=$USER "cd $BUGZILLA && $BIN bugzilla.pl >/dev/null" + ret=$? + [ $ret -eq "0" ] && touch /var/lock/subsys/$NAME + echo + return $ret +} + +stop () +{ + cd $BUGZILLA && $BIN bugzilla.pl --stop + rm -f /var/lock/subsys/$NAME +} + +restart () +{ + stop + start +} + +condrestart () +{ + [ -e /var/lock/subsys/$NAME ] && restart || return 0 +} + +status () +{ + if [[ -f $PIDFILE ]] && checkpid "$(cat $PIDFILE)"; then + echo hypnotoad is amazing + return 0 + else + echo hypnotoad is not running + return 1 + fi +} + +case "$1" in + start) start; RETVAL=$? ;; + stop) stop; RETVAL=$? ;; + status) status; RETVAL=$?;; + restart) restart; RETVAL=$? ;; + condrestart) condrestart; RETVAL=$? ;; + *) usage ; RETVAL=2 ;; +esac + +exit $RETVAL diff --git a/vagrant_support/hypnotoad.yml b/vagrant_support/hypnotoad.yml new file mode 100644 index 000000000..a66f9ba04 --- /dev/null +++ b/vagrant_support/hypnotoad.yml @@ -0,0 +1,27 @@ +- name: grant perl ability to listen to privledged ports + command: setcap 'cap_net_bind_service=+ep' /usr/bin/perl + +- name: hypnotoad daemon sysconfig + copy: + dest: /etc/sysconfig/hypnotoad + mode: 0644 + content: | + BUGZILLA=/vagrant + USER=vagrant + MOJO_MODE=development + +- name: hypnotoad daemon init file + template: + dest: /etc/init.d/hypnotoad + src: hypnotoad + owner: root + group: root + mode: 0755 + +- name: enable hypnotoad + service: name=hypnotoad enabled=yes + when: LAZY == 0 + +- name: restart hypnotoad + service: name=hypnotoad state=restarted + diff --git a/vagrant_support/playbook.yml b/vagrant_support/playbook.yml index c067eab05..3dd320f0b 100644 --- a/vagrant_support/playbook.yml +++ b/vagrant_support/playbook.yml @@ -170,9 +170,6 @@ dest: /usr/local/bin/cpanm mode: '0755' - - name: install more recent Apache2::SizeLimit - cpanm: name=Apache2::SizeLimit executable=/usr/local/bin/cpanm - - name: 'check /opt/bmo (failure is ok)' shell: test -d /opt/bmo && test -f /opt/bmo/local/lib/perl5/Plack.pm register: opt_bmo @@ -222,5 +219,8 @@ - include_tasks: apache.yml vars: LAZY: 0 + - include_tasks: hypnotoad.yml + vars: + LAZY: 0 - import_tasks: devtools.yml diff --git a/vagrant_support/update.yml b/vagrant_support/update.yml index c53a0554e..533da5b5e 100644 --- a/vagrant_support/update.yml +++ b/vagrant_support/update.yml @@ -20,4 +20,7 @@ LAZY: 1 - include_tasks: apache.yml vars: - LAZY: 1 \ No newline at end of file + LAZY: 1 + - include_tasks: hypnotoad.yml + vars: + LAZY: 1 -- cgit v1.2.3-24-g4f1b