diff options
author | Dylan William Hardison <dylan@hardison.net> | 2018-10-14 18:19:50 +0200 |
---|---|---|
committer | Dylan William Hardison <dylan@hardison.net> | 2018-10-14 18:19:50 +0200 |
commit | ce00a61057535d49aa0e83181a1d317d7842571b (patch) | |
tree | 280243de9ff791449fb2c82f3f0f2b9bd931d5b2 /Bugzilla/WebService | |
parent | 6367a26da4093a8379e370ef328e9507c98b2e7e (diff) | |
parent | 6657fa9f5210d5b5a9b14c0cba6882bd56232054 (diff) | |
download | bugzilla-ce00a61057535d49aa0e83181a1d317d7842571b.tar.gz bugzilla-ce00a61057535d49aa0e83181a1d317d7842571b.tar.xz |
Merge remote-tracking branch 'bmo/master'
Diffstat (limited to 'Bugzilla/WebService')
-rw-r--r-- | Bugzilla/WebService/Bug.pm | 20 | ||||
-rw-r--r-- | Bugzilla/WebService/Bugzilla.pm | 4 | ||||
-rw-r--r-- | Bugzilla/WebService/JSON.pm | 64 | ||||
-rw-r--r-- | Bugzilla/WebService/JSON/Box.pm | 43 | ||||
-rw-r--r-- | Bugzilla/WebService/Product.pm | 21 | ||||
-rw-r--r-- | Bugzilla/WebService/README | 4 | ||||
-rw-r--r-- | Bugzilla/WebService/Server.pm | 18 | ||||
-rw-r--r-- | Bugzilla/WebService/Server/JSONRPC.pm | 14 | ||||
-rw-r--r-- | Bugzilla/WebService/Server/REST.pm | 1 | ||||
-rw-r--r-- | Bugzilla/WebService/Util.pm | 24 |
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); |