diff options
author | Dylan William Hardison <dylan@hardison.net> | 2018-04-01 16:52:36 +0200 |
---|---|---|
committer | Dylan William Hardison <dylan@hardison.net> | 2018-04-01 16:52:36 +0200 |
commit | ab229b9a828b77f8a3b9ce215f0dfed4c84d4ae5 (patch) | |
tree | 483da9c8b66f4444bb8a410e3d599c7484ad721e | |
parent | daa2d6b1c40354ecce0e48e6c5ee686efe642c4b (diff) | |
parent | 2f8b999750cc700faf03c6aee1c53d1fc4df767f (diff) | |
download | bugzilla-ab229b9a828b77f8a3b9ce215f0dfed4c84d4ae5.tar.gz bugzilla-ab229b9a828b77f8a3b9ce215f0dfed4c84d4ae5.tar.xz |
Merge branch 'master' into unstable
63 files changed, 1171 insertions, 694 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ed28133d..eca472ceb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,8 +11,6 @@ main_filters: &main_filters - /^(?:release|test)-20\d\d\d\d\d\d\.\d+/ - /\// - production - tags: - only: /^(?:release|test)-20\d\d\d\d\d\d\.\d+/ defaults: bmo_slim_image: &bmo_slim_image @@ -63,12 +61,61 @@ defaults: run: name: default qa setup command: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 mv /opt/bmo/local /app/local perl -MSys::Hostname -i -pE 's/bmo.test/hostname() . ":$ENV{PORT}"/ges' $BZ_QA_CONF_FILE - /app/scripts/entrypoint.pl checksetup_gen_files --default-localconfig + /app/scripts/entrypoint.pl checksetup_gen_files --default-localconfig mkdir artifacts jobs: + build_info: + parallelism: 1 + working_directory: /app + docker: + - <<: *bmo_slim_image + environment: + <<: *bmo_env + steps: + - checkout + - run: + name: build push data + command: | + mv /opt/bmo/local /app/local + perl checksetup.pl --no-database --no-templates --no-permissions + perl scripts/build-bmo-push-data.pl + - run: + name: only publish if tag exists + command: | + tag="$(cat build_info/tag.txt)" + git fetch --tags + if git tag | fgrep -q "$tag"; then + echo "tag $tag exists!" + else + echo "tag $tag does not exist" + echo yes > build_info/publish.txt + fi + - run: + name: check if only version changed + command: | + if git diff 'HEAD~..HEAD' --name-only | grep -qv '^Bugzilla.pm'; then + echo "more files than just Bugzilla.pm changed." + exit 0 + fi + if git diff 'HEAD~..HEAD' |grep '^[+-][^+-]' | grep -qv '^[+-]our $VERSION'; then + echo "Something other than the version number changed." + exit 0 + fi + if [[ "$CIRCLE_BRANCH" == "master" ]]; then + echo "Can't cut corners on the master branch" + exit 0 + fi + echo yes > build_info/only_version_changed.txt + - persist_to_workspace: + root: /app/build_info + paths: ["*.txt"] + - store_artifacts: + path: /app/build_info + build: working_directory: /app docker: @@ -85,39 +132,52 @@ jobs: --build-arg CIRCLE_SHA1="$CIRCLE_SHA1" \ --build-arg CIRCLE_BUILD_URL="$CIRCLE_BUILD_URL" \ -t bmo . + - attach_workspace: + at: /app/build_info + - run: "docker run --name bmo --entrypoint true bmo" + - run: "docker cp bmo:/app/version.json build_info/version.json" + - store_artifacts: + path: /app/build_info - deploy: command: | - if [[ -n "$DOCKERHUB_REPO" && -n "$DOCKER_USER" && -n "$DOCKER_PASS" ]]; then - TAG="" - if [[ -n "$CIRCLE_TAG" ]]; then - TAG="$CIRCLE_TAG" - elif [[ "$CIRCLE_BRANCH" == "master" ]]; then - TAG=latest - elif [[ "$CIRCLE_BRANCH" == "unstable" ]]; then - TAG=unstable - fi - if [[ -n "$TAG" ]]; then - docker tag bmo "$DOCKERHUB_REPO:$TAG" - docker login -u "$DOCKER_USER" -p "$DOCKER_PASS" - docker push "$DOCKERHUB_REPO:$TAG" - fi + TAG="$(cat /app/build_info/tag.txt)" + [[ "$CIRCLE_BRANCH" == "master" ]] || exit 0 + [[ -n "$DOCKERHUB_REPO" && -n "$DOCKER_USER" && -n "$DOCKER_PASS" ]] || exit 0 + [[ -n "$GITHUB_PERSONAL_TOKEN" ]] || exit 0 + docker login -u "$DOCKER_USER" -p "$DOCKER_PASS" + if [[ -n "$TAG" && -f build_info/publish.txt ]]; then + git config credential.helper "cache --timeout 120" + git config user.email "$GITHUB_EMAIL" + git config user.name "$GITHUB_NAME" + git tag $TAG + git push https://${GITHUB_PERSONAL_TOKEN}:x-oauth-basic@github.com/$GITHUB_REPO.git $TAG + docker tag bmo "$DOCKERHUB_REPO:$TAG" + docker push "$DOCKERHUB_REPO:$TAG" fi + docker tag bmo "$DOCKERHUB_REPO:latest" + docker push "$DOCKERHUB_REPO:latest" test_sanity: - parallelism: 4 + parallelism: 2 working_directory: /app docker: - <<: *bmo_slim_image environment: *bmo_env steps: - checkout + - attach_workspace: + at: /app/build_info - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 mv /opt/bmo/local /app/local mkdir artifacts - - run: perl Makefile.PL + - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 + perl Makefile.PL - run: name: run sanity tests command: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 /app/scripts/entrypoint.pl prove -qf $(circleci tests glob 't/*.t' | circleci tests split) | tee artifacts/$CIRCLE_JOB.txt - store_artifacts: path: /app/artifacts @@ -128,12 +188,15 @@ jobs: docker: *docker_oldtests steps: - checkout + - attach_workspace: + at: /app/build_info - *default_qa_setup - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 /app/scripts/entrypoint.pl load_test_data - - run: - command: | - /app/scripts/entrypoint.pl test_webservices | tee artifacts/$CIRCLE_JOB.txt + - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 + /app/scripts/entrypoint.pl test_webservices | tee artifacts/$CIRCLE_JOB.txt - store_artifacts: path: /app/artifacts @@ -143,12 +206,15 @@ jobs: docker: *docker_oldtests steps: - checkout + - attach_workspace: + at: /app/build_info - *default_qa_setup - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 /app/scripts/entrypoint.pl load_test_data --legacy - - run: - command: | - /app/scripts/entrypoint.pl test_selenium | tee artifacts/$CIRCLE_JOB.txt + - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 + /app/scripts/entrypoint.pl test_selenium | tee artifacts/$CIRCLE_JOB.txt - store_artifacts: path: /app/artifacts @@ -169,34 +235,45 @@ jobs: - image: selenium/standalone-firefox:2.53.1 steps: - checkout + - attach_workspace: + at: /app/build_info - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 mv /opt/bmo/local /app/local /app/scripts/entrypoint.pl checksetup_gen_files /app/scripts/entrypoint.pl load_test_data mkdir artifacts - - run: /app/scripts/entrypoint.pl test_bmo -q -f t/bmo/*.t + - run: | + [[ -f build_info/only_version_changed.txt ]] && exit 0 + /app/scripts/entrypoint.pl test_bmo -q -f t/bmo/*.t workflows: version: 2 main: jobs: + - build_info: + filters: *main_filters + - build: + filters: *main_filters + requires: + - build_info + - test_sanity + - test_bmo + - test_webservices + - test_selenium - test_sanity: filters: *main_filters + requires: + - build_info - test_bmo: filters: *main_filters requires: - - test_sanity + - build_info - test_webservices: filters: *main_filters requires: - - test_sanity + - build_info - test_selenium: filters: *main_filters requires: - - test_sanity - - build: - filters: *main_filters - requires: - - test_sanity - - test_bmo - - test_webservices + - build_info @@ -27,6 +27,8 @@ RewriteRule ^__version__$ version.json [L] # heartbeat.cgi returns 200 if the DB and memcached are both working, and 500 otherwise. RewriteRule ^__heartbeat__$ heartbeat.cgi [L] +RewriteRule ^(\d+|quicksearch\.html|bugwritinghelp\.html)$ /helper/$1 [L] + RewriteRule ^static/v\d{4}\d{2}\d{2}\.\d+/(.+\.(?:js|css|woff2?|png|jpe?g|gif|ico|svg))$ $1 [NC,E=IMMUTABLE:1,L] Header set Cache-Control "public, max-age=31536000" env=REDIRECT_IMMUTABLE @@ -37,9 +39,9 @@ RewriteRule ^new[-_]bug$ new_bug.cgi [L,QSA] RewriteRule ^template_cache/ - [F,L,NC] RewriteRule ^template_cache.deleteme/ - [F,L,NC] -RewriteRule ^review(.*) page.cgi?id=splinter.html$1 [QSA] -RewriteRule ^user_?profile(.*) page.cgi?id=user_profile.html$1 [QSA] -RewriteRule ^request_defer(.*) page.cgi?id=request_defer.html$1 [QSA] +RewriteRule ^review$ page.cgi?id=splinter.html$1 [QSA] +RewriteRule ^user_?profile$ page.cgi?id=user_profile.html$1 [QSA] +RewriteRule ^request_defer$ page.cgi?id=request_defer.html$1 [QSA] RewriteRule ^favicon\.ico$ extensions/BMO/web/images/favicon.ico RewriteRule ^form[\.:]itrequest$ enter_bug.cgi?product=Infrastructure+\%26+Operations&format=itrequest [QSA] RewriteRule ^form[\.:](mozlist|poweredby|presentation|trademark|recoverykey)$ enter_bug.cgi?product=mozilla.org&format=$1 [QSA] @@ -76,7 +78,7 @@ RewriteRule ^form[\.:]third[\.\-:]party$ enter_bug.cgi?product=Marketing&format= RewriteRule ^form[\.:]fsa[\.:]budget$ enter_bug.cgi?product=FSA&format=fsa-budget [QSA] RewriteRule ^form[\.:]triage[\.\-]request$ page.cgi?id=triage_request.html [QSA] RewriteRule ^form[\.:](crm|CRM)$ enter_bug.cgi?product=Marketing&format=crm [QSA] -RewriteRule ^form[\.:](ipc|IPC)$ enter_bug.cgi?product=Marketing&format=ipc [QSA] +RewriteRule ^form[\.:](ipc|IPC)$ https://airtable.com/shrcMqgbj1H9gXRlp [R,L] RewriteRule ^form[\.:]nda$ enter_bug.cgi?product=Legal&format=nda [QSA] RewriteRule ^form[\.:]name[\.:]clearance$ enter_bug.cgi?product=Legal&format=name-clearance [QSA] RewriteRule ^form[\.:]shield[\.:]studies$ enter_bug.cgi?product=Shield&format=shield-studies [QSA] diff --git a/.perlcriticrc b/.perlcriticrc index 15ff6c82b..44254f64e 100644 --- a/.perlcriticrc +++ b/.perlcriticrc @@ -3,10 +3,28 @@ severity = 1 [-CodeLayout::RequireTidyCode] #perltidyrc = .perltidyrc +#severity = 2 + [InputOutput::RequireCheckedSyscalls] +severity = 2 functions = :builtins exclude_functions = print say sleep binmode +[ValuesAndExpressions::ProhibitInterpolationOfLiterals] +severity = 2 + +[Freenode::EmptyReturn] +severity = 2 + +[CodeLayout::RequireTrailingCommas] +severity = 2 + +[CodeLayout::ProhibitParensWithBuiltins] +severity = 2 + +[RegularExpressions::ProhibitUnusualDelimiters] +severity = 2 + [-BuiltinFunctions::ProhibitUselessTopic] [-ControlStructures::ProhibitCascadingIfElse] [-ControlStructures::ProhibitPostfixControls] @@ -14,12 +32,13 @@ exclude_functions = print say sleep binmode [-Documentation::RequirePodLinksIncludeText] [-Documentation::RequirePodSections] [-ErrorHandling::RequireCarping] +[-InputOutput::RequireBracedFileHandleWithPrint] [-Modules::RequireVersionVar] [-References::ProhibitDoubleSigils] [-RegularExpressions::ProhibitComplexRegexes] [-RegularExpressions::RequireDotMatchAnything] -[-RegularExpressions::RequireLineBoundaryMatching] [-RegularExpressions::RequireExtendedFormatting] +[-RegularExpressions::RequireLineBoundaryMatching] [-Subroutines::ProhibitExcessComplexity] [-ValuesAndExpressions::ProhibitConstantPragma] [-ValuesAndExpressions::ProhibitEmptyQuotes] @@ -46,7 +65,7 @@ exclude_functions = print say sleep binmode # _build_* are allowed [Subroutines::ProhibitUnusedPrivateSubroutines] - private_name_regex = _(?!_|build_)\w+ +private_name_regex = _(?!_|build_)\w+ # I don't agree with this policy because # a bare return can actually cause more problems. diff --git a/Bugzilla.pm b/Bugzilla.pm index afdfcefd2..e43110389 100644 --- a/Bugzilla.pm +++ b/Bugzilla.pm @@ -22,7 +22,7 @@ BEGIN { } } -our $VERSION = '20180306.4'; +our $VERSION = '20180330.1'; use Bugzilla::Auth; use Bugzilla::Auth::Persist::Cookie; @@ -625,7 +625,7 @@ sub switch_to_shadow_db { my $class = shift; if (!$class->request_cache->{dbh_shadow}) { - if ($class->params->{'shadowdb'}) { + if ($class->get_param_with_override('shadowdb')) { $class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow(); } else { $class->request_cache->{dbh_shadow} = $class->dbh_main; diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm index defe7c84f..915405a0e 100644 --- a/Bugzilla/BugMail.pm +++ b/Bugzilla/BugMail.pm @@ -421,7 +421,7 @@ sub sendMail { bugmailtype => $bugmailtype, }; - if (Bugzilla->params->{'use_mailer_queue'}) { + if (Bugzilla->get_param_with_override('use_mailer_queue')) { enqueue($vars); } else { MessageToMTA(_generate_bugmail($vars)); diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm index ba82c83d0..b932116a2 100644 --- a/Bugzilla/CGI.pm +++ b/Bugzilla/CGI.pm @@ -11,6 +11,7 @@ use 5.10.1; use strict; use warnings; +use Bugzilla::Logging; use CGI; use base qw(CGI); @@ -597,7 +598,20 @@ sub header { sub param { my $self = shift; + # We don't let CGI.pm warn about list context, but we do it ourselves. local $CGI::LIST_CONTEXT_WARN = 0; + if (0) { + state $has_warned = {}; + + ## no critic (Freenode::Wantarray) + if ( wantarray && @_ ) { + my ( $package, $filename, $line ) = caller; + if ( $package ne 'CGI' && ! $has_warned->{"$filename:$line"}++) { + WARN("Bugzilla::CGI::param called in list context from $package $filename:$line"); + } + } + ## use critic + } # When we are just requesting the value of a parameter... if (scalar(@_) == 1) { diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 2971c7a53..65b37dced 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -19,7 +19,6 @@ use Memoize; @Bugzilla::Constants::EXPORT = qw( BUGZILLA_VERSION - REST_DOC REMOTE_FILE LOCAL_FILE @@ -211,10 +210,6 @@ sub BUGZILLA_VERSION { eval { Bugzilla->VERSION } || $bugzilla_version; } -# A base link to the current REST Documentation. We place it here -# as it will need to be updated to whatever the current release is. -use constant REST_DOC => "https://bugzilla.readthedocs.io/en/latest/api/"; - # Location of the remote and local XML files to track new releases. use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml'; use constant LOCAL_FILE => 'bugzilla-update.xml'; # Relative to datadir. diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm index a2cff0bd4..15acfd0d9 100644 --- a/Bugzilla/DB.pm +++ b/Bugzilla/DB.pm @@ -109,8 +109,13 @@ sub connect_shadow { my $connect_params = dclone(Bugzilla->localconfig); $connect_params->{db_host} = Bugzilla->get_param_with_override('shadowdbhost'); $connect_params->{db_name} = Bugzilla->get_param_with_override('shadowdb'); - $connect_params->{db_port} = Bugzilla->get_param_with_override('shadowport'); - $connect_params->{db_sock} = Bugzilla->get_param_with_override('shadowsock'); + $connect_params->{db_port} = Bugzilla->get_param_with_override('shadowdbport'); + $connect_params->{db_sock} = Bugzilla->get_param_with_override('shadowdbsock'); + + if ( Bugzilla->localconfig->{'shadowdb_user'} && Bugzilla->localconfig->{'shadowdb_pass'} ) { + $connect_params->{db_user} = Bugzilla->localconfig->{'shadowdb_user'}; + $connect_params->{db_pass} = Bugzilla->localconfig->{'shadowdb_pass'}; + } return _connect($connect_params); } diff --git a/Bugzilla/DaemonControl.pm b/Bugzilla/DaemonControl.pm index b7f7bcbe9..6586cc01b 100644 --- a/Bugzilla/DaemonControl.pm +++ b/Bugzilla/DaemonControl.pm @@ -28,7 +28,8 @@ use POSIX qw(setsid WEXITSTATUS); use base qw(Exporter); our @EXPORT_OK = qw( - run_httpd run_cereal run_cereal_and_httpd + run_httpd run_cereal run_jobqueue + run_cereal_and_httpd run_cereal_and_jobqueue catch_signal on_finish on_exception assert_httpd assert_database assert_selenium ); @@ -39,10 +40,12 @@ our %EXPORT_TAGS = ( utils => [qw(catch_signal on_exception on_finish)], ); -use constant CEREAL_BIN => realpath(catfile( bz_locations->{cgi_path}, 'scripts', 'cereal.pl')); - -use constant HTTPD_BIN => '/usr/sbin/httpd'; -use constant HTTPD_CONFIG => realpath(catfile( bz_locations->{confdir}, 'httpd.conf' )); +use constant { + JOBQUEUE_BIN => realpath( catfile( bz_locations->{cgi_path}, 'jobqueue.pl' ) ), + CEREAL_BIN => realpath( catfile( bz_locations->{cgi_path}, 'scripts', 'cereal.pl' ) ), + HTTPD_BIN => '/usr/sbin/httpd', + HTTPD_CONFIG => realpath( catfile( bz_locations->{confdir}, 'httpd.conf' ) ), +}; sub catch_signal { my ($name, @done) = @_; @@ -75,7 +78,7 @@ sub run_cereal { my $cereal = IO::Async::Process->new( command => [CEREAL_BIN], on_finish => on_finish($exit_f), - on_exception => on_exception( "cereal", $exit_f ), + on_exception => on_exception( 'cereal', $exit_f ), ); $exit_f->on_cancel( sub { $cereal->kill('TERM') } ); $loop->add($cereal); @@ -85,15 +88,18 @@ sub run_cereal { sub run_httpd { my (@args) = @_; - my $loop = IO::Async::Loop->new; + my $loop = IO::Async::Loop->new; 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(); - exec HTTPD_BIN, '-DFOREGROUND', '-f' => HTTPD_CONFIG, @args; + my @command = ( HTTPD_BIN, '-DFOREGROUND', '-f' => HTTPD_CONFIG, @args ); + exec @command + or die "failed to exec $command[0] $!"; }, on_finish => on_finish($exit_f), on_exception => on_exception( 'httpd', $exit_f ), @@ -104,21 +110,52 @@ sub run_httpd { return $exit_f; } +sub run_jobqueue { + my (@args) = @_; + + my $loop = IO::Async::Loop->new; + my $exit_f = $loop->new_future; + my $jobqueue = IO::Async::Process->new( + command => [ JOBQUEUE_BIN, 'start', '-f', '-d', @args ], + on_finish => on_finish($exit_f), + on_exception => on_exception( 'httpd', $exit_f ), + ); + $exit_f->on_cancel( sub { $jobqueue->kill('TERM') } ); + $loop->add($jobqueue); + + return $exit_f; +} + +sub run_cereal_and_jobqueue { + my (@jobqueue_args) = @_; + + my $signal_f = catch_signal('TERM', 0); + my $cereal_exit_f = run_cereal(); + + return assert_cereal()->then( + sub { + my $jobqueue_exit_f = run_jobqueue(@jobqueue_args); + return Future->wait_any($cereal_exit_f, $jobqueue_exit_f, $signal_f); + } + ); +} + sub run_cereal_and_httpd { my @httpd_args = @_; - push @httpd_args, '-DNETCAT_LOGS'; - my $signal_f = catch_signal("TERM", 0); + my $signal_f = catch_signal('TERM', 0); my $cereal_exit_f = run_cereal(); return assert_cereal()->then( sub { + push @httpd_args, '-DNETCAT_LOGS'; + my $lc = Bugzilla::Install::Localconfig::read_localconfig(); if ( ($lc->{inbound_proxies} // '') eq '*' && $lc->{urlbase} =~ /^https/) { push @httpd_args, '-DHTTPS'; } - elsif (not $lc->{urlbase} =~ /^https/) { - WARN("HTTPS urlbase but inbound_proxies is not '*'"); + elsif ($lc->{urlbase} =~ /^https/) { + WARN('HTTPS urlbase but inbound_proxies is not "*"'); } my $httpd_exit_f = run_httpd(@httpd_args); @@ -140,24 +177,23 @@ sub assert_httpd { my $f = shift; ( $f->get =~ /^httpd OK/ ); }; - my $timeout = $loop->timeout_future(after => 20)->else_fail("assert_httpd timeout"); + my $timeout = $loop->timeout_future(after => 20)->else_fail('assert_httpd timeout'); return Future->wait_any($repeat, $timeout); } - sub assert_selenium { my ($host, $port) = @_; $host //= 'localhost'; $port //= 4444; - return assert_connect($host, $port, "assert_selenium"); + return assert_connect($host, $port, 'assert_selenium'); } sub assert_cereal { return assert_connect( 'localhost', $ENV{LOGGING_PORT} // 5880, - "assert_cereal" + 'assert_cereal' ); } @@ -199,7 +235,7 @@ sub assert_database { ); } until => sub { defined shift->get }; - my $timeout = $loop->timeout_future( after => 20 )->else_fail("assert_database timeout"); + my $timeout = $loop->timeout_future( after => 20 )->else_fail('assert_database timeout'); my $any_f = Future->wait_any( $repeat, $timeout ); return $any_f->transform( done => sub { return }, diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm index e7a99dba0..d67571848 100644 --- a/Bugzilla/Error.pm +++ b/Bugzilla/Error.pm @@ -38,15 +38,14 @@ sub _in_eval { sub _throw_error { my ($name, $error, $vars) = @_; - my $dbh = Bugzilla->dbh; $vars ||= {}; - $vars->{error} = $error; # Make sure any transaction is rolled back (if supported). # If we are within an eval(), do not roll back transactions as we are # eval'uating some test on purpose. - $dbh->bz_rollback_transaction() if ($dbh->bz_in_transaction() && !_in_eval()); + my $dbh = eval { Bugzilla->dbh }; + $dbh->bz_rollback_transaction() if ($dbh && $dbh->bz_in_transaction() && !_in_eval()); my $datadir = bz_locations()->{'datadir'}; # If a writable $datadir/errorlog exists, log error details there. @@ -191,10 +190,9 @@ sub ThrowCodeError { sub ThrowTemplateError { my ($template_err) = @_; - my $dbh = Bugzilla->dbh; - + my $dbh = eval { Bugzilla->dbh }; # Make sure the transaction is rolled back (if supported). - $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction(); + $dbh->bz_rollback_transaction() if $dbh && $dbh->bz_in_transaction(); if (blessed($template_err) && $template_err->isa('Template::Exception')) { my $type = $template_err->type; diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 08b824cad..5e51dd9cc 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -271,6 +271,7 @@ sub FILESYSTEM { 'metrics.pl' => { perms => WS_EXECUTE }, 'Makefile.PL' => { perms => OWNER_EXECUTE }, 'gen-cpanfile.pl' => { perms => OWNER_EXECUTE }, + 'jobqueue-worker.pl' => { perms => OWNER_EXECUTE }, 'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE }, 'Bugzilla.pm' => { perms => CGI_READ }, diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm index ba8e8dc57..7a913358c 100644 --- a/Bugzilla/Install/Localconfig.pm +++ b/Bugzilla/Install/Localconfig.pm @@ -43,7 +43,7 @@ our @EXPORT_OK = qw( # might want to change this for upstream use constant ENV_PREFIX => 'BMO_'; -use constant PARAM_OVERRIDE => qw( shadowdb shadowdbhost shadowdbport shadowdbsock ); +use constant PARAM_OVERRIDE => qw( use_mailer_queue mail_delivery_method shadowdb shadowdbhost shadowdbport shadowdbsock ); sub _sensible_group { return '' if ON_WINDOWS; @@ -135,12 +135,12 @@ use constant LOCALCONFIG_VARS => ( { name => 'param_override', default => { - memcached_servers => undef, - memcached_namespace => undef, - shadowdb => undef, - shadowdbhost => undef, - shadowdbport => undef, - shadowdbsock => undef, + use_mailer_queue => undef, + mail_delivery_method => undef, + shadowdb => undef, + shadowdbhost => undef, + shadowdbport => undef, + shadowdbsock => undef, }, }, { @@ -175,6 +175,14 @@ use constant LOCALCONFIG_VARS => ( name => 'inbound_proxies', default => _migrate_param( 'inbound_proxies', '' ), }, + { + name => 'shadowdb_user', + default => '', + }, + { + name => 'shadowdb_pass', + default => '', + } ); diff --git a/Bugzilla/JobQueue.pm b/Bugzilla/JobQueue.pm index 55d40bfb8..53b088c6e 100644 --- a/Bugzilla/JobQueue.pm +++ b/Bugzilla/JobQueue.pm @@ -11,9 +11,14 @@ use 5.10.1; use strict; use warnings; +use Bugzilla::Logging; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Install::Util qw(install_string); +use Bugzilla::DaemonControl qw(catch_signal); +use IO::Async::Timer::Periodic; +use IO::Async::Loop; +use Future; use base qw(TheSchwartz); # This maps job names for Bugzilla::JobQueue to the appropriate modules. @@ -91,6 +96,32 @@ sub insert { return $retval; } +sub debug { + my ($self, @args) = @_; + my $caller_pkg = caller; + local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 1; + my $logger = Log::Log4perl->get_logger($caller_pkg); + $logger->info(@args); +} + +sub work { + my ($self, $delay) = @_; + $delay ||= 1; + my $loop = IO::Async::Loop->new; + my $timer = IO::Async::Timer::Periodic->new( + first_interval => 0, + interval => $delay, + reschedule => 'drift', + on_tick => sub { $self->work_once } + ); + DEBUG("working every $delay seconds"); + $loop->add($timer); + $timer->start; + Future->wait_any(map { catch_signal($_) } qw( INT TERM HUP ))->get; + $timer->stop; + $loop->remove($timer); +} + # Clear the request cache at the start of each run. sub work_once { my $self = shift; diff --git a/Bugzilla/JobQueue/Runner.pm b/Bugzilla/JobQueue/Runner.pm index 5b3164ef9..0177de40a 100644 --- a/Bugzilla/JobQueue/Runner.pm +++ b/Bugzilla/JobQueue/Runner.pm @@ -14,23 +14,34 @@ package Bugzilla::JobQueue::Runner; use 5.10.1; use strict; use warnings; +use autodie qw(open close unlink system); +use Bugzilla::Logging; +use Bugzilla::Constants; +use Bugzilla::DaemonControl qw(:utils); +use Bugzilla::JobQueue::Worker; +use Bugzilla::JobQueue; +use Bugzilla::Util qw(get_text); use Cwd qw(abs_path); +use English qw(-no_match_vars $PROGRAM_NAME $EXECUTABLE_NAME); use File::Basename; use File::Copy; +use File::Spec::Functions qw(catfile tmpdir); +use Future; +use Future::Utils qw(fmap_void); +use IO::Async::Loop; +use IO::Async::Process; +use IO::Async::Signal; use Pod::Usage; -use Bugzilla::Constants; -use Bugzilla::JobQueue; -use Bugzilla::Util qw(get_text); -BEGIN { eval "use base qw(Daemon::Generic)"; } +use parent qw(Daemon::Generic); -our $VERSION = BUGZILLA_VERSION; +our $VERSION = 2; # Info we need to install/uninstall the daemon. -our $chkconfig = "/sbin/chkconfig"; -our $initd = "/etc/init.d"; -our $initscript = "bugzilla-queue"; +our $chkconfig = '/sbin/chkconfig'; +our $initd = '/etc/init.d'; +our $initscript = 'bugzilla-queue'; # The Daemon::Generic docs say that it uses all sorts of # things from gd_preconfig, but in fact it does not. The @@ -40,11 +51,10 @@ sub gd_preconfig { my $self = shift; my $pidfile = $self->{gd_args}{pidfile}; - if (!$pidfile) { - $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} - . ".pid"; + if ( !$pidfile ) { + $pidfile = catfile(tmpdir(), $self->{gd_progname} . '.pid'); } - return (pidfile => $pidfile); + return ( pidfile => $pidfile ); } # All config other than the pidfile has to be done in gd_getopt @@ -54,24 +64,30 @@ sub gd_getopt { $self->SUPER::gd_getopt(); - if ($self->{gd_args}{progname}) { + if ( $self->{gd_args}{progname} ) { $self->{gd_progname} = $self->{gd_args}{progname}; } else { - $self->{gd_progname} = basename($0); + $self->{gd_progname} = basename($PROGRAM_NAME); } - # There are places that Daemon Generic's new() uses $0 instead of + # There are places that Daemon Generic's new() uses $PROGRAM_NAME instead of # gd_progname, which it really shouldn't, but this hack fixes it. - $self->{_original_zero} = $0; - $0 = $self->{gd_progname}; + $self->{_original_program_name} = $PROGRAM_NAME; + + ## no critic (Variables::RequireLocalizedPunctuationVars) + $PROGRAM_NAME = $self->{gd_progname}; + ## use critic } sub gd_postconfig { my $self = shift; + # See the hack above in gd_getopt. This just reverses it # in case anything else needs the accurate $0. - $0 = delete $self->{_original_zero}; + ## no critic (Variables::RequireLocalizedPunctuationVars) + $PROGRAM_NAME = delete $self->{_original_program_name}; + ## use critic } sub gd_more_opt { @@ -79,12 +95,13 @@ sub gd_more_opt { return ( 'pidfile=s' => \$self->{gd_args}{pidfile}, 'n=s' => \$self->{gd_args}{progname}, + 'jobs|j=i' => \$self->{gd_args}{jobs}, ); } sub gd_usage { - pod2usage({ -verbose => 0, -exitval => 'NOEXIT' }); - return 0 + pod2usage( { -verbose => 0, -exitval => 'NOEXIT' } ); + return 0; } sub gd_can_install { @@ -95,66 +112,63 @@ sub gd_can_install { my $sysconfig = '/etc/sysconfig'; my $config_file = "$sysconfig/$initscript"; - if (!-x $chkconfig or !-d $initd) { + if ( !-x $chkconfig || !-d $initd ) { return $self->SUPER::gd_can_install(@_); } return sub { - if (!-w $initd) { + if ( !-w $initd ) { print "You must run the 'install' command as root.\n"; return; } - if (-e $dest_file) { + if ( -e $dest_file ) { print "$initscript already in $initd.\n"; } else { - copy($source_file, $dest_file) + copy( $source_file, $dest_file ) or die "Could not copy $source_file to $dest_file: $!"; - chmod(0755, $dest_file) + chmod 0755, $dest_file or die "Could not change permissions on $dest_file: $!"; } - system($chkconfig, '--add', $initscript); - print "$initscript installed.", - " To start the daemon, do \"$dest_file start\" as root.\n"; + system $chkconfig, '--add', $initscript; + print "$initscript installed.", " To start the daemon, do \"$dest_file start\" as root.\n"; - if (-d $sysconfig and -w $sysconfig) { - if (-e $config_file) { + if ( -d $sysconfig and -w $sysconfig ) { + if ( -e $config_file ) { print "$config_file already exists.\n"; return; } - open(my $config_fh, ">", $config_file) - or die "Could not write to $config_file: $!"; - my $directory = abs_path(dirname($self->{_original_zero})); - my $owner_id = (stat $self->{_original_zero})[4]; - my $owner = getpwuid($owner_id); - print $config_fh <<END; + open my $config_fh, '>', $config_file; + my $directory = abs_path( dirname( $self->{_original_program_name} ) ); + my $owner_id = ( stat $self->{_original_program_name} )[4]; + my $owner = getpwuid $owner_id; + print $config_fh <<"END"; #!/bin/sh BUGZILLA="$directory" USER=$owner END - close($config_fh); + close $config_fh; } else { print "Please edit $dest_file to configure the daemon.\n"; } - } + } } sub gd_can_uninstall { my $self = shift; - if (-x $chkconfig and -d $initd) { + if ( -x $chkconfig and -d $initd ) { return sub { - if (!-e "$initd/$initscript") { + if ( !-e "$initd/$initscript" ) { print "$initscript not installed.\n"; return; } - system($chkconfig, '--del', $initscript); - print "$initscript disabled.", - " To stop it, run: $initd/$initscript stop\n"; - } + system $chkconfig, '--del', $initscript; + print "$initscript disabled.", " To stop it, run: $initd/$initscript stop\n"; + } } return $self->SUPER::gd_can_install(@_); @@ -164,49 +178,76 @@ sub gd_check { my $self = shift; # Get a count of all the jobs currently in the queue. - my $jq = Bugzilla->job_queue(); - my @dbs = $jq->bz_databases(); + my $jq = Bugzilla->job_queue(); + my @dbs = $jq->bz_databases(); my $count = 0; foreach my $driver (@dbs) { - $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []); + $count += $driver->select_one( 'SELECT COUNT(*) FROM ts_job', [] ); } - print get_text('job_queue_depth', { count => $count }) . "\n"; + print get_text( 'job_queue_depth', { count => $count } ) . "\n"; } +# override this to use IO::Async. sub gd_setup_signals { - my $self = shift; - $self->SUPER::gd_setup_signals(); - $SIG{TERM} = sub { $self->gd_quit_event(); } + my $self = shift; + my @signals = qw( INT HUP TERM ); + $self->{_signal_future} = Future->wait_any( map { catch_signal( $_, $_ ) } @signals ); } sub gd_other_cmd { my ($self) = shift; - if ($ARGV[0] eq "once") { - $self->_do_work("work_once"); - - exit(0); + if ( $ARGV[0] eq 'once' ) { + Bugzilla::JobQueue::Worker->run('work_once'); + exit; } - + $self->SUPER::gd_other_cmd(); } -sub gd_run { - my $self = shift; +sub gd_quit_event { FATAL('gd_quit_event() should never be called') } +sub gd_reconfig_event { FATAL('gd_reconfig_event() should never be called') } - $self->_do_work("work"); +sub gd_run { + my $self = shift; + my $jobs = $self->{gd_args}{jobs} // 1; + my $signal_f = $self->{_signal_future}; + my $workers_f = fmap_void { $self->run_worker() } + concurrent => $jobs, + generate => sub { !$signal_f->is_ready }; + + # This is so the process shows up in (h)top in a useful way. + local $PROGRAM_NAME = "$self->{gd_progname} [supervisor]"; + Future->wait_any($signal_f, $workers_f)->get; + unlink $self->{gd_pidfile}; + exit 0; } -sub _do_work { - my ($self, $fn) = @_; +# This executes the script "jobqueue-worker.pl" +# $EXECUTABLE_NAME is the name of the perl interpreter. +sub run_worker { + my ( $self ) = @_; - my $jq = Bugzilla->job_queue(); - $jq->set_verbose($self->{debug}); - foreach my $module (values %{ Bugzilla::JobQueue->job_map() }) { - eval "use $module"; - $jq->can_do($module); + my $script = catfile( bz_locations->{cgi_path}, 'jobqueue-worker.pl' ); + my @command = ( $EXECUTABLE_NAME, $script); + if ( $self->{gd_args}{progname} ) { + push @command, '--name' => "$self->{gd_args}{progname} [worker]"; } - $jq->$fn; + my $loop = IO::Async::Loop->new; + my $exit_f = $loop->new_future; + my $worker = IO::Async::Process->new( + command => \@command, + on_finish => on_finish($exit_f), + on_exception => on_exception( 'jobqueue worker', $exit_f ) + ); + $exit_f->on_cancel( + sub { + DEBUG('terminate worker'); + $worker->kill('TERM'); + } + ); + $loop->add($worker); + return $exit_f; } 1; diff --git a/Bugzilla/JobQueue/Worker.pm b/Bugzilla/JobQueue/Worker.pm new file mode 100644 index 000000000..db8ebe35e --- /dev/null +++ b/Bugzilla/JobQueue/Worker.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::JobQueue::Worker; +use 5.10.1; +use strict; +use warnings; + +use Bugzilla::Logging; +use Module::Runtime qw(require_module); + +sub run { + my ( $class, $fn ) = @_; + DEBUG("Starting up for $fn"); + my $jq = Bugzilla->job_queue(); + + DEBUG('Loading jobqueue modules'); + foreach my $module ( values %{ Bugzilla::JobQueue->job_map() } ) { + DEBUG("JobQueue can do $module"); + require_module($module); + $jq->can_do($module); + } + $jq->$fn; +} + +1; diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm index 6e46d1862..1dec3d4ff 100644 --- a/Bugzilla/Mailer.pm +++ b/Bugzilla/Mailer.pm @@ -37,10 +37,10 @@ use Bugzilla::Version qw(vers_cmp); sub MessageToMTA { my ($msg, $send_now) = (@_); - my $method = Bugzilla->params->{'mail_delivery_method'}; + my $method = Bugzilla->get_param_with_override('mail_delivery_method'); return if $method eq 'None'; - if (Bugzilla->params->{'use_mailer_queue'} and !$send_now) { + if (Bugzilla->get_param_with_override('use_mailer_queue') and !$send_now) { Bugzilla->job_queue->insert('send_mail', { msg => $msg }); return; } @@ -66,7 +66,7 @@ sub MessageToMTA { } # Ensure that we are not sending emails too quickly to recipients. - if (Bugzilla->params->{use_mailer_queue} + if (Bugzilla->get_param_with_override('use_mailer_queue') && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR)) { $dbh->do( @@ -226,7 +226,7 @@ sub MessageToMTA { } # insert into email_rates - if (Bugzilla->params->{use_mailer_queue} + if (Bugzilla->get_param_with_override('use_mailer_queue') && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR)) { $dbh->do( @@ -252,15 +252,14 @@ sub build_thread_marker { $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@' } - my $threadingmarker; + my $threadingmarker = "References: <bug-$bug_id-$user_id$sitespec>"; if ($is_new) { - $threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>"; + $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id$sitespec>"; } else { my $rand_bits = generate_random_password(10); - $threadingmarker = "Message-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" . - "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>" . - "\nReferences: <bug-$bug_id-$user_id$sitespec>"; + $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" . + "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>"; } return $threadingmarker; diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm index 0ceed97c0..d34aaa595 100644 --- a/Bugzilla/Memcached.pm +++ b/Bugzilla/Memcached.pm @@ -16,7 +16,7 @@ use Log::Log4perl qw(:easy); use Bugzilla::Error; use Scalar::Util qw(blessed); use List::Util qw(sum); -use Bugzilla::Util qw(trick_taint); +use Bugzilla::Util qw(trick_taint trim); use URI::Escape; use Encode; use Sys::Syslog qw(:DEFAULT); @@ -36,11 +36,25 @@ sub _new { if (Bugzilla->feature('memcached') && $servers) { $self->{namespace} = Bugzilla->localconfig->{memcached_namespace}; TRACE("connecting servers: $servers, namespace: $self->{namespace}"); - $self->{memcached} = Cache::Memcached::Fast->new({ - servers => [ split(/[, ]+/, $servers) ], - namespace => $self->{namespace}, - max_size => 1024 * 1024 * 4, - }); + $self->{memcached} = Cache::Memcached::Fast->new( + { + servers => [ _parse_memcached_server_list($servers) ], + namespace => $self->{namespace}, + max_size => 1024 * 1024 * 4, + max_failures => 1, + failure_timeout => 60, + io_timeout => 0.2, + connect_timeout => 0.2, + } + ); + my $versions = $self->{memcached}->server_versions; + if (keys %$versions) { + # this is needed to ensure forked processes don't start out with a connected memcached socket. + $self->{memcached}->disconnect_all; + } + else { + WARN("No memcached servers"); + } } else { TRACE("memcached feature is not enabled"); @@ -48,6 +62,13 @@ sub _new { return bless($self, $class); } +sub _parse_memcached_server_list { + my ($server_list) = @_; + my @servers = split(/[, ]+/, trim($server_list)); + + return map { /:[0-9]+$/s ? $_ : "$_:11211" } @servers; +} + sub enabled { return $_[0]->{memcached} ? 1 : 0; } @@ -206,6 +227,8 @@ sub should_rate_limit { my $prefix = RATE_LIMIT_PREFIX . $name . ':'; my $memcached = $self->{memcached}; + return 0 unless $memcached; + $tries //= 3; for (0 .. $tries) { @@ -272,7 +295,7 @@ sub _inc_prefix { delete Bugzilla->request_cache->{"memcached_prefix_$name"}; # BMO - log that we've wiped the cache - INFO("$name cache cleared"); + TRACE("$name cache cleared"); } sub _global_prefix { @@ -315,7 +338,7 @@ sub _get { my $enc_key = $self->_encode_key($key) or return; - my $val = $self->{memcached}->get($key); + my $val = $self->{memcached}->get($enc_key); TRACE("get $enc_key: " . (defined $val ? "HIT" : "MISS")); return $val; } diff --git a/Bugzilla/ModPerl.pm b/Bugzilla/ModPerl.pm index a5c840897..120dd8210 100644 --- a/Bugzilla/ModPerl.pm +++ b/Bugzilla/ModPerl.pm @@ -20,6 +20,7 @@ use Carp (); use Template (); use Bugzilla::ModPerl::BlockIP; +use Bugzilla::ModPerl::Hostage; sub apache_config { my ($class, $cgi_path) = @_; @@ -74,6 +75,7 @@ __DATA__ # 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(); }" +PerlInitHandler Bugzilla::ModPerl::Hostage PerlAccessHandler Bugzilla::ModPerl::BlockIP # It is important to specify ErrorDocuments outside of all directories. @@ -84,6 +86,12 @@ ErrorDocument 403 /errors/403.html ErrorDocument 404 /errors/404.html ErrorDocument 500 /errors/500.html +<Location /helper> + SetHandler perl-script + PerlResponseHandler Plack::Handler::Apache2 + PerlSetVar psgi_app [% cgi_path %]/helper.psgi +</Location> + <Directory "[% cgi_path %]"> AddHandler perl-script .cgi # No need to PerlModule these because they're already defined in mod_perl.pl diff --git a/Bugzilla/ModPerl/BasicAuth.pm b/Bugzilla/ModPerl/BasicAuth.pm index e93680e9d..7248a19f3 100644 --- a/Bugzilla/ModPerl/BasicAuth.pm +++ b/Bugzilla/ModPerl/BasicAuth.pm @@ -25,18 +25,22 @@ use warnings; # 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); +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; - return $status if $status != Apache2::Const::OK; + 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) { - warn "AUTH_VAR_NAME and AUTH_VAR_PASS environmental vars not set\n"; + ERROR('AUTH_VAR_NAME and AUTH_VAR_PASS environmental vars not set'); $r->note_basic_auth_failure; return Apache2::Const::HTTP_UNAUTHORIZED; } @@ -44,13 +48,14 @@ sub handler { my $auth_user = Bugzilla->localconfig->{$auth_var_name}; my $auth_pass = Bugzilla->localconfig->{$auth_var_pass}; unless ($auth_user && $auth_pass) { - warn "$auth_var_name and $auth_var_pass not configured\n"; + 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; } diff --git a/Bugzilla/ModPerl/Hostage.pm b/Bugzilla/ModPerl/Hostage.pm new file mode 100644 index 000000000..a3bdfac58 --- /dev/null +++ b/Bugzilla/ModPerl/Hostage.pm @@ -0,0 +1,71 @@ +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/Send/Sendmail.pm b/Bugzilla/Send/Sendmail.pm index 71c1f67ce..81c2190e5 100644 --- a/Bugzilla/Send/Sendmail.pm +++ b/Bugzilla/Send/Sendmail.pm @@ -37,7 +37,7 @@ sub send { unless (close $pipe) { return failure "error when closing pipe to $mailer: $!" if $!; my ($error_message, $is_transient) = _map_exitcode($? >> 8); - if (Bugzilla->params->{'use_mailer_queue'}) { + if (Bugzilla->get_param_with_override('use_mailer_queue')) { # Return success for errors which are fatal so Bugzilla knows to # remove them from the queue if ($is_transient) { diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index 2ec813303..3ace60cf8 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -284,7 +284,8 @@ sub get_attachment_link { $link_text =~ s/ \[details\]$//; $link_text =~ s/ \[diff\]$//; - my $linkval = "attachment.cgi?id=$attachid"; + state $urlbase = Bugzilla->localconfig->{urlbase}; + my $linkval = "${urlbase}attachment.cgi?id=$attachid"; # If the attachment is a patch and patch_viewer feature is # enabled, add link to the diff. @@ -572,7 +573,9 @@ sub create { ABSOLUTE => 1, RELATIVE => 1, - COMPILE_DIR => bz_locations()->{'template_cache'}, + # Only use an on-disk template cache if we're running as the web + # server. This ensures the permissions of the cache remain correct. + COMPILE_DIR => is_webserver_group() ? bz_locations()->{'template_cache'} : undef, # Don't check for a template update until 1 hour has passed since the # last check. @@ -1071,6 +1074,8 @@ our %_templates_to_precompile; sub precompile_templates { my ($output) = @_; + return unless is_webserver_group(); + # Remove the compiled templates. my $cache_dir = bz_locations()->{'template_cache'}; my $datadir = bz_locations()->{'datadir'}; diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm index 7d85a4dfd..a1316c7ef 100644 --- a/Bugzilla/Util.pm +++ b/Bugzilla/Util.pm @@ -17,7 +17,8 @@ use base qw(Exporter); with_writable_database with_readonly_database html_quote url_quote xml_quote css_class_quote html_light_quote - i_am_cgi i_am_webservice correct_urlbase remote_ip + i_am_cgi i_am_webservice is_webserver_group + correct_urlbase remote_ip validate_ip do_ssl_redirect_if_required use_attachbase diff_arrays on_main_db css_url_rewrite trim wrap_hard wrap_comment find_wrap_point @@ -32,19 +33,20 @@ use base qw(Exporter); use Bugzilla::Constants; use Bugzilla::RNG qw(irand); -use Date::Parse; use Date::Format; -use DateTime; +use Date::Parse; use DateTime::TimeZone; +use DateTime; use Digest; use Email::Address; -use List::MoreUtils qw(none); -use Scalar::Util qw(tainted blessed); -use Text::Wrap; use Encode qw(encode decode resolve_alias); use Encode::Guess; +use English qw(-no_match_vars $EGID); +use List::MoreUtils qw(any none); use POSIX qw(floor ceil); +use Scalar::Util qw(tainted blessed); use Taint::Util qw(untaint); +use Text::Wrap; use Try::Tiny; sub with_writable_database(&) { @@ -280,6 +282,30 @@ sub i_am_webservice { || $usage_mode == USAGE_MODE_REST; } +sub is_webserver_group { + my @effective_gids = split(/ /, $EGID); + + state $web_server_gid; + if (!defined $web_server_gid) { + my $web_server_group = Bugzilla->localconfig->{webservergroup}; + + if ($web_server_group eq '' || ON_WINDOWS) { + $web_server_gid = $effective_gids[0]; + } + + elsif ($web_server_group =~ /^\d+$/) { + $web_server_gid = $web_server_group; + } + + else { + $web_server_gid = eval { getgrnam($web_server_group) }; + $web_server_gid //= 0; + } + } + + return any { $web_server_gid == $_ } @effective_gids; +} + # This exists as a separate function from Bugzilla::CGI::redirect_to_https # because we don't want to create a CGI object during XML-RPC calls # (doing so can mess up XML-RPC). @@ -1071,6 +1097,11 @@ in a command-line script. Tells you whether or not the current usage mode is WebServices related such as JSONRPC or XMLRPC. +=item C<is_webserver_group()> + +Tells you whether or not the current process's group matches that +configured as webservergroup. + =item C<remote_ip()> Returns the IP address of the remote client. If Bugzilla is behind diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm index 6fb86fdd4..b8884b753 100644 --- a/Bugzilla/WebService/Server/REST.pm +++ b/Bugzilla/WebService/Server/REST.pm @@ -132,7 +132,8 @@ sub response { if (exists $json_data->{error}) { $result = $json_data->{error}; $result->{error} = $self->type('boolean', 1); - $result->{documentation} = REST_DOC; + + $result->{documentation} = Bugzilla->params->{docs_urlbase} . "api/"; delete $result->{'name'}; # Remove JSONRPCError } elsif (exists $json_data->{result}) { diff --git a/Makefile.PL b/Makefile.PL index 8e20d52b9..2c7b8fad7 100755 --- a/Makefile.PL +++ b/Makefile.PL @@ -52,6 +52,7 @@ my %requires = ( 'File::Slurp' => '9999.13', 'Future' => '0.34', 'HTML::Escape' => '1.10', + 'IPC::System::Simple' => 0, 'IO::Async' => '0.71', 'JSON::MaybeXS' => '1.003008', 'JSON::XS' => '2.01', @@ -273,6 +274,7 @@ my %optional_features = ( requires => { 'mod_perl2' => '1.999022', 'Apache2::SizeLimit' => '0.96', + 'Plack::Handler::Apache2' => 0, } } } diff --git a/README.rst b/README.rst index a2b23d069..64fae335b 100644 --- a/README.rst +++ b/README.rst @@ -19,6 +19,8 @@ BMO is Mozilla's highly customized version of Bugzilla. 3.1 Container Arguments 3.2 Environmental Variables 3.3 Persistent Data Volume + 4. Development Tips + 4.1 Testing Emails If you want to contribute to BMO, you can fork this repo and get a local copy of BMO running in a few minutes using Vagrant or Docker. @@ -297,6 +299,15 @@ BMO_apache_size_limit This is the max amount of unshared memory (in kb) that the apache process is allowed to use before Apache::SizeLimit kills it. +BMO_mail_delivery_method + Usually configured on the MTA section of admin interface, but may be set here for testing purposes. + Valid values are None, Test, Sendmail, or SMTP. + If set to Test, email will be appended to the /app/data/mailer.testfile. + +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, @@ -356,3 +367,48 @@ Persistent Data Volume This container expects /app/data to be a persistent, shared, writable directory owned by uid 10001. This must be a shared (NFS/EFS/etc) volume between all nodes. + +Development Tips +================ + +Testing Emails +-------------- + +With vagrant have two options to test emails sent by a local bugzilla instance. You can configure +which setting you want to use by going to http://bmo-web.vm/editparams.cgi?section=mta and +changing the mail_delivery_method to either 'Test' or 'Sendmail'. Afterwards restart bmo with +``vagrant reload``. With docker, only the default 'Test' option is supported. + +'Test' option (Default for Docker) +~~~~~~~~~~~~~~~~~~~~~~~ + +With this option, all mail will be appended to a ``mailer.testfile``. + +- Using docker, run ``docker-compose run bmo-web.vm cat /app/data/mailer.testfile``. +- Using vagrant, run ``vagrant ssh web`` and then naviage to ``/vagrant/data/mailer.testfile``. + +'Sendmail' option (Default for Vagrant) +~~~~~~~~~~~~~~~~~ + +This option is useful if you want to preview email using a real mail client. +An imap server is running on bmo-web.vm on port 143 and you can connect to it with +the following settings: + +- host: bmo-web.vm +- port: 143 +- encryption: No SSL, Plaintext password +- username: vagrant +- password: anything + +All email that bmo sends will go to the vagrant user, so there is no need to login with +multiple imap accounts. + +`Thunderbird's`_ wizard to add a new "Existing Mail Account" doesn't work with bmo-web. It +fails because it wants to create a mail account with both incoming mail (IMAP) and outgoing +mail (SMTP, which bmo-web.vm doesn't provide). To work around this, using a regular email +account to first setup, then modify the settings of that account: Right Click the account in +the left side bar > Settings > Server Settings. Update the server settings to match those +listed above. Afterwards, you may update the account name to be vagrant@bmo-web.vm. Thunderbird +will now pull email from bmo. You can try it out by commenting on a bug. + +.. _`Thunderbird's`: https://www.mozilla.org/en-US/thunderbird/ diff --git a/checksetup.pl b/checksetup.pl index 6cea8549d..d3f08e024 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -30,6 +30,7 @@ use Pod::Usage; # Bug 1270550 - Tie::Hash::NamedCapture must be loaded before Safe. use Tie::Hash::NamedCapture; use Safe; +use English qw(-no_match_vars $EUID $EGID); use Bugzilla::Constants; use Bugzilla::Install::Requirements; @@ -155,6 +156,16 @@ unless ($ENV{LOCALCONFIG_ENV}) { } my $lc_hash = Bugzilla->localconfig; +if ( $EUID == 0 && $lc_hash->{webservergroup} && !ON_WINDOWS ) { + # So checksetup was run as root, and we have a webserver group set. + # Let's assume the user wants us to make files that are writable + # by the webserver group. + + $EGID = getgrnam $lc_hash->{webservergroup}; ## no critic (Variables::RequireLocalizedPunctuationVars) + umask 002 + or die "failed to set umask 002: $!"; +} + unless ($switch{'no-database'}) { die "urlbase is not set\n" unless $lc_hash->{urlbase}; die "urlbase must end with slash\n" unless $lc_hash->{urlbase} =~ m{/$}ms; diff --git a/conf/httpd.conf b/conf/httpd.conf index 20a21f5db..7fe859b59 100644 --- a/conf/httpd.conf +++ b/conf/httpd.conf @@ -4,7 +4,8 @@ ServerRoot "/etc/httpd" ServerAdmin root@localhost PidFile /tmp/httpd.pid -Timeout 60 +Timeout 120 +LimitRequestLine 35000 KeepAlive Off MaxKeepAliveRequests 100 KeepAliveTimeout 15 @@ -91,7 +92,7 @@ DocumentRoot "/app" Alias "/bmo" "/app" </IfDefine> <IfDefine HTTPS> - SetEnv HTTPS on + SetEnvIf X-Forwarded-Proto "https" HTTPS=on </IfDefine> <Directory "/app"> Options -Indexes -FollowSymLinks diff --git a/docker-compose.yml b/docker-compose.yml index e04e9c712..5dfb6fad4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,20 @@ services: - bmo-db.vm - memcached + bmo-jobqueue.vm: + build: *bmo_build + command: jobqueue + volumes: + - bmo-data-dir:/app/data + tmpfs: + - /tmp + - /run + environment: *bmo_env + restart: always + depends_on: + - bmo-db.vm + - memcached + bmo-db.vm: image: mozillabteam/bmo-mysql:5.6 volumes: diff --git a/extensions/BMO/template/en/default/bug/create/comment-ipc.txt.tmpl b/extensions/BMO/template/en/default/bug/create/comment-ipc.txt.tmpl deleted file mode 100644 index b644ee469..000000000 --- a/extensions/BMO/template/en/default/bug/create/comment-ipc.txt.tmpl +++ /dev/null @@ -1,47 +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 Bugzilla %] -[% cgi = Bugzilla.cgi %] - ->> What team are you are a part of? -[%+ cgi.param("team_name") %] - ->> Will your snippet need to appear in a language other than English? -[%+ cgi.param("language").join(', ') %] - ->> Does your snippet need to be targeted to a particular country? -[%+ cgi.param("target_country") %] - ->> Insert your tagged link here. -[%+ cgi.param("tagged_link") %] - -[% IF cgi.param('start_run') %] ->> When would you like your snippet to start its run? -[%+ cgi.param("start_run") %] - -[% END %] -[% IF cgi.param('complete_run') %] ->> When would you like your snippet to complete its run? -[%+ cgi.param("complete_run") %] - -[% END %] ->> Will you be able to provide the following assets for your snippet? -[%+ cgi.param("snippet_assets").join(', ') %] - ->> Your name? -[%+ cgi.param("name") %] - ->> E-mail? -[%+ cgi.param("email") %] - ->> Anything else you would like to add? -[%+ cgi.param("else") %] - ->> Who do you think is coolest? -[%+ cgi.param("bonus") %] diff --git a/extensions/BMO/template/en/default/bug/create/create-ipc.html.tmpl b/extensions/BMO/template/en/default/bug/create/create-ipc.html.tmpl deleted file mode 100644 index e32ba62dc..000000000 --- a/extensions/BMO/template/en/default/bug/create/create-ipc.html.tmpl +++ /dev/null @@ -1,259 +0,0 @@ -[%# 1.0@bugzilla.org %] -[%# The contents of this file are subject to the Mozilla Public - # License Version 1.1 (the "License"); you may not use this file - # except in compliance with the License. You may obtain a copy of - # the License at http://www.mozilla.org/MPL/ - - # - # Software distributed under the License is distributed on an "AS - # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - # implied. See the License for the specific language governing - # rights and limitations under the License. - # - # The Original Code is the Bugzilla Bug Tracking System. - # - # The Initial Developer of the Original Code is Mozilla Corporation. - # Portions created by Mozilla are Copyright (C) 2008 Mozilla - # Corporation. All Rights Reserved. - # - # Contributor(s): Sebastin Santy <sebastinssanty@gmail.com> - #%] - -[% PROCESS global/variables.none.tmpl %] - -[% inline_style = BLOCK %] -#heading { - width: 98%; - font-size: 2em; - font-weight: bold; - margin: 12px; -} -.required:before { - content: "* "; - color: red; -} -.required_star { - color: red; -} -p { - max-width: 40%; -} -.row { - padding-bottom: 10px; -} -[% END %] - -[% inline_javascript = BLOCK %] -$(document).ready(function() { - $('.date_field').datetimepicker({ - format: 'Y-m-d', - datepicker: true, - timepicker: false, - scrollInput: false, - lazyInit: false, - closeOnDateSelect: true - }); - $('#langothers').on('focus', function(){ - document.getElementById("langothersradio").checked = true; - }); - $('#assetothers').on('focus', function(){ - document.getElementById("assetothersradio").checked = true; - }); - $('.date_field-img') - .click(function(event) { - var id = $(event.target).attr('id').replace(/-img$/, ''); - $('#' + id).datetimepicker('show'); - }); - $('#ipcForm').submit(function() { - $('#short_desc').val($('#short_desc').val() + ': ' + $('#team_name').val()); - }); - $('#new_email_request').on('change', function() { - if ($('#new_email_request').val() == 'Yes') { - $('#email_cadence_row').show(); - $('input[name="email_cadence"]').each(function() { - $(this).attr('required', true); - }); - } - else { - $('#email_cadence_row').hide(); - $('input[name="email_cadence"]').each(function() { - $(this).removeProp('required'); - }); - } - }); -}); -[% END %] - -[% PROCESS global/header.html.tmpl - title = "IPC Request" - generate_api_token = 1 - style_urls = [ "skins/standard/attachment.css", - "js/jquery/plugins/datetimepicker/datetimepicker.css" ] - style = inline_style - javascript = inline_javascript - javascript_urls = [ "js/field.js", "js/util.js" ] - jquery = [ "datetimepicker" ] -%] - -<div id="heading">IPC Requests</div> -<div> - <p>You’ve decided to use the snippet channel to drive attention to one of your projects, awesome! The snippet channel drives hundreds of millions of impressions a month. It’s great tool to support your marketing efforts. Complete this form and we will follow up with you soon (maybe today, but definitely within 3 days). - </p> - <p> - Tell us more: - </p> - - <form method="post" action="post_bug.cgi" id="ipcForm"> - <input type="hidden" id="short_desc" name="short_desc" value="New IPC Request"> - <input type="hidden" name="product" value="Marketing"> - <input type="hidden" name="component" value="IPC"> - <input type="hidden" name="rep_platform" value="All"> - <input type="hidden" name="op_sys" value="Other"> - <input type="hidden" name="version" value="unspecified"> - <input type="hidden" name="priority" value="--"> - <input type="hidden" name="bug_severity" value="normal"> - <input type="hidden" name="format" value="ipc"> - <input type="hidden" name="token" value="[% token FILTER html %]"> - [% IF user.in_group('canconfirm') %] - <input type="hidden" name="bug_status" value="NEW"> - [% END %] - - <div class="row"> - <div class="row_desc"> - <label class="required" for="team_name"> - <strong>What team are you a part of?</strong> - </label> - </div> - <input required type="text" name="team_name" id="team_name" size="80"> - </div> - - <div class="row"> - <div class="row_desc"> - <label for="cf_user_story"> - <strong>What is the user story associated with your snippet?</strong> - </label> - </div> - <textarea name="cf_user_story" id="cf_user_story" cols="80" rows="10"></textarea> - </div> - - <div class="row"> - <div class="row_desc"> - <strong>Will your snippet need to appear in a language other than English? If so, please specify.</strong> - </div> - <input type="checkbox" name="language" id="spanish" value="Spanish"> - <label for="spanish">Spanish</label><br> - <input type="checkbox" name="language" id="italian" value="Italian"> - <label for="italian">Italian</label><br> - <input type="checkbox" name="language" id="german" value="German"> - <label for="german">German</label><br> - <input type="checkbox" name="language" id="french" value="French"> - <label for="french">French</label><br> - <input type="checkbox" name="language" id="russian" value="Russian"> - <label for="russian">Russian</label><br> - <input type="checkbox" name="language" id="portuguese" value="Portuguese"> - <label for="portuguese">Portuguese</label><br> - <input type="checkbox" name="language" id="langothersradio"> - <label for="langothers">Others: </label> - <input type="text" name="language" id="langothers" size="30"> - </div> - - <div class="row"> - <div class="row_desc"> - <label for="target_country"> - <strong>Does your snippet need to be targeted to a particular country? If so, please specify.</strong> - </label> - </div> - <input type="text" name="target_country" id="target_country" size="80"> - </div> - - <p> - If you would like your snippet to include a link, please tag it by using the URL builder <a href="https://bit.ly/1JOgDr1">(https://bit.ly/1JOgDr1)</a> and following the steps below: - </p> - <img src="extensions/BMO/web/images/ipc_form_buildscreen.png" style = "width:50%"><br><br> - <div class="row"> - <div class="row_desc"> - <label for="tagged_link"> - <strong>Insert your tagged link here.</strong> - </label> - </div> - <input type="text" name="tagged_link" id="tagged_link" size="80"> - </div> - - <p> - Please reference the Mozilla In-Product Messaging Calendar for available dates. <a href="https://bit.ly/2aZ3w9c"> https://bit.ly/2aZ3w9c</a> - </p> - - <div class="row"> - <div class="row_desc"> - <label for="start_run"> - <strong>When would you like your snippet to start its run?</strong> - </label> - </div> - <input class="date_field" name="start_run" id="start_run"> - <img class="date_field-img" id="start_run-img" src="extensions/BugModal/web/calendar.png" width="16" height="16"> - </div> - - <div class="row"> - <div class="row_desc"> - <label for="complete_run"> - <strong>When would you like your snippet to complete its run?</strong> - </label> - </div> - <input class="date_field" name="complete_run" id="complete_run"> - <img class="date_field-img" id="complete_run-img" src="extensions/BugModal/web/calendar.png" width="16" height="16"> - </div> - - <div class="row"> - <div class="row_desc"> - <strong>Will you be able to provide the following assets for your snippet? Check all that apply.</strong><br> - Feel free to look at our existing icon library: - <a href="https://bit.ly/2efXaFo">https://bit.ly/2efXaFo</a> - </div> - <input type="checkbox" name="snippet_assets" id="copy" value="Copy"> - <label for="copy">Copy</label><br> - <input type="checkbox" name="snippet_assets" id="icon" value="Icon"> - <label for="icon">Icon</label><br> - <input type="checkbox" name="snippet_assets" id="special_format" value="Special Format"> - <label for="special_format">Special Format</label><br> - <input type="checkbox" name="snippet_assets" id="assetothersradio"> - <label for="assetothers">Others: </label> - <input type="text" name="snippet_assets" id="assetothers" size="30"> - </div> - - <input type="hidden" required name="name" id="name" value="[% user.name FILTER html %]" size="80"> - - <input type="hidden" required name="email" id="email" value="[% user.email FILTER html %]" size="80"> - - <div class="row"> - <div class="row_desc"> - <label for="else"> - <strong>Anything else you would like to add?</strong><br> - </label> - </div> - <textarea name="else" id="else" cols="80" rows="10"></textarea> - </div> - <h3><strong>Bonus Question</strong></h3> - <div class="row"> - <div class="row_desc"> - <strong>Who do you think is coolest?</strong> - </div> - <input type="radio" name="bonus" id="han_solo" value="Hans Solo"> - <label for="han_solo">Han Solo</label><br> - <input type="radio" name="bonus" id="hermoine_granger" value="Hermoine Granger"> - <label for="hermoine_granger">Hermione Granger</label><br> - <input type="radio" name="bonus" id="picard" value="Captain Jean-Luc Picard"> - <label for="picard">Captain Jean-Luc Picard</label><br> - <input type="radio" name="bonus" id="everdeen" value="Katniss Everdeen"> - <label for="everdeen">Katniss Everdeen</label><br> - </div> - - <p>Thanks for reaching out. Click submit and we will be back with you shortly.</p> - - <input type="submit" id="commit" value="Submit Request"> - <p> - [ <span class="required_star">*</span> <span class="required_explanation">Required Field</span> ] - </p> - </form> -</div> - -[% PROCESS global/footer.html.tmpl %]
\ No newline at end of file diff --git a/extensions/BMO/template/en/default/pages/user_activity.html.tmpl b/extensions/BMO/template/en/default/pages/user_activity.html.tmpl index 075c8edf9..5603b943f 100644 --- a/extensions/BMO/template/en/default/pages/user_activity.html.tmpl +++ b/extensions/BMO/template/en/default/pages/user_activity.html.tmpl @@ -199,6 +199,7 @@ change.fieldname == 'reporter' || change.fieldname == 'qa_contact' || change.fieldname == 'cc' || + change.fieldname == 'bug_mentor' || change.fieldname == 'flagtypes.name' %] [% display_value(change.fieldname, change_type) FILTER email FILTER html %] [% ELSE %] diff --git a/extensions/BMO/web/images/ipc_form_buildscreen.png b/extensions/BMO/web/images/ipc_form_buildscreen.png Binary files differdeleted file mode 100644 index d98207d29..000000000 --- a/extensions/BMO/web/images/ipc_form_buildscreen.png +++ /dev/null diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl index a8d55c137..51919ab27 100644 --- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl @@ -365,7 +365,7 @@ ", " UNLESS loop.last; END; - CASE [ 'assigned_to', 'reporter', 'qa_contact', 'cc', 'flagtypes.name' ]; + CASE [ 'assigned_to', 'reporter', 'qa_contact', 'cc', 'bug_mentor', 'flagtypes.name' ]; value FILTER email; CASE 'reporter_accessible'; diff --git a/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl index 05476fed4..06163a841 100644 --- a/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl @@ -29,14 +29,14 @@ [% INCLUDE nav_link text="Next ❱" bug_id="" %] [% END %] [% INCLUDE nav_link text="Last ❱❱" bug_id=last_bug_list.last %] - <a id="search-nav-reget" href="buglist.cgi?regetlastlist=[% my_search.id FILTER uri %]">Last search results</a> + <a id="search-nav-reget" href="buglist.cgi?regetlastlist=[% search.id FILTER uri %]">Last search results</a> </div> [% BLOCK nav_link %] [% IF bug_id == "" %] <span class="search-nav-disabled">[% text FILTER none %]</span> [% ELSE %] - <a class="search-nav-link" href="show_bug.cgi?id=[% bug_id FILTER uri %]&list_id=[% my_search.id FILTER uri %]"> + <a class="search-nav-link" href="show_bug.cgi?id=[% bug_id FILTER uri %]&list_id=[% search.id FILTER uri %]"> [%~ text FILTER none ~%] </a> [% END %] diff --git a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl index d0a3abb5b..32e6499cb 100644 --- a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl +++ b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl @@ -155,6 +155,7 @@ change.fieldname == 'reporter' || change.fieldname == 'qa_contact' || change.fieldname == 'cc' || + change.fieldname == 'bug_mentor' || change.fieldname == 'flagtypes.name' %] [% value FILTER email FILTER js %] [% ELSIF change.fieldtype == constants.FIELD_TYPE_DATETIME %] diff --git a/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl b/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl index 9b8e0e0cc..2d7c3379f 100644 --- a/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl +++ b/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl @@ -8,6 +8,11 @@ [% USE Bugzilla %] <meta property="og:type" content="website"> -<meta property="og:image" content="[% urlbase FILTER none %]extensions/OpenGraph/web/moz-social-bw-rgb-1200x1200.png"> <meta property="og:title" content="[% title FILTER none %]"> <meta property="og:url" content="[% Bugzilla.cgi.self_url FILTER html %]"> +[% IF bug %] +<meta property="og:description" + content="[% bug.bug_status FILTER html %] ([% bug.assigned_to.login FILTER email FILTER html %]) in [% bug.product FILTER html %] - [% bug.component FILTER html %]. Last updated [% bug.delta_ts FILTER time('%Y-%m-%d') %]."> +[% ELSE %] +<meta property="og:image" content="[% urlbase FILTER none %]extensions/OpenGraph/web/moz-social-bw-rgb-1200x1200.png"> +[% END %] diff --git a/extensions/PhabBugz/Extension.pm b/extensions/PhabBugz/Extension.pm index b3ad44819..ee96901a2 100644 --- a/extensions/PhabBugz/Extension.pm +++ b/extensions/PhabBugz/Extension.pm @@ -15,7 +15,6 @@ use parent qw(Bugzilla::Extension); use Bugzilla::Constants; use Bugzilla::Extension::PhabBugz::Feed; -use Bugzilla::Extension::PhabBugz::Logger; our $VERSION = '0.01'; diff --git a/extensions/PhabBugz/lib/Daemon.pm b/extensions/PhabBugz/lib/Daemon.pm index c8b4f73af..ef4a00534 100644 --- a/extensions/PhabBugz/lib/Daemon.pm +++ b/extensions/PhabBugz/lib/Daemon.pm @@ -13,7 +13,6 @@ use warnings; use Bugzilla::Constants; use Bugzilla::Extension::PhabBugz::Feed; -use Bugzilla::Extension::PhabBugz::Logger; use Carp qw(confess); use Daemon::Generic; @@ -89,11 +88,9 @@ sub gd_setup_signals { sub gd_run { my $self = shift; - $::SIG{__DIE__} = \&Carp::confess if $self->{debug}; + $SIG{__DIE__} = \&Carp::confess if $self->{debug}; my $phabbugz = Bugzilla::Extension::PhabBugz::Feed->new(); $phabbugz->is_daemon(1); - $phabbugz->logger( - Bugzilla::Extension::PhabBugz::Logger->new(debugging => $self->{debug})); $phabbugz->start(); } diff --git a/extensions/PhabBugz/lib/Feed.pm b/extensions/PhabBugz/lib/Feed.pm index 9904d5090..074ecc0f9 100644 --- a/extensions/PhabBugz/lib/Feed.pm +++ b/extensions/PhabBugz/lib/Feed.pm @@ -13,6 +13,7 @@ use List::Util qw(first); use List::MoreUtils qw(any); use Moo; +use Bugzilla::Logging; use Bugzilla::Constants; use Bugzilla::Search; use Bugzilla::Util qw(diff_arrays with_writable_database with_readonly_database); @@ -36,7 +37,6 @@ use Bugzilla::Extension::PhabBugz::Util qw( ); has 'is_daemon' => ( is => 'rw', default => 0 ); -has 'logger' => ( is => 'rw' ); sub start { my ($self) = @_; @@ -48,7 +48,7 @@ sub start { } 1; }; - $self->logger->error( $@ // "unknown exception" ) unless $ok; + ERROR( $@ // "unknown exception" ) unless $ok; sleep(PHAB_POLL_SECONDS); } } @@ -59,19 +59,19 @@ sub feed_query { # Ensure Phabricator syncing is enabled if (!Bugzilla->params->{phabricator_enabled}) { - $self->logger->info("PHABRICATOR SYNC DISABLED"); + INFO("PHABRICATOR SYNC DISABLED"); return; } # PROCESS NEW FEED TRANSACTIONS - $self->logger->info("FEED: Fetching new transactions"); + INFO("FEED: Fetching new transactions"); my $story_last_id = $self->get_last_id('feed'); # Check for new transctions (stories) my $new_stories = $self->new_stories($story_last_id); - $self->logger->info("FEED: No new stories") unless @$new_stories; + INFO("FEED: No new stories") unless @$new_stories; # Process each story foreach my $story_data (@$new_stories) { @@ -81,15 +81,15 @@ sub feed_query { my $object_phid = $story_data->{objectPHID}; my $story_text = $story_data->{text}; - $self->logger->debug("STORY ID: $story_id"); - $self->logger->debug("STORY PHID: $story_phid"); - $self->logger->debug("AUTHOR PHID: $author_phid"); - $self->logger->debug("OBJECT PHID: $object_phid"); - $self->logger->info("STORY TEXT: $story_text"); + DEBUG("STORY ID: $story_id"); + DEBUG("STORY PHID: $story_phid"); + DEBUG("AUTHOR PHID: $author_phid"); + DEBUG("OBJECT PHID: $object_phid"); + INFO("STORY TEXT: $story_text"); # Only interested in changes to revisions for now. if ($object_phid !~ /^PHID-DREV/) { - $self->logger->debug("SKIPPING: Not a revision change"); + DEBUG("SKIPPING: Not a revision change"); $self->save_last_id($story_id, 'feed'); next; } @@ -99,7 +99,7 @@ sub feed_query { if (@$phab_users) { my $user = Bugzilla::User->new({ id => $phab_users->[0]->{id}, cache => 1 }); if ($user->login eq PHAB_AUTOMATION_USER) { - $self->logger->debug("SKIPPING: Change made by phabricator user"); + DEBUG("SKIPPING: Change made by phabricator user"); $self->save_last_id($story_id, 'feed'); next; } @@ -113,13 +113,13 @@ sub feed_query { # PROCESS NEW USERS - $self->logger->info("FEED: Fetching new users"); + INFO("FEED: Fetching new users"); my $user_last_id = $self->get_last_id('user'); # Check for new users my $new_users = $self->new_users($user_last_id); - $self->logger->info("FEED: No new users") unless @$new_users; + INFO("FEED: No new users") unless @$new_users; # Process each new user foreach my $user_data (@$new_users) { @@ -128,10 +128,10 @@ sub feed_query { my $user_realname = $user_data->{fields}{realName}; my $object_phid = $user_data->{phid}; - $self->logger->debug("USER ID: $user_id"); - $self->logger->debug("USER LOGIN: $user_login"); - $self->logger->debug("USER REALNAME: $user_realname"); - $self->logger->debug("OBJECT PHID: $object_phid"); + DEBUG("USER ID: $user_id"); + DEBUG("USER LOGIN: $user_login"); + DEBUG("USER REALNAME: $user_realname"); + DEBUG("OBJECT PHID: $object_phid"); with_readonly_database { $self->process_new_user($user_data); @@ -151,15 +151,15 @@ sub process_revision_change { if (!$revision->bug_id) { if ($story_text =~ /\s+created\s+D\d+/) { # If new revision and bug id was omitted, make revision public - $self->logger->debug("No bug associated with new revision. Marking public."); + DEBUG("No bug associated with new revision. Marking public."); $revision->set_policy('view', 'public'); $revision->set_policy('edit', 'users'); $revision->update(); - $self->logger->info("SUCCESS"); + INFO("SUCCESS"); return; } else { - $self->logger->debug("SKIPPING: No bug associated with revision change"); + DEBUG("SKIPPING: No bug associated with revision change"); return; } } @@ -170,7 +170,7 @@ sub process_revision_change { $revision->title, $revision->bug_id, $story_text); - $self->logger->info($log_message); + INFO($log_message); # Pre setup before making changes my $old_user = set_phab_user(); @@ -180,7 +180,7 @@ sub process_revision_change { # If bug is public then remove privacy policy if (!@{ $bug->groups_in }) { - $self->logger->debug('Bug is public so setting view/edit public'); + DEBUG('Bug is public so setting view/edit public'); $revision->set_policy('view', 'public'); $revision->set_policy('edit', 'users'); my $secure_project_phid = get_project_phid('secure-revision'); @@ -193,7 +193,7 @@ sub process_revision_change { # If bug privacy groups do not have any matching synchronized groups, # then leave revision private and it will have be dealt with manually. if (!@set_groups) { - $self->logger->debug('No matching groups. Adding comments to bug and revision'); + DEBUG('No matching groups. Adding comments to bug and revision'); add_security_sync_comments([$revision], $bug); } # Otherwise, we create a new custom policy containing the project @@ -205,23 +205,23 @@ sub process_revision_change { # we leave the current policy alone. my $current_policy; if ($revision->view_policy =~ /^PHID-PLCY/) { - $self->logger->debug("Loading current policy: " . $revision->view_policy); + DEBUG("Loading current policy: " . $revision->view_policy); $current_policy = Bugzilla::Extension::PhabBugz::Policy->new_from_query({ phids => [ $revision->view_policy ]}); my $current_projects = $current_policy->rule_projects; - $self->logger->debug("Current policy projects: " . join(", ", @$current_projects)); + DEBUG("Current policy projects: " . join(", ", @$current_projects)); my ($added, $removed) = diff_arrays($current_projects, \@set_projects); if (@$added || @$removed) { - $self->logger->debug('Project groups do not match. Need new custom policy'); + DEBUG('Project groups do not match. Need new custom policy'); $current_policy= undef; } else { - $self->logger->debug('Project groups match. Leaving current policy as-is'); + DEBUG('Project groups match. Leaving current policy as-is'); } } if (!$current_policy) { - $self->logger->debug("Creating new custom policy: " . join(", ", @set_projects)); + DEBUG("Creating new custom policy: " . join(", ", @set_projects)); my $new_policy = Bugzilla::Extension::PhabBugz::Policy->create(\@set_projects); $revision->set_policy('view', $new_policy->phid); $revision->set_policy('edit', $new_policy->phid); @@ -229,15 +229,17 @@ sub process_revision_change { my $secure_project_phid = get_project_phid('secure-revision'); $revision->add_project($secure_project_phid); - - my $subscribers = get_bug_role_phids($bug); - $revision->set_subscribers($subscribers); } + + # Subscriber list of the private revision should always match + # the bug roles such as assignee, qa contact, and cc members. + my $subscribers = get_bug_role_phids($bug); + $revision->set_subscribers($subscribers); } my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()"); - my $attachment = create_revision_attachment($bug, $revision->id, $revision->title, $timestamp); + my $attachment = create_revision_attachment($bug, $revision, $timestamp); # ATTACHMENT OBSOLETES @@ -250,11 +252,11 @@ sub process_revision_change { next if $attach_revision_id != $revision->id; my $make_obsolete = $revision->status eq 'abandoned' ? 1 : 0; - $self->logger->debug('Updating obsolete status on attachmment ' . $attachment->id); + DEBUG('Updating obsolete status on attachmment ' . $attachment->id); $attachment->set_is_obsolete($make_obsolete); if ($revision->title ne $attachment->description) { - $self->logger->debug('Updating description on attachment ' . $attachment->id); + DEBUG('Updating description on attachment ' . $attachment->id); $attachment->set_description($revision->title); } @@ -270,7 +272,7 @@ sub process_revision_change { }); foreach my $attachment (@$other_attachments) { $other_bugs{$attachment->bug_id}++; - $self->logger->debug('Updating obsolete status on attachment ' . + DEBUG('Updating obsolete status on attachment ' . $attachment->id . " for bug " . $attachment->bug_id); $attachment->set_is_obsolete(1); $attachment->update($timestamp); @@ -291,6 +293,8 @@ sub process_revision_change { $phab_users = get_phab_bmo_ids({ phids => \@denied_phids }); @denied_user_ids = map { $_->{id} } @$phab_users; + my %reviewers_hash = map { $_->name => 1 } @{ $revision->reviewers }; + foreach my $attachment (@attachments) { my ($attach_revision_id) = ($attachment->filename =~ PHAB_ATTACHMENT_PATTERN); next if $revision->id != $attach_revision_id; @@ -331,7 +335,11 @@ sub process_revision_change { $comment .= $flag_data->{setter}->name . " has requested changes to the revision.\n"; } foreach my $flag_data (@removed_flags) { - $comment .= $flag_data->{setter}->name . " has been removed from the revision.\n"; + if ( exists $reviewers_hash{$flag_data->{setter}->name} ) { + $comment .= "Flag set by " . $flag_data->{setter}->name . " is no longer active.\n"; + } else { + $comment .= $flag_data->{setter}->name . " has been removed from the revision.\n"; + } } if ($comment) { @@ -362,7 +370,7 @@ sub process_revision_change { Bugzilla->set_user($old_user); - $self->logger->info('SUCCESS: Revision D' . $revision->id . ' processed'); + INFO('SUCCESS: Revision D' . $revision->id . ' processed'); } sub process_new_user { @@ -372,7 +380,7 @@ sub process_new_user { my $phab_user = Bugzilla::Extension::PhabBugz::User->new($user_data); if (!$phab_user->bugzilla_id) { - $self->logger->debug("SKIPPING: No bugzilla id associated with user"); + DEBUG("SKIPPING: No bugzilla id associated with user"); return; } @@ -425,7 +433,7 @@ sub process_new_user { my @bug_ids = map { shift @$_ } @$data; foreach my $bug_id (@bug_ids) { - $self->logger->debug("Processing bug $bug_id"); + DEBUG("Processing bug $bug_id"); my $bug = Bugzilla::Bug->new({ id => $bug_id, cache => 1 }); @@ -434,7 +442,7 @@ sub process_new_user { foreach my $attachment (@attachments) { my ($revision_id) = ($attachment->filename =~ PHAB_ATTACHMENT_PATTERN); - $self->logger->debug("Processing revision D$revision_id"); + DEBUG("Processing revision D$revision_id"); my $revision = Bugzilla::Extension::PhabBugz::Revision->new_from_query( { ids => [ int($revision_id) ] }); @@ -442,13 +450,13 @@ sub process_new_user { $revision->add_subscriber($phab_user->phid); $revision->update(); - $self->logger->debug("Revision $revision_id updated"); + DEBUG("Revision $revision_id updated"); } } Bugzilla->set_user($old_user); - $self->logger->info('SUCCESS: User ' . $phab_user->id . ' processed'); + INFO('SUCCESS: User ' . $phab_user->id . ' processed'); } ################## @@ -496,7 +504,7 @@ sub get_last_id { my $last_id = Bugzilla->dbh->selectrow_array( " SELECT value FROM phabbugz WHERE name = ?", undef, $type_full ); $last_id ||= 0; - $self->logger->debug( "QUERY " . uc($type_full) . ": $last_id" ); + DEBUG( "QUERY " . uc($type_full) . ": $last_id" ); return $last_id; } @@ -505,7 +513,7 @@ sub save_last_id { # Store the largest last key so we can start from there in the next session my $type_full = $type . "_last_id"; - $self->logger->debug( "UPDATING " . uc($type_full) . ": $last_id" ); + DEBUG( "UPDATING " . uc($type_full) . ": $last_id" ); Bugzilla->dbh->do( "REPLACE INTO phabbugz (name, value) VALUES (?, ?)", undef, $type_full, $last_id ); } diff --git a/extensions/PhabBugz/lib/Logger.pm b/extensions/PhabBugz/lib/Logger.pm deleted file mode 100644 index 3127b66db..000000000 --- a/extensions/PhabBugz/lib/Logger.pm +++ /dev/null @@ -1,37 +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::Extension::PhabBugz::Logger; - -use 5.10.1; - -use Moo; - -use Bugzilla::Extension::PhabBugz::Constants; - -has 'debugging' => ( is => 'ro' ); - -sub info { shift->_log_it('INFO', @_) } -sub error { shift->_log_it('ERROR', @_) } -sub debug { shift->_log_it('DEBUG', @_) } - -sub _log_it { - my ($self, $method, $message) = @_; - - return if $method eq 'DEBUG' && !$self->debugging; - chomp $message; - if ($ENV{MOD_PERL}) { - require Apache2::Log; - Apache2::ServerRec::warn("FEED $method: $message"); - } elsif ($ENV{SCRIPT_FILENAME}) { - print STDERR "FEED $method: $message\n"; - } else { - print STDERR '[' . localtime(time) ."] $method: $message\n"; - } -} - -1; diff --git a/extensions/PhabBugz/lib/Revision.pm b/extensions/PhabBugz/lib/Revision.pm index c114de78c..98c3196c2 100644 --- a/extensions/PhabBugz/lib/Revision.pm +++ b/extensions/PhabBugz/lib/Revision.pm @@ -29,6 +29,7 @@ use Bugzilla::Extension::PhabBugz::Util qw( has id => ( is => 'ro', isa => Int ); has phid => ( is => 'ro', isa => Str ); has title => ( is => 'ro', isa => Str ); +has summary => ( is => 'ro', isa => Str ); has status => ( is => 'ro', isa => Str ); has creation_ts => ( is => 'ro', isa => Str ); has modification_ts => ( is => 'ro', isa => Str ); @@ -93,6 +94,7 @@ sub BUILDARGS { my ( $class, $params ) = @_; $params->{title} = $params->{fields}->{title}; + $params->{summary} = $params->{fields}->{summary}; $params->{status} = $params->{fields}->{status}->{value}; $params->{creation_ts} = $params->{fields}->{dateCreated}; $params->{modification_ts} = $params->{fields}->{dateModified}; diff --git a/extensions/PhabBugz/lib/Util.pm b/extensions/PhabBugz/lib/Util.pm index 8085828f9..844d8c0b5 100644 --- a/extensions/PhabBugz/lib/Util.pm +++ b/extensions/PhabBugz/lib/Util.pm @@ -77,12 +77,12 @@ sub _get_revisions { } sub create_revision_attachment { - my ( $bug, $revision_id, $revision_title, $timestamp ) = @_; + my ( $bug, $revision, $timestamp ) = @_; my $phab_base_uri = Bugzilla->params->{phabricator_base_uri}; ThrowUserError('invalid_phabricator_uri') unless $phab_base_uri; - my $revision_uri = $phab_base_uri . "D" . $revision_id; + my $revision_uri = $phab_base_uri . "D" . $revision->id; # Check for previous attachment with same revision id. # If one matches then return it instead. This is fine as @@ -102,8 +102,8 @@ sub create_revision_attachment { bug => $bug, creation_ts => $timestamp, data => $revision_uri, - description => $revision_title, - filename => 'phabricator-D' . $revision_id . '-url.txt', + description => $revision->title, + filename => 'phabricator-D' . $revision->id . '-url.txt', ispatch => 0, isprivate => 0, mimetype => PHAB_CONTENT_TYPE, @@ -111,8 +111,8 @@ sub create_revision_attachment { ); # Insert a comment about the new attachment into the database. - $bug->add_comment('', { type => CMT_ATTACHMENT_CREATED, - extra_data => $attachment->id }); + $bug->add_comment($revision->summary, { type => CMT_ATTACHMENT_CREATED, + extra_data => $attachment->id }); return $attachment; } diff --git a/extensions/Push/lib/Logger.pm b/extensions/Push/lib/Logger.pm index 7ae96b58a..5d92010ee 100644 --- a/extensions/Push/lib/Logger.pm +++ b/extensions/Push/lib/Logger.pm @@ -8,53 +8,43 @@ package Bugzilla::Extension::Push::Logger; use 5.10.1; -use strict; -use warnings; +use Moo; +use Bugzilla::Logging; +use Log::Log4perl; use Bugzilla::Extension::Push::Constants; use Bugzilla::Extension::Push::LogEntry; -sub new { - my ($class) = @_; - my $self = {}; - bless($self, $class); - return $self; -} +# If Log4perl then finds that it's being called from a registered wrapper, it +# will automatically step up to the next call frame. +Log::Log4perl->wrapper_register(__PACKAGE__); -sub info { shift->_log_it('INFO', @_) } -sub error { shift->_log_it('ERROR', @_) } -sub debug { shift->_log_it('DEBUG', @_) } +sub info { + my ($this, $message) = @_; + INFO($message); +} -sub debugging { - my ($self) = @_; - return $self->{debug}; +sub error { + my ($this, $message) = @_; + ERROR($message); } -sub _log_it { - my ($self, $method, $message) = @_; - return if $method eq 'DEBUG' && !$self->debugging; - chomp $message; - if ($ENV{MOD_PERL}) { - require Apache2::Log; - Apache2::ServerRec::warn("Push $method: $message"); - } elsif ($ENV{SCRIPT_FILENAME}) { - print STDERR "Push $method: $message\n"; - } else { - print STDERR '[' . localtime(time) ."] $method: $message\n"; - } +sub debug { + my ($this, $message) = @_; + DEBUG($message); } sub result { my ($self, $connector, $message, $result, $data) = @_; $data ||= ''; - $self->info(sprintf( - "%s: Message #%s: %s %s", + my $log_msg = sprintf + '%s: Message #%s: %s %s', $connector->name, $message->message_id, push_result_to_string($result), - $data - )); + $data; + $self->info($log_msg); Bugzilla::Extension::Push::LogEntry->create({ message_id => $message->message_id, @@ -68,4 +58,6 @@ sub result { }); } +sub _build_logger { Log::Log4perl->get_logger(__PACKAGE__); } + 1; diff --git a/extensions/Review/Extension.pm b/extensions/Review/Extension.pm index db2b475a1..f05f2ba8b 100644 --- a/extensions/Review/Extension.pm +++ b/extensions/Review/Extension.pm @@ -93,6 +93,8 @@ sub _reviewers_objs { sub _user_is_active { my ($self) = @_; + # never consider .bugs or .tld addresses as inactive. + return 1 if $self->login =~ /bugs$/ || $self->login =~ /\.tld$/; return 1 unless Bugzilla->params->{max_reviewer_last_seen}; return 0 if !defined($self->last_seen_date); diff --git a/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl b/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl index 27cb825ed..bea0d16d7 100644 --- a/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl +++ b/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl @@ -27,7 +27,7 @@ <td> </td> <th>Search</th> <td colspan="2"> - <form action="user_profile"> + <form action="[% urlbase FILTER html %]user_profile"> [% INCLUDE global/userselect.html.tmpl id => "login" name => "login" diff --git a/heartbeat.cgi b/heartbeat.cgi index 917853d2b..08cf13fa7 100755 --- a/heartbeat.cgi +++ b/heartbeat.cgi @@ -13,6 +13,7 @@ use warnings; use lib qw(. lib local/lib/perl5); use Bugzilla; +use Bugzilla::Logging; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Update; @@ -29,10 +30,9 @@ my $ok = eval { die "database not available" unless $database_ok; die "memcached server(s) not available" unless $memcached_ok; die "mod_perl/psgi not configured?" unless BZ_PERSISTENT; - die "missing bmo feature dependencies" unless Bugzilla->has_feature('bmo'); 1; }; -warn "heartbeat error: $@" if !$ok && $@; +FATAL("heartbeat error: $@") if !$ok && $@; my $cgi = Bugzilla->cgi; print $cgi->header(-type => 'text/plain', -status => $ok ? '200 OK' : '500 Internal Server Error'); diff --git a/helper.psgi b/helper.psgi new file mode 100644 index 000000000..cc8c648a8 --- /dev/null +++ b/helper.psgi @@ -0,0 +1,35 @@ +#!/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 Plack::Request; +use Plack::Response; + +my $app = sub { + my $env = shift; + my $req = Plack::Request->new($env); + my $res = Plack::Response->new(404); + my $urlbase = Bugzilla->localconfig->{urlbase}; + my $path = $req->path; + + if ( $path eq '/quicksearch.html' ) { + $res->redirect( $urlbase . 'page.cgi?id=quicksearch.html', 301 ); + } + elsif ( $path eq '/bugwritinghelp.html') { + $res->redirect( $urlbase . 'page.cgi?id=bug-writing.html', 301 ); + } + elsif ( $path =~ m{^/(\d+)$}s ) { + $res->redirect( $urlbase . "show_bug.cgi?id=$1", 301 ); + } + else { + $res->body('not found'); + } + return $res->finalize; +};
\ No newline at end of file diff --git a/jobqueue-worker.pl b/jobqueue-worker.pl new file mode 100644 index 000000000..b26aacdba --- /dev/null +++ b/jobqueue-worker.pl @@ -0,0 +1,47 @@ +#!/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 File::Basename qw(basename dirname); +use File::Spec::Functions qw(catdir rel2abs); + +BEGIN { + require lib; + my $dir = rel2abs( dirname(__FILE__) ); + lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) ); + chdir $dir or die "chdir $dir failed: $!"; + +} + +use Bugzilla::JobQueue::Worker; +use Bugzilla::JobQueue; +use Bugzilla; +use English qw(-no_match_vars $PROGRAM_NAME $OSNAME); +use Getopt::Long qw(:config gnu_getopt); +use if $OSNAME eq 'linux', 'Linux::Pdeathsig', 'set_pdeathsig'; + +BEGIN { Bugzilla->extensions } +my $name = basename(__FILE__); + +GetOptions( 'name=s' => \$name ); + +if ($name) { + ## no critic (Variables::RequireLocalizedPunctuationVars) + $PROGRAM_NAME = $name; + ## use critic +} + +if ($OSNAME eq 'linux') { + # get SIGTEMR (15) when parent exits. + set_pdeathsig(15); +} + +Bugzilla::JobQueue::Worker->run('work'); diff --git a/jobqueue.pl b/jobqueue.pl index f5541e0fb..7a884b811 100755 --- a/jobqueue.pl +++ b/jobqueue.pl @@ -10,12 +10,13 @@ use 5.10.1; use strict; use warnings; -use File::Basename; -use File::Spec; +use File::Basename qw(dirname); +use File::Spec::Functions qw(catdir rel2abs); + BEGIN { require lib; - my $dir = File::Spec->rel2abs(dirname(__FILE__)); - lib->import($dir, File::Spec->catdir($dir, 'lib'), File::Spec->catdir($dir, qw(local lib perl5))); + my $dir = rel2abs( dirname(__FILE__) ); + lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) ); chdir $dir or die "chdir $dir failed: $!"; } @@ -39,6 +40,8 @@ jobqueue.pl - Runs jobs in the background for Bugzilla. process id. Defaults to F<data/jobqueue.pl.pid>. -n name What should this process call itself in the system log? Defaults to the full path you used to invoke the script. + -j jobs How many child processes should be run? + Defaults to 1. COMMANDS: start Starts a new jobqueue daemon if there isn't one running already diff --git a/mod_perl.pl b/mod_perl.pl index 3221af19f..c64352600 100644 --- a/mod_perl.pl +++ b/mod_perl.pl @@ -58,6 +58,7 @@ use Apache2::SizeLimit; use ModPerl::RegistryLoader (); use File::Basename (); use File::Find (); +use English qw(-no_match_vars $OSNAME); # This loads most of our modules. use Bugzilla (); @@ -81,8 +82,9 @@ Bugzilla::CGI->compile(qw(:cgi :push)); # is taking up more than $apache_size_limit of RAM all by itself, not counting RAM it is # sharing with the other httpd processes. my $limit = Bugzilla->localconfig->{apache_size_limit}; -if ($limit < 400_000) { - $limit = 400_000; +if ($OSNAME eq 'linux' && ! eval { require Linux::Smaps }) { + warn "SizeLimit requires Linux::Smaps on linux. size limit set to 800MB"; + $limit = 800_000; } Apache2::SizeLimit->set_max_unshared_size($limit); diff --git a/scripts/build-bmo-push-data.pl b/scripts/build-bmo-push-data.pl new file mode 100755 index 000000000..e3cefe533 --- /dev/null +++ b/scripts/build-bmo-push-data.pl @@ -0,0 +1,203 @@ +#!/usr/bin/perl +use 5.10.1; +use strict; +use warnings; + +use File::Basename qw(basename dirname); +use File::Spec::Functions qw(catdir rel2abs); +use Cwd qw(realpath); + +BEGIN { + require lib; + my $dir = realpath( catdir(dirname(__FILE__), '..') ); + lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) ); + chdir $dir or die "chdir $dir failed: $!"; +} + +use autodie; +use Bugzilla; +use English qw(-no_match_vars $PROGRAM_NAME); +use IPC::System::Simple qw(runx capture); +use JSON::MaybeXS qw(decode_json); +use LWP::Simple qw(get); +use LWP::UserAgent; +use MIME::Base64 qw(decode_base64); +use URI::QueryParam; +use URI; + +my $github_repo = "https://github.com/mozilla-bteam/bmo"; +my $version_info = decode_json(get('https://bugzilla.mozilla.org/__version__')); +my $tag = 'release-' . Bugzilla->VERSION; +my $prod_tag = "release-$version_info->{version}"; +my $tag_url = "$github_repo/tree/$tag"; + +my @log = capture(qw(git log --oneline), "$prod_tag..HEAD"); +die "nothing to commit\n" unless @log; +chomp @log; + +my @revisions; +foreach my $line (@log) { + say $line; + my ($revision, $message); + unless ( ( $revision, $message ) = $line =~ /^(\S+) (.+)$/ ) { + warn "skipping $line\n"; + next; + } + + my @bug_ids; + if ($message =~ /\bBug (\d+)/i) { + push @bug_ids, $1; + } + + if (!@bug_ids) { + warn "skipping $line (no bug)\n"; + next; + } + + foreach my $bug_id (@bug_ids) { + my $duplicate = 0; + foreach my $revisions (@revisions) { + if ($revisions->{bug_id} == $bug_id) { + $duplicate = 1; + last; + } + } + next if $duplicate; + + my $bug = fetch_bug($bug_id); + if ($bug->{status} eq 'RESOLVED' && $bug->{resolution} ne 'FIXED') { + next; + } + if ($bug->{summary} =~ /\bbackport\s+(?:upstream\s+)?bug\s+(\d+)/i) { + my $upstream = $1; + $bug->{summary} = fetch_bug($upstream)->{summary}; + } + push @revisions, { + hash => $revision, + bug_id => $bug_id, + summary => $bug->{summary}, + }; + } +} +if (!@revisions) { + die "no new revisions. make sure you run this script before production is updated.\n"; +} +else { + @revisions = reverse @revisions; +} + +my $first_revision = $revisions[0]->{hash}; +my $last_revision = $revisions[-1]->{hash}; + +mkdir 'build_info' unless -d 'build_info'; +chdir 'build_info'; + +say "write tag.txt"; +open my $tag_fh, '>', 'tag.txt'; +say $tag_fh $tag; +close $tag_fh; + +say 'write bug.push.txt'; + +open my $bug_fh, '>', 'bug.push.txt'; +say $bug_fh 'https://bugzilla.mozilla.org/enter_bug.cgi?product=bugzilla.mozilla.org&component=Infrastructure&short_desc=push+updated+bugzilla.mozilla.org+live'; +say $bug_fh "revisions: $first_revision - $last_revision"; +foreach my $revision (@revisions) { + say $bug_fh "bug $revision->{bug_id} : $revision->{summary}"; +} +close $bug_fh; + +say 'write blog.push.txt'; + +open my $blog_fh, '>', 'blog.push.txt'; +say $blog_fh "[release tag]($tag_url)\n"; +say $blog_fh "the following changes have been pushed to bugzilla.mozilla.org:\n<ul>"; +foreach my $revision (@revisions) { + printf $blog_fh '<li>[<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=%s" target="_blank">%s</a>] %s</li>%s', + $revision->{bug_id}, $revision->{bug_id}, html_escape($revision->{summary}), "\n"; +} +say $blog_fh '</ul>'; +say $blog_fh q{discuss these changes on <a href="https://lists.mozilla.org/listinfo/tools-bmo" target="_blank">mozilla.tools.bmo</a>.}; +close $blog_fh; + +say 'write email.push.txt'; + +open my $email_fh, '>', 'email.push.txt'; +say $email_fh "the following changes have been pushed to bugzilla.mozilla.org:\n"; +say $email_fh "(tag: $tag_url)\n"; +foreach my $revision (@revisions) { + printf $email_fh "https://bugzil.la/%s : %s\n", $revision->{bug_id}, $revision->{summary}; +} +close $email_fh; + +say 'write wiki.push.txt'; + +open my $wiki_fh, '>', 'wiki.push.txt'; +say $wiki_fh 'https://wiki.mozilla.org/BMO/Recent_Changes'; +say $wiki_fh '== ' . DateTime->now->set_time_zone('UTC')->ymd('-') . " ==\n"; +say $wiki_fh "[$tag_url $tag]"; +foreach my $revision (@revisions) { + printf $wiki_fh "* {{bug|%s}} %s\n", $revision->{bug_id}, $revision->{summary}; +} +close $wiki_fh; + +sub html_escape { + my ($s) = @_; + $s =~ s/&/&/g; + $s =~ s/</</g; + $s =~ s/>/>/g; + return $s; +} + +use constant BUG_FIELDS => [qw( + id + product + version + target_milestone + summary + status + resolution + assigned_to +)]; + +sub fetch_bug { + my ($bug_id) = @_; + die 'missing id' unless $bug_id; + + my $response = _get( 'bug/' . $bug_id, { include_fields => BUG_FIELDS, } ); + return $response->{bugs}->[0]; +} + +sub _get { + my ($endpoint, $args) = @_; + my $ua = LWP::UserAgent->new( agent => $PROGRAM_NAME ); + $args //= {}; + + if (exists $args->{include_fields} && ref($args->{include_fields})) { + $args->{include_fields} = join ',', @{ $args->{include_fields} }; + } + + my $uri = URI->new('https://bugzilla.mozilla.org/rest/' . $endpoint); + foreach my $name (sort keys %$args) { + $uri->query_param($name => $args->{$name}); + } + + my $request = HTTP::Request->new('GET', $uri->as_string); + $request->header( Content_Type => 'application/json' ); + $request->header( Accept => 'application/json' ); + if ( $ENV{BMO_API_KEY} ) { + $request->header( X_Bugzilla_API_Key => $ENV{BMO_API_KEY} ); + } + + my $response = $ua->request($request); + if ($response->code !~ /^2/) { + my $error = $response->message; + my $ok = eval { + $error = decode_json($response->decoded_content)->{message}; + 1; + }; + $error = $@ unless $ok; + die $error . "\n"; + } + return decode_json($response->decoded_content); +} diff --git a/scripts/cereal.pl b/scripts/cereal.pl index d5b556451..bc4d8abd4 100755 --- a/scripts/cereal.pl +++ b/scripts/cereal.pl @@ -22,9 +22,12 @@ use Bugzilla::DaemonControl qw(catch_signal); use Future; use IO::Async::Loop; use IO::Async::Protocol::LineStream; +use IO::Handle; $ENV{LOGGING_PORT} //= 5880; +STDOUT->autoflush(1); + my $loop = IO::Async::Loop->new; my $on_stream = sub { my ($stream) = @_; @@ -46,4 +49,4 @@ $loop->listen( on_stream => $on_stream, )->get; -exit Future->wait_any(map { catch_signal($_, 0) } @signals)->get;
\ No newline at end of file +exit Future->wait_any(map { catch_signal($_, 0) } @signals)->get; diff --git a/scripts/entrypoint.pl b/scripts/entrypoint.pl index d5612dd85..350dcac8e 100755 --- a/scripts/entrypoint.pl +++ b/scripts/entrypoint.pl @@ -10,6 +10,7 @@ use Bugzilla::Install::Util qw(install_string); use Bugzilla::Test::Util qw(create_user); use Bugzilla::DaemonControl qw( run_cereal_and_httpd + run_cereal_and_jobqueue assert_httpd assert_database assert_selenium on_finish on_exception ); @@ -89,6 +90,13 @@ sub cmd_httpd { exit $httpd_exit_f->get(); } +sub cmd_jobqueue { + my (@args) = @_; + check_data_dir(); + wait_for_db(); + exit run_cereal_and_jobqueue(@args)->get; +} + sub cmd_dev_httpd { my $have_params = -f "/app/data/params"; assert_database->get(); diff --git a/ses/index.cgi b/ses/index.cgi index aa5b34704..9e1632586 100755 --- a/ses/index.cgi +++ b/ses/index.cgi @@ -13,49 +13,55 @@ use warnings; use lib qw(.. ../lib ../local/lib/perl5); use Bugzilla (); +use Bugzilla::Logging; use Bugzilla::Constants qw( ERROR_MODE_DIE ); use Bugzilla::Mailer qw( MessageToMTA ); use Bugzilla::User (); use Bugzilla::Util qw( html_quote remote_ip ); -use JSON::XS qw( decode_json encode_json ); +use JSON::MaybeXS qw( decode_json encode_json ); use LWP::UserAgent (); use Try::Tiny qw( try catch ); Bugzilla->error_mode(ERROR_MODE_DIE); try { main(); -} catch { - warn "SES: Fatal error: $_\n"; - respond(500 => 'Internal Server Error'); +} +catch { + FATAL("Fatal error: $_"); + respond( 500 => 'Internal Server Error' ); }; sub main { - my $message = decode_json_wrapper(Bugzilla->cgi->param('POSTDATA')) // return; + 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') { + if ( $message_type eq 'SubscriptionConfirmation' ) { confirm_subscription($message); } - elsif ($message_type eq 'Notification') { - my $notification = decode_json_wrapper($message->{Message}) // return; + elsif ( $message_type eq 'Notification' ) { + my $notification = decode_json_wrapper( $message->{Message} ) // return; my $notification_type = $notification->{notificationType} // ''; - if ($notification_type eq 'Bounce') { + if ( $notification_type eq '' ) { + my $keys = join ', ', keys %$notification; + WARN("No notificationType in notification (keys: $keys)"); + } + if ( $notification_type eq 'Bounce' ) { process_bounce($notification); } - elsif ($notification_type eq 'Complaint') { + elsif ( $notification_type eq 'Complaint' ) { process_complaint($notification); } else { - warn "SES: Unsupported notification-type: $notification_type\n"; - respond(200 => 'OK'); + WARN("Unsupported notification-type: $notification_type"); + respond( 200 => 'OK' ); } } else { - warn "SES: Unsupported message-type: $message_type\n"; - respond(200 => 'OK'); + WARN("Unsupported message-type: $message_type"); + respond( 200 => 'OK' ); } } @@ -63,112 +69,117 @@ sub confirm_subscription { my ($message) = @_; my $subscribe_url = $message->{SubscribeURL}; - if (!$subscribe_url) { - warn "SES: Bad SubscriptionConfirmation request: missing SubscribeURL\n"; - respond(400 => 'Bad Request'); + 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 "SES: Bad response from SubscribeURL: " . $res->status_line . "\n"; - respond(400 => 'Bad Request'); + 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'); + respond( 200 => 'OK' ); } sub process_bounce { my ($notification) = @_; my $type = $notification->{bounce}->{bounceType}; - # these should be infrequent and hopefully small - warn("SES: notification: " . encode_json($notification)); + if ( $type eq 'Transient' ) { - if ($type eq 'Transient') { # just log transient bounces - foreach my $recipient (@{ $notification->{bounce}->{bouncedRecipients} }) { + foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) { my $address = $recipient->{emailAddress}; - Bugzilla->audit("SES: transient bounce for <$address>"); + Bugzilla->audit("transient bounce for <$address>"); } } - elsif ($type eq 'Permanent') { + elsif ( $type eq 'Permanent' ) { + # disable each account that is permanently bouncing - foreach my $recipient (@{ $notification->{bounce}->{bouncedRecipients} }) { + foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) { my $address = $recipient->{emailAddress}; - my $reason = sprintf('(%s) %s', $recipient->{action} // 'error', - $recipient->{diagnosticCode} // 'unknown'); + my $reason + = sprintf( '(%s) %s', $recipient->{action} // 'error', $recipient->{diagnosticCode} // 'unknown' ); - my $user = Bugzilla::User->new({ name => $address, cache => 1 }); + my $user = Bugzilla::User->new( { name => $address, cache => 1 } ); if ($user) { + # never auto-disable admin accounts - if ($user->in_group('admin')) { - Bugzilla->audit("SES: ignoring permanent bounce for admin <$address>: $reason"); + if ( $user->in_group('admin') ) { + Bugzilla->audit("ignoring permanent bounce for admin <$address>: $reason"); } else { my $template = Bugzilla->template_inner(); - my $vars = { - mta => $notification->{bounce}->{reportingMTA} // 'unknown', + my $vars = { + mta => $notification->{bounce}->{reportingMTA} // 'unknown', reason => $reason, }; my $disable_text; - $template->process('admin/users/bounce-disabled.txt.tmpl', $vars, \$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("SES: permanent bounce for <$address> disabled userid-" . $user->id . ": $reason"); + $user->set_disabledtext($disable_text); + $user->set_disable_mail(1); + $user->update(); + Bugzilla->audit( + "permanent bounce for <$address> disabled userid-" . $user->id . ": $reason" ); } } else { - Bugzilla->audit("SES: permanent bounce for <$address> has no user: $reason"); + Bugzilla->audit("permanent bounce for <$address> has no user: $reason"); } } } else { - warn "SES: Unsupported bounce type: $type\n"; + WARN("Unsupported bounce type: $type\n"); } - respond(200 => 'OK'); + respond( 200 => 'OK' ); } sub process_complaint { + # email notification to bugzilla admin my ($notification) = @_; - my $template = Bugzilla->template_inner(); - my $json = JSON::XS->new->pretty->utf8->canonical; + 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'; + foreach my $recipient ( @{ $notification->{complaint}->{complainedRecipients} } ) { + my $reason = $notification->{complaint}->{complaintFeedbackType} // 'unknown'; my $address = $recipient->{emailAddress}; - Bugzilla->audit("SES: complaint for <$address> for '$reason'"); + Bugzilla->audit("complaint for <$address> for '$reason'"); my $vars = { email => $address, - user => Bugzilla::User->new({ name => $address, cache => 1 }), + 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) + $template->process( 'email/ses-complaint.txt.tmpl', $vars, \$message ) || die $template->error(); MessageToMTA($message); } - respond(200 => 'OK'); + respond( 200 => 'OK' ); } sub respond { - my ($code, $message) = @_; - print Bugzilla->cgi->header( - -status => "$code $message", - ); + 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; } @@ -176,17 +187,17 @@ sub respond { sub decode_json_wrapper { my ($json) = @_; my $result; - if (!defined $json) { - warn 'SES: Missing JSON from ' . remote_ip() . "\n"; - respond(400 => 'Bad Request'); + if ( !defined $json ) { + WARN( 'Missing JSON from ' . remote_ip() ); + respond( 400 => 'Bad Request' ); return undef; } my $ok = try { $result = decode_json($json); } catch { - warn 'SES: Malformed JSON from ' . remote_ip() . "\n"; - respond(400 => 'Bad Request'); + WARN( 'Malformed JSON from ' . remote_ip() ); + respond( 400 => 'Bad Request' ); return undef; }; return $ok ? $result : undef; @@ -195,9 +206,9 @@ sub decode_json_wrapper { 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); + $ua->protocols_allowed( [ 'http', 'https' ] ); + if ( my $proxy_url = Bugzilla->params->{'proxy_url'} ) { + $ua->proxy( [ 'http', 'https' ], $proxy_url ); } else { $ua->env_proxy; diff --git a/template/en/default/account/auth/login.html.tmpl b/template/en/default/account/auth/login.html.tmpl index 160fad43b..8cf5e85ef 100644 --- a/template/en/default/account/auth/login.html.tmpl +++ b/template/en/default/account/auth/login.html.tmpl @@ -42,7 +42,7 @@ </p> <div id="login" class="login-form"> - <form name="login" action="[% target FILTER html %]" method="POST" + <form name="login" action="[% urlbase FILTER html %][% target FILTER uri FILTER html %]" method="POST" [%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]> <div class="field-login"> <label for="Bugzilla_login">Email Address:</label> diff --git a/template/en/default/account/prefs/mfa.html.tmpl b/template/en/default/account/prefs/mfa.html.tmpl index f75320892..973d38432 100644 --- a/template/en/default/account/prefs/mfa.html.tmpl +++ b/template/en/default/account/prefs/mfa.html.tmpl @@ -91,7 +91,7 @@ [% END %] </p> - [% IF user.mfa && user.in_group("mozilla-employee-confidential") %] + [% IF user.mfa %] <p class="mfa-disable-blurb"> You will need to disable your two-factor authentication in order to change to a different method. There will be a small amount of time when your account will not be as secure, so you may use the @@ -178,7 +178,7 @@ or <a href="https://freeotp.github.io/" target="_blank" rel="noopener noreferrer">Red Hat FreeOTP</a>), </blockquote> - [% IF Param("duo_host") && user.in_group("mozilla-employee-confidential") %] + [% IF Param("duo_host") %] <button type="button" id="mfa-select-duo">Duo Security</button><br> <blockquote> Requires a <a href="https://mana.mozilla.org/wiki/display/SD/DuoSecurity" target="_blank" rel="noopener noreferrer">Duo Security</a> @@ -245,7 +245,7 @@ </div> - [% IF Param("duo_host") && user.in_group("mozilla-employee-confidential") %] + [% IF Param("duo_host") %] [%# enable - duo %] <div id="mfa-enable-duo" style="display:none"> diff --git a/template/en/default/admin/admin.html.tmpl b/template/en/default/admin/admin.html.tmpl index 86bd8b973..62a246ceb 100644 --- a/template/en/default/admin/admin.html.tmpl +++ b/template/en/default/admin/admin.html.tmpl @@ -127,7 +127,7 @@ and time, and get the result of these queries directly per email. This is a good way to create reminders and to keep track of the activity in your installation.</dd> - [% IF Param('use_mailer_queue') %] + [% IF Bugzilla.localconfig.param_override.use_mailer_queue OR Param('use_mailer_queue') %] [% class = user.in_group('admin') ? "" : "forbidden" %] <dt id="view_job_queue" class="[% class %]"><a href="view_job_queue.cgi">Job Queue</a></dt> <dd class="[% class %]">View the queue of undelivered/deferred jobs/emails.</dd> diff --git a/template/en/default/bug/activity/table.html.tmpl b/template/en/default/bug/activity/table.html.tmpl index 50193f894..101e43546 100644 --- a/template/en/default/bug/activity/table.html.tmpl +++ b/template/en/default/bug/activity/table.html.tmpl @@ -107,6 +107,7 @@ change.fieldname == 'reporter' || change.fieldname == 'qa_contact' || change.fieldname == 'cc' || + change.fieldname == 'bug_mentor' || change.fieldname == 'flagtypes.name' %] [% display_value(change.fieldname, change_type) FILTER email FILTER html %] [% ELSE %] diff --git a/template/en/default/email/new-api-key.txt.tmpl b/template/en/default/email/new-api-key.txt.tmpl index 4a03fe800..aed904def 100644 --- a/template/en/default/email/new-api-key.txt.tmpl +++ b/template/en/default/email/new-api-key.txt.tmpl @@ -26,9 +26,17 @@ or update the key at the following URL: [%+ urlbase %]userprefs.cgi?tab=apikey +[% IF new_key.app_id == Param('mozreview_app_id') %] +This API key was automatically created by MozReview. If you did not recently log in to +MozReview, please disable the key at the above URL, and change your password immediately. +[% ELSIF new_key.app_id == Param('phabricator_app_id') %] +This API key was automatically created by Mozilla's Phabricator instance. If you did not recently +log in to Phabricator, please disable the key at the above URL, and change your password immediately. +[% ELSE %] IMPORTANT: If you did not request a new key, your [% terms.Bugzilla %] account may have been compromised. In this case, please disable the key at the above URL, and change your password immediately. +[% END %] For security reasons, we have not included your new key in this e-mail. diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl index 9de426972..5fc860519 100644 --- a/template/en/default/setup/strings.txt.pl +++ b/template/en/default/setup/strings.txt.pl @@ -269,6 +269,12 @@ END This is the max amount of unshared memory the apache process is allowed to use before Apache::SizeLimit kills it. This is only applicable when run under mod_perl. EOT + localconfig_shadowdb_user => <<EOT, +The username used to authenticate to the shadow db. +EOT + localconfig_shadowdb_pass => <<EOT, +The password used to authenticate to the shadow db. +EOT max_allowed_packet => <<EOT, WARNING: You need to set the max_allowed_packet parameter in your MySQL configuration to at least ##needed##. Currently it is set to ##current##. diff --git a/vagrant_support/bmo-generate-data.j2 b/vagrant_support/bmo-generate-data.j2 index 79798ba4d..6ad9303d6 100644 --- a/vagrant_support/bmo-generate-data.j2 +++ b/vagrant_support/bmo-generate-data.j2 @@ -2,3 +2,4 @@ cd /vagrant perl scripts/generate_bmo_data.pl 'vagrant@{{ WEB_HOSTNAME }}' +perl scripts/update_params.pl 'mail_delivery_method' 'Sendmail' diff --git a/vagrant_support/checksetup_answers.j2 b/vagrant_support/checksetup_answers.j2 index 683a28a6f..441a4d41d 100644 --- a/vagrant_support/checksetup_answers.j2 +++ b/vagrant_support/checksetup_answers.j2 @@ -23,6 +23,7 @@ $answer{'user_verify_class'} = 'GitHubAuth,DB'; $answer{'urlbase'} = "http://{{WEB_HOSTNAME}}/"; $answer{'memcached_namespace'} = 'bmo:'; $answer{'memcached_servers'} = '127.0.0.1:11211'; +$answer{'mail_delivery_method'} = 'Sendmail'; $answer{'use_mailer_queue'} = 1; $answer{'useclassification'} = 1; $answer{'usebugaliases'} = 1; |