]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 866927 - Enhance Bugzilla WebServices to allow data access using REST
authorDave Lawrence <dlawrence@mozilla.com>
Fri, 12 Jul 2013 20:39:50 +0000 (16:39 -0400)
committerDave Lawrence <dlawrence@mozilla.com>
Fri, 12 Jul 2013 20:39:50 +0000 (16:39 -0400)
r=glob,a=justdave

25 files changed:
.htaccess
Bugzilla.pm
Bugzilla/CGI.pm
Bugzilla/Constants.pm
Bugzilla/Error.pm
Bugzilla/Install/Requirements.pm
Bugzilla/Mailer.pm
Bugzilla/WebService.pm
Bugzilla/WebService/Bug.pm
Bugzilla/WebService/Bugzilla.pm
Bugzilla/WebService/Classification.pm
Bugzilla/WebService/Constants.pm
Bugzilla/WebService/Group.pm
Bugzilla/WebService/Product.pm
Bugzilla/WebService/Server/REST.pm [new file with mode: 0644]
Bugzilla/WebService/Server/REST/Resources/Bug.pm [new file with mode: 0644]
Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm [new file with mode: 0644]
Bugzilla/WebService/Server/REST/Resources/Classification.pm [new file with mode: 0644]
Bugzilla/WebService/Server/REST/Resources/Group.pm [new file with mode: 0644]
Bugzilla/WebService/Server/REST/Resources/Product.pm [new file with mode: 0644]
Bugzilla/WebService/Server/REST/Resources/User.pm [new file with mode: 0644]
Bugzilla/WebService/User.pm
rest.cgi [new file with mode: 0755]
template/en/default/global/user-error.html.tmpl
template/en/default/rest.html.tmpl [new file with mode: 0644]

index 3b464a475bba3ce362dc11c1067efb056476052c..4ec921b2cef903e398f7eebbea92b5132e365913 100644 (file)
--- a/.htaccess
+++ b/.htaccess
@@ -26,3 +26,9 @@ Options -Indexes
 </IfModule>
 </IfModule>
 </IfModule>
+
+<IfModule mod_rewrite.c>
+  RewriteEngine On
+RewriteBase /866927/
+  RewriteRule ^rest/(.*)$ rest.cgi/$1 [NE]
+</IfModule>
index 6331c60fe5cd74264f8c21db28d698454ee7605e..eb1c2f8eac223ce4750f2181b6f8e196aa441722 100644 (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});
index 513babd4f4974a769be77c3e3e2f6ef294db52ba..dacd900a0eca5c180d01d0df2e5f995f292ba3ad 100644 (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,
index 765dacd99eb9d83616f49fe9d1b3f13c48c10dc1..4580d56b78ba09bae9ebef551a5551b921b5f475 100644 (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';
index d64219e87c8507c6b568bbdcc72adae117f75037..ee84183b18594fffd5e22c2c4ed1617cd93f5a44 100644 (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
index f4c24f038e0dd9f29d22881b504bcd26de167e02..4b59838f8aee472697ef33e652458ce013a2f62b 100644 (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'],
index 6602c6cef087d5d195442a0db429cb73cec78251..b60ddb72e5f71bd8d14ddc8ab3b32f138ef33f5f 100644 (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
index 5b28253753aa8ebe58489b3ea8a947200e3fa0dc..03548d2579f9670f693356f1173434beb7813dde 100644 (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
index 5522af2bd9eb817e75abc17a1b1169ae05b624e4..391ea69bcaa9fc2fc71737037775e5af07ff6a9a 100644 (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
index 6fd7a023ae5fb61881e51eb07ddef5ada97213ed..10ba38babe8b52067b8b9c8896e24402111cedfb 100644 (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
index c104994a545588f29bb651e022eb16c2a4a705a7..22358c784586af59ef5b2cf11f05ed6a88b46e69 100644 (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
index b1503772e488dbbbd71b34c3117fda1a7e7edc28..e4325d9d3f41cf556651bd2f831e8f0abe869683 100644 (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;
index e9aa405f6a2498f1d4d252018681a378681bca8e..2d6689880bda7d86dc372d6825e89e2686e3c606 100644 (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>
+
+POST /group
+
+The params to include in the POST body as well as the returned data format,
+are the same as below.
 
-Some params must be set, or an error will be thrown. These params are 
+=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
index 755477acf7b048dabd82d414836f174db558c6a3..83a6583fa53b0b1b01e475d83c8ca7e443207d5d 100644 (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>
+
+POST /product
+
+The params to include in the POST body as well as the returned data format,
+are the same as below.
 
-Some params must be set, or an error will be thrown. These params are 
+=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
diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm
new file mode 100644 (file)
index 0000000..00c7111
--- /dev/null
@@ -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>
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bug.pm b/Bugzilla/WebService/Server/REST/Resources/Bug.pm
new file mode 100644 (file)
index 0000000..a82625b
--- /dev/null
@@ -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.
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm b/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
new file mode 100644 (file)
index 0000000..1c86f77
--- /dev/null
@@ -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.
diff --git a/Bugzilla/WebService/Server/REST/Resources/Classification.pm b/Bugzilla/WebService/Server/REST/Resources/Classification.pm
new file mode 100644 (file)
index 0000000..5bb697a
--- /dev/null
@@ -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.
diff --git a/Bugzilla/WebService/Server/REST/Resources/Group.pm b/Bugzilla/WebService/Server/REST/Resources/Group.pm
new file mode 100644 (file)
index 0000000..9200d60
--- /dev/null
@@ -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.
diff --git a/Bugzilla/WebService/Server/REST/Resources/Product.pm b/Bugzilla/WebService/Server/REST/Resources/Product.pm
new file mode 100644 (file)
index 0000000..1949282
--- /dev/null
@@ -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.
diff --git a/Bugzilla/WebService/Server/REST/Resources/User.pm b/Bugzilla/WebService/Server/REST/Resources/User.pm
new file mode 100644 (file)
index 0000000..9424b51
--- /dev/null
@@ -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.
index b8a3763a2bdeb0845d47eecab31e65e909f3a036..08c8120767d382f0728b260fa45c854bf268e2a7 100644 (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
diff --git a/rest.cgi b/rest.cgi
new file mode 100755 (executable)
index 0000000..928eb9c
--- /dev/null
+++ b/rest.cgi
@@ -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();
index 58d3473761596cc135f5d968273ecacc7ce1fe3c..6bb0a2baea8948eaf3ff458080476ba4b68957a1 100644 (file)
     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.
diff --git a/template/en/default/rest.html.tmpl b/template/en/default/rest.html.tmpl
new file mode 100644 (file)
index 0000000..0b8321d
--- /dev/null
@@ -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>