Fix "email" type for merged XML-RPC webservices, update Bugzilla WS and XMLRPC subclass
parent
cb5b2f06e0
commit
58346deb00
|
@ -45,6 +45,12 @@ sub type {
|
|||
if ($type eq 'dateTime') {
|
||||
$value = $self->datetime_format_outbound($value);
|
||||
}
|
||||
elsif ($type eq 'email') {
|
||||
$type = 'string';
|
||||
if (Bugzilla->params->{'webservice_email_filter'}) {
|
||||
$value = email_filter($value);
|
||||
}
|
||||
}
|
||||
return XMLRPC::Data->type($type)->value($value);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +1,90 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
# 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/.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
# defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
package Bugzilla::WebService::Bugzilla;
|
||||
|
||||
use 5.10.1;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util qw(datetime_from);
|
||||
use Bugzilla::WebService::Util qw(validate filter_wants);
|
||||
use Bugzilla::Util qw(trick_taint);
|
||||
|
||||
use DateTime;
|
||||
|
||||
# Basic info that is needed before logins
|
||||
use constant LOGIN_EXEMPT => {
|
||||
parameters => 1,
|
||||
timezone => 1,
|
||||
version => 1,
|
||||
};
|
||||
|
||||
use constant READ_ONLY => qw(
|
||||
extensions
|
||||
parameters
|
||||
timezone
|
||||
time
|
||||
version
|
||||
);
|
||||
|
||||
use constant PUBLIC_METHODS => qw(
|
||||
extensions
|
||||
last_audit_time
|
||||
parameters
|
||||
time
|
||||
timezone
|
||||
version
|
||||
);
|
||||
|
||||
# Logged-out users do not need to know more than that.
|
||||
use constant PARAMETERS_LOGGED_OUT => qw(
|
||||
maintainer
|
||||
requirelogin
|
||||
);
|
||||
|
||||
# These parameters are guessable from the web UI when the user
|
||||
# is logged in. So it's safe to access them.
|
||||
use constant PARAMETERS_LOGGED_IN => qw(
|
||||
allowemailchange
|
||||
attachment_base
|
||||
commentonchange_resolution
|
||||
commentonduplicate
|
||||
cookiepath
|
||||
defaultopsys
|
||||
defaultplatform
|
||||
defaultpriority
|
||||
defaultseverity
|
||||
duplicate_or_move_bug_status
|
||||
emailregexpdesc
|
||||
emailsuffix
|
||||
letsubmitterchoosemilestone
|
||||
letsubmitterchoosepriority
|
||||
mailfrom
|
||||
maintainer
|
||||
maxattachmentsize
|
||||
maxlocalattachment
|
||||
musthavemilestoneonaccept
|
||||
noresolveonopenblockers
|
||||
password_complexity
|
||||
rememberlogin
|
||||
requirelogin
|
||||
search_allow_no_criteria
|
||||
urlbase
|
||||
use_see_also
|
||||
useclassification
|
||||
usemenuforusers
|
||||
useqacontact
|
||||
usestatuswhiteboard
|
||||
usetargetmilestone
|
||||
);
|
||||
|
||||
sub version {
|
||||
my $self = shift;
|
||||
return { version => $self->type('string', BUGZILLA_VERSION) };
|
||||
|
@ -74,6 +121,32 @@ sub time {
|
|||
};
|
||||
}
|
||||
|
||||
sub last_audit_time {
|
||||
my ($self, $params) = validate(@_, 'class');
|
||||
return {
|
||||
last_audit_time => undef
|
||||
};
|
||||
}
|
||||
|
||||
sub parameters {
|
||||
my ($self, $args) = @_;
|
||||
my $user = Bugzilla->login(LOGIN_OPTIONAL);
|
||||
my $params = Bugzilla->params;
|
||||
$args ||= {};
|
||||
|
||||
my @params_list = $user->in_group('tweakparams')
|
||||
? keys(%$params)
|
||||
: $user->id ? PARAMETERS_LOGGED_IN : PARAMETERS_LOGGED_OUT;
|
||||
|
||||
my %parameters;
|
||||
foreach my $param (@params_list) {
|
||||
next unless filter_wants($args, $param);
|
||||
$parameters{$param} = $self->type('string', $params->{$param});
|
||||
}
|
||||
|
||||
return { parameters => \%parameters };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
@ -91,9 +164,11 @@ This provides functions that tell you about Bugzilla in general.
|
|||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=over
|
||||
Although the data input and output is the same for JSONRPC, XMLRPC and REST,
|
||||
the directions for how to access the data via REST is noted in each method
|
||||
where applicable.
|
||||
|
||||
=item C<version>
|
||||
=head2 version
|
||||
|
||||
B<STABLE>
|
||||
|
||||
|
@ -103,6 +178,12 @@ B<STABLE>
|
|||
|
||||
Returns the current version of Bugzilla.
|
||||
|
||||
=item B<REST>
|
||||
|
||||
GET /rest/version
|
||||
|
||||
The returned data format is the same as below.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
@ -112,9 +193,17 @@ string.
|
|||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item REST API call added in Bugzilla B<5.0>.
|
||||
|
||||
=back
|
||||
|
||||
=item C<extensions>
|
||||
=back
|
||||
|
||||
=head2 extensions
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
|
@ -125,6 +214,12 @@ B<EXPERIMENTAL>
|
|||
Gets information about the extensions that are currently installed and enabled
|
||||
in this Bugzilla.
|
||||
|
||||
=item B<REST>
|
||||
|
||||
GET /rest/extensions
|
||||
|
||||
The returned data format is the same as below.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
@ -155,11 +250,13 @@ The return value looks something like this:
|
|||
that the extensions define themselves. Before 3.6, the names of the
|
||||
extensions depended on the directory they were in on the Bugzilla server.
|
||||
|
||||
=item REST API call added in Bugzilla B<5.0>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<timezone>
|
||||
=head2 timezone
|
||||
|
||||
B<DEPRECATED> This method may be removed in a future version of Bugzilla.
|
||||
Use L</time> instead.
|
||||
|
@ -170,6 +267,12 @@ Use L</time> instead.
|
|||
|
||||
Returns the timezone that Bugzilla expects dates and times in.
|
||||
|
||||
=item B<REST>
|
||||
|
||||
GET /rest/timezone
|
||||
|
||||
The returned data format is the same as below.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
@ -184,12 +287,14 @@ string in (+/-)XXXX (RFC 2822) format.
|
|||
=item As of Bugzilla B<3.6>, the timezone returned is always C<+0000>
|
||||
(the UTC timezone).
|
||||
|
||||
=item REST API call added in Bugzilla B<5.0>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=item C<time>
|
||||
=head2 time
|
||||
|
||||
B<STABLE>
|
||||
|
||||
|
@ -200,6 +305,12 @@ B<STABLE>
|
|||
Gets information about what time the Bugzilla server thinks it is, and
|
||||
what timezone it's running in.
|
||||
|
||||
=item B<REST>
|
||||
|
||||
GET /rest/time
|
||||
|
||||
The returned data format is the same as below.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
@ -210,7 +321,7 @@ A struct with the following items:
|
|||
|
||||
=item C<db_time>
|
||||
|
||||
C<dateTime> The current time in UTC, according to the Bugzilla
|
||||
C<dateTime> The current time in UTC, according to the Bugzilla
|
||||
I<database server>.
|
||||
|
||||
Note that Bugzilla assumes that the database and the webserver are running
|
||||
|
@ -220,7 +331,7 @@ rely on for doing searches and other input to the WebService.
|
|||
|
||||
=item C<web_time>
|
||||
|
||||
C<dateTime> This is the current time in UTC, according to Bugzilla's
|
||||
C<dateTime> This is the current time in UTC, according to Bugzilla's
|
||||
I<web server>.
|
||||
|
||||
This might be different by a second from C<db_time> since this comes from
|
||||
|
@ -236,7 +347,7 @@ versions of Bugzilla before 3.6.)
|
|||
=item C<tz_name>
|
||||
|
||||
C<string> The literal string C<UTC>. (Exists only for backwards-compatibility
|
||||
with versions of Bugzilla before 3.6.)
|
||||
with versions of Bugzilla before 3.6.)
|
||||
|
||||
=item C<tz_short_name>
|
||||
|
||||
|
@ -260,9 +371,136 @@ with versions of Bugzilla before 3.6.)
|
|||
were in the UTC timezone, instead of returning information in the server's
|
||||
local timezone.
|
||||
|
||||
=item REST API call added in Bugzilla B<5.0>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 parameters
|
||||
|
||||
B<UNSTABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns parameter values currently used in this Bugzilla.
|
||||
|
||||
=item B<REST>
|
||||
|
||||
GET /rest/parameters
|
||||
|
||||
The returned data format is the same as below.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item C<parameters> which contains a hash with
|
||||
the name of the parameters as keys and their value as values. All
|
||||
values are returned as strings.
|
||||
The list of parameters returned by this method depends on the user
|
||||
credentials:
|
||||
|
||||
A logged-out user can only access the C<maintainer> and C<requirelogin> parameters.
|
||||
|
||||
A logged-in user can access the following parameters (listed alphabetically):
|
||||
C<allowemailchange>,
|
||||
C<attachment_base>,
|
||||
C<commentonchange_resolution>,
|
||||
C<commentonduplicate>,
|
||||
C<cookiepath>,
|
||||
C<defaultopsys>,
|
||||
C<defaultplatform>,
|
||||
C<defaultpriority>,
|
||||
C<defaultseverity>,
|
||||
C<duplicate_or_move_bug_status>,
|
||||
C<emailregexpdesc>,
|
||||
C<emailsuffix>,
|
||||
C<letsubmitterchoosemilestone>,
|
||||
C<letsubmitterchoosepriority>,
|
||||
C<mailfrom>,
|
||||
C<maintainer>,
|
||||
C<maxattachmentsize>,
|
||||
C<maxlocalattachment>,
|
||||
C<musthavemilestoneonaccept>,
|
||||
C<noresolveonopenblockers>,
|
||||
C<password_complexity>,
|
||||
C<rememberlogin>,
|
||||
C<requirelogin>,
|
||||
C<search_allow_no_criteria>,
|
||||
C<urlbase>,
|
||||
C<use_see_also>,
|
||||
C<useclassification>,
|
||||
C<usemenuforusers>,
|
||||
C<useqacontact>,
|
||||
C<usestatuswhiteboard>,
|
||||
C<usetargetmilestone>.
|
||||
|
||||
A user in the tweakparams group can access all existing parameters.
|
||||
New parameters can appear or obsolete parameters can disappear depending
|
||||
on the version of Bugzilla and on extensions being installed.
|
||||
The list of parameters returned by this method is not stable and will
|
||||
never be stable.
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<4.4>.
|
||||
|
||||
=item REST API call added in Bugzilla B<5.0>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 last_audit_time
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets the latest time of the audit_log table.
|
||||
|
||||
=item B<REST>
|
||||
|
||||
GET /rest/last_audit_time
|
||||
|
||||
The returned data format is the same as below.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
You can pass the optional parameter C<class> to get the maximum for only
|
||||
the listed classes.
|
||||
|
||||
=over
|
||||
|
||||
=item C<class> (array) - An array of strings representing the class names.
|
||||
|
||||
B<Note:> The class names are defined as "Bugzilla::<class_name>". For the product
|
||||
use Bugzilla:Product.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<last_audit_time>, that is the maximum of the
|
||||
at_time from the audit_log.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<4.4>.
|
||||
|
||||
=item REST API call added in Bugzilla B<5.0>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
# 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/.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
# defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
package Bugzilla::WebService::Constants;
|
||||
|
||||
use 5.10.1;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
our @EXPORT = qw(
|
||||
WS_ERROR_CODE
|
||||
|
||||
STATUS_OK
|
||||
STATUS_CREATED
|
||||
STATUS_ACCEPTED
|
||||
STATUS_NO_CONTENT
|
||||
STATUS_MULTIPLE_CHOICES
|
||||
STATUS_BAD_REQUEST
|
||||
STATUS_NOT_FOUND
|
||||
STATUS_GONE
|
||||
REST_STATUS_CODE_MAP
|
||||
|
||||
ERROR_UNKNOWN_FATAL
|
||||
ERROR_UNKNOWN_TRANSIENT
|
||||
|
||||
XMLRPC_CONTENT_TYPE_WHITELIST
|
||||
REST_CONTENT_TYPE_WHITELIST
|
||||
|
||||
WS_DISPATCH
|
||||
);
|
||||
|
||||
|
@ -49,13 +56,17 @@ our @EXPORT = qw(
|
|||
use constant WS_ERROR_CODE => {
|
||||
# Generic errors (Bugzilla::Object and others) are 50-99.
|
||||
object_not_specified => 50,
|
||||
reassign_to_empty => 50,
|
||||
param_required => 50,
|
||||
params_required => 50,
|
||||
undefined_field => 50,
|
||||
object_does_not_exist => 51,
|
||||
param_must_be_numeric => 52,
|
||||
number_not_numeric => 52,
|
||||
param_invalid => 53,
|
||||
number_too_large => 54,
|
||||
number_too_small => 55,
|
||||
illegal_date => 56,
|
||||
# Bug errors usually occupy the 100-200 range.
|
||||
improper_bug_id_field_value => 100,
|
||||
bug_id_does_not_exist => 101,
|
||||
|
@ -66,12 +77,14 @@ use constant WS_ERROR_CODE => {
|
|||
alias_in_use => 103,
|
||||
alias_is_numeric => 103,
|
||||
alias_has_comma_or_space => 103,
|
||||
multiple_alias_not_allowed => 103,
|
||||
# Misc. bug field errors
|
||||
illegal_field => 104,
|
||||
freetext_too_long => 104,
|
||||
# Component errors
|
||||
require_component => 105,
|
||||
component_name_too_long => 105,
|
||||
require_component => 105,
|
||||
component_name_too_long => 105,
|
||||
product_unknown_component => 105,
|
||||
# Invalid Product
|
||||
no_products => 106,
|
||||
entry_access_denied => 106,
|
||||
|
@ -87,6 +100,12 @@ use constant WS_ERROR_CODE => {
|
|||
comment_is_private => 110,
|
||||
comment_id_invalid => 111,
|
||||
comment_too_long => 114,
|
||||
comment_invalid_isprivate => 117,
|
||||
# Comment tagging
|
||||
comment_tag_disabled => 125,
|
||||
comment_tag_invalid => 126,
|
||||
comment_tag_too_long => 127,
|
||||
comment_tag_too_short => 128,
|
||||
# See Also errors
|
||||
bug_url_invalid => 112,
|
||||
bug_url_too_long => 112,
|
||||
|
@ -95,14 +114,39 @@ use constant WS_ERROR_CODE => {
|
|||
# Note: 114 is above in the Comment-related section.
|
||||
# Bug update errors
|
||||
illegal_change => 115,
|
||||
# Dependency errors
|
||||
dependency_loop_single => 116,
|
||||
dependency_loop_multi => 116,
|
||||
# Note: 117 is above in the Comment-related section.
|
||||
# Dup errors
|
||||
dupe_loop_detected => 118,
|
||||
dupe_id_required => 119,
|
||||
# Bug-related group errors
|
||||
group_invalid_removal => 120,
|
||||
group_restriction_not_allowed => 120,
|
||||
# Status/Resolution errors
|
||||
missing_resolution => 121,
|
||||
resolution_not_allowed => 122,
|
||||
illegal_bug_status_transition => 123,
|
||||
# Flag errors
|
||||
flag_status_invalid => 129,
|
||||
flag_update_denied => 130,
|
||||
flag_type_requestee_disabled => 131,
|
||||
flag_not_unique => 132,
|
||||
flag_type_not_unique => 133,
|
||||
flag_type_inactive => 134,
|
||||
|
||||
# Authentication errors are usually 300-400.
|
||||
invalid_username_or_password => 300,
|
||||
invalid_login_or_password => 300,
|
||||
account_disabled => 301,
|
||||
auth_invalid_email => 302,
|
||||
extern_id_conflict => -303,
|
||||
auth_failure => 304,
|
||||
password_current_too_short => 305,
|
||||
password_too_short => 305,
|
||||
password_not_complex => 305,
|
||||
api_key_not_valid => 306,
|
||||
api_key_revoked => 306,
|
||||
auth_invalid_token => 307,
|
||||
|
||||
# Except, historically, AUTH_NODATA, which is 410.
|
||||
login_required => 410,
|
||||
|
@ -122,10 +166,105 @@ use constant WS_ERROR_CODE => {
|
|||
user_access_by_id_denied => 505,
|
||||
user_access_by_match_denied => 505,
|
||||
|
||||
# RPC Server Errors. See the following URL:
|
||||
# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
|
||||
xmlrpc_invalid_value => -32600,
|
||||
unknown_method => -32601,
|
||||
# Attachment errors are 600-700.
|
||||
file_too_large => 600,
|
||||
invalid_content_type => 601,
|
||||
# Error 602 attachment_illegal_url no longer exists.
|
||||
file_not_specified => 603,
|
||||
missing_attachment_description => 604,
|
||||
# Error 605 attachment_url_disabled no longer exists.
|
||||
zero_length_file => 606,
|
||||
|
||||
# Product erros are 700-800
|
||||
product_blank_name => 700,
|
||||
product_name_too_long => 701,
|
||||
product_name_already_in_use => 702,
|
||||
product_name_diff_in_case => 702,
|
||||
product_must_have_description => 703,
|
||||
product_must_have_version => 704,
|
||||
product_must_define_defaultmilestone => 705,
|
||||
|
||||
# Group errors are 800-900
|
||||
empty_group_name => 800,
|
||||
group_exists => 801,
|
||||
empty_group_description => 802,
|
||||
invalid_regexp => 803,
|
||||
invalid_group_name => 804,
|
||||
group_cannot_view => 805,
|
||||
|
||||
# Classification errors are 900-1000
|
||||
auth_classification_not_enabled => 900,
|
||||
|
||||
# Search errors are 1000-1100
|
||||
buglist_parameters_required => 1000,
|
||||
|
||||
# Flag type errors are 1100-1200
|
||||
flag_type_name_invalid => 1101,
|
||||
flag_type_description_invalid => 1102,
|
||||
flag_type_cc_list_invalid => 1103,
|
||||
flag_type_sortkey_invalid => 1104,
|
||||
flag_type_not_editable => 1105,
|
||||
|
||||
# Component errors are 1200-1300
|
||||
component_already_exists => 1200,
|
||||
component_is_last => 1201,
|
||||
component_has_bugs => 1202,
|
||||
|
||||
# Errors thrown by the WebService itself. The ones that are negative
|
||||
# conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
|
||||
xmlrpc_invalid_value => -32600,
|
||||
unknown_method => -32601,
|
||||
json_rpc_post_only => 32610,
|
||||
json_rpc_invalid_callback => 32611,
|
||||
xmlrpc_illegal_content_type => 32612,
|
||||
json_rpc_illegal_content_type => 32613,
|
||||
rest_invalid_resource => 32614,
|
||||
};
|
||||
|
||||
# RESTful webservices use the http status code
|
||||
# to describe whether a call was successful or
|
||||
# to describe the type of error that occurred.
|
||||
use constant STATUS_OK => 200;
|
||||
use constant STATUS_CREATED => 201;
|
||||
use constant STATUS_ACCEPTED => 202;
|
||||
use constant STATUS_NO_CONTENT => 204;
|
||||
use constant STATUS_MULTIPLE_CHOICES => 300;
|
||||
use constant STATUS_BAD_REQUEST => 400;
|
||||
use constant STATUS_NOT_AUTHORIZED => 401;
|
||||
use constant STATUS_NOT_FOUND => 404;
|
||||
use constant STATUS_GONE => 410;
|
||||
|
||||
# The integer value is the error code above returned by
|
||||
# the related webvservice call. We choose the appropriate
|
||||
# http status code based on the error code or use the
|
||||
# default STATUS_BAD_REQUEST.
|
||||
sub REST_STATUS_CODE_MAP {
|
||||
my $status_code_map = {
|
||||
51 => STATUS_NOT_FOUND,
|
||||
101 => STATUS_NOT_FOUND,
|
||||
102 => STATUS_NOT_AUTHORIZED,
|
||||
106 => STATUS_NOT_AUTHORIZED,
|
||||
109 => STATUS_NOT_AUTHORIZED,
|
||||
110 => STATUS_NOT_AUTHORIZED,
|
||||
113 => STATUS_NOT_AUTHORIZED,
|
||||
115 => STATUS_NOT_AUTHORIZED,
|
||||
120 => STATUS_NOT_AUTHORIZED,
|
||||
300 => STATUS_NOT_AUTHORIZED,
|
||||
301 => STATUS_NOT_AUTHORIZED,
|
||||
302 => STATUS_NOT_AUTHORIZED,
|
||||
303 => STATUS_NOT_AUTHORIZED,
|
||||
304 => STATUS_NOT_AUTHORIZED,
|
||||
410 => STATUS_NOT_AUTHORIZED,
|
||||
504 => STATUS_NOT_AUTHORIZED,
|
||||
505 => STATUS_NOT_AUTHORIZED,
|
||||
32614 => STATUS_NOT_FOUND,
|
||||
_default => STATUS_BAD_REQUEST
|
||||
};
|
||||
|
||||
Bugzilla::Hook::process('webservice_status_code_map',
|
||||
{ status_code_map => $status_code_map });
|
||||
|
||||
return $status_code_map;
|
||||
};
|
||||
|
||||
# These are the fallback defaults for errors not in ERROR_CODE.
|
||||
|
@ -134,6 +273,19 @@ use constant ERROR_UNKNOWN_TRANSIENT => 32000;
|
|||
|
||||
use constant ERROR_GENERAL => 999;
|
||||
|
||||
use constant XMLRPC_CONTENT_TYPE_WHITELIST => qw(
|
||||
text/xml
|
||||
application/xml
|
||||
);
|
||||
|
||||
# The first content type specified is used as the default.
|
||||
use constant REST_CONTENT_TYPE_WHITELIST => qw(
|
||||
application/json
|
||||
application/javascript
|
||||
text/javascript
|
||||
text/html
|
||||
);
|
||||
|
||||
sub WS_DISPATCH {
|
||||
# We "require" here instead of "use" above to avoid a dependency loop.
|
||||
require Bugzilla::Hook;
|
||||
|
@ -141,14 +293,28 @@ sub WS_DISPATCH {
|
|||
Bugzilla::Hook::process('webservice', { dispatch => \%hook_dispatch });
|
||||
|
||||
my $dispatch = {
|
||||
'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
|
||||
'Bug' => 'Bugzilla::WebService::Bug',
|
||||
'User' => 'Bugzilla::WebService::User',
|
||||
'Product' => 'Bugzilla::WebService::Product',
|
||||
'Field' => 'Bugzilla::WebService::Field',
|
||||
'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
|
||||
'Bug' => 'Bugzilla::WebService::Bug',
|
||||
'Classification' => 'Bugzilla::WebService::Classification',
|
||||
'Component' => 'Bugzilla::WebService::Component',
|
||||
'FlagType' => 'Bugzilla::WebService::FlagType',
|
||||
'Group' => 'Bugzilla::WebService::Group',
|
||||
'Product' => 'Bugzilla::WebService::Product',
|
||||
'User' => 'Bugzilla::WebService::User',
|
||||
'Field' => 'Bugzilla::WebService::Field',
|
||||
%hook_dispatch
|
||||
};
|
||||
return $dispatch;
|
||||
};
|
||||
|
||||
1;
|
||||
|
||||
=head1 B<Methods in need of POD>
|
||||
|
||||
=over
|
||||
|
||||
=item REST_STATUS_CODE_MAP
|
||||
|
||||
=item WS_DISPATCH
|
||||
|
||||
=back
|
||||
|
|
|
@ -1,62 +1,45 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
# 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/.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Rosie Clarkson <rosie.clarkson@planningportal.gov.uk>
|
||||
#
|
||||
# Portions © Crown copyright 2009 - Rosie Clarkson (development@planningportal.gov.uk) for the Planning Portal
|
||||
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
# defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
package Bugzilla::WebService::Server::XMLRPC;
|
||||
|
||||
use 5.10.1;
|
||||
use strict;
|
||||
use SOAP::Transport::HTTP;
|
||||
use warnings;
|
||||
|
||||
use XMLRPC::Transport::HTTP;
|
||||
use Bugzilla::WebService::Server;
|
||||
|
||||
our @ISA = qw(Bugzilla::WebService::Server);
|
||||
push @ISA, 'XMLRPC::Transport::HTTP::' . ($ENV{MOD_PERL} ? 'Apache' : 'CGI');
|
||||
if ($ENV{MOD_PERL}) {
|
||||
our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
|
||||
} else {
|
||||
our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
|
||||
}
|
||||
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
sub initialize {
|
||||
my $self = shift;
|
||||
my %retval = $self->SUPER::initialize(@_);
|
||||
$retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
|
||||
$retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
|
||||
$retval{'dispatch_with'} = WS_DISPATCH;
|
||||
return %retval;
|
||||
}
|
||||
use List::MoreUtils qw(none);
|
||||
|
||||
sub make_response {
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::make_response(@_);
|
||||
|
||||
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
|
||||
# its cookies in Bugzilla::CGI, so we need to copy them over.
|
||||
foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
|
||||
$self->response->headers->push_header('Set-Cookie', $_);
|
||||
sub type {
|
||||
my ($self, $type, $value) = @_;
|
||||
if ($type eq 'dateTime') {
|
||||
# This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
|
||||
# Our "base" implementation is in Bugzilla::WebService::Server.
|
||||
$value = Bugzilla::WebService::Server->datetime_format_outbound($value);
|
||||
$value =~ s/-//g;
|
||||
}
|
||||
}
|
||||
|
||||
sub handle_login {
|
||||
my ($self, $classes, $action, $uri, $method) = @_;
|
||||
my $class = $classes->{$uri};
|
||||
my $full_method = $uri . "." . $method;
|
||||
$self->SUPER::handle_login($class, $method, $full_method);
|
||||
return;
|
||||
elsif ($type eq 'email') {
|
||||
$type = 'string';
|
||||
if (Bugzilla->params->{'webservice_email_filter'}) {
|
||||
$value = email_filter($value);
|
||||
}
|
||||
}
|
||||
return XMLRPC::Data->type($type)->value($value);
|
||||
}
|
||||
|
||||
# Patch SOAP::Transport::HTTP::CGI so it works under CGI like HTTP::Server::Simple
|
||||
|
@ -147,22 +130,101 @@ sub handle_login {
|
|||
$self->response->content;
|
||||
};
|
||||
|
||||
sub initialize {
|
||||
my $self = shift;
|
||||
my %retval = $self->SUPER::initialize(@_);
|
||||
$retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
|
||||
$retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
|
||||
$retval{'dispatch_with'} = WS_DISPATCH;
|
||||
return %retval;
|
||||
}
|
||||
|
||||
sub make_response {
|
||||
my $self = shift;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# Fix various problems with IIS.
|
||||
if ($ENV{'SERVER_SOFTWARE'} =~ /IIS/) {
|
||||
$ENV{CONTENT_LENGTH} = 0;
|
||||
binmode(STDOUT, ':bytes');
|
||||
}
|
||||
|
||||
$self->SUPER::make_response(@_);
|
||||
|
||||
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
|
||||
# its cookies in Bugzilla::CGI, so we need to copy them over.
|
||||
foreach my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
|
||||
$self->response->headers->push_header('Set-Cookie', $cookie);
|
||||
}
|
||||
|
||||
# Copy across security related headers from Bugzilla::CGI
|
||||
foreach my $header (split(/[\r\n]+/, $cgi->header)) {
|
||||
my ($name, $value) = $header =~ /^([^:]+): (.*)/;
|
||||
if ($name && !$self->response->headers->header($name)) {
|
||||
$self->response->headers->header($name => $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub handle_login {
|
||||
my ($self, $classes, $action, $uri, $method) = @_;
|
||||
my $class = $classes->{$uri};
|
||||
my $full_method = $uri . "." . $method;
|
||||
# Only allowed methods to be used from the module's whitelist
|
||||
my $file = $class;
|
||||
$file =~ s{::}{/}g;
|
||||
$file .= ".pm";
|
||||
require $file;
|
||||
if (none { $_ eq $method } $class->PUBLIC_METHODS) {
|
||||
ThrowCodeError('unknown_method', { method => $full_method });
|
||||
}
|
||||
|
||||
$ENV{CONTENT_LENGTH} = 0 if $ENV{'SERVER_SOFTWARE'} =~ /IIS/;
|
||||
$self->SUPER::handle_login($class, $method, $full_method);
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# This exists to validate input parameters (which XMLRPC::Lite doesn't do)
|
||||
# and also, in some cases, to more-usefully decode them.
|
||||
package Bugzilla::XMLRPC::Deserializer;
|
||||
|
||||
use 5.10.1;
|
||||
use strict;
|
||||
# We can't use "use base" because XMLRPC::Serializer doesn't return
|
||||
use warnings;
|
||||
|
||||
# We can't use "use parent" because XMLRPC::Serializer doesn't return
|
||||
# a true value.
|
||||
use XMLRPC::Lite;
|
||||
our @ISA = qw(XMLRPC::Deserializer);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::WebService::Constants qw(XMLRPC_CONTENT_TYPE_WHITELIST);
|
||||
use Bugzilla::WebService::Util qw(fix_credentials);
|
||||
use Scalar::Util qw(tainted);
|
||||
|
||||
sub new {
|
||||
my $self = shift->SUPER::new(@_);
|
||||
# Initialise XML::Parser to not expand references to entities, to prevent DoS
|
||||
require XML::Parser;
|
||||
my $parser = XML::Parser->new( NoExpand => 1, Handlers => { Default => sub {} } );
|
||||
$self->{_parser}->parser($parser, $parser);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub deserialize {
|
||||
my $self = shift;
|
||||
|
||||
# Only allow certain content types to protect against CSRF attacks
|
||||
my $content_type = lc($ENV{'CONTENT_TYPE'});
|
||||
# Remove charset, etc, if provided
|
||||
$content_type =~ s/^([^;]+);.*/$1/;
|
||||
if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
|
||||
ThrowUserError('xmlrpc_illegal_content_type',
|
||||
{ content_type => $ENV{'CONTENT_TYPE'} });
|
||||
}
|
||||
|
||||
my ($xml) = @_;
|
||||
my $som = $self->SUPER::deserialize(@_);
|
||||
if (tainted($xml)) {
|
||||
|
@ -172,7 +234,13 @@ sub deserialize {
|
|||
my $params = $som->paramsin;
|
||||
# This allows positional parameters for Testopia.
|
||||
$params = {} if ref $params ne 'HASH';
|
||||
|
||||
# Update the params to allow for several convenience key/values
|
||||
# use for authentication
|
||||
fix_credentials($params);
|
||||
|
||||
Bugzilla->input_params($params);
|
||||
|
||||
return $som;
|
||||
}
|
||||
|
||||
|
@ -236,7 +304,11 @@ sub _validation_subs {
|
|||
1;
|
||||
|
||||
package Bugzilla::XMLRPC::SOM;
|
||||
|
||||
use 5.10.1;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use XMLRPC::Lite;
|
||||
our @ISA = qw(XMLRPC::SOM);
|
||||
use Bugzilla::WebService::Util qw(taint_data);
|
||||
|
@ -259,9 +331,13 @@ sub paramsin {
|
|||
# This package exists to fix a UTF-8 bug in SOAP::Lite.
|
||||
# See http://rt.cpan.org/Public/Bug/Display.html?id=32952.
|
||||
package Bugzilla::XMLRPC::Serializer;
|
||||
use Scalar::Util qw(blessed);
|
||||
|
||||
use 5.10.1;
|
||||
use strict;
|
||||
# We can't use "use base" because XMLRPC::Serializer doesn't return
|
||||
use warnings;
|
||||
|
||||
use Scalar::Util qw(blessed reftype);
|
||||
# We can't use "use parent" because XMLRPC::Serializer doesn't return
|
||||
# a true value.
|
||||
use XMLRPC::Lite;
|
||||
our @ISA = qw(XMLRPC::Serializer);
|
||||
|
@ -294,8 +370,8 @@ sub envelope {
|
|||
my $self = shift;
|
||||
my ($type, $method, $data) = @_;
|
||||
# If the type isn't a successful response we don't want to change the values.
|
||||
if ($type eq 'response'){
|
||||
$data = _strip_undefs($data);
|
||||
if ($type eq 'response') {
|
||||
_strip_undefs($data);
|
||||
}
|
||||
return $self->SUPER::envelope($type, $method, $data);
|
||||
}
|
||||
|
@ -306,7 +382,9 @@ sub envelope {
|
|||
# so it cannot be recursed like the other hash type objects.
|
||||
sub _strip_undefs {
|
||||
my ($initial) = @_;
|
||||
if (ref $initial eq "HASH" || (blessed $initial && $initial->isa("HASH"))) {
|
||||
my $type = reftype($initial) or return;
|
||||
|
||||
if ($type eq "HASH") {
|
||||
while (my ($key, $value) = each(%$initial)) {
|
||||
if ( !defined $value
|
||||
|| (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
|
||||
|
@ -315,11 +393,11 @@ sub _strip_undefs {
|
|||
delete $initial->{$key};
|
||||
}
|
||||
else {
|
||||
$initial->{$key} = _strip_undefs($value);
|
||||
_strip_undefs($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ref $initial eq "ARRAY" || (blessed $initial && $initial->isa("ARRAY"))) {
|
||||
elsif ($type eq "ARRAY") {
|
||||
for (my $count = 0; $count < scalar @{$initial}; $count++) {
|
||||
my $value = $initial->[$count];
|
||||
if ( !defined $value
|
||||
|
@ -330,11 +408,10 @@ sub _strip_undefs {
|
|||
$count--;
|
||||
}
|
||||
else {
|
||||
$initial->[$count] = _strip_undefs($value);
|
||||
_strip_undefs($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $initial;
|
||||
}
|
||||
|
||||
sub BEGIN {
|
||||
|
@ -436,3 +513,15 @@ perl-SOAP-Lite package in versions 0.68-1 and above.
|
|||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::WebService>
|
||||
|
||||
=head1 B<Methods in need of POD>
|
||||
|
||||
=over
|
||||
|
||||
=item make_response
|
||||
|
||||
=item initialize
|
||||
|
||||
=item handle_login
|
||||
|
||||
=back
|
||||
|
|
|
@ -391,8 +391,8 @@ sub _user_in_any_group {
|
|||
sub read_new_functionality {
|
||||
my $time = time;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my @lu = map { $_ - 0} Bugzilla->params->{new_functionality_tsp} =~ m/(\d+)/g;
|
||||
my $last_updated = POSIX::mktime(@lu[5], @lu[4], @lu[3], @lu[2], @lu[1] - 1, @lu[0] - 1900);
|
||||
my @lu = map { $_ - 0 } Bugzilla->params->{new_functionality_tsp} =~ m/(\d+)/g;
|
||||
my $last_updated = POSIX::mktime($lu[5], $lu[4], $lu[3], $lu[2], $lu[1] - 1, $lu[0] - 1900);
|
||||
$time = $last_updated + 1 if $time < $last_updated;
|
||||
$cgi->send_cookie('-name', 'read_new_functionality', '-value', $time);
|
||||
return {status => 'ok'};
|
||||
|
|
|
@ -17,7 +17,7 @@ use Bugzilla::Error;
|
|||
|
||||
use Storable qw(dclone);
|
||||
|
||||
use parent qw(Exporter);
|
||||
use base qw(Exporter);
|
||||
|
||||
# We have to "require", not "use" this, because otherwise it tries to
|
||||
# use features of Test::More during import().
|
||||
|
|
Loading…
Reference in New Issue