summaryrefslogtreecommitdiffstats
path: root/extensions/BzAPI/Extension.pm
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/BzAPI/Extension.pm')
-rw-r--r--extensions/BzAPI/Extension.pm291
1 files changed, 241 insertions, 50 deletions
diff --git a/extensions/BzAPI/Extension.pm b/extensions/BzAPI/Extension.pm
index aeaa0bce4..4d144b881 100644
--- a/extensions/BzAPI/Extension.pm
+++ b/extensions/BzAPI/Extension.pm
@@ -1,71 +1,262 @@
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is the BzAPI Bugzilla Extension.
-#
-# The Initial Developer of the Original Code is
-# the Mozilla Foundation.
-# Portions created by the Initial Developer are Copyright (C) 2010
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Gervase Markham <gerv@gerv.net>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::Extension::BzAPI;
+
use strict;
use base qw(Bugzilla::Extension);
+use Bugzilla::Extension::BzAPI::Constants;
+use Bugzilla::Extension::BzAPI::Util qw(fix_credentials filter_wants_nocache);
+
+use Bugzilla::Error;
+use Bugzilla::Util qw(trick_taint datetime_from);
+use Bugzilla::Constants;
+use Bugzilla::Install::Filesystem;
+
+use File::Basename;
+
our $VERSION = '0.1';
-# Add JSON filter for JSON templates
-sub template_before_create {
- my ($self, $args) = @_;
- my $config = $args->{'config'};
-
- $config->{'FILTERS'}->{'json'} = sub {
- my ($var) = @_;
- $var =~ s/([\\\"\/])/\\$1/g;
- $var =~ s/\n/\\n/g;
- $var =~ s/\r/\\r/g;
- $var =~ s/\f/\\f/g;
- $var =~ s/\t/\\t/g;
- return $var;
+################
+# Installation #
+################
+
+sub install_filesystem {
+ my ($self, $args) = @_;
+ my $files = $args->{'files'};
+
+ my $extensionsdir = bz_locations()->{'extensionsdir'};
+ my $scriptname = $extensionsdir . "/" . __PACKAGE__->NAME . "/bin/rest.cgi";
+
+ $files->{$scriptname} = {
+ perms => Bugzilla::Install::Filesystem::WS_EXECUTE
};
}
+##################
+# Template Hooks #
+##################
+
sub template_before_process {
my ($self, $args) = @_;
my $vars = $args->{'vars'};
my $file = $args->{'file'};
-
+
if ($file =~ /config\.json\.tmpl$/) {
$vars->{'initial_status'} = Bugzilla::Status->can_change_to;
- $vars->{'status_objects'} = [Bugzilla::Status->get_all];
+ $vars->{'status_objects'} = [ Bugzilla::Status->get_all ];
}
}
+##############
+# Code Hooks #
+##############
+
+sub bug_start_of_update {
+ my ($self, $args) = @_;
+ my $old_bug = $args->{old_bug};
+ my $params = Bugzilla->input_params;
+
+ return if !Bugzilla->request_cache->{bzapi};
+
+ # Check for a mid-air collision. Currently this only works when updating
+ # an individual bug and if last_changed_time is provided. Otherwise it
+ # allows the changes.
+ my $delta_ts = $params->{last_change_time} || '';
+
+ if ($delta_ts && exists $params->{ids} && @{ $params->{ids} } == 1) {
+ _midair_check($delta_ts, $old_bug->delta_ts);
+ }
+}
+
+sub object_end_of_set_all {
+ my ($self, $args) = @_;
+ my $object = $args->{object};
+ my $params = Bugzilla->input_params;
+
+ return if !Bugzilla->request_cache->{bzapi};
+ return if !$object->isa('Bugzilla::Attachment');
+
+ # Check for a mid-air collision. Currently this only works when updating
+ # an individual attachment and if last_changed_time is provided. Otherwise it
+ # allows the changes.
+ my $stash = Bugzilla->request_cache->{bzapi_stash} ||= {};
+ my $delta_ts = $stash->{last_change_time};
+
+ _midair_check($delta_ts, $object->modification_time) if $delta_ts;
+}
+
+sub _midair_check {
+ my ($delta_ts, $old_delta_ts) = @_;
+ my $delta_ts_z = datetime_from($delta_ts)
+ || ThrowCodeError('invalid_timestamp', { timestamp => $delta_ts });
+ my $old_delta_tz_z = datetime_from($old_delta_ts);
+ if ($old_delta_tz_z ne $delta_ts_z) {
+ ThrowUserError('bzapi_midair_collision');
+ }
+}
+
+sub webservice_error_codes {
+ my ($self, $args) = @_;
+ my $error_map = $args->{error_map};
+ $error_map->{'bzapi_midair_collision'} = 400;
+}
+
+sub webservice_fix_credentials {
+ my ($self, $args) = @_;
+ my $rpc = $args->{rpc};
+ my $params = $args->{params};
+ fix_credentials($params);
+}
+
+sub webservice_rest_request {
+ my ($self, $args) = @_;
+ my $rpc = $args->{rpc};
+ my $params = $args->{params};
+ my $cache = Bugzilla->request_cache;
+
+ return if !$cache->{bzapi};
+
+ # Stash certain values for later use
+ $cache->{bzapi_rpc} = $rpc;
+
+ # Internal websevice method being used
+ $cache->{bzapi_rpc_method} = $rpc->path_info . "." . $rpc->bz_method_name;
+
+ # Load the appropriate request handler based on path and type
+ if (my $handler = _find_handler($rpc, 'request')) {
+ &$handler($params);
+ }
+}
+
+sub webservice_rest_response {
+ my ($self, $args) = @_;
+ my $rpc = $args->{rpc};
+ my $result = $args->{result};
+ my $response = $args->{response};
+ my $cache = Bugzilla->request_cache;
+
+ # Stash certain values for later use
+ $cache->{bzapi_rpc} ||= $rpc;
+
+ return if !Bugzilla->request_cache->{bzapi}
+ || ref $$result ne 'HASH'
+ || exists $$result->{error};
+
+ # Load the appropriate response handler based on path and type
+ if (my $handler = _find_handler($rpc, 'response')) {
+ &$handler($result, $response);
+ }
+}
+
+sub webservice_rest_resources {
+ my ($self, $args) = @_;
+ my $rpc = $args->{rpc};
+ my $resources = $args->{resources};
+
+ return if !Bugzilla->request_cache->{bzapi};
+
+ _add_resources($rpc, $resources);
+}
+
+#####################
+# Utility Functions #
+#####################
+
+sub _find_handler {
+ my ($rpc, $type) = @_;
+
+ my $path_info = $rpc->cgi->path_info;
+ my $request_method = $rpc->request->method;
+
+ my $module = $rpc->bz_class_name || '';
+ $module =~ s/^Bugzilla::WebService:://;
+
+ my $cache = _preload_handlers();
+
+ return undef if !exists $cache->{$module};
+
+ # Make a copy of the handler array so
+ # as to not alter the actual cached data.
+ my @handlers = @{ $cache->{$module} };
+
+ while (my $regex = shift @handlers) {
+ my $data = shift @handlers;
+ next if ref $data ne 'HASH';
+ if ($path_info =~ $regex
+ && exists $data->{$request_method}
+ && exists $data->{$request_method}->{$type})
+ {
+ return $data->{$request_method}->{$type};
+ }
+ }
+
+ return undef;
+}
+
+sub _add_resources {
+ my ($rpc, $native_resources) = @_;
+
+ my $cache = _preload_handlers();
+
+ foreach my $module (keys %$cache) {
+ my $native_module = "Bugzilla::WebService::$module";
+ next if !$native_resources->{$native_module};
+
+ # Make a copy of the handler array so
+ # as to not alter the actual cached data.
+ my @handlers = @{ $cache->{$module} };
+
+ my @ext_resources = ();
+ while (my $regex = shift @handlers) {
+ my $data = shift @handlers;
+ next if ref $data ne 'HASH';
+ my $new_data = {};
+ foreach my $request_method (keys %$data) {
+ next if !exists $data->{$request_method}->{resource};
+ $new_data->{$request_method} = $data->{$request_method}->{resource};
+ }
+ push(@ext_resources, $regex, $new_data);
+ }
+
+ # Places the new resources at the beginning of the list
+ # so we can capture specific paths before the native resources
+ unshift(@{$native_resources->{$native_module}}, @ext_resources);
+ }
+}
+
+sub _resource_modules {
+ my $extdir = bz_locations()->{extensionsdir};
+ return map { basename($_, '.pm') } glob("$extdir/" . __PACKAGE__->NAME . "/lib/Resources/*.pm");
+}
+
+# preload all handlers into cache
+# since we don't want to parse all
+# this multiple times
+sub _preload_handlers {
+ my $cache = Bugzilla->request_cache;
+
+ if (!exists $cache->{rest_handlers}) {
+ my $all_handlers = {};
+ foreach my $module (_resource_modules()) {
+ my $resource_class = "Bugzilla::Extension::BzAPI::Resources::$module";
+ trick_taint($resource_class);
+ eval("require $resource_class");
+ warn $@ if $@;
+ next if ($@ || !$resource_class->can('rest_handlers'));
+ my $handlers = $resource_class->rest_handlers;
+ next if (ref $handlers ne 'ARRAY' || scalar @$handlers % 2 != 0);
+ $all_handlers->{$module} = $handlers;
+ }
+ $cache->{rest_handlers} = $all_handlers;
+ }
+
+ return $cache->{rest_handlers};
+}
+
__PACKAGE__->NAME;