summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--extensions/MozReview/Extension.pm26
-rw-r--r--extensions/MozReview/lib/WebService.pm192
-rw-r--r--extensions/MozReview/template/en/default/hook/global/user-error-errors.html.tmpl4
3 files changed, 214 insertions, 8 deletions
diff --git a/extensions/MozReview/Extension.pm b/extensions/MozReview/Extension.pm
index 621e81f43..e523bc2d8 100644
--- a/extensions/MozReview/Extension.pm
+++ b/extensions/MozReview/Extension.pm
@@ -14,6 +14,7 @@ use parent qw(Bugzilla::Extension);
use Bugzilla::Attachment;
use Bugzilla::Error;
+use Bugzilla::Extension::MozReview::WebService;
use List::MoreUtils qw( any );
our $VERSION = '0.01';
@@ -83,22 +84,31 @@ sub config_add_panels {
$modules->{MozReview} = "Bugzilla::Extension::MozReview::Config";
}
+sub webservice {
+ my ($self, $args) = @_;
+ my $dispatch = $args->{dispatch};
+ $dispatch->{MozReview} = "Bugzilla::Extension::MozReview::WebService";
+}
+
sub webservice_before_call {
my ($self, $args) = @_;
my ($method, $full_method) = ($args->{method}, $args->{full_method});
- my $mozreview_app_id = Bugzilla->params->{mozreview_app_id};
+ my $mozreview_app_id = Bugzilla->params->{mozreview_app_id} // '';
my $user = Bugzilla->user;
+ my $getter = eval { $user->authorizer->successful_info_getter() } or return;
+ my $app_id = $getter->can("app_id") ? $getter->app_id // '' : '';
- return unless $mozreview_app_id;
- return unless $user->authorizer;
+ $full_method =~ s/^Bugzilla::Extension::(\w+)::WebService\./$1./;
- my $getter = $user->authorizer->successful_info_getter()
- or return;
+ my $is_mozreview_method = $full_method =~ /^MozReview\./;
+
+ if ($is_mozreview_method && (!$mozreview_app_id || !$app_id || $mozreview_app_id ne $app_id)) {
+ ThrowUserError('forbidden_method', { method => $full_method });
+ }
- return unless $getter->can("app_id") && $getter->app_id;
+ return unless $mozreview_app_id && $app_id;
- my $app_id = $getter->app_id;
- if ($app_id eq $mozreview_app_id) {
+ if ($app_id eq $mozreview_app_id && !$is_mozreview_method) {
unless (any { $full_method eq $_ } @METHOD_WHITELIST) {
ThrowUserError('forbidden_method', { method => $full_method });
}
diff --git a/extensions/MozReview/lib/WebService.pm b/extensions/MozReview/lib/WebService.pm
new file mode 100644
index 000000000..d814e05d6
--- /dev/null
+++ b/extensions/MozReview/lib/WebService.pm
@@ -0,0 +1,192 @@
+# 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::MozReview::WebService;
+
+use strict;
+use warnings;
+
+use base qw(Bugzilla::WebService);
+
+use Bugzilla::Attachment;
+use Bugzilla::Bug;
+use Bugzilla::Comment;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants;
+use Bugzilla::WebService::Util qw(extract_flags validate translate);
+use Bugzilla::Util qw(trim);
+
+use List::MoreUtils qw(uniq);
+use List::Util qw(max);
+use Storable qw(dclone);
+
+use constant PUBLIC_METHODS => qw( attachments );
+
+BEGIN {
+ *_attachment_to_hash = \&Bugzilla::WebService::Bug::_attachment_to_hash;
+}
+
+sub attachments {
+ my ($self, $params) = validate(@_, 'attachments');
+ my $dbh = Bugzilla->dbh;
+
+ # BMO: Don't allow updating of bugs if disabled
+ if (Bugzilla->params->{disable_bug_updates}) {
+ ThrowErrorPage('bug/process/updates-disabled.html.tmpl',
+ 'Bug updates are currently disabled.');
+ }
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ ThrowCodeError('param_required', { param => 'attachments' })
+ unless defined $params->{attachments};
+
+ my $bug = Bugzilla::Bug->check($params->{bug_id});
+
+ ThrowUserError("product_edit_denied", { product => $bug->product })
+ unless $user->can_edit_product($bug->product_id);
+
+ my (@modified, @created);
+ $dbh->bz_start_transaction();
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my $comment_tags = $params->{comment_tags};
+ my $attachments = $params->{attachments};
+
+ if ($comment_tags) {
+ ThrowUserError('comment_tag_disabled')
+ unless Bugzilla->params->{comment_taggers_group};
+ ThrowUserError('auth_failure',
+ { group => Bugzilla->params->{comment_taggers_group},
+ action => 'update',
+ object => 'comment_tags' })
+ unless $user->can_tag_comments;
+ $bug->set_all({ comment_tags => $comment_tags });
+ }
+
+ foreach my $attachment (@$attachments) {
+ my $flags = delete $attachment->{flags};
+ my $attachment_id = delete $attachment->{attachment_id};
+ my $comment = delete $attachment->{comment};
+ my $attachment_obj;
+
+ if ($attachment_id) {
+ $attachment_obj = Bugzilla::Attachment->check({ id => $attachment_id });
+ ThrowUserError("mozreview_attachment_bug_mismatch", { bug => $bug, attachment => $attachment_obj })
+ if $attachment_obj->bug_id != $bug->id;
+
+ $attachment = translate($attachment, Bugzilla::WebService::Bug::ATTACHMENT_MAPPED_SETTERS);
+
+ my ($update_flags, $new_flags) = $flags
+ ? extract_flags($flags, $bug, $attachment_obj)
+ : ([], []);
+ if ($attachment_obj->validate_can_edit) {
+ $attachment_obj->set_all($attachment);
+ $attachment_obj->set_flags($update_flags, $new_flags) if $flags;
+ }
+ elsif (scalar @$update_flags && !scalar(@$new_flags) && !scalar keys %$attachment) {
+ # Requestees can set flags targetted to them, even if they cannot
+ # edit the attachment. Flag setters can edit their own flags too.
+ my %flag_list = map { $_->{id} => $_ } @$update_flags;
+ my $flag_objs = Bugzilla::Flag->new_from_list([ keys %flag_list ]);
+ my @editable_flags;
+ foreach my $flag_obj (@$flag_objs) {
+ if ($flag_obj->setter_id == $user->id
+ || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
+ {
+ push(@editable_flags, $flag_list{$flag_obj->id});
+ }
+ }
+ if (!scalar @editable_flags) {
+ ThrowUserError('illegal_attachment_edit', { attach_id => $attachment_obj->id });
+ }
+ $attachment_obj->set_flags(\@editable_flags, []);
+ }
+ else {
+ ThrowUserError('illegal_attachment_edit', { attach_id => $attachment_obj->id });
+ }
+
+ my $changes = $attachment_obj->update($timestamp);
+
+ if (my $comment_text = trim($comment)) {
+ $attachment_obj->bug->add_comment($comment_text,
+ { isprivate => $attachment_obj->isprivate,
+ type => CMT_ATTACHMENT_UPDATED,
+ extra_data => $attachment_obj->id });
+ }
+
+ $changes = translate($changes, Bugzilla::WebService::Bug::ATTACHMENT_MAPPED_RETURNS);
+
+ my %hash = (
+ id => $self->type('int', $attachment_obj->id),
+ last_change_time => $self->type('dateTime', $attachment_obj->modification_time),
+ changes => {},
+ );
+
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $hash{changes}->{$field} = {
+ removed => $self->type('string', $change->[0] // ''),
+ added => $self->type('string', $change->[1] // '')
+ };
+ }
+
+ push(@modified, \%hash);
+ }
+ else {
+ $attachment_obj = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $timestamp,
+ data => $attachment->{data},
+ description => $attachment->{summary},
+ filename => $attachment->{file_name},
+ mimetype => $attachment->{content_type},
+ ispatch => $attachment->{is_patch},
+ isprivate => $attachment->{is_private},
+ });
+
+ push(@created, $attachment_obj);
+
+ $attachment_obj->update($timestamp);
+ $bug->add_comment($comment,
+ { isprivate => $attachment_obj->isprivate,
+ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment_obj->id });
+
+ }
+ }
+
+ $bug->update($timestamp);
+
+ $dbh->bz_commit_transaction();
+ $bug->send_changes();
+
+ my %attachments_created = map { $_->id => $self->_attachment_to_hash($_, $params) } @created;
+ my %attachments_modified = map { $_->{id}->value => $_ } @modified;
+
+ return { attachments_created => \%attachments_created, attachments_modified => \%attachments_modified };
+}
+
+sub rest_resources {
+ return [
+ qr{^/mozreview/(\d+)/attachments$}, {
+ POST => {
+ method => 'attachments',
+ params => sub {
+ return { bug_id => $1 };
+ },
+ },
+ },
+ ];
+}
+
+1;
diff --git a/extensions/MozReview/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/MozReview/template/en/default/hook/global/user-error-errors.html.tmpl
index 4599b8398..151e63b21 100644
--- a/extensions/MozReview/template/en/default/hook/global/user-error-errors.html.tmpl
+++ b/extensions/MozReview/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -8,4 +8,8 @@
[% IF error == "forbidden_method" %]
The requested method '[% method FILTER html %]' is not allowed to be called using the current API Key.
+[% ELSIF error == "mozreview_attachment_bug_mismatch" %]
+ You tried to update attachment [% attachment.id FILTER html %]
+ as part of adding or updating attachments on [% bug.id FILTER html %].
+ That attachment actually belongs to [% terms.bug %] [% attachment.bug_id FILTER html %].
[% END %]