Bug 866927 - Enhance Bugzilla WebServices to allow data access using REST

r=glob,a=justdave
trunk
Dave Lawrence 2013-07-12 16:39:50 -04:00
parent 6f97373a58
commit 44d42898e8
25 changed files with 1649 additions and 39 deletions

View File

@ -26,3 +26,9 @@ Options -Indexes
</IfModule>
</IfModule>
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /866927/
RewriteRule ^rest/(.*)$ rest.cgi/$1 [NE]
</IfModule>

View File

@ -469,6 +469,9 @@ sub usage_mode {
elsif ($newval == USAGE_MODE_TEST) {
$class->error_mode(ERROR_MODE_TEST);
}
elsif ($newval == USAGE_MODE_REST) {
$class->error_mode(ERROR_MODE_REST);
}
else {
ThrowCodeError('usage_mode_invalid',
{'invalid_usage_mode', $newval});

View File

@ -56,7 +56,7 @@ sub new {
# the rendering of pages.
my $script = basename($0);
if (my $path_info = $self->path_info) {
my @whitelist;
my @whitelist = ("rest.cgi");
Bugzilla::Hook::process('path_info_whitelist', { whitelist => \@whitelist });
if (!grep($_ eq $script, @whitelist)) {
# IIS includes the full path to the script in PATH_INFO,

View File

@ -122,12 +122,14 @@ use Memoize;
USAGE_MODE_EMAIL
USAGE_MODE_JSON
USAGE_MODE_TEST
USAGE_MODE_REST
ERROR_MODE_WEBPAGE
ERROR_MODE_DIE
ERROR_MODE_DIE_SOAP_FAULT
ERROR_MODE_JSON_RPC
ERROR_MODE_TEST
ERROR_MODE_REST
COLOR_ERROR
COLOR_SUCCESS
@ -459,6 +461,7 @@ use constant USAGE_MODE_XMLRPC => 2;
use constant USAGE_MODE_EMAIL => 3;
use constant USAGE_MODE_JSON => 4;
use constant USAGE_MODE_TEST => 5;
use constant USAGE_MODE_REST => 6;
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
# usually). Use with Bugzilla->error_mode.
@ -467,6 +470,7 @@ use constant ERROR_MODE_DIE => 1;
use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
use constant ERROR_MODE_JSON_RPC => 3;
use constant ERROR_MODE_TEST => 4;
use constant ERROR_MODE_REST => 5;
# The ANSI colors of messages that command-line scripts use
use constant COLOR_ERROR => 'red';

View File

@ -104,7 +104,8 @@ sub _throw_error {
die("$message\n");
}
elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
|| Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
|| Bugzilla->error_mode == ERROR_MODE_JSON_RPC
|| Bugzilla->error_mode == ERROR_MODE_REST)
{
# Clone the hash so we aren't modifying the constant.
my %error_map = %{ WS_ERROR_CODE() };
@ -121,13 +122,20 @@ sub _throw_error {
}
else {
my $server = Bugzilla->_json_server;
my $status_code = 0;
if (Bugzilla->error_mode == ERROR_MODE_REST) {
my %status_code_map = %{ REST_STATUS_CODE_MAP() };
$status_code = $status_code_map{$code} || $status_code_map{'_default'};
}
# Technically JSON-RPC isn't allowed to have error numbers
# higher than 999, but we do this to avoid conflicts with
# the internal JSON::RPC error codes.
$server->raise_error(code => 100000 + $code,
message => $message,
id => $server->{_bz_request_id},
version => $server->version);
$server->raise_error(code => 100000 + $code,
status_code => $status_code,
message => $message,
id => $server->{_bz_request_id},
version => $server->version);
# Most JSON-RPC Throw*Error calls happen within an eval inside
# of JSON::RPC. So, in that circumstance, instead of exiting,
# we die with no message. JSON::RPC checks raise_error before

View File

@ -284,7 +284,7 @@ sub OPTIONAL_MODULES {
package => 'JSON-RPC',
module => 'JSON::RPC',
version => 0,
feature => ['jsonrpc'],
feature => ['jsonrpc', 'rest'],
},
{
package => 'JSON-XS',
@ -298,7 +298,7 @@ sub OPTIONAL_MODULES {
module => 'Test::Taint',
# 1.06 no longer throws warnings with Perl 5.10+.
version => 1.06,
feature => ['jsonrpc', 'xmlrpc'],
feature => ['jsonrpc', 'xmlrpc', 'rest'],
},
{
# We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
@ -397,6 +397,7 @@ use constant FEATURE_FILES => (
jsonrpc => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'],
xmlrpc => ['Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi',
'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'],
rest => ['Bugzilla/WebService/Server/REST.pm', 'rest.cgi'],
moving => ['importxml.pl'],
auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'],
auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'],

View File

@ -136,6 +136,8 @@ sub MessageToMTA {
Bugzilla::Hook::process('mailer_before_send',
{ email => $email, mailer_args => \@args });
return if $email->header('to') eq '';
$email->walk_parts(sub {
my ($part) = @_;
return if $part->parts > 1; # Top-level

View File

@ -45,15 +45,20 @@ This is the standard API for external programs that want to interact
with Bugzilla. It provides various methods in various modules.
You can interact with this API via
L<XML-RPC|Bugzilla::WebService::Server::XMLRPC> or
L<JSON-RPC|Bugzilla::WebService::Server::JSONRPC>.
L<XML-RPC|Bugzilla::WebService::Server::XMLRPC>,
L<JSON-RPC|Bugzilla::WebService::Server::JSONRPC> or
L<REST|Bugzilla::WebService::Server::REST>.
=head1 CALLING METHODS
Methods are grouped into "packages", like C<Bug> for
Methods are grouped into "packages", like C<Bug> for
L<Bugzilla::WebService::Bug>. So, for example,
L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get>.
For REST, the "package" is more determined by the path
used to access the resource. See each relevant method
for specific details on how to access via REST.
=head1 PARAMETERS
The Bugzilla API takes the following various types of parameters:
@ -135,7 +140,7 @@ There are various ways to log in:
=item C<User.login>
You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
user. This issues standard HTTP cookies that you must then use in future
calls, so your client must be capable of receiving and transmitting
cookies.
@ -165,13 +170,17 @@ not expire.
=back
The C<Bugzilla_restrictlogin> and C<Bugzilla_rememberlogin> options
are only used when you have also specified C<Bugzilla_login> and
are only used when you have also specified C<Bugzilla_login> and
C<Bugzilla_password>.
Note that Bugzilla will return HTTP cookies along with the method
response when you use these arguments (just like the C<User.login> method
above).
For REST, you may also use the C<username> and C<password> variable
names instead of C<Bugzilla_login> and C<Bugzilla_password> as a
convenience.
=back
=head1 STABLE, EXPERIMENTAL, and UNSTABLE
@ -266,6 +275,9 @@ would return something like:
{ users => [{ id => 1, name => 'user@domain.com' }] }
Note for REST, C<include_fields> may instead be a comma delimited string
for GET type requests.
=item C<exclude_fields>
C<array> An array of strings, representing the (case-sensitive) names of
@ -295,6 +307,9 @@ would return something like:
{ users => [{ id => 1, real_name => 'John Smith' }] }
Note for REST, C<exclude_fields> may instead be a comma delimited string
for GET type requests.
=back
=head1 SEE ALSO

View File

@ -1075,6 +1075,10 @@ or get information about bugs that have already been filed.
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
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.
=head1 Utility Functions
=head2 fields
@ -1088,11 +1092,26 @@ B<UNSTABLE>
Get information about valid bug fields, including the lists of legal values
for each field.
=item B<REST>
You have several options for retreiving information about fields. The first
part is the request method and the rest is the related path needed.
To get information about all fields:
GET /field/bug
To get information related to a single field:
GET /field/bug/<id_or_name>
The returned data format is the same as below.
=item B<Params>
You can pass either field ids or field names.
B<Note>: If neither C<ids> nor C<names> is specified, then all
B<Note>: If neither C<ids> nor C<names> is specified, then all
non-obsolete fields will be returned.
In addition to the parameters below, this method also accepts the
@ -1288,6 +1307,8 @@ You specified an invalid field name or id.
=item C<is_active> return key for C<values> was added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>
=back
=back
@ -1303,6 +1324,18 @@ B<DEPRECATED> - Use L</fields> instead.
Tells you what values are allowed for a particular field.
=item B<REST>
To get information on the values for a field based on field name:
GET /field/bug/<field_name>/values
To get information based on field name and a specific product:
GET /field/bug/<field_name>/<product_id>/values
The returned data format is the same as below.
=item B<Params>
=over
@ -1335,6 +1368,14 @@ You specified a field that doesn't exist or isn't a drop-down field.
=back
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head1 Bug Information
@ -1353,6 +1394,18 @@ and/or attachment ids.
B<Note>: Private attachments will only be returned if you are in the
insidergroup or if you are the submitter of the attachment.
=item B<REST>
To get all current attachments for a bug:
GET /bug/<bug_id>/attachment
To get a specific attachment based on attachment ID:
GET /bug/attachment/<attachment_id>
The returned data format is the same as below.
=item B<Params>
B<Note>: At least one of C<ids> or C<attachment_ids> is required.
@ -1550,6 +1603,8 @@ C<summary>.
=item The C<flags> array was added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -1566,6 +1621,18 @@ B<STABLE>
This allows you to get data about comments, given a list of bugs
and/or comment ids.
=item B<REST>
To get all comments for a particular bug using the bug ID or alias:
GET /bug/<id_or_alias>/comment
To get a specific comment based on the comment ID:
GET /bug/comment/<comment_id>
The returned data format is the same as below.
=item B<Params>
B<Note>: At least one of C<ids> or C<comment_ids> is required.
@ -1711,6 +1778,8 @@ C<creator>.
=item C<creation_time> was added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -1728,6 +1797,14 @@ Gets information about particular bugs in the database.
Note: Can also be called as "get_bugs" for compatibilty with Bugzilla 3.0 API.
=item B<REST>
To get information about a particular bug using its ID or alias:
GET /bug/<id_or_alias>
The returned data format is the same as below.
=item B<Params>
In addition to the parameters below, this method also accepts the
@ -2060,6 +2137,8 @@ You do not have access to the bug_id you specified.
=item The following properties were added to this method's return values
in Bugzilla B<3.4>:
=item REST API call added in Bugzilla B<5.0>
=over
=item For C<bugs>
@ -2117,6 +2196,14 @@ B<EXPERIMENTAL>
Gets the history of changes for particular bugs in the database.
=item B<REST>
To get the history for a specific bug ID:
GET /bug/<bug_id>/history
The returned data format will be the same as below.
=item B<Params>
=over
@ -2208,6 +2295,8 @@ The same as L</get>.
consistent with other methods. Since Bugzilla B<4.4>, they now match
names used by L<Bug.update|/"update"> for consistency.
=item REST API call added Bugzilla B<5.0>.
=back
=back
@ -2223,6 +2312,14 @@ B<UNSTABLE>
Allows you to search for bugs based on particular criteria.
=item <REST>
To search for bugs:
GET /bug
The URL parameters and the returned data format are the same as below.
=item B<Params>
Unless otherwise specified in the description of a parameter, bugs are
@ -2408,6 +2505,8 @@ in Bugzilla B<4.0>.
C<limit> is set equal to zero. Otherwise maximum results returned are limited
by system configuration.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -2434,10 +2533,19 @@ The WebService interface may allow you to set things other than those listed
here, but realize that anything undocumented is B<UNSTABLE> and will very
likely change in the future.
=item B<REST>
To create a new bug in Bugzilla:
POST /bug
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
Some params must be set, or an error will be thrown. These params are
marked B<Required>.
marked B<Required>.
Some parameters can have defaults set in Bugzilla, by the administrator.
If these parameters have defaults set, you can omit them. These parameters
@ -2598,6 +2706,8 @@ loop errors had a generic code of C<32000>.
=item The ability to file new bugs with a C<resolution> was added in
Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -2613,6 +2723,16 @@ B<STABLE>
This allows you to add an attachment to a bug in Bugzilla.
=item B<REST>
To create attachment on a current bug:
POST /bug/<bug_id>/attachment
The params to include in the POST body, as well as the returned
data format are the same as below. The C<ids> param will be
overridden as it it pulled from the URL path.
=item B<Params>
=over
@ -2710,6 +2830,8 @@ You set the "data" field to an empty string.
=item The return value has changed in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -2725,6 +2847,15 @@ B<STABLE>
This allows you to add a comment to a bug in Bugzilla.
=item B<REST>
To create a comment on a current bug:
POST /bug/<bug_id>/comment
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
=over
@ -2800,6 +2931,8 @@ purposes if you wish.
=item Before Bugzilla B<3.6>, error 54 and error 114 had a generic error
code of 32000.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -2816,6 +2949,16 @@ B<UNSTABLE>
Allows you to update the fields of a bug. Automatically sends emails
out about the changes.
=item B<REST>
To update the fields of a current bug:
PUT /bug/<bug_id>
The params to include in the PUT body as well as the returned data format,
are the same as below. The C<ids> param will be overridden as it is
pulled from the URL path.
=item B<Params>
=over
@ -3260,6 +3403,8 @@ rules don't allow that change.
=item Added in Bugzilla B<4.0>.
=item REST API call added Bugzilla B<5.0>.
=back
=back

View File

@ -121,12 +121,12 @@ sub time {
sub last_audit_time {
my ($self, $params) = validate(@_, 'class');
my $dbh = Bugzilla->dbh;
my $sql_statement = "SELECT MAX(at_time) FROM audit_log";
my $class_values = $params->{class};
my @class_values_quoted;
foreach my $class_value (@$class_values) {
push (@class_values_quoted, $dbh->quote($class_value))
push (@class_values_quoted, $dbh->quote($class_value))
if $class_value =~ /^Bugzilla(::[a-zA-Z0-9_]+)*$/;
}
@ -135,11 +135,11 @@ sub last_audit_time {
}
my $last_audit_time = $dbh->selectrow_array("$sql_statement");
# All Webservices return times in UTC; Use UTC here for backwards compat.
# Hardcode values where appropriate
$last_audit_time = datetime_from($last_audit_time, 'UTC');
return {
last_audit_time => $self->type('dateTime', $last_audit_time)
};
@ -181,6 +181,10 @@ 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.
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.
=head2 version
B<STABLE>
@ -191,6 +195,12 @@ B<STABLE>
Returns the current version of Bugzilla.
=item B<REST>
GET /version
The returned data format is the same as below.
=item B<Params> (none)
=item B<Returns>
@ -200,6 +210,14 @@ string.
=item B<Errors> (none)
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 extensions
@ -213,6 +231,12 @@ B<EXPERIMENTAL>
Gets information about the extensions that are currently installed and enabled
in this Bugzilla.
=item B<REST>
GET /extensions
The returned data format is the same as below.
=item B<Params> (none)
=item B<Returns>
@ -243,6 +267,8 @@ 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
@ -258,6 +284,12 @@ Use L</time> instead.
Returns the timezone that Bugzilla expects dates and times in.
=item B<REST>
GET /timezone
The returned data format is the same as below.
=item B<Params> (none)
=item B<Returns>
@ -272,6 +304,8 @@ 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
@ -288,6 +322,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 /time
The returned data format is the same as below.
=item B<Params> (none)
=item B<Returns>
@ -298,7 +338,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
@ -308,7 +348,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
@ -324,7 +364,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>
@ -348,6 +388,8 @@ 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
@ -362,6 +404,12 @@ B<UNSTABLE>
Returns parameter values currently used in this Bugzilla.
=item B<REST>
GET /parameters
The returned data format is the same as below.
=item B<Params> (none)
=item B<Returns>
@ -419,6 +467,8 @@ never be stable.
=item Added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -433,9 +483,15 @@ B<EXPERIMENTAL>
Gets the latest time of the audit_log table.
=item B<REST>
GET /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
You can pass the optional parameter C<class> to get the maximum for only
the listed classes.
=over
@ -460,6 +516,8 @@ at_time from the audit_log.
=item Added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back

View File

@ -86,7 +86,7 @@ Bugzilla::Webservice::Classification - The Classification API
=head1 DESCRIPTION
This part of the Bugzilla API allows you to deal with the available Classifications.
This part of the Bugzilla API allows you to deal with the available Classifications.
You will be able to get information about them as well as manipulate them.
=head1 METHODS
@ -94,6 +94,10 @@ You will be able to get information about them as well as manipulate them.
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
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.
=head1 Classification Retrieval
=head2 get
@ -106,13 +110,21 @@ B<EXPERIMENTAL>
Returns a hash containing information about a set of classifications.
=item B<REST>
To return information on a single classification:
GET /classification/<classification_id_or_name>
The returned data format will be the same as below.
=item B<Params>
In addition to the parameters below, this method also accepts the
standard L<include_fields|Bugzilla::WebService/include_fields> and
L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
You could get classifications info by supplying their names and/or ids.
You could get classifications info by supplying their names and/or ids.
So, this method accepts the following parameters:
=over
@ -127,10 +139,10 @@ An array of classification names.
=back
=item B<Returns>
=item B<Returns>
A hash with the key C<classifications> and an array of hashes as the corresponding value.
Each element of the array represents a classification that the user is authorized to see
Each element of the array represents a classification that the user is authorized to see
and has the following keys:
=over
@ -190,6 +202,8 @@ Classification is not enabled on this installation.
=item Added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back

View File

@ -14,9 +14,22 @@ use parent 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
);
@ -172,8 +185,47 @@ use constant WS_ERROR_CODE => {
unknown_method => -32601,
json_rpc_post_only => 32610,
json_rpc_invalid_callback => 32611,
xmlrpc_illegal_content_type => 32612,
json_rpc_illegal_content_type => 32613,
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.
use constant REST_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,
_default => STATUS_BAD_REQUEST
};
# These are the fallback defaults for errors not in ERROR_CODE.
@ -187,6 +239,13 @@ use constant XMLRPC_CONTENT_TYPE_WHITELIST => qw(
application/xml
);
use constant REST_CONTENT_TYPE_WHITELIST => qw(
text/html
application/javascript
application/json
text/javascript
);
sub WS_DISPATCH {
# We "require" here instead of "use" above to avoid a dependency loop.
require Bugzilla::Hook;

View File

@ -113,6 +113,10 @@ get information about them.
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
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.
=head1 Group Creation and Modification
=head2 create
@ -125,9 +129,16 @@ B<UNSTABLE>
This allows you to create a new group in Bugzilla.
=item B<Params>
=item B<REST>
Some params must be set, or an error will be thrown. These params are
POST /group
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
Some params must be set, or an error will be thrown. These params are
marked B<Required>.
=over
@ -148,7 +159,7 @@ name of the group.
C<string> A regular expression. Any user whose Bugzilla username matches
this regular expression will automatically be granted membership in this group.
=item C<is_active>
=item C<is_active>
C<boolean> C<True> if new group can be used for bugs, C<False> if this
is a group that will only contain users and no bugs will be restricted
@ -162,7 +173,7 @@ if they are in this group.
=back
=item B<Returns>
=item B<Returns>
A hash with one element, C<id>. This is the id of the newly-created group.
@ -188,7 +199,15 @@ You specified an invalid regular expression in the C<user_regexp> field.
=back
=back
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 update
@ -200,6 +219,14 @@ B<UNSTABLE>
This allows you to update a group in Bugzilla.
=item B<REST>
PUT /group/<group_name_or_id>
The params to include in the PUT body as well as the returned data format,
are the same as below. The C<ids> param will be overridden as it is pulled
from the URL path.
=item B<Params>
At least C<ids> or C<names> must be set, or an error will be thrown.
@ -278,6 +305,14 @@ comma-and-space-separated list if multiple values were removed.
The same as L</create>.
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=cut

View File

@ -336,6 +336,10 @@ get information about them.
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
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.
=head1 List Products
=head2 get_selectable_products
@ -348,15 +352,29 @@ B<EXPERIMENTAL>
Returns a list of the ids of the products the user can search on.
=item B<REST>
GET /product?type=selectable
the returned data format is same as below.
=item B<Params> (none)
=item B<Returns>
=item B<Returns>
A hash containing one item, C<ids>, that contains an array of product
ids.
=item B<Errors> (none)
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 get_enterable_products
@ -370,6 +388,12 @@ B<EXPERIMENTAL>
Returns a list of the ids of the products the user can enter bugs
against.
=item B<REST>
GET /product?type=enterable
the returned data format is same as below.
=item B<Params> (none)
=item B<Returns>
@ -379,6 +403,14 @@ ids.
=item B<Errors> (none)
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 get_accessible_products
@ -392,6 +424,12 @@ B<UNSTABLE>
Returns a list of the ids of the products the user can search or enter
bugs against.
=item B<REST>
GET /product?type=accessible
the returned data format is same as below.
=item B<Params> (none)
=item B<Returns>
@ -401,6 +439,14 @@ ids.
=item B<Errors> (none)
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 get
@ -417,6 +463,12 @@ B<Note>: You must at least specify one of C<ids> or C<names>.
B<Note>: Can also be called as "get_products" for compatibilty with Bugzilla 3.0 API.
=item B<REST>
GET /product/<product_id_or_name>
the returned data format is same as below.
=item B<Params>
In addition to the parameters below, this method also accepts the
@ -612,6 +664,8 @@ been removed.
=item In Bugzilla B<4.4>, C<flag_types> was added to the fields returned
by C<get>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -628,9 +682,16 @@ B<EXPERIMENTAL>
This allows you to create a new product in Bugzilla.
=item B<Params>
=item B<REST>
Some params must be set, or an error will be thrown. These params are
POST /product
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
Some params must be set, or an error will be thrown. These params are
marked B<Required>.
=over
@ -709,6 +770,14 @@ You must specify a version for this product.
=back
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 update
@ -721,6 +790,14 @@ B<EXPERIMENTAL>
This allows you to update a product in Bugzilla.
=item B<REST>
PUT /product/<product_id_or_name>
The params to include in the PUT body as well as the returned data format,
are the same as below. The C<ids> and C<names> params will be overridden as
it is pulled from the URL path.
=item B<Params>
B<Note:> The following parameters specify which products you are updating.
@ -859,6 +936,8 @@ You must define a default milestone.
=item Added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back

View File

@ -0,0 +1,617 @@
# 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::Server::REST;
use 5.10.1;
use strict;
use parent qw(Bugzilla::WebService::Server::JSONRPC);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Util qw(taint_data);
use Bugzilla::Util qw(correct_urlbase html_quote);
# Load resource modules
use Bugzilla::WebService::Server::REST::Resources::Bug;
use Bugzilla::WebService::Server::REST::Resources::Bugzilla;
use Bugzilla::WebService::Server::REST::Resources::Classification;
use Bugzilla::WebService::Server::REST::Resources::Group;
use Bugzilla::WebService::Server::REST::Resources::Product;
use Bugzilla::WebService::Server::REST::Resources::User;
use Scalar::Util qw(blessed reftype);
use MIME::Base64 qw(decode_base64);
###########################
# Public Method Overrides #
###########################
sub handle {
my ($self) = @_;
# Determine how the data should be represented. We do this early so
# errors will also be returned with the proper content type.
$self->content_type($self->_best_content_type(REST_CONTENT_TYPE_WHITELIST()));
# Using current path information, decide which class/method to
# use to serve the request. Throw error if no resource was found
# unless we were looking for OPTIONS
if (!$self->_find_resource($self->cgi->path_info)) {
if ($self->request->method eq 'OPTIONS'
&& $self->bz_rest_options)
{
my $response = $self->response_header(STATUS_OK, "");
my $options_string = join(', ', @{ $self->bz_rest_options });
$response->header('Allow' => $options_string,
'Access-Control-Allow-Methods' => $options_string);
return $self->response($response);
}
ThrowUserError("rest_invalid_resource",
{ path => $self->cgi->path_info,
method => $self->request->method });
}
# Dispatch to the proper module
my $class = $self->bz_class_name;
my ($path) = $class =~ /::([^:]+)$/;
$self->path_info($path);
delete $self->{dispatch_path};
$self->dispatch({ $path => $class });
my $params = $self->_retrieve_json_params;
$self->_fix_credentials($params);
# Fix includes/excludes for each call
rest_include_exclude($params);
# Set callback name if exists
$self->_bz_callback($params->{'callback'}) if $params->{'callback'};
Bugzilla->input_params($params);
# Set the JSON version to 1.1 and the id to the current urlbase
# also set up the correct handler method
my $obj = {
version => '1.1',
id => correct_urlbase(),
method => $self->bz_method_name,
params => $params
};
# Execute the handler
my $result = $self->_handle($obj);
if (!$self->error_response_header) {
return $self->response(
$self->response_header($self->bz_success_code || STATUS_OK, $result));
}
$self->response($self->error_response_header);
}
sub response {
my ($self, $response) = @_;
# If we have thrown an error, the 'error' key will exist
# otherwise we use 'result'. JSONRPC returns other data
# along with the result/error such as version and id which
# we will strip off for REST calls.
my $content = $response->content;
my $json_data = {};
if ($content) {
$json_data = $self->json->decode($content);
}
my $result = {};
if (exists $json_data->{error}) {
$result = $json_data->{error};
$result->{error} = $self->type('boolean', 1);
delete $result->{'name'}; # Remove JSONRPCError
}
elsif (exists $json_data->{result}) {
$result = $json_data->{result};
}
# Access Control
$response->header("Access-Control-Allow-Origin", "*");
# If accessing through web browser, then display in readable format
if ($self->content_type eq 'text/html') {
$result = $self->json->pretty->canonical->encode($result);
my $template = Bugzilla->template;
$content = "";
$template->process("rest.html.tmpl", { result => $result }, \$content)
|| ThrowTemplateError($template->error());
$response->content_type('text/html');
}
else {
$content = $self->json->encode($result);
}
$response->content($content);
$self->SUPER::response($response);
}
#######################################
# Bugzilla::WebService Implementation #
#######################################
sub handle_login {
my $self = shift;
# If we're being called using GET, we don't allow cookie-based or Env
# login, because GET requests can be done cross-domain, and we don't
# want private data showing up on another site unless the user
# explicitly gives that site their username and password. (This is
# particularly important for JSONP, which would allow a remote site
# to use private data without the user's knowledge, unless we had this
# protection in place.)
if (!grep($_ eq $self->request->method, ('POST', 'PUT'))) {
# XXX There's no particularly good way for us to get a parameter
# to Bugzilla->login at this point, so we pass this information
# around using request_cache, which is a bit of a hack. The
# implementation of it is in Bugzilla::Auth::Login::Stack.
Bugzilla->request_cache->{'auth_no_automatic_login'} = 1;
}
my $class = $self->bz_class_name;
my $method = $self->bz_method_name;
my $full_method = $class . "." . $method;
# Bypass JSONRPC::handle_login
Bugzilla::WebService::Server->handle_login($class, $method, $full_method);
}
############################
# Private Method Overrides #
############################
# We do not want to run Bugzilla::WebService::Server::JSONRPC->_find_prodedure
# as it determines the method name differently.
sub _find_procedure {
my $self = shift;
if ($self->isa('JSON::RPC::Server::CGI')) {
return JSON::RPC::Server::_find_procedure($self, @_);
}
else {
return JSON::RPC::Legacy::Server::_find_procedure($self, @_);
}
}
sub _argument_type_check {
my $self = shift;
my $params;
if ($self->isa('JSON::RPC::Server::CGI')) {
$params = JSON::RPC::Server::_argument_type_check($self, @_);
}
else {
$params = JSON::RPC::Legacy::Server::_argument_type_check($self, @_);
}
# JSON-RPC 1.0 requires all parameters to be passed as an array, so
# we just pull out the first item and assume it's an object.
my $params_is_array;
if (ref $params eq 'ARRAY') {
$params = $params->[0];
$params_is_array = 1;
}
taint_data($params);
Bugzilla->input_params($params);
# Now, convert dateTime fields on input.
my $method = $self->bz_method_name;
my $pkg = $self->{dispatch_path}->{$self->path_info};
my @date_fields = @{ $pkg->DATE_FIELDS->{$method} || [] };
foreach my $field (@date_fields) {
if (defined $params->{$field}) {
my $value = $params->{$field};
if (ref $value eq 'ARRAY') {
$params->{$field} =
[ map { $self->datetime_format_inbound($_) } @$value ];
}
else {
$params->{$field} = $self->datetime_format_inbound($value);
}
}
}
my @base64_fields = @{ $pkg->BASE64_FIELDS->{$method} || [] };
foreach my $field (@base64_fields) {
if (defined $params->{$field}) {
$params->{$field} = decode_base64($params->{$field});
}
}
# This is the best time to do login checks.
$self->handle_login();
# Bugzilla::WebService packages call internal methods like
# $self->_some_private_method. So we have to inherit from
# that class as well as this Server class.
my $new_class = ref($self) . '::' . $pkg;
my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
eval "package $new_class;$isa_string;";
bless $self, $new_class;
if ($params_is_array) {
$params = [$params];
}
return $params;
}
###################
# Utility Methods #
###################
sub bz_method_name {
my ($self, $method) = @_;
$self->{_bz_method_name} = $method if $method;
return $self->{_bz_method_name};
}
sub bz_class_name {
my ($self, $class) = @_;
$self->{_bz_class_name} = $class if $class;
return $self->{_bz_class_name};
}
sub bz_success_code {
my ($self, $value) = @_;
$self->{_bz_success_code} = $value if $value;
return $self->{_bz_success_code};
}
sub bz_rest_params {
my ($self, $params) = @_;
$self->{_bz_rest_params} = $params if $params;
return $self->{_bz_rest_params};
}
sub bz_rest_options {
my ($self, $options) = @_;
$self->{_bz_rest_options} = $options if $options;
return $self->{_bz_rest_options};
}
sub rest_include_exclude {
my ($params) = @_;
# _all is same as default columns
if ($params->{'include_fields'}
&& ($params->{'include_fields'} eq '_all'
|| $params->{'include_fields'} eq '_default'))
{
delete $params->{'include_fields'};
delete $params->{'exclude_fields'} if $params->{'exclude_fields'};
}
if ($params->{'include_fields'} && !ref $params->{'include_fields'}) {
$params->{'include_fields'} = [ split(/[\s+,]/, $params->{'include_fields'}) ];
}
if ($params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
$params->{'exclude_fields'} = [ split(/[\s+,]/, $params->{'exclude_fields'}) ];
}
return $params;
}
##########################
# Private Custom Methods #
##########################
sub _retrieve_json_params {
my $self = shift;
# Make a copy of the current input_params rather than edit directly
my $params = {};
%{$params} = %{ Bugzilla->input_params };
# First add any params we were able to pull out of the path
# based on the resource regexp
%{$params} = (%{$params}, %{$self->bz_rest_params}) if $self->bz_rest_params;
# Merge any additional query key/values with $obj->{params} if not a GET request
# We do this manually cause CGI.pm doesn't understand JSON strings.
if ($self->request->method ne 'GET') {
my $extra_params = {};
my $json = delete $params->{'POSTDATA'} || delete $params->{'PUTDATA'};
if ($json) {
eval { $extra_params = $self->json->decode($json); };
if ($@) {
ThrowUserError('json_rpc_invalid_params', { err_msg => $@ });
}
}
%{$params} = (%{$params}, %{$extra_params}) if %{$extra_params};
}
return $params;
}
sub _find_resource {
my ($self, $path) = @_;
# Load in the WebService module from the dispatch map and then call
# $module->rest_resources to get the resources array ref.
my $resources = {};
foreach my $module (values %{ $self->{dispatch_path} }) {
eval("require $module") || die $@;
next if !$module->can('rest_resources');
$resources->{$module} = $module->rest_resources;
}
# Use the resources hash from each module loaded earlier to determine
# which handler to use based on a regex match of the CGI path.
# Also any matches found in the regex will be passed in later to the
# handler for possible use.
my $request_method = $self->request->method;
my (@matches, $handler_found, $handler_method, $handler_class);
foreach my $class (keys %{ $resources }) {
# The resource data for each module needs to be
# an array ref with an even number of elements
# to work correctly.
next if (ref $resources->{$class} ne 'ARRAY'
|| scalar @{ $resources->{$class} } % 2 != 0);
while (my $regex = shift @{ $resources->{$class} }) {
my $options_data = shift @{ $resources->{$class} };
next if ref $options_data ne 'HASH';
if (@matches = ($path =~ $regex)) {
# If a specific path is accompanied by a OPTIONS request
# method, the user is asking for a list of possible request
# methods for a specific path.
$self->bz_rest_options([ keys %{ $options_data } ]);
if ($options_data->{$request_method}) {
my $resource_data = $options_data->{$request_method};
$self->bz_class_name($class);
# The method key/value can be a simple scalar method name
# or a anonymous subroutine so we execute it here.
my $method = ref $resource_data->{method} eq 'CODE'
? $resource_data->{method}->($self)
: $resource_data->{method};
$self->bz_method_name($method);
# Pull out any parameters parsed from the URL path
# and store them for use by the method.
if ($resource_data->{params}) {
$self->bz_rest_params($resource_data->{params}->(@matches));
}
# If a special success code is needed for this particular
# method, then store it for later when generating response.
if ($resource_data->{success_code}) {
$self->bz_success_code($resource_data->{success_code});
}
$handler_found = 1;
}
}
last if $handler_found;
}
last if $handler_found;
}
return $handler_found;
}
sub _fix_credentials {
my ($self, $params) = @_;
# Allow user to pass in &username=foo&password=bar
if (exists $params->{'username'} && exists $params->{'password'}) {
$params->{'Bugzilla_login'} = delete $params->{'username'};
$params->{'Bugzilla_password'} = delete $params->{'password'};
}
}
sub _best_content_type {
my ($self, @types) = @_;
return ($self->_simple_content_negotiation(@types))[0] || '*/*';
}
sub _simple_content_negotiation {
my ($self, @types) = @_;
my @accept_types = $self->_get_content_prefs();
my $score = sub { $self->_score_type(shift, @accept_types) };
return sort {$score->($b) <=> $score->($a)} @types;
}
sub _score_type {
my ($self, $type, @accept_types) = @_;
my $score = scalar(@accept_types);
for my $accept_type (@accept_types) {
return $score if $type eq $accept_type;
$score--;
}
return 0;
}
sub _get_content_prefs {
my $self = shift;
my $default_weight = 1;
my @prefs;
# Parse the Accept header, and save type name, score, and position.
my @accept_types = split /,/, $self->cgi->http('accept') || '';
my $order = 0;
for my $accept_type (@accept_types) {
my ($weight) = ($accept_type =~ /q=(\d\.\d+|\d+)/);
my ($name) = ($accept_type =~ m#(\S+/[^;]+)#);
next unless $name;
push @prefs, { name => $name, order => $order++};
if (defined $weight) {
$prefs[-1]->{score} = $weight;
} else {
$prefs[-1]->{score} = $default_weight;
$default_weight -= 0.001;
}
}
# Sort the types by score, subscore by order, and pull out just the name
@prefs = map {$_->{name}} sort {$b->{score} <=> $a->{score} ||
$a->{order} <=> $b->{order}} @prefs;
return @prefs, '*/*'; # Allows allow for */*
}
1;
__END__
=head1 NAME
Bugzilla::WebService::Server::REST - The REST Interface to Bugzilla
=head1 DESCRIPTION
This documentation describes things about the Bugzilla WebService that
are specific to REST. For a general overview of the Bugzilla WebServices,
see L<Bugzilla::WebService>. The L<Bugzilla::WebService::Server::REST>
module is a sub-class of L<Bugzilla::WebService::Server::JSONRPC> so any
method documentation not found here can be viewed in it's POD.
Please note that I<everything> about this REST interface is
B<EXPERIMENTAL>. If you want a fully stable API, please use the
C<Bugzilla::WebService::Server::XMLRPC|XML-RPC> interface.
=head1 CONNECTING
The endpoint for the REST interface is the C<rest.cgi> script in
your Bugzilla installation. If using Apache and mod_rewrite is installed
and enabled, you can also use /rest/ as your endpoint. For example, if your
Bugzilla is at C<bugzilla.yourdomain.com>, then your REST client would
access the API via: C<http://bugzilla.yourdomain.com/rest/bug/35> which
looks cleaner.
=head1 BROWSING
If the Accept: header of a request is set to text/html (as it is by an
ordinary web browser) then the API will return the JSON data as a HTML
page which the browser can display. In other words, you can play with the
API using just your browser and see results in a human-readable form.
This is a good way to try out the various GET calls, even if you can't use
it for POST or PUT.
=head1 DATA FORMAT
The REST API only supports JSON input, and either JSON and JSONP output.
So objects sent and received must be in JSON format. Basically since
the REST API is a sub class of the JSONRPC API, you can refer to
L<JSONRPC|Bugzilla::WebService::Server::JSONRPC> for more information
on data types that are valid for REST.
On every request, you must set both the "Accept" and "Content-Type" HTTP
headers to the MIME type of the data format you are using to communicate with
the API. Content-Type tells the API how to interpret your request, and Accept
tells it how you want your data back. "Content-Type" must be "application/json".
"Accept" can be either that, or "application/javascript" for JSONP - add a "callback"
parameter to name your callback.
=head1 AUTHENTICATION
Along with viewing data as an anonymous user, you may also see private information
if you have a Bugzilla account by providing your login credentials.
=over
=item Username and password
Pass in as query parameters of any request:
username=fred@bedrock.com&password=ilovewilma
Remember to URL encode any special characters, which are often seen in passwords and to
also enable SSL support.
=back
=head1 ERRORS
When an error occurs over REST, a hash structure is returned with the key C<error>
set to C<true>.
The error contents look similar to:
{ "error": true, "message": "Some message here", "code": 123 }
Every error has a "code", as described in L<Bugzilla::WebService/ERRORS>.
Errors with a numeric C<code> higher than 100000 are errors thrown by
the JSON-RPC library that Bugzilla uses, not by Bugzilla.
=head1 UTILITY FUNCTIONS
=over
=item B<handle>
This method overrides the handle method provided by JSONRPC so that certain
actions related to REST such as determining the proper resource to use,
loading query parameters, etc. can be done before the proper WebService
method is executed.
=item B<response>
This method overrides the response method provided by JSONRPC so that
the response content can be altered for REST before being returned to
the client.
=item B<handle_login>
This method determines the proper WebService all to make based on class
and method name determined earlier. Then calls L<Bugzilla::WebService::Server::handle_login>
which will attempt to authenticate the client.
=item B<bz_method_name>
The WebService method name that matches the path used by the client.
=item B<bz_class_name>
The WebService class containing the method that matches the path used by the client.
=item B<bz_rest_params>
Each REST resource contains a hash key called C<params> that is a subroutine reference.
This subroutine will return a hash structure based on matched values from the path
information that is formatted properly for the WebService method that will be called.
=item B<bz_rest_options>
When a client uses the OPTIONS request method along with a specific path, they are
requesting the list of request methods that are valid for the path. Such as for the
path /bug, the valid request methods are GET (search) and POST (create). So the
client would receive in the response header, C<Access-Control-Allow-Methods: GET, POST>.
=item B<bz_success_code>
Each resource can specify a specific SUCCESS CODE if the operation completes successfully.
OTherwise STATUS OK (200) is the default returned.
=item B<rest_include_exclude>
Normally the WebService methods required C<include_fields> and C<exclude_fields> to be an
array of field names. REST allows for the values for these to be instead comma delimited
string of field names. This method converts the latter into the former so the WebService
methods will not complain.
=back
=head1 SEE ALSO
L<Bugzilla::WebService>

View File

@ -0,0 +1,152 @@
# 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::Server::REST::Resources::Bug;
use 5.10.1;
use strict;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bug;
BEGIN {
*Bugzilla::WebService::Bug::rest_resources = \&_rest_resources;
};
sub _rest_resources {
my $rest_resources = [
qr{^/bug$}, {
GET => {
method => 'search',
},
POST => {
method => 'create',
status_code => STATUS_CREATED
}
},
qr{^/bug/([^/]+)$}, {
GET => {
method => 'get',
params => sub {
return { ids => [ $_[0] ] };
}
},
PUT => {
method => 'update',
params => sub {
return { ids => [ $_[0] ] };
}
}
},
qr{^/bug/([^/]+)/comment$}, {
GET => {
method => 'comments',
params => sub {
return { ids => [ $_[0] ] };
}
},
POST => {
method => 'add_comment',
params => sub {
return { id => $_[0] };
},
success_code => STATUS_CREATED
}
},
qr{^/bug/comment/([^/]+)$}, {
GET => {
method => 'comments',
params => sub {
return { comment_ids => [ $_[0] ] };
}
}
},
qr{^/bug/([^/]+)/history$}, {
GET => {
method => 'history',
params => sub {
return { ids => [ $_[0] ] };
},
}
},
qr{^/bug/([^/]+)/attachment$}, {
GET => {
method => 'attachments',
params => sub {
return { ids => [ $_[0] ] };
}
},
POST => {
method => 'add_attachment',
params => sub {
return { ids => [ $_[0] ] };
},
success_code => STATUS_CREATED
}
},
qr{^/bug/attachment/([^/]+)$}, {
GET => {
method => 'attachments',
params => sub {
return { attachment_ids => [ $_[0] ] };
}
}
},
qr{^/field/bug$}, {
GET => {
method => 'fields',
}
},
qr{^/field/bug/([^/]+)$}, {
GET => {
method => 'fields',
params => sub {
my $value = $_[0];
my $param = 'names';
$param = 'ids' if $value =~ /^\d+$/;
return { $param => [ $_[0] ] };
}
}
},
qr{^/field/bug/([^/]+)/values$}, {
GET => {
method => 'legal_values',
params => sub {
return { field => $_[0] };
}
}
},
qr{^/field/bug/([^/]+)/([^/]+)/values$}, {
GET => {
method => 'legal_values',
params => sub {
return { field => $_[0],
product_id => $_[1] };
}
}
},
];
return $rest_resources;
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Server::REST::Resources::Bug - The REST API for creating,
changing, and getting the details of bugs.
=head1 DESCRIPTION
This part of the Bugzilla REST API allows you to file a new bug in Bugzilla,
or get information about bugs that have already been filed.
See L<Bugzilla::WebService::Bug> for more details on how to use this part of
the REST API.

View File

@ -0,0 +1,69 @@
# 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::Server::REST::Resources::Bugzilla;
use 5.10.1;
use strict;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bugzilla;
BEGIN {
*Bugzilla::WebService::Bugzilla::rest_resources = \&_rest_resources;
};
sub _rest_resources {
my $rest_resources = [
qr{^/version$}, {
GET => {
method => 'version'
}
},
qr{^/extensions$}, {
GET => {
method => 'extensions'
}
},
qr{^/timezone$}, {
GET => {
method => 'timezone'
}
},
qr{^/time$}, {
GET => {
method => 'time'
}
},
qr{^/last_audit_time$}, {
GET => {
method => 'last_audit_time'
}
},
qr{^/parameters$}, {
GET => {
method => 'parameters'
}
}
];
return $rest_resources;
}
1;
__END__
=head1 NAME
Bugzilla::WebService::Bugzilla - Global functions for the webservice interface.
=head1 DESCRIPTION
This provides functions that tell you about Bugzilla in general.
See L<Bugzilla::WebService::Bugzilla> for more details on how to use this part
of the REST API.

View File

@ -0,0 +1,49 @@
# 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::Server::REST::Resources::Classification;
use 5.10.1;
use strict;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Classification;
BEGIN {
*Bugzilla::WebService::Classification::rest_resources = \&_rest_resources;
};
sub _rest_resources {
my $rest_resources = [
qr{^/classification/([^/]+)$}, {
GET => {
method => 'get',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] };
}
}
}
];
return $rest_resources;
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Server::REST::Resources::Classification - The Classification REST API
=head1 DESCRIPTION
This part of the Bugzilla REST API allows you to deal with the available Classifications.
You will be able to get information about them as well as manipulate them.
See L<Bugzilla::WebService::Bug> for more details on how to use this part
of the REST API.

View File

@ -0,0 +1,56 @@
# 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::Server::REST::Resources::Group;
use 5.10.1;
use strict;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Group;
BEGIN {
*Bugzilla::WebService::Group::rest_resources = \&_rest_resources;
};
sub _rest_resources {
my $rest_resources = [
qr{^/group$}, {
POST => {
method => 'create',
success_code => STATUS_CREATED
}
},
qr{^/group/([^/]+)$}, {
PUT => {
method => 'update',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] };
}
}
}
];
return $rest_resources;
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Server::REST::Resources::Group - The REST API for
creating, changing, and getting information about Groups.
=head1 DESCRIPTION
This part of the Bugzilla REST API allows you to create Groups and
get information about them.
See L<Bugzilla::WebService::Group> for more details on how to use this part
of the REST API.

View File

@ -0,0 +1,75 @@
# 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::Server::REST::Resources::Product;
use 5.10.1;
use strict;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Product;
use Bugzilla::Error;
BEGIN {
*Bugzilla::WebService::Product::rest_resources = \&_rest_resources;
};
sub _rest_resources {
my $rest_resources = [
qr{^/product$}, {
GET => {
method => sub {
my $type = Bugzilla->input_params->{type};
return 'get_accessible_products'
if !defined $type || $type eq 'accessible';
return 'get_enterable_products' if $type eq 'enterable';
return 'get_selectable_products' if $type eq 'selectable';
ThrowUserError('rest_get_products_invalid_type',
{ type => $type });
},
},
POST => {
method => 'create',
success_code => STATUS_CREATED
}
},
qr{^/product/([^/]+)$}, {
GET => {
method => 'get',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] };
}
},
PUT => {
method => 'update',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] };
}
}
},
];
return $rest_resources;
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Server::REST::Resources::Product - The Product REST API
=head1 DESCRIPTION
This part of the Bugzilla REST API allows you to list the available Products and
get information about them.
See L<Bugzilla::WebService::Bug> for more details on how to use this part of
the REST API.

View File

@ -0,0 +1,65 @@
# 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::Server::REST::Resources::User;
use 5.10.1;
use strict;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::User;
BEGIN {
*Bugzilla::WebService::User::rest_resources = \&_rest_resources;
};
sub _rest_resources {
my $rest_resources = [
qr{^/user$}, {
GET => {
method => 'get'
},
POST => {
method => 'create',
success_code => STATUS_CREATED
}
},
qr{^/user/([^/]+)$}, {
GET => {
method => 'get',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] };
}
},
PUT => {
method => 'update',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] };
}
}
}
];
return $rest_resources;
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Server::REST::Resources::User - The User Account REST API
=head1 DESCRIPTION
This part of the Bugzilla REST API allows you to get User information as well
as create User Accounts.
See L<Bugzilla::WebService::Bug> for more details on how to use this part of
the REST API.

View File

@ -127,7 +127,7 @@ sub create {
# $call = $rpc->call( 'User.get', { ids => [1,2,3],
# names => ['testusera@redhat.com', 'testuserb@redhat.com'] });
sub get {
my ($self, $params) = validate(@_, 'names', 'ids');
my ($self, $params) = validate(@_, 'names', 'ids', 'match', 'group_ids', 'groups');
Bugzilla->switch_to_shadow_db();
@ -399,6 +399,10 @@ log in/out using an existing account.
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
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.
=head1 Logging In and Out
=head2 login
@ -417,7 +421,7 @@ etc. This method logs in an user.
=over
=item C<login> (string) - The user's login name.
=item C<login> (string) - The user's login name.
=item C<password> (string) - The user's password.
@ -541,6 +545,13 @@ actually receive an email. This function does not check that.
You must be logged in and have the C<editusers> privilege in order to
call this function.
=item B<REST>
POST /user
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
=over
@ -584,6 +595,8 @@ password is under three characters.)
=item Error 503 (Password Too Long) removed in Bugzilla B<3.6>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back
@ -598,6 +611,14 @@ B<EXPERIMENTAL>
Updates user accounts in Bugzilla.
=item B<REST>
PUT /user/<user_id_or_name>
The params to include in the PUT body as well as the returned data format,
are the same as below. The C<ids> and C<names> params are overridden as they
are pulled from the URL path.
=item B<Params>
=over
@ -684,6 +705,14 @@ Logged-in users are not authorized to edit other users.
=back
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head1 User Info
@ -698,6 +727,18 @@ B<STABLE>
Gets information about user accounts in Bugzilla.
=item B<REST>
To get information about a single user:
GET /user/<user_id_or_name>
To search for users by name, group using URL params same as below:
GET /user
The returned data format is the same as below.
=item B<Params>
B<Note>: At least one of C<ids>, C<names>, or C<match> must be specified.
@ -920,6 +961,8 @@ illegal to pass a group name you don't belong to.
=item C<groups>, C<saved_searches>, and C<saved_reports> were added
in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back

29
rest.cgi Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/perl -wT
# 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.
use 5.10.1;
use strict;
use lib qw(. lib);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Constants;
BEGIN {
if (!Bugzilla->feature('rest')
|| !Bugzilla->feature('jsonrpc'))
{
ThrowUserError('feature_disabled', { feature => 'rest' });
}
}
use Bugzilla::WebService::Server::REST;
Bugzilla->usage_mode(USAGE_MODE_REST);
local @INC = (bz_locations()->{extensionsdir}, @INC);
my $server = new Bugzilla::WebService::Server::REST;
$server->version('1.1');
$server->handle();

View File

@ -1073,6 +1073,13 @@
For security reasons, you must use HTTP POST to call the
'[% method FILTER html %]' method.
[% ELSIF error == "rest_invalid_resource" %]
A REST API resource was not found for '[% method FILTER html +%] [%+ path FILTER html %]'.
[% ELSIF error == "rest_get_products_invalid_type" %]
The type '[% type FILTER html %]' is invalid for 'GET /product'. Valid choices
are 'accessible' (default if type value is undefined), 'selectable', and 'enterable'.
[% ELSIF error == "keyword_already_exists" %]
[% title = "Keyword Already Exists" %]
A keyword with the name [% name FILTER html %] already exists.

View File

@ -0,0 +1,19 @@
[%# 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.
#%]
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Bugzilla::REST::API</title>
<link href="[% urlbase FILTER none %][% 'skins/standard/global.css' FILTER mtime %]"
rel="stylesheet" type="text/css">
</head>
<body>
<pre>[% result FILTER html %]</pre>
</body>
</html>