diff options
Diffstat (limited to 'Bugzilla')
-rw-r--r-- | Bugzilla/Config/General.pm | 8 | ||||
-rw-r--r-- | Bugzilla/Constants.pm | 4 | ||||
-rw-r--r-- | Bugzilla/Elastic/Role/HasClient.pm | 2 | ||||
-rw-r--r-- | Bugzilla/Error/disabled | 0 | ||||
-rw-r--r-- | Bugzilla/Group.pm | 29 | ||||
-rw-r--r-- | Bugzilla/Install.pm | 40 | ||||
-rw-r--r-- | Bugzilla/Install/DB.pm | 11 | ||||
-rw-r--r-- | Bugzilla/Install/Filesystem.pm | 8 | ||||
-rw-r--r-- | Bugzilla/PSGI.pm | 42 | ||||
-rw-r--r-- | Bugzilla/Quantum/Plugin/BasicAuth.pm | 40 | ||||
-rw-r--r-- | Bugzilla/Quantum/Plugin/Hostage.pm | 113 | ||||
-rw-r--r-- | Bugzilla/Quantum/SES.pm | 414 | ||||
-rw-r--r-- | Bugzilla/Search.pm | 26 | ||||
-rw-r--r-- | Bugzilla/Template.pm | 2 |
14 files changed, 423 insertions, 316 deletions
diff --git a/Bugzilla/Config/General.pm b/Bugzilla/Config/General.pm index c870c7376..fa7cf2d08 100644 --- a/Bugzilla/Config/General.pm +++ b/Bugzilla/Config/General.pm @@ -25,6 +25,14 @@ use constant get_param_list => ( }, { + name => 'nobody_user', + type => 't', + no_reset => '1', + default => 'nobody@mozilla.org', + checker => \&check_email + }, + + { name => 'docs_urlbase', type => 't', default => 'docs/%lang%/html/', diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index d0b74b5e3..cd478c33e 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -196,6 +196,8 @@ use Memoize; EMAIL_LIMIT_EXCEPTION JOB_QUEUE_VIEW_MAX_JOBS + + BZ_PERSISTENT ); @Bugzilla::Constants::EXPORT_OK = qw(contenttypes); @@ -700,6 +702,8 @@ sub _bz_locations { }; } +use constant BZ_PERSISTENT => $main::BUGZILLA_PERSISTENT; + # This makes us not re-compute all the bz_locations data every time it's # called. BEGIN { memoize('_bz_locations') }; diff --git a/Bugzilla/Elastic/Role/HasClient.pm b/Bugzilla/Elastic/Role/HasClient.pm index 8e2687880..a971392e0 100644 --- a/Bugzilla/Elastic/Role/HasClient.pm +++ b/Bugzilla/Elastic/Role/HasClient.pm @@ -8,7 +8,6 @@ package Bugzilla::Elastic::Role::HasClient; use 5.10.1; use Moo::Role; -use Search::Elasticsearch; has 'client' => (is => 'lazy'); @@ -16,6 +15,7 @@ has 'client' => (is => 'lazy'); sub _build_client { my ($self) = @_; + require Search::Elasticsearch; return Search::Elasticsearch->new( nodes => [ split(/\s+/, Bugzilla->params->{elasticsearch_nodes}) ], cxn_pool => 'Sniff', diff --git a/Bugzilla/Error/disabled b/Bugzilla/Error/disabled new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/Bugzilla/Error/disabled diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm index c941482f0..7f684ea15 100644 --- a/Bugzilla/Group.pm +++ b/Bugzilla/Group.pm @@ -26,17 +26,24 @@ use Scalar::Util qw(blessed); use constant IS_CONFIG => 1; -use constant DB_COLUMNS => qw( - groups.id - groups.name - groups.description - groups.isbuggroup - groups.userregexp - groups.isactive - groups.icon_url - groups.owner_user_id - groups.idle_member_removal -); +sub DB_COLUMNS { + my $class = shift; + my @columns = qw( + id + name + description + isbuggroup + userregexp + isactive + icon_url + owner_user_id + idle_member_removal + ); + my $dbh = Bugzilla->dbh; + my $table = $class->DB_TABLE; + + return map { "$table.$_" } grep { $dbh->bz_column_info($table, $_) } @columns; +} use constant DB_TABLE => 'groups'; diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm index 8bce9b5e7..c9935f34d 100644 --- a/Bugzilla/Install.pm +++ b/Bugzilla/Install.pm @@ -31,21 +31,31 @@ use Bugzilla::Util qw(get_text); use Bugzilla::Version; use constant STATUS_WORKFLOW => ( - [undef, 'UNCONFIRMED'], - [undef, 'CONFIRMED'], - [undef, 'IN_PROGRESS'], - ['UNCONFIRMED', 'CONFIRMED'], - ['UNCONFIRMED', 'IN_PROGRESS'], - ['UNCONFIRMED', 'RESOLVED'], - ['CONFIRMED', 'IN_PROGRESS'], - ['CONFIRMED', 'RESOLVED'], - ['IN_PROGRESS', 'CONFIRMED'], - ['IN_PROGRESS', 'RESOLVED'], - ['RESOLVED', 'UNCONFIRMED'], - ['RESOLVED', 'CONFIRMED'], - ['RESOLVED', 'VERIFIED'], - ['VERIFIED', 'UNCONFIRMED'], - ['VERIFIED', 'CONFIRMED'], + [ undef, 'UNCONFIRMED' ], + [ undef, 'NEW' ], + [ undef, 'ASSIGNED' ], + [ 'UNCONFIRMED', 'NEW' ], + [ 'UNCONFIRMED', 'ASSIGNED' ], + [ 'UNCONFIRMED', 'RESOLVED' ], + [ 'NEW', 'UNCONFIRMED' ], + [ 'NEW', 'ASSIGNED' ], + [ 'NEW', 'RESOLVED' ], + [ 'ASSIGNED', 'UNCONFIRMED' ], + [ 'ASSIGNED', 'NEW' ], + [ 'ASSIGNED', 'RESOLVED' ], + [ 'REOPENED', 'UNCONFIRMED' ], + [ 'REOPENED', 'NEW' ], + [ 'REOPENED', 'ASSIGNED' ], + [ 'REOPENED', 'RESOLVED' ], + [ 'RESOLVED', 'UNCONFIRMED' ], + [ 'RESOLVED', 'REOPENED' ], + [ 'RESOLVED', 'VERIFIED' ], + [ 'VERIFIED', 'UNCONFIRMED' ], + [ 'VERIFIED', 'REOPENED' ], + [ 'VERIFIED', 'RESOLVED' ], + [ 'CLOSED', 'UNCONFIRMED' ], + [ 'CLOSED', 'REOPENED' ], + [ 'CLOSED', 'RESOLVED' ], ); sub SETTINGS { diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 2e5ae5ff2..8b3d4b8cc 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -3908,7 +3908,16 @@ sub _migrate_group_owners { my $dbh = Bugzilla->dbh; return if $dbh->bz_column_info('groups', 'owner_user_id'); $dbh->bz_add_column('groups', 'owner_user_id', {TYPE => 'INT3'}); - my $nobody = Bugzilla::User->check('nobody@mozilla.org'); + my $nobody = Bugzilla::User->new({ name => Bugzilla->params->{'nobody_user'}, cache => 1 }); + unless ($nobody) { + $nobody = Bugzilla::User->create( + { + login_name => Bugzilla->params->{'nobody_user'}, + realname => 'Nobody (ok to assign bugs to)', + cryptpassword => '*', + } + ); + } $dbh->do('UPDATE groups SET owner_user_id = ?', undef, $nobody->id); } diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 317152962..cb1b1ad15 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -164,6 +164,7 @@ sub FILESYSTEM { # users to be able to cron them or otherwise run # them as a secure user, like the webserver owner. '*.cgi' => { perms => WS_EXECUTE }, + '*.psgi' => { perms => CGI_READ }, 'whineatnews.pl' => { perms => WS_EXECUTE }, 'collectstats.pl' => { perms => WS_EXECUTE }, 'importxml.pl' => { perms => WS_EXECUTE }, @@ -295,7 +296,7 @@ sub FILESYSTEM { 'contrib' => { files => OWNER_EXECUTE, dirs => DIR_OWNER_WRITE, }, 'scripts' => { files => OWNER_EXECUTE, - dirs => DIR_OWNER_WRITE, }, + dirs => DIR_WS_SERVE, }, ); # --- FILES TO CREATE --- # @@ -379,6 +380,9 @@ sub FILESYSTEM { contents => $yui3_all_css }, ); + # Create static error pages. + $create_dirs{"errors"} = DIR_CGI_READ; + # Because checksetup controls the creation of index.html separately # from all other files, it gets its very own hash. my %index_html = ( @@ -464,7 +468,7 @@ sub update_filesystem { # Delete old files that no longer need to exist # 2001-04-29 jake@bugzilla.org - Remove oldemailtech - # http://bugzilla.mozilla.org/show_bugs.cgi?id=71552 + # http://bugzilla.mozilla.org/show_bug.cgi?id=71552 if (-d 'shadow') { print "Removing shadow directory...\n"; rmtree("shadow"); diff --git a/Bugzilla/PSGI.pm b/Bugzilla/PSGI.pm new file mode 100644 index 000000000..46352b319 --- /dev/null +++ b/Bugzilla/PSGI.pm @@ -0,0 +1,42 @@ +# 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::PSGI; +use 5.10.1; +use strict; +use warnings; + +use base qw(Exporter); + +use Bugzilla::Logging; +our @EXPORT_OK = qw(compile_cgi); + +sub compile_cgi { + my ($script) = @_; + require CGI::Compile; + require CGI::Emulate::PSGI; + + my $cgi = CGI::Compile->compile($script); + my $app = CGI::Emulate::PSGI->handler( + sub { + Bugzilla::init_page(); + $cgi->(); + } + ); + return sub { + my $env = shift; + if ($env->{'psgix.cleanup'}) { + push @{ $env->{'psgix.cleanup.handler'} }, \&Bugzilla::_cleanup; + } + my $res = $app->($env); + Bugzilla::_cleanup() if not $env->{'psgix.cleanup'}; + return $res; + }; +} + + +1;
\ No newline at end of file diff --git a/Bugzilla/Quantum/Plugin/BasicAuth.pm b/Bugzilla/Quantum/Plugin/BasicAuth.pm new file mode 100644 index 000000000..e17273404 --- /dev/null +++ b/Bugzilla/Quantum/Plugin/BasicAuth.pm @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. +package Bugzilla::Quantum::Plugin::BasicAuth; +use 5.10.1; +use Mojo::Base qw(Mojolicious::Plugin); + +use Bugzilla::Logging; +use Carp; + +sub register { + my ( $self, $app, $conf ) = @_; + + $app->renderer->add_helper( + basic_auth => sub { + my ( $c, $realm, $auth_user, $auth_pass ) = @_; + my $req = $c->req; + my ( $user, $password ) = $req->url->to_abs->userinfo =~ /^([^:]+):(.*)/; + + unless ( $realm && $auth_user && $auth_pass ) { + croak 'basic_auth() called with missing parameters.'; + } + + unless ( $user eq $auth_user && $password eq $auth_pass ) { + WARN('username and password do not match'); + $c->res->headers->www_authenticate("Basic realm=\"$realm\""); + $c->res->code(401); + $c->rendered; + return 0; + } + + return 1; + } + ); +} + +1;
\ No newline at end of file diff --git a/Bugzilla/Quantum/Plugin/Hostage.pm b/Bugzilla/Quantum/Plugin/Hostage.pm index 63fad2be2..418b09a0c 100644 --- a/Bugzilla/Quantum/Plugin/Hostage.pm +++ b/Bugzilla/Quantum/Plugin/Hostage.pm @@ -1,85 +1,80 @@ package Bugzilla::Quantum::Plugin::Hostage; use 5.10.1; use Mojo::Base 'Mojolicious::Plugin'; -use Bugzilla::Logging; sub _attachment_root { - my ($base) = @_; - return undef unless $base; - return $base =~ m{^https?://(?:bug)?\%bugid\%\.([a-zA-Z\.-]+)} ? $1 : undef; + 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; + my ($base) = @_; + return undef unless $base; + my $val = $base; + $val =~ s{^https?://}{}s; + $val =~ s{/$}{}s; + my $regex = quotemeta $val; + $regex =~ s/\\\%bugid\\\%/\\d+/g; + return qr/^$regex$/s; } sub register { - my ($self, $app, $conf) = @_; + my ( $self, $app, $conf ) = @_; - $app->hook(before_routes => \&_before_routes); + $app->hook(before_routes => \&_before_routes); } sub _before_routes { - my ($c) = @_; - state $urlbase = Bugzilla->localconfig->{urlbase}; - state $urlbase_uri = URI->new($urlbase); - state $urlbase_host = $urlbase_uri->host; - state $urlbase_host_regex = qr/^bug(\d+)\.\Q$urlbase_host\E$/; - state $attachment_base = Bugzilla->localconfig->{attachment_base}; - state $attachment_root = _attachment_root($attachment_base); - state $attachment_host_regex = _attachment_host_regex($attachment_base); + my ( $c ) = @_; + state $urlbase = Bugzilla->localconfig->{urlbase}; + state $urlbase_uri = URI->new($urlbase); + state $urlbase_host = $urlbase_uri->host; + state $urlbase_host_regex = qr/^bug(\d+)\.\Q$urlbase_host\E$/; + state $attachment_base = Bugzilla->localconfig->{attachment_base}; + state $attachment_root = _attachment_root($attachment_base); + state $attachment_host_regex = _attachment_host_regex($attachment_base); - my $stash = $c->stash; - my $req = $c->req; - my $url = $req->url->to_abs; + my $stash = $c->stash; + my $req = $c->req; + my $url = $req->url->to_abs; - return if $stash->{'mojo.static'}; + return if $stash->{'mojo.static'}; - my $hostname = $url->host; - return if $hostname eq $urlbase_host; + my $hostname = $url->host; + return if $hostname eq $urlbase_host; - my $path = $url->path; - return if $path eq '/__lbheartbeat__'; + my $path = $url->path; + return if $path eq '/__lbheartbeat__'; - if ($attachment_base && $hostname eq $attachment_root) { - DEBUG("redirecting to $urlbase because $hostname is $attachment_root"); - $c->redirect_to($urlbase); - return; - } - elsif ($attachment_base && $hostname =~ $attachment_host_regex) { - if ($path =~ m{^/attachment\.cgi}s) { - return; + if ($attachment_base && $hostname eq $attachment_root) { + $c->redirect_to($urlbase); + return; + } + elsif ($attachment_base && $hostname =~ $attachment_host_regex) { + if ($path =~ m{^/attachment\.cgi}s) { + return; + } else { + my $new_uri = $url->clone; + $new_uri->scheme($urlbase_uri->scheme); + $new_uri->host($urlbase_host); + $c->redirect_to($new_uri); + return; + } + } + elsif (my ($id) = $hostname =~ $urlbase_host_regex) { + my $new_uri = $urlbase_uri->clone; + $new_uri->path('/show_bug.cgi'); + $new_uri->query_form(id => $id); + $c->redirect_to($new_uri); + return; } else { - my $new_uri = $url->clone; - $new_uri->scheme($urlbase_uri->scheme); - $new_uri->host($urlbase_host); - DEBUG( - "redirecting to $new_uri because $hostname matches attachment regex"); - $c->redirect_to($new_uri); - return; + $c->redirect_to($urlbase); + return; } - } - elsif (my ($id) = $hostname =~ $urlbase_host_regex) { - my $new_uri = $urlbase_uri->clone; - $new_uri->path('/show_bug.cgi'); - $new_uri->query_form(id => $id); - DEBUG("redirecting to $new_uri because $hostname includes bug id"); - $c->redirect_to($new_uri); - return; - } - else { - DEBUG("redirecting to $urlbase because $hostname doesn't make sense"); - $c->redirect_to($urlbase); - return; - } } 1; diff --git a/Bugzilla/Quantum/SES.pm b/Bugzilla/Quantum/SES.pm index 2d19de240..03916075d 100644 --- a/Bugzilla/Quantum/SES.pm +++ b/Bugzilla/Quantum/SES.pm @@ -1,5 +1,4 @@ package Bugzilla::Quantum::SES; - # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -19,252 +18,237 @@ use JSON::MaybeXS qw(decode_json); use LWP::UserAgent (); use Try::Tiny qw(catch try); -use Type::Library -base, -declare => qw( - Self - Notification NotificationType TypeField - BounceNotification BouncedRecipients - ComplaintNotification ComplainedRecipients -); -use Type::Utils -all; -use Types::Standard -all; -use Type::Params qw(compile); - -class_type Self, {class => __PACKAGE__}; - -declare ComplainedRecipients, - as ArrayRef [Dict [emailAddress => Str, slurpy Any]]; -declare ComplaintNotification, - as Dict [ - complaint => Dict [ - complainedRecipients => ComplainedRecipients, - complaintFeedbackType => Str, - slurpy Any, - ], - slurpy Any, - ]; - -declare BouncedRecipients, - as ArrayRef [ - Dict [ - emailAddress => Str, - action => Optional [Str], - diagnosticCode => Optional [Str], - status => Optional [Str], - slurpy Any, - ], - ]; -declare BounceNotification, - as Dict [ - bounce => Dict [ - bouncedRecipients => BouncedRecipients, - reportingMTA => Str, - bounceSubType => Str, - bounceType => Str, - slurpy Any, - ], - slurpy Any, - ]; - -declare NotificationType, as Enum [qw( Bounce Complaint )]; -declare TypeField, as Enum [qw(eventType notificationType)]; -declare Notification, - as Dict [ - eventType => Optional [NotificationType], - notificationType => Optional [NotificationType], - slurpy Any, - ]; +use Types::Standard qw( :all ); +use Type::Utils; +use Type::Params qw( compile ); + +my $Invocant = class_type { class => __PACKAGE__ }; sub main { - my ($self) = @_; - try { - $self->_main; - } - catch { - FATAL("Error in SES Handler: ", $_); - $self->_respond(400 => 'Bad Request'); - }; + my ($self) = @_; + try { + $self->_main; + } + catch { + FATAL("Error in SES Handler: ", $_); + $self->_respond( 400 => 'Bad Request' ); + }; } sub _main { - my ($self) = @_; - Bugzilla->error_mode(ERROR_MODE_DIE); - my $message = $self->_decode_json_wrapper($self->req->body) // return; - my $message_type = $self->req->headers->header('X-Amz-SNS-Message-Type') - // '(missing)'; - - if ($message_type eq 'SubscriptionConfirmation') { - $self->_confirm_subscription($message); - } - - elsif ($message_type eq 'Notification') { - my $notification = $self->_decode_json_wrapper($message->{Message}) - // return; - unless ( -# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html - $self->_handle_notification($notification, 'eventType') - -# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html - || $self->_handle_notification($notification, 'notificationType') - ) - { - WARN('Failed to find notification type'); - $self->_respond(400 => 'Bad Request'); + my ($self) = @_; + Bugzilla->error_mode(ERROR_MODE_DIE); + my $message = $self->_decode_json_wrapper( $self->req->body ) // return; + my $message_type = $self->req->headers->header('X-Amz-SNS-Message-Type') // '(missing)'; + + if ( $message_type eq 'SubscriptionConfirmation' ) { + $self->_confirm_subscription($message); + } + + elsif ( $message_type eq 'Notification' ) { + my $notification = $self->_decode_json_wrapper( $message->{Message} ) // return; + unless ( + # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html + $self->_handle_notification( $notification, 'eventType' ) + + # https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html + || $self->_handle_notification( $notification, 'notificationType' ) + ) + { + WARN('Failed to find notification type'); + $self->_respond( 400 => 'Bad Request' ); + } } - } - else { - WARN("Unsupported message-type: $message_type"); - $self->_respond(200 => 'OK'); - } + else { + WARN("Unsupported message-type: $message_type"); + $self->_respond( 200 => 'OK' ); + } } sub _confirm_subscription { - state $check = compile(Self, Dict [SubscribeURL => Str, slurpy Any]); - my ($self, $message) = $check->(@_); - - my $subscribe_url = $message->{SubscribeURL}; - if (!$subscribe_url) { - WARN('Bad SubscriptionConfirmation request: missing SubscribeURL'); - $self->_respond(400 => 'Bad Request'); - return; - } - - my $ua = ua(); - my $res = $ua->get($message->{SubscribeURL}); - if (!$res->is_success) { - WARN('Bad response from SubscribeURL: ' . $res->status_line); - $self->_respond(400 => 'Bad Request'); - return; - } - - $self->_respond(200 => 'OK'); + state $check = compile($Invocant, Dict[SubscribeURL => Str, slurpy Any]); + my ($self, $message) = $check->(@_); + + my $subscribe_url = $message->{SubscribeURL}; + if ( !$subscribe_url ) { + WARN('Bad SubscriptionConfirmation request: missing SubscribeURL'); + $self->_respond( 400 => 'Bad Request' ); + return; + } + + my $ua = ua(); + my $res = $ua->get( $message->{SubscribeURL} ); + if ( !$res->is_success ) { + WARN( 'Bad response from SubscribeURL: ' . $res->status_line ); + $self->_respond( 400 => 'Bad Request' ); + return; + } + + $self->_respond( 200 => 'OK' ); } +my $NotificationType = Enum [qw( Bounce Complaint )]; +my $TypeField = Enum [qw(eventType notificationType)]; +my $Notification = Dict [ + eventType => Optional [$NotificationType], + notificationType => Optional [$NotificationType], + slurpy Any, +]; + sub _handle_notification { - state $check = compile(Self, Notification, TypeField); - my ($self, $notification, $type_field) = $check->(@_); - - if (!exists $notification->{$type_field}) { - return 0; - } - my $type = $notification->{$type_field}; - - if ($type eq 'Bounce') { - $self->_process_bounce($notification); - } - elsif ($type eq 'Complaint') { - $self->_process_complaint($notification); - } - else { - WARN("Unsupported notification-type: $type"); - $self->_respond(200 => 'OK'); - } - return 1; -} + state $check = compile($Invocant, $Notification, $TypeField ); + my ( $self, $notification, $type_field ) = $check->(@_); -sub _process_bounce { - state $check = compile(Self, BounceNotification); - my ($self, $notification) = $check->(@_); - - # disable each account that is bouncing - foreach my $recipient (@{$notification->{bounce}->{bouncedRecipients}}) { - my $address = $recipient->{emailAddress}; - my $reason = sprintf '(%s) %s', $recipient->{action} // 'error', - $recipient->{diagnosticCode} // 'unknown'; - - my $user = Bugzilla::User->new({name => $address, cache => 1}); - if ($user) { - - # never auto-disable admin accounts - if ($user->in_group('admin')) { - Bugzilla->audit("ignoring bounce for admin <$address>: $reason"); - } - - else { - my $template = Bugzilla->template_inner(); - my $vars = { - mta => $notification->{bounce}->{reportingMTA} // 'unknown', - reason => $reason, - }; - my $disable_text; - $template->process('admin/users/bounce-disabled.txt.tmpl', - $vars, \$disable_text) - || die $template->error(); - - $user->set_disabledtext($disable_text); - $user->set_disable_mail(1); - $user->update(); - Bugzilla->audit( - "bounce for <$address> disabled userid-" . $user->id . ": $reason"); - } + if ( !exists $notification->{$type_field} ) { + return 0; } + my $type = $notification->{$type_field}; + if ( $type eq 'Bounce' ) { + $self->_process_bounce($notification); + } + elsif ( $type eq 'Complaint' ) { + $self->_process_complaint($notification); + } else { - Bugzilla->audit("bounce for <$address> has no user: $reason"); + WARN("Unsupported notification-type: $type"); + $self->_respond( 200 => 'OK' ); + } + return 1; +} + +my $BouncedRecipients = ArrayRef[ + Dict[ + emailAddress => Str, + action => Str, + diagnosticCode => Str, + slurpy Any, + ], +]; +my $BounceNotification = Dict [ + bounce => Dict [ + bouncedRecipients => $BouncedRecipients, + reportingMTA => Str, + bounceSubType => Str, + bounceType => Str, + slurpy Any, + ], + slurpy Any, +]; + +sub _process_bounce { + state $check = compile($Invocant, $BounceNotification); + my ($self, $notification) = $check->(@_); + + # disable each account that is bouncing + foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) { + my $address = $recipient->{emailAddress}; + my $reason = sprintf '(%s) %s', $recipient->{action} // 'error', $recipient->{diagnosticCode} // 'unknown'; + + my $user = Bugzilla::User->new( { name => $address, cache => 1 } ); + if ($user) { + + # never auto-disable admin accounts + if ( $user->in_group('admin') ) { + Bugzilla->audit("ignoring bounce for admin <$address>: $reason"); + } + + else { + my $template = Bugzilla->template_inner(); + my $vars = { + mta => $notification->{bounce}->{reportingMTA} // 'unknown', + reason => $reason, + }; + my $disable_text; + $template->process( 'admin/users/bounce-disabled.txt.tmpl', $vars, \$disable_text ) + || die $template->error(); + + $user->set_disabledtext($disable_text); + $user->set_disable_mail(1); + $user->update(); + Bugzilla->audit( "bounce for <$address> disabled userid-" . $user->id . ": $reason" ); + } + } + + else { + Bugzilla->audit("bounce for <$address> has no user: $reason"); + } } - } - $self->_respond(200 => 'OK'); + $self->_respond( 200 => 'OK' ); } +my $ComplainedRecipients = ArrayRef[Dict[ emailAddress => Str, slurpy Any ]]; +my $ComplaintNotification = Dict[ + complaint => Dict [ + complainedRecipients => $ComplainedRecipients, + complaintFeedbackType => Str, + slurpy Any, + ], + slurpy Any, +]; + sub _process_complaint { - state $check = compile(Self, ComplaintNotification); - my ($self, $notification) = $check->(@_); - my $template = Bugzilla->template_inner(); - my $json = JSON::MaybeXS->new(pretty => 1, utf8 => 1, canonical => 1,); - - foreach my $recipient (@{$notification->{complaint}->{complainedRecipients}}) - { - my $reason = $notification->{complaint}->{complaintFeedbackType} - // 'unknown'; - my $address = $recipient->{emailAddress}; - Bugzilla->audit("complaint for <$address> for '$reason'"); - my $vars = { - email => $address, - user => Bugzilla::User->new({name => $address, cache => 1}), - reason => $reason, - notification => $json->encode($notification), - }; - my $message; - $template->process('email/ses-complaint.txt.tmpl', $vars, \$message) - || die $template->error(); - MessageToMTA($message); - } + state $check = compile($Invocant, $ComplaintNotification); + my ($self, $notification) = $check->(@_); + my $template = Bugzilla->template_inner(); + my $json = JSON::MaybeXS->new( + pretty => 1, + utf8 => 1, + canonical => 1, + ); + + foreach my $recipient ( @{ $notification->{complaint}->{complainedRecipients} } ) { + my $reason = $notification->{complaint}->{complaintFeedbackType} // 'unknown'; + my $address = $recipient->{emailAddress}; + Bugzilla->audit("complaint for <$address> for '$reason'"); + my $vars = { + email => $address, + user => Bugzilla::User->new( { name => $address, cache => 1 } ), + reason => $reason, + notification => $json->encode($notification), + }; + my $message; + $template->process( 'email/ses-complaint.txt.tmpl', $vars, \$message ) + || die $template->error(); + MessageToMTA($message); + } - $self->_respond(200 => 'OK'); + $self->_respond( 200 => 'OK' ); } sub _respond { - my ($self, $code, $message) = @_; - $self->render(text => "$message\n", status => $code); + my ( $self, $code, $message ) = @_; + $self->render(text => "$message\n", status => $code); } sub _decode_json_wrapper { - state $check = compile(Self, Str); - my ($self, $json) = $check->(@_); - my $result; - my $ok = try { - $result = decode_json($json); - } - catch { - WARN('Malformed JSON from ' . $self->tx->remote_address); - $self->_respond(400 => 'Bad Request'); - return undef; - }; - return $ok ? $result : undef; + state $check = compile($Invocant, Str); + my ($self, $json) = $check->(@_); + my $result; + my $ok = try { + $result = decode_json($json); + } + catch { + WARN( 'Malformed JSON from ' . $self->tx->remote_address ); + $self->_respond( 400 => 'Bad Request' ); + return undef; + }; + return $ok ? $result : undef; } sub ua { - my $ua = LWP::UserAgent->new(); - $ua->timeout(10); - $ua->protocols_allowed(['http', 'https']); - if (my $proxy_url = Bugzilla->params->{'proxy_url'}) { - $ua->proxy(['http', 'https'], $proxy_url); - } - else { - $ua->env_proxy; - } - return $ua; + my $ua = LWP::UserAgent->new(); + $ua->timeout(10); + $ua->protocols_allowed( [ 'http', 'https' ] ); + if ( my $proxy_url = Bugzilla->params->{'proxy_url'} ) { + $ua->proxy( [ 'http', 'https' ], $proxy_url ); + } + else { + $ua->env_proxy; + } + return $ua; } 1; diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index f419955dc..e15c60f7f 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -802,18 +802,20 @@ sub data { # BMO - to avoid massive amounts of joins, if we're selecting a lot of # tracking flags, replace them with placeholders. the values will be # retrieved later and injected into the result. - my %tf_map = map { $_ => 1 } Bugzilla::Extension::TrackingFlags::Flag->get_all_names(); - my @tf_selected = grep { exists $tf_map{$_} } @orig_fields; - # mysql has a limit of 61 joins, and we want to avoid massive amounts of joins - # 30 ensures we won't hit the limit, nor generate too many joins - if (scalar @tf_selected > 30) { - foreach my $column (@tf_selected) { - $self->COLUMNS->{$column}->{name} = "'---'"; + if (Bugzilla->has_extension('TrackingFlags')) { + my %tf_map = map { $_ => 1 } Bugzilla::Extension::TrackingFlags::Flag->get_all_names(); + my @tf_selected = grep { exists $tf_map{$_} } @orig_fields; + # mysql has a limit of 61 joins, and we want to avoid massive amounts of joins + # 30 ensures we won't hit the limit, nor generate too many joins + if (scalar @tf_selected > 30) { + foreach my $column (@tf_selected) { + $self->COLUMNS->{$column}->{name} = "'---'"; + } + $self->{tracking_flags} = \@tf_selected; + } + else { + $self->{tracking_flags} = []; } - $self->{tracking_flags} = \@tf_selected; - } - else { - $self->{tracking_flags} = []; } my $start_time = [gettimeofday()]; @@ -863,7 +865,7 @@ sub data { $self->{data} = [map { $data{$_} } @$bug_ids]; # BMO - get tracking flags values, and insert into result - if (@{ $self->{tracking_flags} }) { + if (Bugzilla->has_extension('TrackingFlags') && @{ $self->{tracking_flags} }) { # read values my $values; $sql = " diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index c337b5af8..36435d637 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -995,6 +995,8 @@ sub create { 'feature_enabled' => sub { return Bugzilla->feature(@_); }, + 'has_extension' => sub { return Bugzilla->has_extension(@_); }, + # field_descs can be somewhat slow to generate, so we generate # it only once per-language no matter how many times # $template->process() is called. |