summaryrefslogtreecommitdiffstats
path: root/Bugzilla/WebService
diff options
context:
space:
mode:
authorDylan William Hardison <dylan@hardison.net>2018-10-14 18:19:50 +0200
committerDylan William Hardison <dylan@hardison.net>2018-10-14 18:19:50 +0200
commitce00a61057535d49aa0e83181a1d317d7842571b (patch)
tree280243de9ff791449fb2c82f3f0f2b9bd931d5b2 /Bugzilla/WebService
parent6367a26da4093a8379e370ef328e9507c98b2e7e (diff)
parent6657fa9f5210d5b5a9b14c0cba6882bd56232054 (diff)
downloadbugzilla-ce00a61057535d49aa0e83181a1d317d7842571b.tar.gz
bugzilla-ce00a61057535d49aa0e83181a1d317d7842571b.tar.xz
Merge remote-tracking branch 'bmo/master'
Diffstat (limited to 'Bugzilla/WebService')
-rw-r--r--Bugzilla/WebService/Bug.pm20
-rw-r--r--Bugzilla/WebService/Bugzilla.pm4
-rw-r--r--Bugzilla/WebService/JSON.pm64
-rw-r--r--Bugzilla/WebService/JSON/Box.pm43
-rw-r--r--Bugzilla/WebService/Product.pm21
-rw-r--r--Bugzilla/WebService/README4
-rw-r--r--Bugzilla/WebService/Server.pm18
-rw-r--r--Bugzilla/WebService/Server/JSONRPC.pm14
-rw-r--r--Bugzilla/WebService/Server/REST.pm1
-rw-r--r--Bugzilla/WebService/Util.pm24
10 files changed, 188 insertions, 25 deletions
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
index feb541c2e..61a95e07d 100644
--- a/Bugzilla/WebService/Bug.pm
+++ b/Bugzilla/WebService/Bug.pm
@@ -666,7 +666,7 @@ sub possible_duplicates {
include_fields => Optional [ ArrayRef [Str] ],
Bugzilla_api_token => Optional [Str]
];
-
+
ThrowCodeError( 'param_invalid', { function => 'Bug.possible_duplicates', param => 'A param' } )
if !$params_type->check($params);
@@ -674,10 +674,10 @@ sub possible_duplicates {
if ($params->{id}) {
my $bug = Bugzilla::Bug->check({ id => $params->{id}, cache => 1 });
$summary = $bug->short_desc;
- }
+ }
elsif ($params->{summary}) {
$summary = $params->{summary};
- }
+ }
else {
ThrowCodeError('param_required',
{ function => 'Bug.possible_duplicates', param => 'id or summary' });
@@ -701,7 +701,7 @@ sub possible_duplicates {
if ($params->{id}) {
@$possible_dupes = grep { $_->id != $params->{id} } @$possible_dupes;
}
-
+
my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
$self->_add_update_tokens($params, $possible_dupes, \@hashes);
return { bugs => \@hashes };
@@ -1412,6 +1412,9 @@ sub _bug_to_hash {
if (filter_wants $params, 'dupe_of') {
$item{'dupe_of'} = $self->type('int', $bug->dup_id);
}
+ if (filter_wants $params, 'duplicates') {
+ $item{'duplicates'} = [ map { $self->type('int', $_->id) } @{ $bug->duplicates } ];
+ }
if (filter_wants $params, 'groups') {
my @groups = map { $self->type('string', $_->name) }
@{ $bug->groups_in };
@@ -1459,7 +1462,8 @@ sub _bug_to_hash {
elsif ($field->type == FIELD_TYPE_DATETIME
|| $field->type == FIELD_TYPE_DATE)
{
- $item{$name} = $self->type('dateTime', $bug->$name);
+ my $value = $bug->$name;
+ $item{$name} = defined($value) ? $self->type('dateTime', $value) : undef;
}
elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
my @values = map { $self->type('string', $_) } @{ $bug->$name };
@@ -2594,6 +2598,10 @@ C<array> of C<int>s. The ids of bugs that this bug "depends on".
C<int> The bug ID of the bug that this bug is a duplicate of. If this bug
isn't a duplicate of any bug, this will be null.
+=item C<duplicates>
+
+C<array> of C<int>s. The ids of bugs that are marked as duplicate of this bug.
+
=item C<estimated_time>
C<double> The number of hours that it was estimated that this bug would
@@ -2911,6 +2919,8 @@ and all custom fields.
=item The C<actual_time> item was added to the C<bugs> return value
in Bugzilla B<4.4>.
+=item The C<duplicates> array was added in Bugzilla B<6.0>.
+
=back
=back
diff --git a/Bugzilla/WebService/Bugzilla.pm b/Bugzilla/WebService/Bugzilla.pm
index 145502445..8e95028cb 100644
--- a/Bugzilla/WebService/Bugzilla.pm
+++ b/Bugzilla/WebService/Bugzilla.pm
@@ -87,7 +87,7 @@ sub time {
sub jobqueue_status {
my ( $self, $params ) = @_;
-
+
Bugzilla->login(LOGIN_REQUIRED);
my $dbh = Bugzilla->dbh;
@@ -98,7 +98,7 @@ sub jobqueue_status {
(SELECT COUNT(*)
FROM ts_error
WHERE ts_error.jobid = j.jobid
- )
+ )
, 0) AS errors
FROM ts_job j
INNER JOIN ts_funcmap f
diff --git a/Bugzilla/WebService/JSON.pm b/Bugzilla/WebService/JSON.pm
new file mode 100644
index 000000000..5c28b20f4
--- /dev/null
+++ b/Bugzilla/WebService/JSON.pm
@@ -0,0 +1,64 @@
+# 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::WebService::JSON;
+use 5.10.1;
+use Moo;
+
+use Bugzilla::Logging;
+use Bugzilla::WebService::JSON::Box;
+use JSON::MaybeXS;
+use Scalar::Util qw(refaddr blessed);
+use Package::Stash;
+
+use constant Box => 'Bugzilla::WebService::JSON::Box';
+
+has 'json' => (
+ init_arg => undef,
+ is => 'lazy',
+ handles => {_encode => 'encode', _decode => 'decode'},
+);
+
+sub encode {
+ my ($self, $value) = @_;
+ return Box->new(json => $self, value => $value);
+}
+
+sub decode {
+ my ($self, $box) = @_;
+
+ if (blessed($box) && $box->isa(Box)) {
+ return $box->value;
+ }
+ else {
+ return $self->_decode($box);
+ }
+}
+
+sub _build_json { JSON::MaybeXS->new }
+
+# delegation all the json options to the real json encoder.
+{
+ my @json_methods = qw(
+ utf8 ascii pretty canonical
+ allow_nonref allow_blessed convert_blessed
+ );
+ my $stash = Package::Stash->new(__PACKAGE__);
+ foreach my $method (@json_methods) {
+ my $symbol = '&' . $method;
+ $stash->add_symbol(
+ $symbol => sub {
+ my $self = shift;
+ $self->json->$method(@_);
+ return $self;
+ }
+ );
+ }
+}
+
+
+1;
diff --git a/Bugzilla/WebService/JSON/Box.pm b/Bugzilla/WebService/JSON/Box.pm
new file mode 100644
index 000000000..fc39aeee8
--- /dev/null
+++ b/Bugzilla/WebService/JSON/Box.pm
@@ -0,0 +1,43 @@
+# 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::WebService::JSON::Box;
+use 5.10.1;
+use Moo;
+
+use overload '${}' => 'value', '""' => 'to_string', fallback => 1;
+
+has 'value' => (is => 'ro', required => 1);
+has 'json' => (is => 'ro', required => 1);
+has 'label' => (is => 'lazy');
+has 'encode' => (init_arg => undef, is => 'lazy', predicate => 'is_encoded');
+
+sub TO_JSON {
+ my ($self) = @_;
+
+ return $self->to_string;
+}
+
+sub to_string {
+ my ($self) = @_;
+
+ return $self->is_encoded ? $self->encode : $self->label;
+}
+
+sub _build_encode {
+ my ($self) = @_;
+
+ return $self->json->_encode($self->value);
+}
+
+sub _build_label {
+ my ($self) = @_;
+
+ return "" . $self->value;
+}
+
+1;
diff --git a/Bugzilla/WebService/Product.pm b/Bugzilla/WebService/Product.pm
index 6ca3fee90..cdd8a0a92 100644
--- a/Bugzilla/WebService/Product.pm
+++ b/Bugzilla/WebService/Product.pm
@@ -64,10 +64,13 @@ sub get_accessible_products {
}
# Get a list of actual products, based on list of ids or names
+our %FLAG_CACHE;
sub get {
my ($self, $params) = validate(@_, 'ids', 'names', 'type');
my $user = Bugzilla->user;
+ Bugzilla->request_cache->{bz_etag_disable} = 1;
+
defined $params->{ids} || defined $params->{names} || defined $params->{type}
|| ThrowCodeError("params_required", { function => "Product.get",
params => ['ids', 'names', 'type'] });
@@ -134,6 +137,7 @@ sub get {
}
# Now create a result entry for each.
+ local %FLAG_CACHE = ();
my @products = map { $self->_product_to_hash($params, $_) }
@requested_products;
return { products => \@products };
@@ -215,6 +219,8 @@ sub _component_to_hash {
$self->type('email', $component->default_assignee->login),
default_qa_contact =>
$self->type('email', $component->default_qa_contact->login),
+ triage_owner =>
+ $self->type('email', $component->triage_owner->login),
sort_key => # sort_key is returned to match Bug.fields
0,
is_active =>
@@ -225,11 +231,11 @@ sub _component_to_hash {
$field_data->{flag_types} = {
bug =>
[map {
- $self->_flag_type_to_hash($_)
+ $FLAG_CACHE{ $_->id } //= $self->_flag_type_to_hash($_)
} @{$component->flag_types->{'bug'}}],
attachment =>
[map {
- $self->_flag_type_to_hash($_)
+ $FLAG_CACHE{ $_->id } //= $self->_flag_type_to_hash($_)
} @{$component->flag_types->{'attachment'}}],
};
}
@@ -238,8 +244,8 @@ sub _component_to_hash {
}
sub _flag_type_to_hash {
- my ($self, $flag_type, $params) = @_;
- return filter $params, {
+ my ($self, $flag_type) = @_;
+ return {
id =>
$self->type('int', $flag_type->id),
name =>
@@ -262,7 +268,7 @@ sub _flag_type_to_hash {
$self->type('int', $flag_type->grant_group_id),
request_group =>
$self->type('int', $flag_type->request_group_id),
- }, undef, 'flag_types';
+ };
}
sub _version_to_hash {
@@ -548,6 +554,11 @@ default.
C<string> The login name of the user who will be set as the QA Contact for
new bugs by default.
+=item C<triage_owner>
+
+C<string> The login name of the user who is named as the Triage Owner of the
+component.
+
=item C<sort_key>
C<int> Components, when displayed in a list, are sorted first by this integer
diff --git a/Bugzilla/WebService/README b/Bugzilla/WebService/README
index bbe320979..788c550bb 100644
--- a/Bugzilla/WebService/README
+++ b/Bugzilla/WebService/README
@@ -7,11 +7,11 @@ Our goal is to make JSON::RPC and XMLRPC::Lite both work with the same code.
The problem is that these both pass different things for $self to WebService
methods.
-When XMLRPC::Lite calls a method, $self is the name of the *class* the
+When XMLRPC::Lite calls a method, $self is the name of the *class* the
method is in. For example, if we call Bugzilla.version(), the first argument
is Bugzilla::WebService::Bugzilla. So in order to have $self
(our first argument) act correctly in XML-RPC, we make all WebService
-classes use base qw(Bugzilla::WebService).
+classes use base qw(Bugzilla::WebService).
When JSON::RPC calls a method, $self is the JSON-RPC *server object*. In other
words, it's an instance of Bugzilla::WebService::Server::JSONRPC. So we have
diff --git a/Bugzilla/WebService/Server.pm b/Bugzilla/WebService/Server.pm
index a76c4c48c..c4bd3e605 100644
--- a/Bugzilla/WebService/Server.pm
+++ b/Bugzilla/WebService/Server.pm
@@ -11,12 +11,15 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Logging;
use Bugzilla::Error;
use Bugzilla::Util qw(datetime_from);
use Digest::MD5 qw(md5_base64);
use Scalar::Util qw(blessed);
use Storable qw(freeze);
+use Module::Runtime qw(require_module);
+use Try::Tiny;
sub handle_login {
my ($self, $class, $method, $full_method) = @_;
@@ -30,8 +33,13 @@ sub handle_login {
Bugzilla->request_cache->{dont_persist_session} = 1;
}
- eval "require $class";
- ThrowCodeError('unknown_method', {method => $full_method}) if $@;
+ try {
+ require_module($class);
+ }
+ catch {
+ ThrowCodeError('unknown_method', {method => $full_method});
+ FATAL($_);
+ };
return if ($class->login_exempt($method)
and !defined Bugzilla->input_params->{Bugzilla_login});
Bugzilla->login();
@@ -73,7 +81,11 @@ sub datetime_format_outbound {
sub bz_etag {
my ($self, $data) = @_;
my $cache = Bugzilla->request_cache;
- if (defined $data) {
+
+ if (Bugzilla->request_cache->{bz_etag_disable}) {
+ return undef;
+ }
+ elsif (defined $data) {
# Serialize the data if passed a reference
local $Storable::canonical = 1;
$data = freeze($data) if ref $data;
diff --git a/Bugzilla/WebService/Server/JSONRPC.pm b/Bugzilla/WebService/Server/JSONRPC.pm
index 093167048..12a3143cc 100644
--- a/Bugzilla/WebService/Server/JSONRPC.pm
+++ b/Bugzilla/WebService/Server/JSONRPC.pm
@@ -31,7 +31,9 @@ use Bugzilla::Util;
use HTTP::Message;
use MIME::Base64 qw(decode_base64 encode_base64);
+use Scalar::Util qw(blessed);
use List::MoreUtils qw(none);
+use Bugzilla::WebService::JSON;
#####################################
# Public JSON::RPC Method Overrides #
@@ -48,7 +50,7 @@ sub new {
sub create_json_coder {
my $self = shift;
- my $json = $self->SUPER::create_json_coder(@_);
+ my $json = Bugzilla::WebService::JSON->new;
$json->allow_blessed(1);
$json->convert_blessed(1);
$json->allow_nonref(1);
@@ -83,6 +85,9 @@ sub response {
# Implement JSONP.
if (my $callback = $self->_bz_callback) {
my $content = $response->content;
+ if (blessed $content) {
+ $content = $content->encode;
+ }
# Prepend the JSONP response with /**/ in order to protect
# against possible encoding attacks (e.g., affecting Flash).
$response->content("/**/$callback($content)");
@@ -110,7 +115,12 @@ sub response {
else {
push(@header_args, "-ETag", $etag) if $etag;
print $cgi->header(-status => $response->code, @header_args);
- print $response->content;
+ my $content = $response->content;
+ if (blessed $content) {
+ $content = $content->encode;
+ utf8::encode($content);
+ }
+ print $content;
}
}
diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm
index 13896b248..5d8367410 100644
--- a/Bugzilla/WebService/Server/REST.pm
+++ b/Bugzilla/WebService/Server/REST.pm
@@ -165,6 +165,7 @@ sub response {
my $template = Bugzilla->template;
$content = "";
+ $result->encode if blessed $result;
$template->process("rest.html.tmpl", { result => $result }, \$content)
|| ThrowTemplateError($template->error());
diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm
index d462c884a..ce5586911 100644
--- a/Bugzilla/WebService/Util.pm
+++ b/Bugzilla/WebService/Util.pm
@@ -11,6 +11,7 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Logging;
use Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::Error;
@@ -18,6 +19,8 @@ use Bugzilla::WebService::Constants;
use Storable qw(dclone);
use URI::Escape qw(uri_unescape);
+use Type::Params qw( compile );
+use Types::Standard -all;
use base qw(Exporter);
@@ -217,6 +220,17 @@ sub _delete_bad_keys {
sub validate {
my ($self, $params, @keys) = @_;
+ my $cache_key = join('|', (caller(1))[3], sort @keys);
+ # Type->of() is the same as Type[], used here because it is easier
+ # to chain with plus_coercions.
+ state $array_of_nonrefs = ArrayRef->of(Maybe[Value])->plus_coercions(
+ Maybe[Value], q{ [ $_ ] },
+ );
+ state $type_cache = {};
+ my $params_type = $type_cache->{$cache_key} //= do {
+ my %fields = map { $_ => Optional[$array_of_nonrefs] } @keys;
+ Maybe[ Dict[%fields, slurpy Any] ];
+ };
# If $params is defined but not a reference, then we weren't
# sent any parameters at all, and we're getting @keys where
@@ -226,12 +240,10 @@ sub validate {
# If @keys is not empty then we convert any named
# parameters that have scalar values to arrayrefs
# that match.
- foreach my $key (@keys) {
- if (exists $params->{$key}) {
- $params->{$key} = ref $params->{$key}
- ? $params->{$key}
- : [ $params->{$key} ];
- }
+ $params = $params_type->coerce($params);
+ if (my $type_error = $params_type->validate($params)) {
+ FATAL("validate() found type error: $type_error");
+ ThrowUserError('invalid_params', { type_error => $type_error } ) if $type_error;
}
return ($self, $params);