summaryrefslogtreecommitdiffstats
path: root/Bugzilla/WebService
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/WebService')
-rw-r--r--Bugzilla/WebService/Bug.pm2661
-rw-r--r--Bugzilla/WebService/BugUserLastVisit.pm118
-rw-r--r--Bugzilla/WebService/Bugzilla.pm120
-rw-r--r--Bugzilla/WebService/Classification.pm89
-rw-r--r--Bugzilla/WebService/Constants.pm478
-rw-r--r--Bugzilla/WebService/Elastic.pm32
-rw-r--r--Bugzilla/WebService/Group.pm325
-rw-r--r--Bugzilla/WebService/JSON.pm2
-rw-r--r--Bugzilla/WebService/Product.pm428
-rw-r--r--Bugzilla/WebService/Server.pm147
-rw-r--r--Bugzilla/WebService/Server/JSONRPC.pm714
-rw-r--r--Bugzilla/WebService/Server/REST.pm769
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Bug.pm331
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm46
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm52
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Classification.pm27
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Elastic.pm13
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Group.pm55
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Product.pm78
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/User.pm107
-rw-r--r--Bugzilla/WebService/Server/XMLRPC.pm488
-rw-r--r--Bugzilla/WebService/User.pm690
-rw-r--r--Bugzilla/WebService/Util.pm474
23 files changed, 4138 insertions, 4106 deletions
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
index 61a95e07d..5b6c31063 100644
--- a/Bugzilla/WebService/Bug.pm
+++ b/Bugzilla/WebService/Bug.pm
@@ -19,7 +19,8 @@ use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field;
use Bugzilla::WebService::Constants;
-use Bugzilla::WebService::Util qw(extract_flags filter filter_wants validate translate);
+use Bugzilla::WebService::Util
+ qw(extract_flags filter filter_wants validate translate);
use Bugzilla::Bug;
use Bugzilla::BugMail;
use Bugzilla::Util qw(trick_taint trim detaint_natural remote_ip);
@@ -45,71 +46,68 @@ use Type::Utils;
use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
sub DATE_FIELDS {
- my $fields = {
- comments => ['new_since'],
- create => [],
- history => ['new_since'],
- search => ['last_change_time', 'creation_time'],
- update => []
- };
-
- # Add date related custom fields
- foreach my $field (Bugzilla->active_custom_fields) {
- next unless ($field->type == FIELD_TYPE_DATETIME
- || $field->type == FIELD_TYPE_DATE);
- push(@{ $fields->{create} }, $field->name);
- push(@{ $fields->{update} }, $field->name);
- }
-
- return $fields;
+ my $fields = {
+ comments => ['new_since'],
+ create => [],
+ history => ['new_since'],
+ search => ['last_change_time', 'creation_time'],
+ update => []
+ };
+
+ # Add date related custom fields
+ foreach my $field (Bugzilla->active_custom_fields) {
+ next
+ unless ($field->type == FIELD_TYPE_DATETIME
+ || $field->type == FIELD_TYPE_DATE);
+ push(@{$fields->{create}}, $field->name);
+ push(@{$fields->{update}}, $field->name);
+ }
+
+ return $fields;
}
-use constant BASE64_FIELDS => {
- add_attachment => ['data'],
-};
+use constant BASE64_FIELDS => {add_attachment => ['data'],};
use constant READ_ONLY => qw(
- attachments
- comments
- fields
- get
- history
- legal_values
- search
+ attachments
+ comments
+ fields
+ get
+ history
+ legal_values
+ search
);
use constant PUBLIC_METHODS => qw(
- add_attachment
- add_comment
- attachments
- comments
- create
- fields
- get
- history
- legal_values
- possible_duplicates
- render_comment
- search
- search_comment_tags
- update
- update_attachment
- update_comment_tags
- update_see_also
+ add_attachment
+ add_comment
+ attachments
+ comments
+ create
+ fields
+ get
+ history
+ legal_values
+ possible_duplicates
+ render_comment
+ search
+ search_comment_tags
+ update
+ update_attachment
+ update_comment_tags
+ update_see_also
);
-use constant ATTACHMENT_MAPPED_SETTERS => {
- file_name => 'filename',
- summary => 'description',
-};
+use constant ATTACHMENT_MAPPED_SETTERS =>
+ {file_name => 'filename', summary => 'description',};
use constant ATTACHMENT_MAPPED_RETURNS => {
- description => 'summary',
- ispatch => 'is_patch',
- isprivate => 'is_private',
- isobsolete => 'is_obsolete',
- filename => 'file_name',
- mimetype => 'content_type',
+ description => 'summary',
+ ispatch => 'is_patch',
+ isprivate => 'is_private',
+ isobsolete => 'is_obsolete',
+ filename => 'file_name',
+ mimetype => 'content_type',
};
######################################################
@@ -119,6 +117,7 @@ use constant ATTACHMENT_MAPPED_RETURNS => {
BEGIN {
# In 3.0, get was called get_bugs
*get_bugs = \&get;
+
# Before 3.4rc1, "history" was get_history.
*get_history = \&history;
}
@@ -128,1216 +127,1240 @@ BEGIN {
###########
sub fields {
- my ($self, $params) = validate(@_, 'ids', 'names');
+ my ($self, $params) = validate(@_, 'ids', 'names');
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- my @fields;
- if (defined $params->{ids}) {
- my $ids = $params->{ids};
- foreach my $id (@$ids) {
- my $loop_field = Bugzilla::Field->check({ id => $id });
- push(@fields, $loop_field);
- }
+ my @fields;
+ if (defined $params->{ids}) {
+ my $ids = $params->{ids};
+ foreach my $id (@$ids) {
+ my $loop_field = Bugzilla::Field->check({id => $id});
+ push(@fields, $loop_field);
}
-
- if (defined $params->{names}) {
- my $names = $params->{names};
- foreach my $field_name (@$names) {
- my $loop_field = Bugzilla::Field->check($field_name);
- # Don't push in duplicate fields if we also asked for this field
- # in "ids".
- if (!grep($_->id == $loop_field->id, @fields)) {
- push(@fields, $loop_field);
- }
- }
+ }
+
+ if (defined $params->{names}) {
+ my $names = $params->{names};
+ foreach my $field_name (@$names) {
+ my $loop_field = Bugzilla::Field->check($field_name);
+
+ # Don't push in duplicate fields if we also asked for this field
+ # in "ids".
+ if (!grep($_->id == $loop_field->id, @fields)) {
+ push(@fields, $loop_field);
+ }
}
-
- if (!defined $params->{ids} and !defined $params->{names}) {
- @fields = @{ Bugzilla->fields({ obsolete => 0 }) };
+ }
+
+ if (!defined $params->{ids} and !defined $params->{names}) {
+ @fields = @{Bugzilla->fields({obsolete => 0})};
+ }
+
+ my @fields_out;
+ foreach my $field (@fields) {
+ my $visibility_field
+ = $field->visibility_field ? $field->visibility_field->name : undef;
+ my $vis_values = $field->visibility_values;
+ my $value_field = $field->value_field ? $field->value_field->name : undef;
+
+ my (@values, $has_values);
+ if ( ($field->is_select and $field->name ne 'product')
+ or grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)
+ or $field->name eq 'keywords')
+ {
+ $has_values = 1;
+ @values = @{$self->_legal_field_values({field => $field})};
}
- my @fields_out;
- foreach my $field (@fields) {
- my $visibility_field = $field->visibility_field
- ? $field->visibility_field->name : undef;
- my $vis_values = $field->visibility_values;
- my $value_field = $field->value_field
- ? $field->value_field->name : undef;
-
- my (@values, $has_values);
- if ( ($field->is_select and $field->name ne 'product')
- or grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)
- or $field->name eq 'keywords')
- {
- $has_values = 1;
- @values = @{ $self->_legal_field_values({ field => $field }) };
- }
-
- if (grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)) {
- $value_field = 'product';
- }
+ if (grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)) {
+ $value_field = 'product';
+ }
- my %field_data = (
- id => $self->type('int', $field->id),
- type => $self->type('int', $field->type),
- is_custom => $self->type('boolean', $field->custom),
- name => $self->type('string', $field->name),
- display_name => $self->type('string', $field->description),
- is_mandatory => $self->type('boolean', $field->is_mandatory),
- is_on_bug_entry => $self->type('boolean', $field->enter_bug),
- visibility_field => $self->type('string', $visibility_field),
- visibility_values =>
- [ map { $self->type('string', $_->name) } @$vis_values ],
- );
- if ($has_values) {
- $field_data{value_field} = $self->type('string', $value_field);
- $field_data{values} = \@values;
- };
- push(@fields_out, filter $params, \%field_data);
+ my %field_data = (
+ id => $self->type('int', $field->id),
+ type => $self->type('int', $field->type),
+ is_custom => $self->type('boolean', $field->custom),
+ name => $self->type('string', $field->name),
+ display_name => $self->type('string', $field->description),
+ is_mandatory => $self->type('boolean', $field->is_mandatory),
+ is_on_bug_entry => $self->type('boolean', $field->enter_bug),
+ visibility_field => $self->type('string', $visibility_field),
+ visibility_values => [map { $self->type('string', $_->name) } @$vis_values],
+ );
+ if ($has_values) {
+ $field_data{value_field} = $self->type('string', $value_field);
+ $field_data{values} = \@values;
}
+ push(@fields_out, filter $params, \%field_data);
+ }
- return { fields => \@fields_out };
+ return {fields => \@fields_out};
}
sub _legal_field_values {
- my ($self, $params) = @_;
- my $field = $params->{field};
- my $field_name = $field->name;
- my $user = Bugzilla->user;
-
- my @result;
- if (grep($_ eq $field_name, PRODUCT_SPECIFIC_FIELDS)) {
- my @list;
- if ($field_name eq 'version') {
- @list = Bugzilla::Version->get_all;
- }
- elsif ($field_name eq 'component') {
- @list = Bugzilla::Component->get_all;
- }
- else {
- @list = Bugzilla::Milestone->get_all;
- }
+ my ($self, $params) = @_;
+ my $field = $params->{field};
+ my $field_name = $field->name;
+ my $user = Bugzilla->user;
+
+ my @result;
+ if (grep($_ eq $field_name, PRODUCT_SPECIFIC_FIELDS)) {
+ my @list;
+ if ($field_name eq 'version') {
+ @list = Bugzilla::Version->get_all;
+ }
+ elsif ($field_name eq 'component') {
+ @list = Bugzilla::Component->get_all;
+ }
+ else {
+ @list = Bugzilla::Milestone->get_all;
+ }
- foreach my $value (@list) {
- my $sortkey = $field_name eq 'target_milestone'
- ? $value->sortkey : 0;
- # XXX This is very slow for large numbers of values.
- my $product_name = $value->product->name;
- if ($user->can_see_product($product_name)) {
- push(@result, {
- name => $self->type('string', $value->name),
- sort_key => $self->type('int', $sortkey),
- sortkey => $self->type('int', $sortkey), # deprecated
- visibility_values => [$self->type('string', $product_name)],
- });
- }
- }
+ foreach my $value (@list) {
+ my $sortkey = $field_name eq 'target_milestone' ? $value->sortkey : 0;
+
+ # XXX This is very slow for large numbers of values.
+ my $product_name = $value->product->name;
+ if ($user->can_see_product($product_name)) {
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int', $sortkey),
+ sortkey => $self->type('int', $sortkey), # deprecated
+ visibility_values => [$self->type('string', $product_name)],
+ }
+ );
+ }
}
+ }
+
+ elsif ($field_name eq 'bug_status') {
+ my @status_all = Bugzilla::Status->get_all;
+ foreach my $status (@status_all) {
+ my @can_change_to;
+ foreach my $change_to (@{$status->can_change_to}) {
+
+ # There's no need to note that a status can transition
+ # to itself.
+ next if $change_to->id == $status->id;
+ my %change_to_hash = (
+ name => $self->type('string', $change_to->name),
+ comment_required =>
+ $self->type('boolean', $change_to->comment_required_on_change_from($status)),
+ );
+ push(@can_change_to, \%change_to_hash);
+ }
- elsif ($field_name eq 'bug_status') {
- my @status_all = Bugzilla::Status->get_all;
- foreach my $status (@status_all) {
- my @can_change_to;
- foreach my $change_to (@{ $status->can_change_to }) {
- # There's no need to note that a status can transition
- # to itself.
- next if $change_to->id == $status->id;
- my %change_to_hash = (
- name => $self->type('string', $change_to->name),
- comment_required => $self->type('boolean',
- $change_to->comment_required_on_change_from($status)),
- );
- push(@can_change_to, \%change_to_hash);
- }
-
- push (@result, {
- name => $self->type('string', $status->name),
- is_open => $self->type('boolean', $status->is_open),
- sort_key => $self->type('int', $status->sortkey),
- sortkey => $self->type('int', $status->sortkey), # deprecated
- can_change_to => \@can_change_to,
- visibility_values => [],
- });
+ push(
+ @result,
+ {
+ name => $self->type('string', $status->name),
+ is_open => $self->type('boolean', $status->is_open),
+ sort_key => $self->type('int', $status->sortkey),
+ sortkey => $self->type('int', $status->sortkey), # deprecated
+ can_change_to => \@can_change_to,
+ visibility_values => [],
}
+ );
}
-
- elsif ($field_name eq 'keywords') {
- my @legal_keywords = Bugzilla::Keyword->get_all;
- foreach my $value (@legal_keywords) {
- next unless $value->is_active;
- push (@result, {
- name => $self->type('string', $value->name),
- description => $self->type('string', $value->description),
- });
+ }
+
+ elsif ($field_name eq 'keywords') {
+ my @legal_keywords = Bugzilla::Keyword->get_all;
+ foreach my $value (@legal_keywords) {
+ next unless $value->is_active;
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ description => $self->type('string', $value->description),
}
+ );
}
- else {
- my @values = Bugzilla::Field::Choice->type($field)->get_all();
- foreach my $value (@values) {
- my $vis_val = $value->visibility_value;
- push(@result, {
- name => $self->type('string', $value->name),
- sort_key => $self->type('int' , $value->sortkey),
- sortkey => $self->type('int' , $value->sortkey), # deprecated
- visibility_values => [
- defined $vis_val ? $self->type('string', $vis_val->name)
- : ()
- ],
- });
+ }
+ else {
+ my @values = Bugzilla::Field::Choice->type($field)->get_all();
+ foreach my $value (@values) {
+ my $vis_val = $value->visibility_value;
+ push(
+ @result,
+ {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int', $value->sortkey),
+ sortkey => $self->type('int', $value->sortkey), # deprecated
+ visibility_values =>
+ [defined $vis_val ? $self->type('string', $vis_val->name) : ()],
}
+ );
}
+ }
- return \@result;
+ return \@result;
}
sub comments {
- my ($self, $params) = validate(@_, 'ids', 'comment_ids');
+ my ($self, $params) = validate(@_, 'ids', 'comment_ids');
- if (!(defined $params->{ids} || defined $params->{comment_ids})) {
- ThrowCodeError('params_required',
- { function => 'Bug.comments',
- params => ['ids', 'comment_ids'] });
- }
+ if (!(defined $params->{ids} || defined $params->{comment_ids})) {
+ ThrowCodeError('params_required',
+ {function => 'Bug.comments', params => ['ids', 'comment_ids']});
+ }
- my $bug_ids = $params->{ids} || [];
- my $comment_ids = $params->{comment_ids} || [];
+ my $bug_ids = $params->{ids} || [];
+ my $comment_ids = $params->{comment_ids} || [];
- my $dbh = Bugzilla->switch_to_shadow_db();
- my $user = Bugzilla->user;
+ my $dbh = Bugzilla->switch_to_shadow_db();
+ my $user = Bugzilla->user;
- my %bugs;
- foreach my $bug_id (@$bug_ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- # We want the API to always return comments in the same order.
+ my %bugs;
+ foreach my $bug_id (@$bug_ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
- my $comments = $bug->comments({ order => 'oldest_to_newest',
- after => $params->{new_since} });
- my @result;
- foreach my $comment (@$comments) {
- next if $comment->is_private && !$user->is_insider;
- push(@result, $self->_translate_comment($comment, $params));
- }
- $bugs{$bug->id}{'comments'} = \@result;
- }
+ # We want the API to always return comments in the same order.
- my %comments;
- if (scalar @$comment_ids) {
- my @ids = map { trim($_) } @$comment_ids;
- my $comment_data = Bugzilla::Comment->new_from_list(\@ids);
-
- # See if we were passed any invalid comment ids.
- my %got_ids = map { $_->id => 1 } @$comment_data;
- foreach my $comment_id (@ids) {
- if (!$got_ids{$comment_id}) {
- ThrowUserError('comment_id_invalid', { id => $comment_id });
- }
- }
+ my $comments
+ = $bug->comments({order => 'oldest_to_newest', after => $params->{new_since}
+ });
+ my @result;
+ foreach my $comment (@$comments) {
+ next if $comment->is_private && !$user->is_insider;
+ push(@result, $self->_translate_comment($comment, $params));
+ }
+ $bugs{$bug->id}{'comments'} = \@result;
+ }
+
+ my %comments;
+ if (scalar @$comment_ids) {
+ my @ids = map { trim($_) } @$comment_ids;
+ my $comment_data = Bugzilla::Comment->new_from_list(\@ids);
+
+ # See if we were passed any invalid comment ids.
+ my %got_ids = map { $_->id => 1 } @$comment_data;
+ foreach my $comment_id (@ids) {
+ if (!$got_ids{$comment_id}) {
+ ThrowUserError('comment_id_invalid', {id => $comment_id});
+ }
+ }
- # Now make sure that we can see all the associated bugs.
- my %got_bug_ids = map { $_->bug_id => 1 } @$comment_data;
- Bugzilla::Bug->check($_) foreach (keys %got_bug_ids);
+ # Now make sure that we can see all the associated bugs.
+ my %got_bug_ids = map { $_->bug_id => 1 } @$comment_data;
+ Bugzilla::Bug->check($_) foreach (keys %got_bug_ids);
- foreach my $comment (@$comment_data) {
- if ($comment->is_private && !$user->is_insider) {
- ThrowUserError('comment_is_private', { id => $comment->id });
- }
- $comments{$comment->id} =
- $self->_translate_comment($comment, $params);
- }
+ foreach my $comment (@$comment_data) {
+ if ($comment->is_private && !$user->is_insider) {
+ ThrowUserError('comment_is_private', {id => $comment->id});
+ }
+ $comments{$comment->id} = $self->_translate_comment($comment, $params);
}
+ }
- return { bugs => \%bugs, comments => \%comments };
+ return {bugs => \%bugs, comments => \%comments};
}
sub render_comment {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- unless (defined $params->{text}) {
- ThrowCodeError('params_required',
- { function => 'Bug.render_comment',
- params => ['text'] });
- }
+ unless (defined $params->{text}) {
+ ThrowCodeError('params_required',
+ {function => 'Bug.render_comment', params => ['text']});
+ }
- Bugzilla->switch_to_shadow_db();
- my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
+ Bugzilla->switch_to_shadow_db();
+ my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
- my $html = Bugzilla::Template::quoteUrls($params->{text}, $bug);
+ my $html = Bugzilla::Template::quoteUrls($params->{text}, $bug);
- return { html => $html };
+ return {html => $html};
}
# Helper for Bug.comments
sub _translate_comment {
- my ($self, $comment, $filters, $types, $prefix) = @_;
- my $attach_id = $comment->is_about_attachment ? $comment->extra_data
- : undef;
-
- my $comment_hash = {
- id => $self->type('int', $comment->id),
- bug_id => $self->type('int', $comment->bug_id),
- creator => $self->type('email', $comment->author->login),
- author => $self->type('email', $comment->author->login),
- time => $self->type('dateTime', $comment->creation_ts),
- creation_time => $self->type('dateTime', $comment->creation_ts),
- is_private => $self->type('boolean', $comment->is_private),
- text => $self->type('string', $comment->body_full),
- attachment_id => $self->type('int', $attach_id),
- count => $self->type('int', $comment->count),
- };
-
- # Don't load comment tags unless enabled
- if (Bugzilla->params->{'comment_taggers_group'}) {
- $comment_hash->{tags} = [
- map { $self->type('string', $_) }
- @{ $comment->tags }
- ];
- }
-
- return filter($filters, $comment_hash, $types, $prefix);
+ my ($self, $comment, $filters, $types, $prefix) = @_;
+ my $attach_id = $comment->is_about_attachment ? $comment->extra_data : undef;
+
+ my $comment_hash = {
+ id => $self->type('int', $comment->id),
+ bug_id => $self->type('int', $comment->bug_id),
+ creator => $self->type('email', $comment->author->login),
+ author => $self->type('email', $comment->author->login),
+ time => $self->type('dateTime', $comment->creation_ts),
+ creation_time => $self->type('dateTime', $comment->creation_ts),
+ is_private => $self->type('boolean', $comment->is_private),
+ text => $self->type('string', $comment->body_full),
+ attachment_id => $self->type('int', $attach_id),
+ count => $self->type('int', $comment->count),
+ };
+
+ # Don't load comment tags unless enabled
+ if (Bugzilla->params->{'comment_taggers_group'}) {
+ $comment_hash->{tags} = [map { $self->type('string', $_) } @{$comment->tags}];
+ }
+
+ return filter($filters, $comment_hash, $types, $prefix);
}
sub get {
- my ($self, $params) = validate(@_, 'ids');
-
- unless (Bugzilla->user->id) {
- Bugzilla->check_rate_limit("get_bug", remote_ip());
+ my ($self, $params) = validate(@_, 'ids');
+
+ unless (Bugzilla->user->id) {
+ Bugzilla->check_rate_limit("get_bug", remote_ip());
+ }
+ Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
+
+ my $ids = $params->{ids};
+ (defined $ids && scalar @$ids)
+ || ThrowCodeError('param_required', {param => 'ids'});
+
+ my (@bugs, @faults, @hashes);
+
+ # Cache permissions for bugs. This highly reduces the number of calls to the DB.
+ # visible_bugs() is only able to handle bug IDs, so we have to skip aliases.
+ my @int = grep { $_ =~ /^\d+$/ } @$ids;
+ Bugzilla->user->visible_bugs(\@int);
+
+ foreach my $bug_id (@$ids) {
+ my $bug;
+ if ($params->{permissive}) {
+ eval { $bug = Bugzilla::Bug->check($bug_id); };
+ if ($@) {
+ push(@faults,
+ {id => $bug_id, faultString => $@->faultstring, faultCode => $@->faultcode,});
+ undef $@;
+ next;
+ }
}
- Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
-
- my $ids = $params->{ids};
- (defined $ids && scalar @$ids)
- || ThrowCodeError('param_required', { param => 'ids' });
-
- my (@bugs, @faults, @hashes);
-
- # Cache permissions for bugs. This highly reduces the number of calls to the DB.
- # visible_bugs() is only able to handle bug IDs, so we have to skip aliases.
- my @int = grep { $_ =~ /^\d+$/ } @$ids;
- Bugzilla->user->visible_bugs(\@int);
-
- foreach my $bug_id (@$ids) {
- my $bug;
- if ($params->{permissive}) {
- eval { $bug = Bugzilla::Bug->check($bug_id); };
- if ($@) {
- push(@faults, {id => $bug_id,
- faultString => $@->faultstring,
- faultCode => $@->faultcode,
- }
- );
- undef $@;
- next;
- }
- }
- else {
- $bug = Bugzilla::Bug->check($bug_id);
- }
- push(@bugs, $bug);
- push(@hashes, $self->_bug_to_hash($bug, $params));
+ else {
+ $bug = Bugzilla::Bug->check($bug_id);
}
+ push(@bugs, $bug);
+ push(@hashes, $self->_bug_to_hash($bug, $params));
+ }
- # Set the ETag before inserting the update tokens
- # since the tokens will always be unique even if
- # the data has not changed.
- $self->bz_etag(\@hashes);
+ # Set the ETag before inserting the update tokens
+ # since the tokens will always be unique even if
+ # the data has not changed.
+ $self->bz_etag(\@hashes);
- $self->_add_update_tokens($params, \@bugs, \@hashes);
+ $self->_add_update_tokens($params, \@bugs, \@hashes);
- if (Bugzilla->user->id) {
- foreach my $bug (@bugs) {
- Bugzilla->log_user_request($bug->id, undef, 'bug-get');
- }
+ if (Bugzilla->user->id) {
+ foreach my $bug (@bugs) {
+ Bugzilla->log_user_request($bug->id, undef, 'bug-get');
}
- return { bugs => \@hashes, faults => \@faults };
+ }
+ return {bugs => \@hashes, faults => \@faults};
}
# this is a function that gets bug activity for list of bug ids
# it can be called as the following:
# $call = $rpc->call( 'Bug.history', { ids => [1,2] });
sub history {
- my ($self, $params) = validate(@_, 'ids');
-
- Bugzilla->switch_to_shadow_db();
-
- my $ids = $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
-
- my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
- $api_name{'bug_group'} = 'groups';
-
- my @return;
- foreach my $bug_id (@$ids) {
- my %item;
- my $bug = Bugzilla::Bug->check($bug_id);
- $bug_id = $bug->id;
- $item{id} = $self->type('int', $bug_id);
-
- my ($activity) = Bugzilla::Bug::GetBugActivity($bug_id, undef, $params->{new_since});
-
- my @history;
- foreach my $changeset (@$activity) {
- my %bug_history;
- $bug_history{when} = $self->type('dateTime', $changeset->{when});
- $bug_history{who} = $self->type('email', $changeset->{who});
- $bug_history{changes} = [];
- foreach my $change (@{ $changeset->{changes} }) {
- my $api_field = $api_name{$change->{fieldname}} || $change->{fieldname};
- my $attach_id = delete $change->{attachid};
- if ($attach_id) {
- $change->{attachment_id} = $self->type('int', $attach_id);
- }
- $change->{removed} = $self->type('string', $change->{removed});
- $change->{added} = $self->type('string', $change->{added});
- $change->{field_name} = $self->type('string', $api_field);
- delete $change->{fieldname};
- push (@{$bug_history{changes}}, $change);
- }
-
- push (@history, \%bug_history);
- }
-
- $item{history} = \@history;
-
- # alias is returned in case users passes a mixture of ids and aliases
- # then they get to know which bug activity relates to which value
- # they passed
- if (Bugzilla->params->{'usebugaliases'}) {
- $item{alias} = $self->type('string', $bug->alias);
+ my ($self, $params) = validate(@_, 'ids');
+
+ Bugzilla->switch_to_shadow_db();
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ my %api_name = reverse %{Bugzilla::Bug::FIELD_MAP()};
+ $api_name{'bug_group'} = 'groups';
+
+ my @return;
+ foreach my $bug_id (@$ids) {
+ my %item;
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bug_id = $bug->id;
+ $item{id} = $self->type('int', $bug_id);
+
+ my ($activity)
+ = Bugzilla::Bug::GetBugActivity($bug_id, undef, $params->{new_since});
+
+ my @history;
+ foreach my $changeset (@$activity) {
+ my %bug_history;
+ $bug_history{when} = $self->type('dateTime', $changeset->{when});
+ $bug_history{who} = $self->type('email', $changeset->{who});
+ $bug_history{changes} = [];
+ foreach my $change (@{$changeset->{changes}}) {
+ my $api_field = $api_name{$change->{fieldname}} || $change->{fieldname};
+ my $attach_id = delete $change->{attachid};
+ if ($attach_id) {
+ $change->{attachment_id} = $self->type('int', $attach_id);
}
- else {
- # For API reasons, we always want the value to appear, we just
- # don't want it to have a value if aliases are turned off.
- $item{alias} = undef;
- }
-
- push(@return, \%item);
- }
-
- return { bugs => \@return };
-}
-
-sub search {
- my ($self, $params) = @_;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
-
- Bugzilla->switch_to_shadow_db();
-
- my $match_params = dclone($params);
- delete $match_params->{include_fields};
- delete $match_params->{exclude_fields};
-
- # Determine whether this is a quicksearch query
- if (exists $match_params->{quicksearch}) {
- my $quicksearch = quicksearch($match_params->{'quicksearch'});
- my $cgi = Bugzilla::CGI->new($quicksearch);
- $match_params = $cgi->Vars;
+ $change->{removed} = $self->type('string', $change->{removed});
+ $change->{added} = $self->type('string', $change->{added});
+ $change->{field_name} = $self->type('string', $api_field);
+ delete $change->{fieldname};
+ push(@{$bug_history{changes}}, $change);
+ }
+
+ push(@history, \%bug_history);
}
- if ( defined($match_params->{offset}) and !defined($match_params->{limit}) ) {
- ThrowCodeError('param_required',
- { param => 'limit', function => 'Bug.search()' });
- }
+ $item{history} = \@history;
- my $max_results = Bugzilla->params->{max_search_results};
- unless (defined $match_params->{limit} && $match_params->{limit} == 0) {
- if (!defined $match_params->{limit} || $match_params->{limit} > $max_results) {
- $match_params->{limit} = $max_results;
- }
+ # alias is returned in case users passes a mixture of ids and aliases
+ # then they get to know which bug activity relates to which value
+ # they passed
+ if (Bugzilla->params->{'usebugaliases'}) {
+ $item{alias} = $self->type('string', $bug->alias);
}
else {
- delete $match_params->{limit};
- delete $match_params->{offset};
+ # For API reasons, we always want the value to appear, we just
+ # don't want it to have a value if aliases are turned off.
+ $item{alias} = undef;
}
- $match_params = Bugzilla::Bug::map_fields($match_params);
-
- my %options = ( fields => ['bug_id'] );
+ push(@return, \%item);
+ }
- # Find the highest custom field id
- my @field_ids = grep(/^f(\d+)$/, keys %$match_params);
- my $last_field_id = @field_ids ? max @field_ids + 1 : 1;
+ return {bugs => \@return};
+}
- # Do special search types for certain fields.
- if (my $change_when = delete $match_params->{'delta_ts'}) {
- $match_params->{"f${last_field_id}"} = 'delta_ts';
- $match_params->{"o${last_field_id}"} = 'greaterthaneq';
- $match_params->{"v${last_field_id}"} = $change_when;
- $last_field_id++;
+sub search {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->switch_to_shadow_db();
+
+ my $match_params = dclone($params);
+ delete $match_params->{include_fields};
+ delete $match_params->{exclude_fields};
+
+ # Determine whether this is a quicksearch query
+ if (exists $match_params->{quicksearch}) {
+ my $quicksearch = quicksearch($match_params->{'quicksearch'});
+ my $cgi = Bugzilla::CGI->new($quicksearch);
+ $match_params = $cgi->Vars;
+ }
+
+ if (defined($match_params->{offset}) and !defined($match_params->{limit})) {
+ ThrowCodeError('param_required',
+ {param => 'limit', function => 'Bug.search()'});
+ }
+
+ my $max_results = Bugzilla->params->{max_search_results};
+ unless (defined $match_params->{limit} && $match_params->{limit} == 0) {
+ if (!defined $match_params->{limit} || $match_params->{limit} > $max_results) {
+ $match_params->{limit} = $max_results;
}
- if (my $creation_when = delete $match_params->{'creation_ts'}) {
- $match_params->{"f${last_field_id}"} = 'creation_ts';
- $match_params->{"o${last_field_id}"} = 'greaterthaneq';
- $match_params->{"v${last_field_id}"} = $creation_when;
- $last_field_id++;
+ }
+ else {
+ delete $match_params->{limit};
+ delete $match_params->{offset};
+ }
+
+ $match_params = Bugzilla::Bug::map_fields($match_params);
+
+ my %options = (fields => ['bug_id']);
+
+ # Find the highest custom field id
+ my @field_ids = grep(/^f(\d+)$/, keys %$match_params);
+ my $last_field_id = @field_ids ? max @field_ids + 1 : 1;
+
+ # Do special search types for certain fields.
+ if (my $change_when = delete $match_params->{'delta_ts'}) {
+ $match_params->{"f${last_field_id}"} = 'delta_ts';
+ $match_params->{"o${last_field_id}"} = 'greaterthaneq';
+ $match_params->{"v${last_field_id}"} = $change_when;
+ $last_field_id++;
+ }
+ if (my $creation_when = delete $match_params->{'creation_ts'}) {
+ $match_params->{"f${last_field_id}"} = 'creation_ts';
+ $match_params->{"o${last_field_id}"} = 'greaterthaneq';
+ $match_params->{"v${last_field_id}"} = $creation_when;
+ $last_field_id++;
+ }
+
+ # Some fields require a search type such as short desc, keywords, etc.
+ foreach my $param (qw(short_desc longdesc status_whiteboard bug_file_loc)) {
+ if (defined $match_params->{$param}
+ && !defined $match_params->{$param . '_type'})
+ {
+ $match_params->{$param . '_type'} = 'allwordssubstr';
}
-
- # Some fields require a search type such as short desc, keywords, etc.
- foreach my $param (qw(short_desc longdesc status_whiteboard bug_file_loc)) {
- if (defined $match_params->{$param} && !defined $match_params->{$param . '_type'}) {
- $match_params->{$param . '_type'} = 'allwordssubstr';
- }
+ }
+ if (defined $match_params->{'keywords'}
+ && !defined $match_params->{'keywords_type'})
+ {
+ $match_params->{'keywords_type'} = 'allwords';
+ }
+
+ # Backwards compatibility with old method regarding role search
+ $match_params->{'reporter'} = delete $match_params->{'creator'}
+ if $match_params->{'creator'};
+ foreach my $role (qw(assigned_to reporter qa_contact commenter cc)) {
+ next if !exists $match_params->{$role};
+ my $value = delete $match_params->{$role};
+ $match_params->{"f${last_field_id}"} = $role;
+ $match_params->{"o${last_field_id}"} = "anywordssubstr";
+ $match_params->{"v${last_field_id}"}
+ = ref $value ? join(" ", @{$value}) : $value;
+ $last_field_id++;
+ }
+
+ # If no other parameters have been passed other than limit and offset
+ # then we throw error if system is configured to do so.
+ if ( !grep(!/^(limit|offset)$/, keys %$match_params)
+ && !Bugzilla->params->{search_allow_no_criteria})
+ {
+ ThrowUserError('buglist_parameters_required');
+ }
+
+ # Allow the use of order shortcuts similar to web UI
+ if ($match_params->{order}) {
+
+ # Convert the value of the "order" form field into a list of columns
+ # by which to sort the results.
+ my %order_types = (
+ "Bug Number" => ["bug_id"],
+ "Importance" => ["priority", "bug_severity"],
+ "Assignee" => ["assigned_to", "bug_status", "priority", "bug_id"],
+ "Last Changed" =>
+ ["changeddate", "bug_status", "priority", "assigned_to", "bug_id"],
+ );
+ if ($order_types{$match_params->{order}}) {
+ $options{order} = $order_types{$match_params->{order}};
}
- if (defined $match_params->{'keywords'} && !defined $match_params->{'keywords_type'}) {
- $match_params->{'keywords_type'} = 'allwords';
+ else {
+ $options{order} = [split(/\s*,\s*/, $match_params->{order})];
}
+ }
- # Backwards compatibility with old method regarding role search
- $match_params->{'reporter'} = delete $match_params->{'creator'} if $match_params->{'creator'};
- foreach my $role (qw(assigned_to reporter qa_contact commenter cc)) {
- next if !exists $match_params->{$role};
- my $value = delete $match_params->{$role};
- $match_params->{"f${last_field_id}"} = $role;
- $match_params->{"o${last_field_id}"} = "anywordssubstr";
- $match_params->{"v${last_field_id}"} = ref $value ? join(" ", @{$value}) : $value;
- $last_field_id++;
- }
+ $options{params} = $match_params;
- # If no other parameters have been passed other than limit and offset
- # then we throw error if system is configured to do so.
- if (!grep(!/^(limit|offset)$/, keys %$match_params)
- && !Bugzilla->params->{search_allow_no_criteria})
- {
- ThrowUserError('buglist_parameters_required');
- }
+ my $search = new Bugzilla::Search(%options);
+ my ($data) = $search->data;
- # Allow the use of order shortcuts similar to web UI
- if ($match_params->{order}) {
- # Convert the value of the "order" form field into a list of columns
- # by which to sort the results.
- my %order_types = (
- "Bug Number" => [ "bug_id" ],
- "Importance" => [ "priority", "bug_severity" ],
- "Assignee" => [ "assigned_to", "bug_status", "priority", "bug_id" ],
- "Last Changed" => [ "changeddate", "bug_status", "priority",
- "assigned_to", "bug_id" ],
- );
- if ($order_types{$match_params->{order}}) {
- $options{order} = $order_types{$match_params->{order}};
- }
- else {
- $options{order} = [ split(/\s*,\s*/, $match_params->{order}) ];
- }
+ # BMO if the caller only wants the count, that's all we need to return
+ if ($params->{count_only}) {
+ if (Bugzilla->usage_mode == USAGE_MODE_XMLRPC) {
+ return $data;
}
-
- $options{params} = $match_params;
-
- my $search = new Bugzilla::Search(%options);
- my ($data) = $search->data;
-
- # BMO if the caller only wants the count, that's all we need to return
- if ($params->{count_only}) {
- if (Bugzilla->usage_mode == USAGE_MODE_XMLRPC) {
- return $data;
- }
- else {
- return { bug_count => $data };
- }
+ else {
+ return {bug_count => $data};
}
+ }
- if (!scalar @$data) {
- return { bugs => [] };
- }
+ if (!scalar @$data) {
+ return {bugs => []};
+ }
- # Search.pm won't return bugs that the user shouldn't see so no filtering is needed.
- my @bug_ids = map { $_->[0] } @$data;
- my %bug_objects = map { $_->id => $_ } @{ Bugzilla::Bug->new_from_list(\@bug_ids) };
- my @bugs = map { $bug_objects{$_} } @bug_ids;
- @bugs = map { $self->_bug_to_hash($_, $params) } @bugs;
+# Search.pm won't return bugs that the user shouldn't see so no filtering is needed.
+ my @bug_ids = map { $_->[0] } @$data;
+ my %bug_objects
+ = map { $_->id => $_ } @{Bugzilla::Bug->new_from_list(\@bug_ids)};
+ my @bugs = map { $bug_objects{$_} } @bug_ids;
+ @bugs = map { $self->_bug_to_hash($_, $params) } @bugs;
- # BzAPI
- Bugzilla->request_cache->{bzapi_search_bugs} = [ map { $bug_objects{$_} } @bug_ids ];
+ # BzAPI
+ Bugzilla->request_cache->{bzapi_search_bugs}
+ = [map { $bug_objects{$_} } @bug_ids];
- return { bugs => \@bugs };
+ return {bugs => \@bugs};
}
sub possible_duplicates {
- my ($self, $params) = validate(@_, 'product');
- my $user = Bugzilla->user;
-
- Bugzilla->switch_to_shadow_db();
-
- state $params_type = Dict [
- id => Optional [Int],
- product => Optional [ ArrayRef [Str] ],
- limit => Optional [Int],
- summary => Optional [Str],
- include_fields => Optional [ ArrayRef [Str] ],
- Bugzilla_api_token => Optional [Str]
- ];
-
- ThrowCodeError( 'param_invalid', { function => 'Bug.possible_duplicates', param => 'A param' } )
- if !$params_type->check($params);
-
- my $summary;
- if ($params->{id}) {
- my $bug = Bugzilla::Bug->check({ id => $params->{id}, cache => 1 });
- $summary = $bug->short_desc;
- }
- elsif ($params->{summary}) {
- $summary = $params->{summary};
- }
- else {
- ThrowCodeError('param_required',
- { function => 'Bug.possible_duplicates', param => 'id or summary' });
- }
-
- my @products;
- foreach my $name (@{ $params->{'product'} || [] }) {
- my $object = $user->can_enter_product($name, THROW_ERROR);
- push(@products, $object);
- }
-
- my $possible_dupes = Bugzilla::Bug->possible_duplicates(
- {
- summary => $summary,
- products => \@products,
- limit => $params->{limit}
- }
- );
+ my ($self, $params) = validate(@_, 'product');
+ my $user = Bugzilla->user;
+
+ Bugzilla->switch_to_shadow_db();
+
+ state $params_type = Dict [
+ id => Optional [Int],
+ product => Optional [ArrayRef [Str]],
+ limit => Optional [Int],
+ summary => Optional [Str],
+ include_fields => Optional [ArrayRef [Str]],
+ Bugzilla_api_token => Optional [Str]
+ ];
+
+ ThrowCodeError('param_invalid',
+ {function => 'Bug.possible_duplicates', param => 'A param'})
+ if !$params_type->check($params);
+
+ my $summary;
+ if ($params->{id}) {
+ my $bug = Bugzilla::Bug->check({id => $params->{id}, cache => 1});
+ $summary = $bug->short_desc;
+ }
+ elsif ($params->{summary}) {
+ $summary = $params->{summary};
+ }
+ else {
+ ThrowCodeError('param_required',
+ {function => 'Bug.possible_duplicates', param => 'id or summary'});
+ }
+
+ my @products;
+ foreach my $name (@{$params->{'product'} || []}) {
+ my $object = $user->can_enter_product($name, THROW_ERROR);
+ push(@products, $object);
+ }
+
+ my $possible_dupes
+ = Bugzilla::Bug->possible_duplicates({
+ summary => $summary, products => \@products, limit => $params->{limit}
+ });
- # If a bug id was used, remove the bug with the same id from the list.
- if ($params->{id}) {
- @$possible_dupes = grep { $_->id != $params->{id} } @$possible_dupes;
- }
+ # If a bug id was used, remove the bug with the same id from the list.
+ if ($params->{id}) {
+ @$possible_dupes = grep { $_->id != $params->{id} } @$possible_dupes;
+ }
- my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
- $self->_add_update_tokens($params, $possible_dupes, \@hashes);
- return { bugs => \@hashes };
+ my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
+ $self->_add_update_tokens($params, $possible_dupes, \@hashes);
+ return {bugs => \@hashes};
}
sub update {
- my ($self, $params) = validate(@_, 'ids');
+ my ($self, $params) = validate(@_, 'ids');
- # BMO: Don't allow updating of bugs if disabled
- if (Bugzilla->params->{disable_bug_updates}) {
- ThrowErrorPage('bug/process/updates-disabled.html.tmpl',
- 'Bug updates are currently disabled.');
- }
+ # BMO: Don't allow updating of bugs if disabled
+ if (Bugzilla->params->{disable_bug_updates}) {
+ ThrowErrorPage(
+ 'bug/process/updates-disabled.html.tmpl',
+ 'Bug updates are currently disabled.'
+ );
+ }
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
- # We skip certain fields because their set_ methods actually use
- # the external names instead of the internal names.
- $params = Bugzilla::Bug::map_fields($params,
- { summary => 1, platform => 1, severity => 1, url => 1 });
+ # We skip certain fields because their set_ methods actually use
+ # the external names instead of the internal names.
+ $params = Bugzilla::Bug::map_fields($params,
+ {summary => 1, platform => 1, severity => 1, url => 1});
- my $ids = delete $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
- my @bugs = map { Bugzilla::Bug->check($_) } @$ids;
+ my @bugs = map { Bugzilla::Bug->check($_) } @$ids;
- my %values = %$params;
- $values{other_bugs} = \@bugs;
+ my %values = %$params;
+ $values{other_bugs} = \@bugs;
- if (exists $values{comment} and exists $values{comment}{comment}) {
- $values{comment}{body} = delete $values{comment}{comment};
- }
+ if (exists $values{comment} and exists $values{comment}{comment}) {
+ $values{comment}{body} = delete $values{comment}{comment};
+ }
- # Prevent bugs that could be triggered by specifying fields that
- # have valid "set_" functions in Bugzilla::Bug, but shouldn't be
- # called using those field names.
- delete $values{dependencies};
+ # Prevent bugs that could be triggered by specifying fields that
+ # have valid "set_" functions in Bugzilla::Bug, but shouldn't be
+ # called using those field names.
+ delete $values{dependencies};
- my $flags = delete $values{flags};
+ my $flags = delete $values{flags};
- foreach my $bug (@bugs) {
- if (!$user->can_edit_product($bug->product_obj->id) ) {
- ThrowUserError("product_edit_denied",
- { product => $bug->product });
- }
-
- $bug->set_all(\%values);
- if ($flags) {
- my ($old_flags, $new_flags) = extract_flags($flags, $bug);
- $bug->set_flags($old_flags, $new_flags);
- }
+ foreach my $bug (@bugs) {
+ if (!$user->can_edit_product($bug->product_obj->id)) {
+ ThrowUserError("product_edit_denied", {product => $bug->product});
}
- my %all_changes;
- $dbh->bz_start_transaction();
- foreach my $bug (@bugs) {
- $all_changes{$bug->id} = $bug->update();
+ $bug->set_all(\%values);
+ if ($flags) {
+ my ($old_flags, $new_flags) = extract_flags($flags, $bug);
+ $bug->set_flags($old_flags, $new_flags);
}
- $dbh->bz_commit_transaction();
+ }
+
+ my %all_changes;
+ $dbh->bz_start_transaction();
+ foreach my $bug (@bugs) {
+ $all_changes{$bug->id} = $bug->update();
+ }
+ $dbh->bz_commit_transaction();
+
+ foreach my $bug (@bugs) {
+ $bug->send_changes($all_changes{$bug->id});
+ }
+
+ my %api_name = reverse %{Bugzilla::Bug::FIELD_MAP()};
+
+ # This doesn't normally belong in FIELD_MAP, but we do want to translate
+ # "bug_group" back into "groups".
+ $api_name{'bug_group'} = 'groups';
+
+ my @result;
+ foreach my $bug (@bugs) {
+ my %hash = (
+ id => $self->type('int', $bug->id),
+ last_change_time => $self->type('dateTime', $bug->delta_ts),
+ changes => {},
+ );
- foreach my $bug (@bugs) {
- $bug->send_changes($all_changes{$bug->id});
+ # alias is returned in case users pass a mixture of ids and aliases,
+ # so that they can know which set of changes relates to which value
+ # they passed.
+ if (Bugzilla->params->{'usebugaliases'}) {
+ $hash{alias} = $self->type('string', $bug->alias);
+ }
+ else {
+ # For API reasons, we always want the alias field to appear, we
+ # just don't want it to have a value if aliases are turned off.
+ $hash{alias} = $self->type('string', '');
}
- my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
- # This doesn't normally belong in FIELD_MAP, but we do want to translate
- # "bug_group" back into "groups".
- $api_name{'bug_group'} = 'groups';
-
- my @result;
- foreach my $bug (@bugs) {
- my %hash = (
- id => $self->type('int', $bug->id),
- last_change_time => $self->type('dateTime', $bug->delta_ts),
- changes => {},
- );
-
- # alias is returned in case users pass a mixture of ids and aliases,
- # so that they can know which set of changes relates to which value
- # they passed.
- if (Bugzilla->params->{'usebugaliases'}) {
- $hash{alias} = $self->type('string', $bug->alias);
- }
- else {
- # For API reasons, we always want the alias field to appear, we
- # just don't want it to have a value if aliases are turned off.
- $hash{alias} = $self->type('string', '');
- }
-
- my %changes = %{ $all_changes{$bug->id} };
- foreach my $field (keys %changes) {
- my $change = $changes{$field};
- my $api_field = $api_name{$field} || $field;
- # We normalize undef to an empty string, so that the API
- # stays consistent for things like Deadline that can become
- # empty.
- $change->[0] = '' if !defined $change->[0];
- $change->[1] = '' if !defined $change->[1];
- $hash{changes}->{$api_field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
-
- push(@result, \%hash);
+ my %changes = %{$all_changes{$bug->id}};
+ foreach my $field (keys %changes) {
+ my $change = $changes{$field};
+ my $api_field = $api_name{$field} || $field;
+
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
+ $hash{changes}->{$api_field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
}
- return { bugs => \@result };
+ push(@result, \%hash);
+ }
+
+ return {bugs => \@result};
}
sub create {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
-
- # BMO: Don't allow updating of bugs if disabled
- if (Bugzilla->params->{disable_bug_updates}) {
- ThrowErrorPage('bug/process/updates-disabled.html.tmpl',
- 'Bug updates are currently disabled.');
- }
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # BMO: Don't allow updating of bugs if disabled
+ if (Bugzilla->params->{disable_bug_updates}) {
+ ThrowErrorPage(
+ 'bug/process/updates-disabled.html.tmpl',
+ 'Bug updates are currently disabled.'
+ );
+ }
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
- # Some fields cannot be sent to Bugzilla::Bug->create
- foreach my $key (qw(login password token)) {
- delete $params->{$key};
- }
+ # Some fields cannot be sent to Bugzilla::Bug->create
+ foreach my $key (qw(login password token)) {
+ delete $params->{$key};
+ }
- $params = Bugzilla::Bug::map_fields($params);
+ $params = Bugzilla::Bug::map_fields($params);
- my $flags = delete $params->{flags};
+ my $flags = delete $params->{flags};
- # We start a nested transaction in case flag setting fails
- # we want the bug creation to roll back as well.
- $dbh->bz_start_transaction();
+ # We start a nested transaction in case flag setting fails
+ # we want the bug creation to roll back as well.
+ $dbh->bz_start_transaction();
- my $bug = Bugzilla::Bug->create($params);
+ my $bug = Bugzilla::Bug->create($params);
- # Set bug flags
- if ($flags) {
- my ($flags, $new_flags) = extract_flags($flags, $bug);
- $bug->set_flags($flags, $new_flags);
- $bug->update($bug->creation_ts);
- }
+ # Set bug flags
+ if ($flags) {
+ my ($flags, $new_flags) = extract_flags($flags, $bug);
+ $bug->set_flags($flags, $new_flags);
+ $bug->update($bug->creation_ts);
+ }
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
- $bug->send_changes();
+ $bug->send_changes();
- return { id => $self->type('int', $bug->bug_id) };
+ return {id => $self->type('int', $bug->bug_id)};
}
sub legal_values {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- defined $params->{field}
- or ThrowCodeError('param_required', { param => 'field' });
+ defined $params->{field}
+ or ThrowCodeError('param_required', {param => 'field'});
- my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}}
- || $params->{field};
+ my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}} || $params->{field};
- my @global_selects =
- @{ Bugzilla->fields({ is_select => 1, is_abnormal => 0 }) };
+ my @global_selects = @{Bugzilla->fields({is_select => 1, is_abnormal => 0})};
- my $values;
- if (grep($_->name eq $field, @global_selects)) {
- # The field is a valid one.
- trick_taint($field);
- $values = get_legal_field_values($field);
- }
- elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
- my $id = $params->{product_id};
- defined $id || ThrowCodeError('param_required',
- { function => 'Bug.legal_values', param => 'product_id' });
- grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
- || ThrowUserError('product_access_denied', { id => $id });
-
- my $product = new Bugzilla::Product($id);
- my @objects;
- if ($field eq 'version') {
- @objects = @{$product->versions};
- }
- elsif ($field eq 'target_milestone') {
- @objects = @{$product->milestones};
- }
- elsif ($field eq 'component') {
- @objects = @{$product->components};
- }
+ my $values;
+ if (grep($_->name eq $field, @global_selects)) {
- $values = [map { $_->name } @objects];
+ # The field is a valid one.
+ trick_taint($field);
+ $values = get_legal_field_values($field);
+ }
+ elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
+ my $id = $params->{product_id};
+ defined $id
+ || ThrowCodeError('param_required',
+ {function => 'Bug.legal_values', param => 'product_id'});
+ grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
+ || ThrowUserError('product_access_denied', {id => $id});
+
+ my $product = new Bugzilla::Product($id);
+ my @objects;
+ if ($field eq 'version') {
+ @objects = @{$product->versions};
}
- else {
- ThrowCodeError('invalid_field_name', { field => $params->{field} });
+ elsif ($field eq 'target_milestone') {
+ @objects = @{$product->milestones};
}
-
- my @result;
- foreach my $val (@$values) {
- push(@result, $self->type('string', $val));
+ elsif ($field eq 'component') {
+ @objects = @{$product->components};
}
- return { values => \@result };
+ $values = [map { $_->name } @objects];
+ }
+ else {
+ ThrowCodeError('invalid_field_name', {field => $params->{field}});
+ }
+
+ my @result;
+ foreach my $val (@$values) {
+ push(@result, $self->type('string', $val));
+ }
+
+ return {values => \@result};
}
sub add_attachment {
- my ($self, $params) = validate(@_, 'ids');
- my $dbh = Bugzilla->dbh;
+ my ($self, $params) = validate(@_, 'ids');
+ my $dbh = Bugzilla->dbh;
+
+ # BMO: Don't allow updating of bugs if disabled
+ if (Bugzilla->params->{disable_bug_updates}) {
+ ThrowErrorPage(
+ 'bug/process/updates-disabled.html.tmpl',
+ 'Bug updates are currently disabled.'
+ );
+ }
- # BMO: Don't allow updating of bugs if disabled
- if (Bugzilla->params->{disable_bug_updates}) {
- ThrowErrorPage('bug/process/updates-disabled.html.tmpl',
- 'Bug updates are currently disabled.');
- }
+ Bugzilla->login(LOGIN_REQUIRED);
+ defined $params->{ids} || ThrowCodeError('param_required', {param => 'ids'});
+ defined $params->{data} || ThrowCodeError('param_required', {param => 'data'});
- Bugzilla->login(LOGIN_REQUIRED);
- defined $params->{ids}
- || ThrowCodeError('param_required', { param => 'ids' });
- defined $params->{data}
- || ThrowCodeError('param_required', { param => 'data' });
+ my @bugs = map { Bugzilla::Bug->check($_) } @{$params->{ids}};
+ foreach my $bug (@bugs) {
+ Bugzilla->user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied", {product => $bug->product});
+ }
+
+ my @created;
+ $dbh->bz_start_transaction();
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my $flags = delete $params->{flags};
+
+ foreach my $bug (@bugs) {
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $timestamp,
+ data => $params->{data},
+ description => $params->{summary},
+ filename => $params->{file_name},
+ mimetype => $params->{content_type},
+ ispatch => $params->{is_patch},
+ isprivate => $params->{is_private},
+ });
- my @bugs = map { Bugzilla::Bug->check($_) } @{ $params->{ids} };
- foreach my $bug (@bugs) {
- Bugzilla->user->can_edit_product($bug->product_id)
- || ThrowUserError("product_edit_denied", {product => $bug->product});
+ if ($flags) {
+ my ($old_flags, $new_flags) = extract_flags($flags, $bug, $attachment);
+ $attachment->set_flags($old_flags, $new_flags);
}
- my @created;
- $dbh->bz_start_transaction();
- my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- my $flags = delete $params->{flags};
-
- foreach my $bug (@bugs) {
- my $attachment = Bugzilla::Attachment->create({
- bug => $bug,
- creation_ts => $timestamp,
- data => $params->{data},
- description => $params->{summary},
- filename => $params->{file_name},
- mimetype => $params->{content_type},
- ispatch => $params->{is_patch},
- isprivate => $params->{is_private},
- });
-
- if ($flags) {
- my ($old_flags, $new_flags) = extract_flags($flags, $bug, $attachment);
- $attachment->set_flags($old_flags, $new_flags);
- }
-
- $attachment->update($timestamp);
- my $comment = $params->{comment} || '';
- $attachment->bug->add_comment($comment,
- { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_CREATED,
- extra_data => $attachment->id });
- push(@created, $attachment);
- }
- $_->bug->update($timestamp) foreach @created;
- $dbh->bz_commit_transaction();
+ $attachment->update($timestamp);
+ my $comment = $params->{comment} || '';
+ $attachment->bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id
+ }
+ );
+ push(@created, $attachment);
+ }
+ $_->bug->update($timestamp) foreach @created;
+ $dbh->bz_commit_transaction();
- $_->send_changes() foreach @bugs;
+ $_->send_changes() foreach @bugs;
- my %attachments = map { $_->id => $self->_attachment_to_hash($_, $params) }
- @created;
+ my %attachments
+ = map { $_->id => $self->_attachment_to_hash($_, $params) } @created;
- return { attachments => \%attachments };
+ return {attachments => \%attachments};
}
sub update_attachment {
- my ($self, $params) = validate(@_, 'ids');
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
-
- my $ids = delete $params->{ids};
- defined $ids || ThrowCodeError('param_required', { param => 'ids' });
-
- # Some fields cannot be sent to set_all
- foreach my $key (qw(login password token)) {
- delete $params->{$key};
- }
-
- $params = translate($params, ATTACHMENT_MAPPED_SETTERS);
-
- # Get all the attachments, after verifying that they exist and are editable
- my @attachments = ();
- my %bugs = ();
- foreach my $id (@$ids) {
- my $attachment = Bugzilla::Attachment->new($id)
- || ThrowUserError("invalid_attach_id", { attach_id => $id });
- my $bug = $attachment->bug;
- $attachment->_check_bug;
-
- push @attachments, $attachment;
- $bugs{$bug->id} = $bug;
+ my ($self, $params) = validate(@_, 'ids');
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
+
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', {param => 'ids'});
+
+ # Some fields cannot be sent to set_all
+ foreach my $key (qw(login password token)) {
+ delete $params->{$key};
+ }
+
+ $params = translate($params, ATTACHMENT_MAPPED_SETTERS);
+
+ # Get all the attachments, after verifying that they exist and are editable
+ my @attachments = ();
+ my %bugs = ();
+ foreach my $id (@$ids) {
+ my $attachment = Bugzilla::Attachment->new($id)
+ || ThrowUserError("invalid_attach_id", {attach_id => $id});
+ my $bug = $attachment->bug;
+ $attachment->_check_bug;
+
+ push @attachments, $attachment;
+ $bugs{$bug->id} = $bug;
+ }
+
+ my $flags = delete $params->{flags};
+ my $comment = delete $params->{comment};
+
+ # Update the values
+ foreach my $attachment (@attachments) {
+ my ($update_flags, $new_flags)
+ = $flags ? extract_flags($flags, $attachment->bug, $attachment) : ([], []);
+ if ($attachment->validate_can_edit) {
+ $attachment->set_all($params);
+ $attachment->set_flags($update_flags, $new_flags) if $flags;
}
-
- my $flags = delete $params->{flags};
- my $comment = delete $params->{comment};
-
- # Update the values
- foreach my $attachment (@attachments) {
- my ($update_flags, $new_flags) = $flags
- ? extract_flags($flags, $attachment->bug, $attachment)
- : ([], []);
- if ($attachment->validate_can_edit) {
- $attachment->set_all($params);
- $attachment->set_flags($update_flags, $new_flags) if $flags;
- }
- elsif (scalar @$update_flags && !scalar(@$new_flags) && !scalar keys %$params) {
- # Requestees can set flags targetted to them, even if they cannot
- # edit the attachment. Flag setters can edit their own flags too.
- my %flag_list = map { $_->{id} => $_ } @$update_flags;
- my $flag_objs = Bugzilla::Flag->new_from_list([ keys %flag_list ]);
- my @editable_flags;
- foreach my $flag_obj (@$flag_objs) {
- if ($flag_obj->setter_id == $user->id
- || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
- {
- push(@editable_flags, $flag_list{$flag_obj->id});
- }
- }
- if (!scalar @editable_flags) {
- ThrowUserError("illegal_attachment_edit", { attach_id => $attachment->id });
- }
- $attachment->set_flags(\@editable_flags, []);
- }
- else {
- ThrowUserError("illegal_attachment_edit", { attach_id => $attachment->id });
+ elsif (scalar @$update_flags && !scalar(@$new_flags) && !scalar keys %$params) {
+
+ # Requestees can set flags targetted to them, even if they cannot
+ # edit the attachment. Flag setters can edit their own flags too.
+ my %flag_list = map { $_->{id} => $_ } @$update_flags;
+ my $flag_objs = Bugzilla::Flag->new_from_list([keys %flag_list]);
+ my @editable_flags;
+ foreach my $flag_obj (@$flag_objs) {
+ if ($flag_obj->setter_id == $user->id
+ || ($flag_obj->requestee_id && $flag_obj->requestee_id == $user->id))
+ {
+ push(@editable_flags, $flag_list{$flag_obj->id});
}
+ }
+ if (!scalar @editable_flags) {
+ ThrowUserError("illegal_attachment_edit", {attach_id => $attachment->id});
+ }
+ $attachment->set_flags(\@editable_flags, []);
}
+ else {
+ ThrowUserError("illegal_attachment_edit", {attach_id => $attachment->id});
+ }
+ }
- $dbh->bz_start_transaction();
+ $dbh->bz_start_transaction();
- # Do the actual update and get information to return to user
- my @result;
- foreach my $attachment (@attachments) {
- my $changes = $attachment->update();
-
- if ($comment = trim($comment)) {
- $attachment->bug->add_comment($comment,
- { isprivate => $attachment->isprivate,
- type => CMT_ATTACHMENT_UPDATED,
- extra_data => $attachment->id });
- }
+ # Do the actual update and get information to return to user
+ my @result;
+ foreach my $attachment (@attachments) {
+ my $changes = $attachment->update();
- $changes = translate($changes, ATTACHMENT_MAPPED_RETURNS);
+ if ($comment = trim($comment)) {
+ $attachment->bug->add_comment(
+ $comment,
+ {
+ isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_UPDATED,
+ extra_data => $attachment->id
+ }
+ );
+ }
- my %hash = (
- id => $self->type('int', $attachment->id),
- last_change_time => $self->type('dateTime', $attachment->modification_time),
- changes => {},
- );
+ $changes = translate($changes, ATTACHMENT_MAPPED_RETURNS);
- foreach my $field (keys %$changes) {
- my $change = $changes->{$field};
+ my %hash = (
+ id => $self->type('int', $attachment->id),
+ last_change_time => $self->type('dateTime', $attachment->modification_time),
+ changes => {},
+ );
- # We normalize undef to an empty string, so that the API
- # stays consistent for things like Deadline that can become
- # empty.
- $hash{changes}->{$field} = {
- removed => $self->type('string', $change->[0] // ''),
- added => $self->type('string', $change->[1] // '')
- };
- }
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
- push(@result, \%hash);
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $hash{changes}->{$field} = {
+ removed => $self->type('string', $change->[0] // ''),
+ added => $self->type('string', $change->[1] // '')
+ };
}
- $dbh->bz_commit_transaction();
+ push(@result, \%hash);
+ }
- # Email users about the change
- foreach my $bug (values %bugs) {
- $bug->update();
- $bug->send_changes();
- }
+ $dbh->bz_commit_transaction();
- # Return the information to the user
- return { attachments => \@result };
+ # Email users about the change
+ foreach my $bug (values %bugs) {
+ $bug->update();
+ $bug->send_changes();
+ }
+
+ # Return the information to the user
+ return {attachments => \@result};
}
sub add_comment {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- # BMO: Don't allow updating of bugs if disabled
- if (Bugzilla->params->{disable_bug_updates}) {
- ThrowErrorPage('bug/process/updates-disabled.html.tmpl',
- 'Bug updates are currently disabled.');
- }
+ # BMO: Don't allow updating of bugs if disabled
+ if (Bugzilla->params->{disable_bug_updates}) {
+ ThrowErrorPage(
+ 'bug/process/updates-disabled.html.tmpl',
+ 'Bug updates are currently disabled.'
+ );
+ }
- #The user must login in order add a comment
- Bugzilla->login(LOGIN_REQUIRED);
+ #The user must login in order add a comment
+ Bugzilla->login(LOGIN_REQUIRED);
- # Check parameters
- defined $params->{id}
- || ThrowCodeError('param_required', { param => 'id' });
- my $comment = $params->{comment};
- (defined $comment && trim($comment) ne '')
- || ThrowCodeError('param_required', { param => 'comment' });
+ # Check parameters
+ defined $params->{id} || ThrowCodeError('param_required', {param => 'id'});
+ my $comment = $params->{comment};
+ (defined $comment && trim($comment) ne '')
+ || ThrowCodeError('param_required', {param => 'comment'});
- my $bug = Bugzilla::Bug->check($params->{id});
+ my $bug = Bugzilla::Bug->check($params->{id});
- Bugzilla->user->can_edit_product($bug->product_id)
- || ThrowUserError("product_edit_denied", {product => $bug->product});
+ Bugzilla->user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied", {product => $bug->product});
- # Backwards-compatibility for versions before 3.6
- if (defined $params->{private}) {
- $params->{is_private} = delete $params->{private};
- }
- # Append comment
- $bug->add_comment($comment, { isprivate => $params->{is_private},
- work_time => $params->{work_time} });
+ # Backwards-compatibility for versions before 3.6
+ if (defined $params->{private}) {
+ $params->{is_private} = delete $params->{private};
+ }
- # Add comment tags
- $bug->set_all({ comment_tags => $params->{comment_tags} })
- if defined $params->{comment_tags};
+ # Append comment
+ $bug->add_comment($comment,
+ {isprivate => $params->{is_private}, work_time => $params->{work_time}});
- # Capture the call to bug->update (which creates the new comment) in
- # a transaction so we're sure to get the correct comment_id.
+ # Add comment tags
+ $bug->set_all({comment_tags => $params->{comment_tags}})
+ if defined $params->{comment_tags};
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ # Capture the call to bug->update (which creates the new comment) in
+ # a transaction so we're sure to get the correct comment_id.
- $bug->update();
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
- my $new_comment_id = $dbh->bz_last_key('longdescs', 'comment_id');
+ $bug->update();
- $dbh->bz_commit_transaction();
+ my $new_comment_id = $dbh->bz_last_key('longdescs', 'comment_id');
- # Send mail.
- Bugzilla::BugMail::Send($bug->bug_id, { changer => Bugzilla->user });
+ $dbh->bz_commit_transaction();
- return { id => $self->type('int', $new_comment_id) };
+ # Send mail.
+ Bugzilla::BugMail::Send($bug->bug_id, {changer => Bugzilla->user});
+
+ return {id => $self->type('int', $new_comment_id)};
}
sub update_see_also {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- # BMO: Don't allow updating of bugs if disabled
- if (Bugzilla->params->{disable_bug_updates}) {
- ThrowErrorPage('bug/process/updates-disabled.html.tmpl',
- 'Bug updates are currently disabled.');
+ # BMO: Don't allow updating of bugs if disabled
+ if (Bugzilla->params->{disable_bug_updates}) {
+ ThrowErrorPage(
+ 'bug/process/updates-disabled.html.tmpl',
+ 'Bug updates are currently disabled.'
+ );
+ }
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ # Check parameters
+ $params->{ids} || ThrowCodeError('param_required', {param => 'id'});
+ my ($add, $remove) = @$params{qw(add remove)};
+ ($add || $remove)
+ or ThrowCodeError('params_required', {params => ['add', 'remove']});
+
+ my @bugs;
+ foreach my $id (@{$params->{ids}}) {
+ my $bug = Bugzilla::Bug->check($id);
+ $user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied", {product => $bug->product});
+ push(@bugs, $bug);
+ if ($remove) {
+ $bug->remove_see_also($_) foreach @$remove;
}
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
- # Check parameters
- $params->{ids}
- || ThrowCodeError('param_required', { param => 'id' });
- my ($add, $remove) = @$params{qw(add remove)};
- ($add || $remove)
- or ThrowCodeError('params_required', { params => ['add', 'remove'] });
-
- my @bugs;
- foreach my $id (@{ $params->{ids} }) {
- my $bug = Bugzilla::Bug->check($id);
- $user->can_edit_product($bug->product_id)
- || ThrowUserError("product_edit_denied",
- { product => $bug->product });
- push(@bugs, $bug);
- if ($remove) {
- $bug->remove_see_also($_) foreach @$remove;
- }
- if ($add) {
- $bug->add_see_also($_) foreach @$add;
- }
+ if ($add) {
+ $bug->add_see_also($_) foreach @$add;
}
-
- my %changes;
- foreach my $bug (@bugs) {
- my $change = $bug->update();
- if (my $see_also = $change->{see_also}) {
- $changes{$bug->id}->{see_also} = {
- removed => [split(', ', $see_also->[0])],
- added => [split(', ', $see_also->[1])],
- };
- }
- else {
- # We still want a changes entry, for API consistency.
- $changes{$bug->id}->{see_also} = { added => [], removed => [] };
- }
-
- Bugzilla::BugMail::Send($bug->id, { changer => $user });
+ }
+
+ my %changes;
+ foreach my $bug (@bugs) {
+ my $change = $bug->update();
+ if (my $see_also = $change->{see_also}) {
+ $changes{$bug->id}->{see_also} = {
+ removed => [split(', ', $see_also->[0])],
+ added => [split(', ', $see_also->[1])],
+ };
}
+ else {
+ # We still want a changes entry, for API consistency.
+ $changes{$bug->id}->{see_also} = {added => [], removed => []};
+ }
+
+ Bugzilla::BugMail::Send($bug->id, {changer => $user});
+ }
- return { changes => \%changes };
+ return {changes => \%changes};
}
sub attachments {
- my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
+ my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
- Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
+ Bugzilla->switch_to_shadow_db() unless Bugzilla->user->id;
- if (!(defined $params->{ids}
- or defined $params->{attachment_ids}))
- {
- ThrowCodeError('param_required',
- { function => 'Bug.attachments',
- params => ['ids', 'attachment_ids'] });
- }
+ if (!(defined $params->{ids} or defined $params->{attachment_ids})) {
+ ThrowCodeError('param_required',
+ {function => 'Bug.attachments', params => ['ids', 'attachment_ids']});
+ }
- my $ids = $params->{ids} || [];
- my $attach_ids = $params->{attachment_ids} || [];
+ my $ids = $params->{ids} || [];
+ my $attach_ids = $params->{attachment_ids} || [];
- my %bugs;
- foreach my $bug_id (@$ids) {
- my $bug = Bugzilla::Bug->check($bug_id);
- $bugs{$bug->id} = [];
- foreach my $attach (@{$bug->attachments}) {
- push @{$bugs{$bug->id}},
- $self->_attachment_to_hash($attach, $params);
- }
+ my %bugs;
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bugs{$bug->id} = [];
+ foreach my $attach (@{$bug->attachments}) {
+ push @{$bugs{$bug->id}}, $self->_attachment_to_hash($attach, $params);
}
-
- my %attachments;
- my @log_attachments;
- foreach my $attach (@{Bugzilla::Attachment->new_from_list($attach_ids)}) {
- Bugzilla::Bug->check($attach->bug_id);
- if ($attach->isprivate && !Bugzilla->user->is_insider) {
- ThrowUserError('auth_failure', {action => 'access',
- object => 'attachment',
- attach_id => $attach->id});
- }
- push @log_attachments, $attach;
-
- $attachments{$attach->id} =
- $self->_attachment_to_hash($attach, $params);
+ }
+
+ my %attachments;
+ my @log_attachments;
+ foreach my $attach (@{Bugzilla::Attachment->new_from_list($attach_ids)}) {
+ Bugzilla::Bug->check($attach->bug_id);
+ if ($attach->isprivate && !Bugzilla->user->is_insider) {
+ ThrowUserError('auth_failure',
+ {action => 'access', object => 'attachment', attach_id => $attach->id});
}
+ push @log_attachments, $attach;
- if (Bugzilla->user->id) {
- foreach my $attachment (@log_attachments) {
- Bugzilla->log_user_request($attachment->bug_id, $attachment->id, "attachment-get");
- }
+ $attachments{$attach->id} = $self->_attachment_to_hash($attach, $params);
+ }
+
+ if (Bugzilla->user->id) {
+ foreach my $attachment (@log_attachments) {
+ Bugzilla->log_user_request($attachment->bug_id, $attachment->id,
+ "attachment-get");
}
+ }
- return { bugs => \%bugs, attachments => \%attachments };
+ return {bugs => \%bugs, attachments => \%attachments};
}
sub flag_types {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->switch_to_shadow_db();
- my $user = Bugzilla->user;
-
- defined $params->{product}
- || ThrowCodeError('param_required',
- { function => 'Bug.flag_types',
- param => 'product' });
-
- my $product = delete $params->{product};
- my $component = delete $params->{component};
-
- $product = Bugzilla::Product->check({ name => $product, cache => 1 });
- $component = Bugzilla::Component->check(
- { name => $component, product => $product, cache => 1 }) if $component;
-
- my $flag_params = { product_id => $product->id };
- $flag_params->{component_id} = $component->id if $component;
- my $matched_flag_types = Bugzilla::FlagType::match($flag_params);
-
- my $flag_types = { bug => [], attachment => [] };
- foreach my $flag_type (@$matched_flag_types) {
- push(@{ $flag_types->{bug} }, $self->_flagtype_to_hash($flag_type, $product))
- if $flag_type->target_type eq 'bug';
- push(@{ $flag_types->{attachment} }, $self->_flagtype_to_hash($flag_type, $product))
- if $flag_type->target_type eq 'attachment';
- }
-
- return $flag_types;
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->switch_to_shadow_db();
+ my $user = Bugzilla->user;
+
+ defined $params->{product}
+ || ThrowCodeError('param_required',
+ {function => 'Bug.flag_types', param => 'product'});
+
+ my $product = delete $params->{product};
+ my $component = delete $params->{component};
+
+ $product = Bugzilla::Product->check({name => $product, cache => 1});
+ $component
+ = Bugzilla::Component->check(
+ {name => $component, product => $product, cache => 1})
+ if $component;
+
+ my $flag_params = {product_id => $product->id};
+ $flag_params->{component_id} = $component->id if $component;
+ my $matched_flag_types = Bugzilla::FlagType::match($flag_params);
+
+ my $flag_types = {bug => [], attachment => []};
+ foreach my $flag_type (@$matched_flag_types) {
+ push(@{$flag_types->{bug}}, $self->_flagtype_to_hash($flag_type, $product))
+ if $flag_type->target_type eq 'bug';
+ push(
+ @{$flag_types->{attachment}},
+ $self->_flagtype_to_hash($flag_type, $product)
+ ) if $flag_type->target_type eq 'attachment';
+ }
+
+ return $flag_types;
}
sub update_comment_tags {
- my ($self, $params) = @_;
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->params->{'comment_taggers_group'}
- || ThrowUserError("comment_tag_disabled");
- $user->can_tag_comments
- || ThrowUserError("auth_failure",
- { group => Bugzilla->params->{'comment_taggers_group'},
- action => "update",
- object => "comment_tags" });
-
- my $comment_id = $params->{comment_id}
- // ThrowCodeError('param_required',
- { function => 'Bug.update_comment_tags',
- param => 'comment_id' });
-
- my $comment = Bugzilla::Comment->new($comment_id)
- || return [];
- $comment->bug->check_is_visible();
- if ($comment->is_private && !$user->is_insider) {
- ThrowUserError('comment_is_private', { id => $comment_id });
- }
+ my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- foreach my $tag (@{ $params->{add} || [] }) {
- $comment->add_tag($tag) if defined $tag;
- }
- foreach my $tag (@{ $params->{remove} || [] }) {
- $comment->remove_tag($tag) if defined $tag;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->params->{'comment_taggers_group'}
+ || ThrowUserError("comment_tag_disabled");
+ $user->can_tag_comments || ThrowUserError(
+ "auth_failure",
+ {
+ group => Bugzilla->params->{'comment_taggers_group'},
+ action => "update",
+ object => "comment_tags"
}
- $comment->update();
- $dbh->bz_commit_transaction();
-
- return $comment->tags;
+ );
+
+ my $comment_id = $params->{comment_id} // ThrowCodeError('param_required',
+ {function => 'Bug.update_comment_tags', param => 'comment_id'});
+
+ my $comment = Bugzilla::Comment->new($comment_id) || return [];
+ $comment->bug->check_is_visible();
+ if ($comment->is_private && !$user->is_insider) {
+ ThrowUserError('comment_is_private', {id => $comment_id});
+ }
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ foreach my $tag (@{$params->{add} || []}) {
+ $comment->add_tag($tag) if defined $tag;
+ }
+ foreach my $tag (@{$params->{remove} || []}) {
+ $comment->remove_tag($tag) if defined $tag;
+ }
+ $comment->update();
+ $dbh->bz_commit_transaction();
+
+ return $comment->tags;
}
sub search_comment_tags {
- my ($self, $params) = @_;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->params->{'comment_taggers_group'}
- || ThrowUserError("comment_tag_disabled");
- Bugzilla->user->can_tag_comments
- || ThrowUserError("auth_failure", { group => Bugzilla->params->{'comment_taggers_group'},
- action => "search",
- object => "comment_tags"});
-
- my $query = $params->{query};
- $query
- // ThrowCodeError('param_required', { param => 'query' });
- my $limit = $params->{limit} || 7;
- detaint_natural($limit)
- || ThrowCodeError('param_must_be_numeric', { param => 'limit',
- function => 'Bug.search_comment_tags' });
-
-
- my $tags = Bugzilla::Comment::TagWeights->match({
- WHERE => {
- 'tag LIKE ?' => "\%$query\%",
- },
- LIMIT => $limit,
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->params->{'comment_taggers_group'}
+ || ThrowUserError("comment_tag_disabled");
+ Bugzilla->user->can_tag_comments || ThrowUserError(
+ "auth_failure",
+ {
+ group => Bugzilla->params->{'comment_taggers_group'},
+ action => "search",
+ object => "comment_tags"
+ }
+ );
+
+ my $query = $params->{query};
+ $query // ThrowCodeError('param_required', {param => 'query'});
+ my $limit = $params->{limit} || 7;
+ detaint_natural($limit)
+ || ThrowCodeError('param_must_be_numeric',
+ {param => 'limit', function => 'Bug.search_comment_tags'});
+
+
+ my $tags
+ = Bugzilla::Comment::TagWeights->match({
+ WHERE => {'tag LIKE ?' => "\%$query\%",}, LIMIT => $limit,
});
- return [ map { $_->tag } @$tags ];
+ return [map { $_->tag } @$tags];
}
##############################
@@ -1350,304 +1373,320 @@ sub search_comment_tags {
# return them directly.
sub _bug_to_hash {
- my ($self, $bug, $params) = @_;
-
- # All the basic bug attributes are here, in alphabetical order.
- # A bug attribute is "basic" if it doesn't require an additional
- # database call to get the info.
- my %item = %{ filter $params, {
- alias => $self->type('string', $bug->alias),
- id => $self->type('int', $bug->bug_id),
- is_confirmed => $self->type('boolean', $bug->everconfirmed),
- op_sys => $self->type('string', $bug->op_sys),
- platform => $self->type('string', $bug->rep_platform),
- priority => $self->type('string', $bug->priority),
- resolution => $self->type('string', $bug->resolution),
- severity => $self->type('string', $bug->bug_severity),
- status => $self->type('string', $bug->bug_status),
- summary => $self->type('string', $bug->short_desc),
- target_milestone => $self->type('string', $bug->target_milestone),
- url => $self->type('string', $bug->bug_file_loc),
- version => $self->type('string', $bug->version),
- whiteboard => $self->type('string', $bug->status_whiteboard),
- } };
-
- state $voting_enabled //= $bug->can('votes') ? 1 : 0;
- if ($voting_enabled && filter_wants $params, 'votes') {
- $item{votes} = $self->type('int', $bug->votes);
- }
+ my ($self, $bug, $params) = @_;
- # First we handle any fields that require extra work (such as date parsing
- # or SQL calls).
- if (filter_wants $params, 'assigned_to') {
- $item{'assigned_to'} = $self->type('email', $bug->assigned_to->login);
- $item{'assigned_to_detail'} = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to');
- }
- if (filter_wants $params, 'blocks') {
- my @blocks = map { $self->type('int', $_) } @{ $bug->blocked };
- $item{'blocks'} = \@blocks;
- }
- if (filter_wants $params, 'classification') {
- $item{classification} = $self->type('string', $bug->classification);
- }
- if (filter_wants $params, 'component') {
- $item{component} = $self->type('string', $bug->component);
- }
- if (filter_wants $params, 'cc') {
- my @cc = map { $self->type('email', $_) } @{ $bug->cc || [] };
- $item{'cc'} = \@cc;
- $item{'cc_detail'} = [ map { $self->_user_to_hash($_, $params, undef, 'cc') } @{ $bug->cc_users } ];
- }
- if (filter_wants $params, 'creation_time') {
- $item{'creation_time'} = $self->type('dateTime', $bug->creation_ts);
- }
- if (filter_wants $params, 'creator') {
- $item{'creator'} = $self->type('email', $bug->reporter->login);
- $item{'creator_detail'} = $self->_user_to_hash($bug->reporter, $params, undef, 'creator');
- }
- if (filter_wants $params, 'depends_on') {
- my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson };
- $item{'depends_on'} = \@depends_on;
- }
- if (filter_wants $params, 'dupe_of') {
- $item{'dupe_of'} = $self->type('int', $bug->dup_id);
- }
- if (filter_wants $params, 'duplicates') {
- $item{'duplicates'} = [ map { $self->type('int', $_->id) } @{ $bug->duplicates } ];
- }
- if (filter_wants $params, 'groups') {
- my @groups = map { $self->type('string', $_->name) }
- @{ $bug->groups_in };
- $item{'groups'} = \@groups;
- }
- if (filter_wants $params, 'is_open') {
- $item{'is_open'} = $self->type('boolean', $bug->status->is_open);
- }
- if (filter_wants $params, 'keywords') {
- my @keywords = map { $self->type('string', $_->name) }
- @{ $bug->keyword_objects };
- $item{'keywords'} = \@keywords;
- }
- if (filter_wants $params, 'last_change_time') {
- $item{'last_change_time'} = $self->type('dateTime', $bug->delta_ts);
+ # All the basic bug attributes are here, in alphabetical order.
+ # A bug attribute is "basic" if it doesn't require an additional
+ # database call to get the info.
+ my %item = %{filter $params,
+ {
+ alias => $self->type('string', $bug->alias),
+ id => $self->type('int', $bug->bug_id),
+ is_confirmed => $self->type('boolean', $bug->everconfirmed),
+ op_sys => $self->type('string', $bug->op_sys),
+ platform => $self->type('string', $bug->rep_platform),
+ priority => $self->type('string', $bug->priority),
+ resolution => $self->type('string', $bug->resolution),
+ severity => $self->type('string', $bug->bug_severity),
+ status => $self->type('string', $bug->bug_status),
+ summary => $self->type('string', $bug->short_desc),
+ target_milestone => $self->type('string', $bug->target_milestone),
+ url => $self->type('string', $bug->bug_file_loc),
+ version => $self->type('string', $bug->version),
+ whiteboard => $self->type('string', $bug->status_whiteboard),
}
- if (filter_wants $params, 'product') {
- $item{product} = $self->type('string', $bug->product);
+ };
+
+ state $voting_enabled //= $bug->can('votes') ? 1 : 0;
+ if ($voting_enabled && filter_wants $params, 'votes') {
+ $item{votes} = $self->type('int', $bug->votes);
+ }
+
+ # First we handle any fields that require extra work (such as date parsing
+ # or SQL calls).
+ if (filter_wants $params, 'assigned_to') {
+ $item{'assigned_to'} = $self->type('email', $bug->assigned_to->login);
+ $item{'assigned_to_detail'}
+ = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to');
+ }
+ if (filter_wants $params, 'blocks') {
+ my @blocks = map { $self->type('int', $_) } @{$bug->blocked};
+ $item{'blocks'} = \@blocks;
+ }
+ if (filter_wants $params, 'classification') {
+ $item{classification} = $self->type('string', $bug->classification);
+ }
+ if (filter_wants $params, 'component') {
+ $item{component} = $self->type('string', $bug->component);
+ }
+ if (filter_wants $params, 'cc') {
+ my @cc = map { $self->type('email', $_) } @{$bug->cc || []};
+ $item{'cc'} = \@cc;
+ $item{'cc_detail'}
+ = [map { $self->_user_to_hash($_, $params, undef, 'cc') } @{$bug->cc_users}];
+ }
+ if (filter_wants $params, 'creation_time') {
+ $item{'creation_time'} = $self->type('dateTime', $bug->creation_ts);
+ }
+ if (filter_wants $params, 'creator') {
+ $item{'creator'} = $self->type('email', $bug->reporter->login);
+ $item{'creator_detail'}
+ = $self->_user_to_hash($bug->reporter, $params, undef, 'creator');
+ }
+ if (filter_wants $params, 'depends_on') {
+ my @depends_on = map { $self->type('int', $_) } @{$bug->dependson};
+ $item{'depends_on'} = \@depends_on;
+ }
+ if (filter_wants $params, 'dupe_of') {
+ $item{'dupe_of'} = $self->type('int', $bug->dup_id);
+ }
+ if (filter_wants $params, 'duplicates') {
+ $item{'duplicates'} = [map { $self->type('int', $_->id) } @{$bug->duplicates}];
+ }
+ if (filter_wants $params, 'groups') {
+ my @groups = map { $self->type('string', $_->name) } @{$bug->groups_in};
+ $item{'groups'} = \@groups;
+ }
+ if (filter_wants $params, 'is_open') {
+ $item{'is_open'} = $self->type('boolean', $bug->status->is_open);
+ }
+ if (filter_wants $params, 'keywords') {
+ my @keywords = map { $self->type('string', $_->name) } @{$bug->keyword_objects};
+ $item{'keywords'} = \@keywords;
+ }
+ if (filter_wants $params, 'last_change_time') {
+ $item{'last_change_time'} = $self->type('dateTime', $bug->delta_ts);
+ }
+ if (filter_wants $params, 'product') {
+ $item{product} = $self->type('string', $bug->product);
+ }
+ if (filter_wants $params, 'qa_contact') {
+ my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
+ $item{'qa_contact'} = $self->type('email', $qa_login);
+ if ($bug->qa_contact) {
+ $item{'qa_contact_detail'}
+ = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact');
}
- if (filter_wants $params, 'qa_contact') {
- my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
- $item{'qa_contact'} = $self->type('email', $qa_login);
- if ($bug->qa_contact) {
- $item{'qa_contact_detail'} = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact');
- }
+ }
+ if (filter_wants $params, 'see_also') {
+ my @see_also = map { $self->type('string', $_->name) } @{$bug->see_also};
+ $item{'see_also'} = \@see_also;
+ }
+ if (filter_wants $params, 'flags') {
+ $item{'flags'} = [map { $self->_flag_to_hash($_) } @{$bug->flags}];
+ }
+
+ # And now custom fields
+ my @custom_fields = Bugzilla->active_custom_fields({
+ product => $bug->product_obj,
+ component => $bug->component_obj,
+ bug_id => $bug->id
+ });
+ foreach my $field (@custom_fields) {
+ my $name = $field->name;
+ next if !filter_wants($params, $name, ['default', 'custom']);
+ if ($field->type == FIELD_TYPE_BUG_ID) {
+ $item{$name} = $self->type('int', $bug->$name);
}
- if (filter_wants $params, 'see_also') {
- my @see_also = map { $self->type('string', $_->name) }
- @{ $bug->see_also };
- $item{'see_also'} = \@see_also;
+ elsif ($field->type == FIELD_TYPE_DATETIME || $field->type == FIELD_TYPE_DATE) {
+ my $value = $bug->$name;
+ $item{$name} = defined($value) ? $self->type('dateTime', $value) : undef;
}
- if (filter_wants $params, 'flags') {
- $item{'flags'} = [ map { $self->_flag_to_hash($_) } @{$bug->flags} ];
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my @values = map { $self->type('string', $_) } @{$bug->$name};
+ $item{$name} = \@values;
}
-
- # And now custom fields
- my @custom_fields = Bugzilla->active_custom_fields({
- product => $bug->product_obj, component => $bug->component_obj, bug_id => $bug->id });
- foreach my $field (@custom_fields) {
- my $name = $field->name;
- next if !filter_wants($params, $name, ['default', 'custom']);
- if ($field->type == FIELD_TYPE_BUG_ID) {
- $item{$name} = $self->type('int', $bug->$name);
- }
- elsif ($field->type == FIELD_TYPE_DATETIME
- || $field->type == FIELD_TYPE_DATE)
- {
- my $value = $bug->$name;
- $item{$name} = defined($value) ? $self->type('dateTime', $value) : undef;
- }
- elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
- my @values = map { $self->type('string', $_) } @{ $bug->$name };
- $item{$name} = \@values;
- }
- else {
- $item{$name} = $self->type('string', $bug->$name);
- }
- }
-
- # Timetracking fields are only sent if the user can see them.
- if (Bugzilla->user->is_timetracker) {
- if (filter_wants $params, 'estimated_time') {
- $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
- }
- if (filter_wants $params, 'remaining_time') {
- $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
- }
- if (filter_wants $params, 'deadline') {
- # No need to format $bug->deadline specially, because Bugzilla::Bug
- # already does it for us.
- $item{'deadline'} = $self->type('string', $bug->deadline);
- }
- if (filter_wants $params, 'actual_time') {
- $item{'actual_time'} = $self->type('double', $bug->actual_time);
- }
+ else {
+ $item{$name} = $self->type('string', $bug->$name);
}
+ }
- # The "accessible" bits go here because they have long names and it
- # makes the code look nicer to separate them out.
- if (filter_wants $params, 'is_cc_accessible') {
- $item{'is_cc_accessible'} = $self->type('boolean', $bug->cclist_accessible);
+ # Timetracking fields are only sent if the user can see them.
+ if (Bugzilla->user->is_timetracker) {
+ if (filter_wants $params, 'estimated_time') {
+ $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
}
- if (filter_wants $params, 'is_creator_accessible') {
- $item{'is_creator_accessible'} = $self->type('boolean', $bug->reporter_accessible);
+ if (filter_wants $params, 'remaining_time') {
+ $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
}
+ if (filter_wants $params, 'deadline') {
- # BMO - support for special mentors field
- if (filter_wants $params, 'mentors') {
- $item{'mentors'}
- = [ map { $self->type('email', $_->login) } @{ $bug->mentors || [] } ];
- $item{'mentors_detail'}
- = [ map { $self->_user_to_hash($_, $params, undef, 'mentors') } @{ $bug->mentors } ];
+ # No need to format $bug->deadline specially, because Bugzilla::Bug
+ # already does it for us.
+ $item{'deadline'} = $self->type('string', $bug->deadline);
}
-
- if (filter_wants $params, 'comment_count') {
- $item{'comment_count'} = $self->type('int', $bug->comment_count);
+ if (filter_wants $params, 'actual_time') {
+ $item{'actual_time'} = $self->type('double', $bug->actual_time);
}
-
- return \%item;
+ }
+
+ # The "accessible" bits go here because they have long names and it
+ # makes the code look nicer to separate them out.
+ if (filter_wants $params, 'is_cc_accessible') {
+ $item{'is_cc_accessible'} = $self->type('boolean', $bug->cclist_accessible);
+ }
+ if (filter_wants $params, 'is_creator_accessible') {
+ $item{'is_creator_accessible'}
+ = $self->type('boolean', $bug->reporter_accessible);
+ }
+
+ # BMO - support for special mentors field
+ if (filter_wants $params, 'mentors') {
+ $item{'mentors'}
+ = [map { $self->type('email', $_->login) } @{$bug->mentors || []}];
+ $item{'mentors_detail'}
+ = [map { $self->_user_to_hash($_, $params, undef, 'mentors') }
+ @{$bug->mentors}];
+ }
+
+ if (filter_wants $params, 'comment_count') {
+ $item{'comment_count'} = $self->type('int', $bug->comment_count);
+ }
+
+ return \%item;
}
sub _user_to_hash {
- my ($self, $user, $filters, $types, $prefix) = @_;
- my $item = filter $filters, {
- id => $self->type('int', $user->id),
- real_name => $self->type('string', $user->name),
- name => $self->type('email', $user->login),
- email => $self->type('email', $user->email),
- }, $types, $prefix;
- return $item;
+ my ($self, $user, $filters, $types, $prefix) = @_;
+ my $item = filter $filters,
+ {
+ id => $self->type('int', $user->id),
+ real_name => $self->type('string', $user->name),
+ name => $self->type('email', $user->login),
+ email => $self->type('email', $user->email),
+ },
+ $types, $prefix;
+ return $item;
}
sub _attachment_to_hash {
- my ($self, $attach, $filters, $types, $prefix) = @_;
-
- my $item = filter $filters, {
- creation_time => $self->type('dateTime', $attach->attached),
- last_change_time => $self->type('dateTime', $attach->modification_time),
- id => $self->type('int', $attach->id),
- bug_id => $self->type('int', $attach->bug_id),
- file_name => $self->type('string', $attach->filename),
- summary => $self->type('string', $attach->description),
- description => $self->type('string', $attach->description),
- content_type => $self->type('string', $attach->contenttype),
- is_private => $self->type('int', $attach->isprivate),
- is_obsolete => $self->type('int', $attach->isobsolete),
- is_patch => $self->type('int', $attach->ispatch),
- }, $types, $prefix;
-
- # creator/attacher require an extra lookup, so we only send them if
- # the filter wants them.
- foreach my $field (qw(creator attacher)) {
- if (filter_wants $filters, $field, $types, $prefix) {
- $item->{$field} = $self->type('email', $attach->attacher->login);
- }
- }
+ my ($self, $attach, $filters, $types, $prefix) = @_;
- if (filter_wants $filters, 'data', $types, $prefix) {
- $item->{'data'} = $self->type('base64', $attach->data);
+ my $item = filter $filters,
+ {
+ creation_time => $self->type('dateTime', $attach->attached),
+ last_change_time => $self->type('dateTime', $attach->modification_time),
+ id => $self->type('int', $attach->id),
+ bug_id => $self->type('int', $attach->bug_id),
+ file_name => $self->type('string', $attach->filename),
+ summary => $self->type('string', $attach->description),
+ description => $self->type('string', $attach->description),
+ content_type => $self->type('string', $attach->contenttype),
+ is_private => $self->type('int', $attach->isprivate),
+ is_obsolete => $self->type('int', $attach->isobsolete),
+ is_patch => $self->type('int', $attach->ispatch),
+ },
+ $types, $prefix;
+
+ # creator/attacher require an extra lookup, so we only send them if
+ # the filter wants them.
+ foreach my $field (qw(creator attacher)) {
+ if (filter_wants $filters, $field, $types, $prefix) {
+ $item->{$field} = $self->type('email', $attach->attacher->login);
}
+ }
- if (filter_wants $filters, 'size', $types, $prefix) {
- $item->{'size'} = $self->type('int', $attach->datasize);
- }
+ if (filter_wants $filters, 'data', $types, $prefix) {
+ $item->{'data'} = $self->type('base64', $attach->data);
+ }
- if (filter_wants $filters, 'flags', $types, $prefix) {
- $item->{'flags'} = [ map { $self->_flag_to_hash($_) } @{$attach->flags} ];
- }
+ if (filter_wants $filters, 'size', $types, $prefix) {
+ $item->{'size'} = $self->type('int', $attach->datasize);
+ }
+
+ if (filter_wants $filters, 'flags', $types, $prefix) {
+ $item->{'flags'} = [map { $self->_flag_to_hash($_) } @{$attach->flags}];
+ }
- return $item;
+ return $item;
}
sub _flag_to_hash {
- my ($self, $flag) = @_;
-
- my $item = {
- id => $self->type('int', $flag->id),
- name => $self->type('string', $flag->name),
- type_id => $self->type('int', $flag->type_id),
- creation_date => $self->type('dateTime', $flag->creation_date),
- modification_date => $self->type('dateTime', $flag->modification_date),
- status => $self->type('string', $flag->status)
- };
-
- foreach my $field (qw(setter requestee)) {
- my $field_id = $field . "_id";
- $item->{$field} = $self->type('email', $flag->$field->login)
- if $flag->$field_id;
- }
-
- return $item;
+ my ($self, $flag) = @_;
+
+ my $item = {
+ id => $self->type('int', $flag->id),
+ name => $self->type('string', $flag->name),
+ type_id => $self->type('int', $flag->type_id),
+ creation_date => $self->type('dateTime', $flag->creation_date),
+ modification_date => $self->type('dateTime', $flag->modification_date),
+ status => $self->type('string', $flag->status)
+ };
+
+ foreach my $field (qw(setter requestee)) {
+ my $field_id = $field . "_id";
+ $item->{$field} = $self->type('email', $flag->$field->login)
+ if $flag->$field_id;
+ }
+
+ return $item;
}
sub _flagtype_to_hash {
- my ($self, $flagtype, $product) = @_;
- my $user = Bugzilla->user;
-
- my @values = ('X');
- push(@values, '?') if ($flagtype->is_requestable && $user->can_request_flag($flagtype));
- push(@values, '+', '-') if $user->can_set_flag($flagtype);
-
- my $item = {
- id => $self->type('int' , $flagtype->id),
- name => $self->type('string' , $flagtype->name),
- description => $self->type('string' , $flagtype->description),
- type => $self->type('string' , $flagtype->target_type),
- values => \@values,
- is_active => $self->type('boolean', $flagtype->is_active),
- is_requesteeble => $self->type('boolean', $flagtype->is_requesteeble),
- is_multiplicable => $self->type('boolean', $flagtype->is_multiplicable)
- };
-
- if ($product) {
- my $inclusions = $self->_flagtype_clusions_to_hash($flagtype->inclusions, $product->id);
- my $exclusions = $self->_flagtype_clusions_to_hash($flagtype->exclusions, $product->id);
- # if we have both inclusions and exclusions, the exclusions are redundant
- $exclusions = [] if @$inclusions && @$exclusions;
- # no need to return anything if there's just "any component"
- $item->{inclusions} = $inclusions if @$inclusions && $inclusions->[0] ne '';
- $item->{exclusions} = $exclusions if @$exclusions && $exclusions->[0] ne '';
- }
-
- return $item;
+ my ($self, $flagtype, $product) = @_;
+ my $user = Bugzilla->user;
+
+ my @values = ('X');
+ push(@values, '?')
+ if ($flagtype->is_requestable && $user->can_request_flag($flagtype));
+ push(@values, '+', '-') if $user->can_set_flag($flagtype);
+
+ my $item = {
+ id => $self->type('int', $flagtype->id),
+ name => $self->type('string', $flagtype->name),
+ description => $self->type('string', $flagtype->description),
+ type => $self->type('string', $flagtype->target_type),
+ values => \@values,
+ is_active => $self->type('boolean', $flagtype->is_active),
+ is_requesteeble => $self->type('boolean', $flagtype->is_requesteeble),
+ is_multiplicable => $self->type('boolean', $flagtype->is_multiplicable)
+ };
+
+ if ($product) {
+ my $inclusions
+ = $self->_flagtype_clusions_to_hash($flagtype->inclusions, $product->id);
+ my $exclusions
+ = $self->_flagtype_clusions_to_hash($flagtype->exclusions, $product->id);
+
+ # if we have both inclusions and exclusions, the exclusions are redundant
+ $exclusions = [] if @$inclusions && @$exclusions;
+
+ # no need to return anything if there's just "any component"
+ $item->{inclusions} = $inclusions if @$inclusions && $inclusions->[0] ne '';
+ $item->{exclusions} = $exclusions if @$exclusions && $exclusions->[0] ne '';
+ }
+
+ return $item;
}
sub _flagtype_clusions_to_hash {
- my ($self, $clusions, $product_id) = @_;
- my $result = [];
- foreach my $key (keys %$clusions) {
- my ($prod_id, $comp_id) = split(/:/, $clusions->{$key}, 2);
- if ($prod_id == 0 || $prod_id == $product_id) {
- if ($comp_id) {
- my $component = Bugzilla::Component->new({ id => $comp_id, cache => 1 });
- push @$result, $component->name;
- }
- else {
- return [ '' ];
- }
- }
+ my ($self, $clusions, $product_id) = @_;
+ my $result = [];
+ foreach my $key (keys %$clusions) {
+ my ($prod_id, $comp_id) = split(/:/, $clusions->{$key}, 2);
+ if ($prod_id == 0 || $prod_id == $product_id) {
+ if ($comp_id) {
+ my $component = Bugzilla::Component->new({id => $comp_id, cache => 1});
+ push @$result, $component->name;
+ }
+ else {
+ return [''];
+ }
}
- return $result;
+ }
+ return $result;
}
sub _add_update_tokens {
- my ($self, $params, $bugs, $hashes) = @_;
+ my ($self, $params, $bugs, $hashes) = @_;
- return if !Bugzilla->user->id;
- return if !filter_wants($params, 'update_token');
+ return if !Bugzilla->user->id;
+ return if !filter_wants($params, 'update_token');
- for(my $i = 0; $i < @$bugs; $i++) {
- my $token = issue_hash_token([$bugs->[$i]->id, $bugs->[$i]->delta_ts]);
- $hashes->[$i]->{'update_token'} = $self->type('string', $token);
- }
+ for (my $i = 0; $i < @$bugs; $i++) {
+ my $token = issue_hash_token([$bugs->[$i]->id, $bugs->[$i]->delta_ts]);
+ $hashes->[$i]->{'update_token'} = $self->type('string', $token);
+ }
}
1;
diff --git a/Bugzilla/WebService/BugUserLastVisit.pm b/Bugzilla/WebService/BugUserLastVisit.pm
index 5e4c0d2ba..9b4261bc3 100644
--- a/Bugzilla/WebService/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/BugUserLastVisit.pm
@@ -19,84 +19,84 @@ use Bugzilla::WebService::Util qw( validate filter );
use Bugzilla::Constants;
use constant PUBLIC_METHODS => qw(
- get
- update
+ get
+ update
);
sub update {
- my ($self, $params) = validate(@_, 'ids');
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
+ my ($self, $params) = validate(@_, 'ids');
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
- $user->login(LOGIN_REQUIRED);
+ $user->login(LOGIN_REQUIRED);
- my $ids = $params->{ids} // [];
- ThrowCodeError('param_required', { param => 'ids' }) unless @$ids;
+ my $ids = $params->{ids} // [];
+ ThrowCodeError('param_required', {param => 'ids'}) unless @$ids;
- # Cache permissions for bugs. This highly reduces the number of calls to the
- # DB. visible_bugs() is only able to handle bug IDs, so we have to skip
- # aliases.
- $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
+ # Cache permissions for bugs. This highly reduces the number of calls to the
+ # DB. visible_bugs() is only able to handle bug IDs, so we have to skip
+ # aliases.
+ $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
- $dbh->bz_start_transaction();
- my @results;
- my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()');
- foreach my $bug_id (@$ids) {
- my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 });
+ $dbh->bz_start_transaction();
+ my @results;
+ my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()');
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check({id => $bug_id, cache => 1});
- ThrowUserError('user_not_involved', { bug_id => $bug->id })
- unless $user->is_involved_in_bug($bug);
+ ThrowUserError('user_not_involved', {bug_id => $bug->id})
+ unless $user->is_involved_in_bug($bug);
- $bug->update_user_last_visit($user, $last_visit_ts);
+ $bug->update_user_last_visit($user, $last_visit_ts);
- push(
- @results,
- $self->_bug_user_last_visit_to_hash(
- $bug_id, $last_visit_ts, $params
- ));
- }
- $dbh->bz_commit_transaction();
+ push(@results,
+ $self->_bug_user_last_visit_to_hash($bug_id, $last_visit_ts, $params));
+ }
+ $dbh->bz_commit_transaction();
- return \@results;
+ return \@results;
}
sub get {
- my ($self, $params) = validate(@_, 'ids');
- my $user = Bugzilla->user;
- my $ids = $params->{ids};
-
- $user->login(LOGIN_REQUIRED);
-
- if ($ids) {
- # Cache permissions for bugs. This highly reduces the number of calls to
- # the DB. visible_bugs() is only able to handle bug IDs, so we have to
- # skip aliases.
- $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
- }
-
- my @last_visits = @{ $user->last_visited };
-
- if ($ids) {
- # remove bugs that we are not interested in if ids is passed in.
- my %id_set = map { ($_ => 1) } @$ids;
- @last_visits = grep { $id_set{ $_->bug_id } } @last_visits;
- }
-
- return [
- map {
- $self->_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts,
- $params)
- } @last_visits
- ];
+ my ($self, $params) = validate(@_, 'ids');
+ my $user = Bugzilla->user;
+ my $ids = $params->{ids};
+
+ $user->login(LOGIN_REQUIRED);
+
+ if ($ids) {
+
+ # Cache permissions for bugs. This highly reduces the number of calls to
+ # the DB. visible_bugs() is only able to handle bug IDs, so we have to
+ # skip aliases.
+ $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
+ }
+
+ my @last_visits = @{$user->last_visited};
+
+ if ($ids) {
+
+ # remove bugs that we are not interested in if ids is passed in.
+ my %id_set = map { ($_ => 1) } @$ids;
+ @last_visits = grep { $id_set{$_->bug_id} } @last_visits;
+ }
+
+ return [
+ map {
+ $self->_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params)
+ } @last_visits
+ ];
}
sub _bug_user_last_visit_to_hash {
- my ($self, $bug_id, $last_visit_ts, $params) = @_;
+ my ($self, $bug_id, $last_visit_ts, $params) = @_;
- my %result = (id => $self->type('int', $bug_id),
- last_visit_ts => $self->type('dateTime', $last_visit_ts));
+ my %result = (
+ id => $self->type('int', $bug_id),
+ last_visit_ts => $self->type('dateTime', $last_visit_ts)
+ );
- return filter($params, \%result);
+ return filter($params, \%result);
}
1;
diff --git a/Bugzilla/WebService/Bugzilla.pm b/Bugzilla/WebService/Bugzilla.pm
index 8e95028cb..f8f745a55 100644
--- a/Bugzilla/WebService/Bugzilla.pm
+++ b/Bugzilla/WebService/Bugzilla.pm
@@ -21,77 +21,76 @@ use Try::Tiny;
use DateTime;
# Basic info that is needed before logins
-use constant LOGIN_EXEMPT => {
- timezone => 1,
- version => 1,
-};
+use constant LOGIN_EXEMPT => {timezone => 1, version => 1,};
use constant READ_ONLY => qw(
- extensions
- timezone
- time
- version
- jobqueue_status
+ extensions
+ timezone
+ time
+ version
+ jobqueue_status
);
use constant PUBLIC_METHODS => qw(
- extensions
- time
- timezone
- version
- jobqueue_status
+ extensions
+ time
+ timezone
+ version
+ jobqueue_status
);
sub version {
- my $self = shift;
- return { version => $self->type('string', BUGZILLA_VERSION) };
+ my $self = shift;
+ return {version => $self->type('string', BUGZILLA_VERSION)};
}
sub extensions {
- my $self = shift;
-
- my %retval;
- foreach my $extension (@{ Bugzilla->extensions }) {
- my $version = $extension->VERSION || 0;
- my $name = $extension->NAME;
- $retval{$name}->{version} = $self->type('string', $version);
- }
- return { extensions => \%retval };
+ my $self = shift;
+
+ my %retval;
+ foreach my $extension (@{Bugzilla->extensions}) {
+ my $version = $extension->VERSION || 0;
+ my $name = $extension->NAME;
+ $retval{$name}->{version} = $self->type('string', $version);
+ }
+ return {extensions => \%retval};
}
sub timezone {
- my $self = shift;
- # All Webservices return times in UTC; Use UTC here for backwards compat.
- return { timezone => $self->type('string', "+0000") };
+ my $self = shift;
+
+ # All Webservices return times in UTC; Use UTC here for backwards compat.
+ return {timezone => $self->type('string', "+0000")};
}
sub time {
- my ($self) = @_;
- # All Webservices return times in UTC; Use UTC here for backwards compat.
- # Hardcode values where appropriate
- my $dbh = Bugzilla->dbh;
-
- my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- $db_time = datetime_from($db_time, 'UTC');
- my $now_utc = DateTime->now();
-
- return {
- db_time => $self->type('dateTime', $db_time),
- web_time => $self->type('dateTime', $now_utc),
- web_time_utc => $self->type('dateTime', $now_utc),
- tz_name => $self->type('string', 'UTC'),
- tz_offset => $self->type('string', '+0000'),
- tz_short_name => $self->type('string', 'UTC'),
- };
+ my ($self) = @_;
+
+ # All Webservices return times in UTC; Use UTC here for backwards compat.
+ # Hardcode values where appropriate
+ my $dbh = Bugzilla->dbh;
+
+ my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $db_time = datetime_from($db_time, 'UTC');
+ my $now_utc = DateTime->now();
+
+ return {
+ db_time => $self->type('dateTime', $db_time),
+ web_time => $self->type('dateTime', $now_utc),
+ web_time_utc => $self->type('dateTime', $now_utc),
+ tz_name => $self->type('string', 'UTC'),
+ tz_offset => $self->type('string', '+0000'),
+ tz_short_name => $self->type('string', 'UTC'),
+ };
}
sub jobqueue_status {
- my ( $self, $params ) = @_;
+ my ($self, $params) = @_;
- Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
- my $query = q{
+ my $dbh = Bugzilla->dbh;
+ my $query = q{
SELECT
COUNT(*) AS total,
COALESCE(
@@ -105,17 +104,18 @@ sub jobqueue_status {
ON f.funcid = j.funcid;
};
- my $status;
- try {
- $status = $dbh->selectrow_hashref($query);
- $status->{errors} = 0 + $status->{errors};
- $status->{total} = 0 + $status->{total};
- } catch {
- ERROR($_);
- ThrowCodeError('jobqueue_status_error');
- };
-
- return $status;
+ my $status;
+ try {
+ $status = $dbh->selectrow_hashref($query);
+ $status->{errors} = 0 + $status->{errors};
+ $status->{total} = 0 + $status->{total};
+ }
+ catch {
+ ERROR($_);
+ ThrowCodeError('jobqueue_status_error');
+ };
+
+ return $status;
}
1;
diff --git a/Bugzilla/WebService/Classification.pm b/Bugzilla/WebService/Classification.pm
index 32139ff3f..35e67ba08 100644
--- a/Bugzilla/WebService/Classification.pm
+++ b/Bugzilla/WebService/Classification.pm
@@ -18,65 +18,76 @@ use Bugzilla::Error;
use Bugzilla::WebService::Util qw(filter validate params_to_objects);
use constant READ_ONLY => qw(
- get
+ get
);
use constant PUBLIC_METHODS => qw(
- get
+ get
);
sub get {
- my ($self, $params) = validate(@_, 'names', 'ids');
+ my ($self, $params) = validate(@_, 'names', 'ids');
- defined $params->{names} || defined $params->{ids}
- || ThrowCodeError('params_required', { function => 'Classification.get',
- params => ['names', 'ids'] });
+ defined $params->{names}
+ || defined $params->{ids}
+ || ThrowCodeError('params_required',
+ {function => 'Classification.get', params => ['names', 'ids']});
- my $user = Bugzilla->user;
+ my $user = Bugzilla->user;
- Bugzilla->params->{'useclassification'}
- || $user->in_group('editclassifications')
- || ThrowUserError('auth_classification_not_enabled');
+ Bugzilla->params->{'useclassification'}
+ || $user->in_group('editclassifications')
+ || ThrowUserError('auth_classification_not_enabled');
- Bugzilla->switch_to_shadow_db;
+ Bugzilla->switch_to_shadow_db;
- my @classification_objs = @{ params_to_objects($params, 'Bugzilla::Classification') };
- unless ($user->in_group('editclassifications')) {
- my %selectable_class = map { $_->id => 1 } @{$user->get_selectable_classifications};
- @classification_objs = grep { $selectable_class{$_->id} } @classification_objs;
- }
+ my @classification_objs
+ = @{params_to_objects($params, 'Bugzilla::Classification')};
+ unless ($user->in_group('editclassifications')) {
+ my %selectable_class
+ = map { $_->id => 1 } @{$user->get_selectable_classifications};
+ @classification_objs = grep { $selectable_class{$_->id} } @classification_objs;
+ }
- my @classifications = map { $self->_classification_to_hash($_, $params) } @classification_objs;
+ my @classifications
+ = map { $self->_classification_to_hash($_, $params) } @classification_objs;
- return { classifications => \@classifications };
+ return {classifications => \@classifications};
}
sub _classification_to_hash {
- my ($self, $classification, $params) = @_;
-
- my $user = Bugzilla->user;
- return unless (Bugzilla->params->{'useclassification'} || $user->in_group('editclassifications'));
-
- my $products = $user->in_group('editclassifications') ?
- $classification->products : $user->get_selectable_products($classification->id);
-
- return filter $params, {
- id => $self->type('int', $classification->id),
- name => $self->type('string', $classification->name),
- description => $self->type('string', $classification->description),
- sort_key => $self->type('int', $classification->sortkey),
- products => [ map { $self->_product_to_hash($_, $params) } @$products ],
+ my ($self, $classification, $params) = @_;
+
+ my $user = Bugzilla->user;
+ return
+ unless (Bugzilla->params->{'useclassification'}
+ || $user->in_group('editclassifications'));
+
+ my $products
+ = $user->in_group('editclassifications')
+ ? $classification->products
+ : $user->get_selectable_products($classification->id);
+
+ return filter $params,
+ {
+ id => $self->type('int', $classification->id),
+ name => $self->type('string', $classification->name),
+ description => $self->type('string', $classification->description),
+ sort_key => $self->type('int', $classification->sortkey),
+ products => [map { $self->_product_to_hash($_, $params) } @$products],
};
}
sub _product_to_hash {
- my ($self, $product, $params) = @_;
-
- return filter $params, {
- id => $self->type('int', $product->id),
- name => $self->type('string', $product->name),
- description => $self->type('string', $product->description),
- }, undef, 'products';
+ my ($self, $product, $params) = @_;
+
+ return filter $params,
+ {
+ id => $self->type('int', $product->id),
+ name => $self->type('string', $product->name),
+ description => $self->type('string', $product->description),
+ },
+ undef, 'products';
}
1;
diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm
index 71435c13a..71fcccd6e 100644
--- a/Bugzilla/WebService/Constants.pm
+++ b/Bugzilla/WebService/Constants.pm
@@ -14,27 +14,27 @@ use warnings;
use base qw(Exporter);
our @EXPORT = qw(
- WS_ERROR_CODE
+ 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
+ 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
+ ERROR_UNKNOWN_FATAL
+ ERROR_UNKNOWN_TRANSIENT
- XMLRPC_CONTENT_TYPE_WHITELIST
- REST_CONTENT_TYPE_WHITELIST
+ XMLRPC_CONTENT_TYPE_WHITELIST
+ REST_CONTENT_TYPE_WHITELIST
- WS_DISPATCH
+ WS_DISPATCH
- API_AUTH_HEADERS
+ API_AUTH_HEADERS
);
# This maps the error names in global/*-error.html.tmpl to numbers.
@@ -56,161 +56,184 @@ our @EXPORT = qw(
# comment that it was retired. Also, if an error changes its name, you'll
# have to fix it here.
use constant WS_ERROR_CODE => {
- # Generic errors (Bugzilla::Object and others) are 50-99.
- object_not_specified => 50,
- reassign_to_empty => 50,
- param_required => 50,
- params_required => 50,
- undefined_field => 50,
- object_does_not_exist => 51,
- param_must_be_numeric => 52,
- number_not_numeric => 52,
- param_invalid => 53,
- number_too_large => 54,
- number_too_small => 55,
- illegal_date => 56,
- # Bug errors usually occupy the 100-200 range.
- improper_bug_id_field_value => 100,
- bug_id_does_not_exist => 101,
- bug_access_denied => 102,
- bug_access_query => 102,
- # These all mean "invalid alias"
- alias_too_long => 103,
- alias_in_use => 103,
- alias_is_numeric => 103,
- alias_has_comma_or_space => 103,
- multiple_alias_not_allowed => 103,
- # Misc. bug field errors
- illegal_field => 104,
- freetext_too_long => 104,
- # Component errors
- require_component => 105,
- component_name_too_long => 105,
- # Invalid Product
- no_products => 106,
- entry_access_denied => 106,
- product_access_denied => 106,
- product_disabled => 106,
- # Invalid Summary
- require_summary => 107,
- # Invalid field name
- invalid_field_name => 108,
- # Not authorized to edit the bug
- product_edit_denied => 109,
- # Comment-related errors
- comment_is_private => 110,
- comment_id_invalid => 111,
- comment_too_long => 114,
- comment_invalid_isprivate => 117,
- # Comment tagging
- comment_tag_disabled => 125,
- comment_tag_invalid => 126,
- comment_tag_too_long => 127,
- comment_tag_too_short => 128,
- # See Also errors
- bug_url_invalid => 112,
- bug_url_too_long => 112,
- # Insidergroup Errors
- user_not_insider => 113,
- # Note: 114 is above in the Comment-related section.
- # Bug update errors
- illegal_change => 115,
- # Dependency errors
- dependency_loop_single => 116,
- dependency_loop_multi => 116,
- # Note: 117 is above in the Comment-related section.
- # Dup errors
- dupe_loop_detected => 118,
- dupe_id_required => 119,
- # Bug-related group errors
- group_invalid_removal => 120,
- group_restriction_not_allowed => 120,
- # Status/Resolution errors
- missing_resolution => 121,
- resolution_not_allowed => 122,
- illegal_bug_status_transition => 123,
- # Flag errors
- flag_status_invalid => 129,
- flag_update_denied => 130,
- flag_type_requestee_disabled => 131,
- flag_not_unique => 132,
- flag_type_not_unique => 133,
- flag_type_inactive => 134,
-
- # Authentication errors are usually 300-400.
- invalid_username_or_password => 300,
- account_disabled => 301,
- auth_invalid_email => 302,
- extern_id_conflict => -303,
- auth_failure => 304,
- password_insecure => 305,
- api_key_not_valid => 306,
- api_key_revoked => 306,
- auth_invalid_token => 307,
- invalid_cookies_or_token => 307,
-
- # Except, historically, AUTH_NODATA, which is 410.
- login_required => 410,
-
- # User errors are 500-600.
- account_exists => 500,
- illegal_email_address => 501,
- auth_cant_create_account => 501,
- account_creation_disabled => 501,
- account_creation_restricted => 501,
- # Error 502 password_too_short no longer exists.
- # Error 503 password_too_long no longer exists.
- invalid_username => 504,
- # This is from strict_isolation, but it also basically means
- # "invalid user."
- invalid_user_group => 504,
- user_access_by_id_denied => 505,
- user_access_by_match_denied => 505,
-
- # Attachment errors are 600-700.
- file_too_large => 600,
- invalid_content_type => 601,
- # Error 602 attachment_illegal_url no longer exists.
- file_not_specified => 603,
- missing_attachment_description => 604,
- # Error 605 attachment_url_disabled no longer exists.
- zero_length_file => 606,
-
- # Product erros are 700-800
- product_blank_name => 700,
- product_name_too_long => 701,
- product_name_already_in_use => 702,
- product_name_diff_in_case => 702,
- product_must_have_description => 703,
- product_must_have_version => 704,
- product_must_define_defaultmilestone => 705,
-
- # Group errors are 800-900
- empty_group_name => 800,
- group_exists => 801,
- empty_group_description => 802,
- invalid_regexp => 803,
- invalid_group_name => 804,
- group_cannot_view => 805,
-
- # Search errors are 1000-1100
- buglist_parameters_required => 1000,
-
- # BugUserLastVisited errors
- user_not_involved => 1300,
-
- # Job queue errors 1400-1500
- jobqueue_status_error => 1400,
-
- # Errors thrown by the WebService itself. The ones that are negative
- # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
- xmlrpc_invalid_value => -32600,
- unknown_method => -32601,
- json_rpc_post_only => 32610,
- json_rpc_invalid_callback => 32611,
- xmlrpc_illegal_content_type => 32612,
- json_rpc_illegal_content_type => 32613,
- rest_invalid_resource => 32614,
+
+ # Generic errors (Bugzilla::Object and others) are 50-99.
+ object_not_specified => 50,
+ reassign_to_empty => 50,
+ param_required => 50,
+ params_required => 50,
+ undefined_field => 50,
+ object_does_not_exist => 51,
+ param_must_be_numeric => 52,
+ number_not_numeric => 52,
+ param_invalid => 53,
+ number_too_large => 54,
+ number_too_small => 55,
+ illegal_date => 56,
+
+ # Bug errors usually occupy the 100-200 range.
+ improper_bug_id_field_value => 100,
+ bug_id_does_not_exist => 101,
+ bug_access_denied => 102,
+ bug_access_query => 102,
+
+ # These all mean "invalid alias"
+ alias_too_long => 103,
+ alias_in_use => 103,
+ alias_is_numeric => 103,
+ alias_has_comma_or_space => 103,
+ multiple_alias_not_allowed => 103,
+
+ # Misc. bug field errors
+ illegal_field => 104,
+ freetext_too_long => 104,
+
+ # Component errors
+ require_component => 105,
+ component_name_too_long => 105,
+
+ # Invalid Product
+ no_products => 106,
+ entry_access_denied => 106,
+ product_access_denied => 106,
+ product_disabled => 106,
+
+ # Invalid Summary
+ require_summary => 107,
+
+ # Invalid field name
+ invalid_field_name => 108,
+
+ # Not authorized to edit the bug
+ product_edit_denied => 109,
+
+ # Comment-related errors
+ comment_is_private => 110,
+ comment_id_invalid => 111,
+ comment_too_long => 114,
+ comment_invalid_isprivate => 117,
+
+ # Comment tagging
+ comment_tag_disabled => 125,
+ comment_tag_invalid => 126,
+ comment_tag_too_long => 127,
+ comment_tag_too_short => 128,
+
+ # See Also errors
+ bug_url_invalid => 112,
+ bug_url_too_long => 112,
+
+ # Insidergroup Errors
+ user_not_insider => 113,
+
+ # Note: 114 is above in the Comment-related section.
+ # Bug update errors
+ illegal_change => 115,
+
+ # Dependency errors
+ dependency_loop_single => 116,
+ dependency_loop_multi => 116,
+
+ # Note: 117 is above in the Comment-related section.
+ # Dup errors
+ dupe_loop_detected => 118,
+ dupe_id_required => 119,
+
+ # Bug-related group errors
+ group_invalid_removal => 120,
+ group_restriction_not_allowed => 120,
+
+ # Status/Resolution errors
+ missing_resolution => 121,
+ resolution_not_allowed => 122,
+ illegal_bug_status_transition => 123,
+
+ # Flag errors
+ flag_status_invalid => 129,
+ flag_update_denied => 130,
+ flag_type_requestee_disabled => 131,
+ flag_not_unique => 132,
+ flag_type_not_unique => 133,
+ flag_type_inactive => 134,
+
+ # Authentication errors are usually 300-400.
+ invalid_username_or_password => 300,
+ account_disabled => 301,
+ auth_invalid_email => 302,
+ extern_id_conflict => -303,
+ auth_failure => 304,
+ password_insecure => 305,
+ api_key_not_valid => 306,
+ api_key_revoked => 306,
+ auth_invalid_token => 307,
+ invalid_cookies_or_token => 307,
+
+ # Except, historically, AUTH_NODATA, which is 410.
+ login_required => 410,
+
+ # User errors are 500-600.
+ account_exists => 500,
+ illegal_email_address => 501,
+ auth_cant_create_account => 501,
+ account_creation_disabled => 501,
+ account_creation_restricted => 501,
+
+ # Error 502 password_too_short no longer exists.
+ # Error 503 password_too_long no longer exists.
+ invalid_username => 504,
+
+ # This is from strict_isolation, but it also basically means
+ # "invalid user."
+ invalid_user_group => 504,
+ user_access_by_id_denied => 505,
+ user_access_by_match_denied => 505,
+
+ # Attachment errors are 600-700.
+ file_too_large => 600,
+ invalid_content_type => 601,
+
+ # Error 602 attachment_illegal_url no longer exists.
+ file_not_specified => 603,
+ missing_attachment_description => 604,
+
+ # Error 605 attachment_url_disabled no longer exists.
+ zero_length_file => 606,
+
+ # Product erros are 700-800
+ product_blank_name => 700,
+ product_name_too_long => 701,
+ product_name_already_in_use => 702,
+ product_name_diff_in_case => 702,
+ product_must_have_description => 703,
+ product_must_have_version => 704,
+ product_must_define_defaultmilestone => 705,
+
+ # Group errors are 800-900
+ empty_group_name => 800,
+ group_exists => 801,
+ empty_group_description => 802,
+ invalid_regexp => 803,
+ invalid_group_name => 804,
+ group_cannot_view => 805,
+
+ # Search errors are 1000-1100
+ buglist_parameters_required => 1000,
+
+ # BugUserLastVisited errors
+ user_not_involved => 1300,
+
+ # Job queue errors 1400-1500
+ jobqueue_status_error => 1400,
+
+ # Errors thrown by the WebService itself. The ones that are negative
+ # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
+ xmlrpc_invalid_value => -32600,
+ unknown_method => -32601,
+ json_rpc_post_only => 32610,
+ json_rpc_invalid_callback => 32611,
+ xmlrpc_illegal_content_type => 32612,
+ json_rpc_illegal_content_type => 32613,
+ rest_invalid_resource => 32614,
};
# RESTful webservices use the http status code
@@ -231,81 +254,82 @@ use constant STATUS_GONE => 410;
# http status code based on the error code or use the
# default STATUS_BAD_REQUEST.
sub REST_STATUS_CODE_MAP {
- my $status_code_map = {
- 51 => STATUS_NOT_FOUND,
- 101 => STATUS_NOT_FOUND,
- 102 => STATUS_NOT_AUTHORIZED,
- 106 => STATUS_NOT_AUTHORIZED,
- 109 => STATUS_NOT_AUTHORIZED,
- 110 => STATUS_NOT_AUTHORIZED,
- 113 => STATUS_NOT_AUTHORIZED,
- 115 => STATUS_NOT_AUTHORIZED,
- 120 => STATUS_NOT_AUTHORIZED,
- 300 => STATUS_NOT_AUTHORIZED,
- 301 => STATUS_NOT_AUTHORIZED,
- 302 => STATUS_NOT_AUTHORIZED,
- 303 => STATUS_NOT_AUTHORIZED,
- 304 => STATUS_NOT_AUTHORIZED,
- 410 => STATUS_NOT_AUTHORIZED,
- 504 => STATUS_NOT_AUTHORIZED,
- 505 => STATUS_NOT_AUTHORIZED,
- 32614 => STATUS_NOT_FOUND,
- _default => STATUS_BAD_REQUEST
- };
-
- Bugzilla::Hook::process('webservice_status_code_map',
- { status_code_map => $status_code_map });
-
- return $status_code_map;
-};
+ my $status_code_map = {
+ 51 => STATUS_NOT_FOUND,
+ 101 => STATUS_NOT_FOUND,
+ 102 => STATUS_NOT_AUTHORIZED,
+ 106 => STATUS_NOT_AUTHORIZED,
+ 109 => STATUS_NOT_AUTHORIZED,
+ 110 => STATUS_NOT_AUTHORIZED,
+ 113 => STATUS_NOT_AUTHORIZED,
+ 115 => STATUS_NOT_AUTHORIZED,
+ 120 => STATUS_NOT_AUTHORIZED,
+ 300 => STATUS_NOT_AUTHORIZED,
+ 301 => STATUS_NOT_AUTHORIZED,
+ 302 => STATUS_NOT_AUTHORIZED,
+ 303 => STATUS_NOT_AUTHORIZED,
+ 304 => STATUS_NOT_AUTHORIZED,
+ 410 => STATUS_NOT_AUTHORIZED,
+ 504 => STATUS_NOT_AUTHORIZED,
+ 505 => STATUS_NOT_AUTHORIZED,
+ 32614 => STATUS_NOT_FOUND,
+ _default => STATUS_BAD_REQUEST
+ };
+
+ Bugzilla::Hook::process('webservice_status_code_map',
+ {status_code_map => $status_code_map});
+
+ return $status_code_map;
+}
# These are the fallback defaults for errors not in ERROR_CODE.
use constant ERROR_UNKNOWN_FATAL => -32000;
use constant ERROR_UNKNOWN_TRANSIENT => 32000;
-use constant ERROR_GENERAL => 999;
+use constant ERROR_GENERAL => 999;
use constant XMLRPC_CONTENT_TYPE_WHITELIST => qw(
- text/xml
- application/xml
+ text/xml
+ application/xml
);
# The first content type specified is used as the default.
use constant REST_CONTENT_TYPE_WHITELIST => qw(
- application/json
- application/javascript
- text/javascript
- text/html
+ application/json
+ application/javascript
+ text/javascript
+ text/html
);
sub WS_DISPATCH {
- # We "require" here instead of "use" above to avoid a dependency loop.
- require Bugzilla::Hook;
- my %hook_dispatch;
- Bugzilla::Hook::process('webservice', { dispatch => \%hook_dispatch });
-
- my $dispatch = {
- 'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
- 'Bug' => 'Bugzilla::WebService::Bug',
- 'Classification' => 'Bugzilla::WebService::Classification',
- 'User' => 'Bugzilla::WebService::User',
- 'Product' => 'Bugzilla::WebService::Product',
- 'Group' => 'Bugzilla::WebService::Group',
- 'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit',
- 'Elastic' => 'Bugzilla::WebService::Elastic',
- %hook_dispatch
- };
- return $dispatch;
-};
+
+ # We "require" here instead of "use" above to avoid a dependency loop.
+ require Bugzilla::Hook;
+ my %hook_dispatch;
+ Bugzilla::Hook::process('webservice', {dispatch => \%hook_dispatch});
+
+ my $dispatch = {
+ 'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
+ 'Bug' => 'Bugzilla::WebService::Bug',
+ 'Classification' => 'Bugzilla::WebService::Classification',
+ 'User' => 'Bugzilla::WebService::User',
+ 'Product' => 'Bugzilla::WebService::Product',
+ 'Group' => 'Bugzilla::WebService::Group',
+ 'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit',
+ 'Elastic' => 'Bugzilla::WebService::Elastic',
+ %hook_dispatch
+ };
+ return $dispatch;
+}
# Custom HTTP headers that can be used for API authentication rather than
# passing as URL parameters. This is useful if you do not want sensitive
# information to show up in webserver log files.
use constant API_AUTH_HEADERS => {
- X_BUGZILLA_LOGIN => 'Bugzilla_login',
- X_BUGZILLA_PASSWORD => 'Bugzilla_password',
- X_BUGZILLA_API_KEY => 'Bugzilla_api_key',
- X_BUGZILLA_TOKEN => 'Bugzilla_token',
+ X_BUGZILLA_LOGIN => 'Bugzilla_login',
+ X_BUGZILLA_PASSWORD => 'Bugzilla_password',
+ X_BUGZILLA_API_KEY => 'Bugzilla_api_key',
+ X_BUGZILLA_TOKEN => 'Bugzilla_token',
};
1;
diff --git a/Bugzilla/WebService/Elastic.pm b/Bugzilla/WebService/Elastic.pm
index 3a33a1dba..373f6db58 100644
--- a/Bugzilla/WebService/Elastic.pm
+++ b/Bugzilla/WebService/Elastic.pm
@@ -30,30 +30,28 @@ use Bugzilla::Error;
use Bugzilla::WebService::Util qw(validate);
use Bugzilla::Util qw(trim detaint_natural trick_taint);
-use constant READ_ONLY => qw( suggest_users );
+use constant READ_ONLY => qw( suggest_users );
use constant PUBLIC_METHODS => qw( suggest_users );
sub suggest_users {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- ThrowCodeError('params_required', { function => 'Elastic.suggest_users', params => ['match'] })
- unless defined $params->{match};
+ ThrowCodeError('params_required',
+ {function => 'Elastic.suggest_users', params => ['match']})
+ unless defined $params->{match};
- ThrowUserError('user_access_by_match_denied')
- unless Bugzilla->user->id;
+ ThrowUserError('user_access_by_match_denied') unless Bugzilla->user->id;
- trick_taint($params->{match});
- my $results = Bugzilla->elastic->suggest_users($params->{match} . "");
- my @users = map {
- {
- real_name => $self->type(string => $_->{real_name}),
- name => $self->type(email => $_->{name}),
- }
- } @$results;
+ trick_taint($params->{match});
+ my $results = Bugzilla->elastic->suggest_users($params->{match} . "");
+ my @users = map { {
+ real_name => $self->type(string => $_->{real_name}),
+ name => $self->type(email => $_->{name}),
+ } } @$results;
- return { users => \@users };
+ return {users => \@users};
}
-1; \ No newline at end of file
+1;
diff --git a/Bugzilla/WebService/Group.pm b/Bugzilla/WebService/Group.pm
index b13003e08..4467883a4 100644
--- a/Bugzilla/WebService/Group.pm
+++ b/Bugzilla/WebService/Group.pm
@@ -17,207 +17,210 @@ use Bugzilla::Error;
use Bugzilla::WebService::Util qw(validate translate params_to_objects);
use constant PUBLIC_METHODS => qw(
- create
- get
- update
+ create
+ get
+ update
);
-use constant MAPPED_RETURNS => {
- userregexp => 'user_regexp',
- isactive => 'is_active'
-};
+use constant MAPPED_RETURNS =>
+ {userregexp => 'user_regexp', isactive => 'is_active'};
sub create {
- my ($self, $params) = @_;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group('creategroups')
- || ThrowUserError("auth_failure", { group => "creategroups",
- action => "add",
- object => "group"});
- # Create group
- my $group = Bugzilla::Group->create({
- name => $params->{name},
- description => $params->{description},
- userregexp => $params->{user_regexp},
- isactive => $params->{is_active},
- isbuggroup => 1,
- icon_url => $params->{icon_url}
- });
- return { id => $self->type('int', $group->id) };
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('creategroups')
+ || ThrowUserError("auth_failure",
+ {group => "creategroups", action => "add", object => "group"});
+
+ # Create group
+ my $group = Bugzilla::Group->create({
+ name => $params->{name},
+ description => $params->{description},
+ userregexp => $params->{user_regexp},
+ isactive => $params->{is_active},
+ isbuggroup => 1,
+ icon_url => $params->{icon_url}
+ });
+ return {id => $self->type('int', $group->id)};
}
sub update {
- my ($self, $params) = @_;
-
- my $dbh = Bugzilla->dbh;
+ my ($self, $params) = @_;
+
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('creategroups')
+ || ThrowUserError("auth_failure",
+ {group => "creategroups", action => "edit", object => "group"});
+
+ defined($params->{names})
+ || defined($params->{ids})
+ || ThrowCodeError('params_required',
+ {function => 'Group.update', params => ['ids', 'names']});
+
+ my $group_objects = params_to_objects($params, 'Bugzilla::Group');
+
+ my %values = %$params;
+
+ # We delete names and ids to keep only new values to set.
+ delete $values{names};
+ delete $values{ids};
+
+ $dbh->bz_start_transaction();
+ foreach my $group (@$group_objects) {
+ $group->set_all(\%values);
+ }
+
+ my %changes;
+ foreach my $group (@$group_objects) {
+ my $returned_changes = $group->update();
+ $changes{$group->id} = translate($returned_changes, MAPPED_RETURNS);
+ }
+ $dbh->bz_commit_transaction();
+
+ my @result;
+ foreach my $group (@$group_objects) {
+ my %hash = (id => $group->id, changes => {},);
+ foreach my $field (keys %{$changes{$group->id}}) {
+ my $change = $changes{$group->id}->{$field};
+ $hash{changes}{$field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
+ }
+ push(@result, \%hash);
+ }
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group('creategroups')
- || ThrowUserError("auth_failure", { group => "creategroups",
- action => "edit",
- object => "group" });
+ return {groups => \@result};
+}
- defined($params->{names}) || defined($params->{ids})
- || ThrowCodeError('params_required',
- { function => 'Group.update', params => ['ids', 'names'] });
+sub get {
+ my ($self, $params) = validate(@_, 'ids', 'names', 'type');
- my $group_objects = params_to_objects($params, 'Bugzilla::Group');
+ Bugzilla->login(LOGIN_REQUIRED);
- my %values = %$params;
+ # Reject access if there is no sense in continuing.
+ my $user = Bugzilla->user;
+ my $all_groups
+ = $user->in_group('editusers') || $user->in_group('creategroups');
+ if (!$all_groups && !$user->can_bless) {
+ ThrowUserError('group_cannot_view');
+ }
- # We delete names and ids to keep only new values to set.
- delete $values{names};
- delete $values{ids};
+ Bugzilla->switch_to_shadow_db();
- $dbh->bz_start_transaction();
- foreach my $group (@$group_objects) {
- $group->set_all(\%values);
- }
+ my $groups = [];
- my %changes;
- foreach my $group (@$group_objects) {
- my $returned_changes = $group->update();
- $changes{$group->id} = translate($returned_changes, MAPPED_RETURNS);
- }
- $dbh->bz_commit_transaction();
-
- my @result;
- foreach my $group (@$group_objects) {
- my %hash = (
- id => $group->id,
- changes => {},
- );
- foreach my $field (keys %{ $changes{$group->id} }) {
- my $change = $changes{$group->id}->{$field};
- $hash{changes}{$field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
- push(@result, \%hash);
- }
-
- return { groups => \@result };
-}
-
-sub get {
- my ($self, $params) = validate(@_, 'ids', 'names', 'type');
+ if (defined $params->{ids}) {
- Bugzilla->login(LOGIN_REQUIRED);
+ # Get the groups by id
+ $groups = Bugzilla::Group->new_from_list($params->{ids});
+ }
- # Reject access if there is no sense in continuing.
- my $user = Bugzilla->user;
- my $all_groups = $user->in_group('editusers') || $user->in_group('creategroups');
- if (!$all_groups && !$user->can_bless) {
- ThrowUserError('group_cannot_view');
- }
+ if (defined $params->{names}) {
- Bugzilla->switch_to_shadow_db();
+ # Get the groups by name. Check will throw an error if a bad name is given
+ foreach my $name (@{$params->{names}}) {
- my $groups = [];
+ # Skip if we got this from params->{id}
+ next if grep { $_->name eq $name } @$groups;
- if (defined $params->{ids}) {
- # Get the groups by id
- $groups = Bugzilla::Group->new_from_list($params->{ids});
+ push @$groups, Bugzilla::Group->check({name => $name});
}
+ }
- if (defined $params->{names}) {
- # Get the groups by name. Check will throw an error if a bad name is given
- foreach my $name (@{$params->{names}}) {
- # Skip if we got this from params->{id}
- next if grep { $_->name eq $name } @$groups;
-
- push @$groups, Bugzilla::Group->check({ name => $name });
- }
+ if (!defined $params->{ids} && !defined $params->{names}) {
+ if ($all_groups) {
+ @$groups = Bugzilla::Group->get_all;
}
-
- if (!defined $params->{ids} && !defined $params->{names}) {
- if ($all_groups) {
- @$groups = Bugzilla::Group->get_all;
- }
- else {
- # Get only groups the user has bless groups too
- $groups = $user->bless_groups;
- }
+ else {
+ # Get only groups the user has bless groups too
+ $groups = $user->bless_groups;
}
+ }
- # Now create a result entry for each.
- my @groups = map { $self->_group_to_hash($params, $_) } @$groups;
- return { groups => \@groups };
+ # Now create a result entry for each.
+ my @groups = map { $self->_group_to_hash($params, $_) } @$groups;
+ return {groups => \@groups};
}
sub _group_to_hash {
- my ($self, $params, $group) = @_;
- my $user = Bugzilla->user;
-
- my $field_data = {
- id => $self->type('int', $group->id),
- name => $self->type('string', $group->name),
- description => $self->type('string', $group->description),
- };
-
- if ($user->in_group('creategroups')) {
- $field_data->{is_active} = $self->type('boolean', $group->is_active);
- $field_data->{is_bug_group} = $self->type('boolean', $group->is_bug_group);
- $field_data->{user_regexp} = $self->type('string', $group->user_regexp);
- }
-
- if ($params->{membership}) {
- $field_data->{membership} = $self->_get_group_membership($group, $params);
- }
- return $field_data;
+ my ($self, $params, $group) = @_;
+ my $user = Bugzilla->user;
+
+ my $field_data = {
+ id => $self->type('int', $group->id),
+ name => $self->type('string', $group->name),
+ description => $self->type('string', $group->description),
+ };
+
+ if ($user->in_group('creategroups')) {
+ $field_data->{is_active} = $self->type('boolean', $group->is_active);
+ $field_data->{is_bug_group} = $self->type('boolean', $group->is_bug_group);
+ $field_data->{user_regexp} = $self->type('string', $group->user_regexp);
+ }
+
+ if ($params->{membership}) {
+ $field_data->{membership} = $self->_get_group_membership($group, $params);
+ }
+ return $field_data;
}
sub _get_group_membership {
- my ($self, $group, $params) = @_;
- my $user = Bugzilla->user;
+ my ($self, $group, $params) = @_;
+ my $user = Bugzilla->user;
- my %users_only;
- my $dbh = Bugzilla->dbh;
- my $editusers = $user->in_group('editusers');
+ my %users_only;
+ my $dbh = Bugzilla->dbh;
+ my $editusers = $user->in_group('editusers');
- my $query = 'SELECT userid FROM profiles';
- my $visibleGroups;
+ my $query = 'SELECT userid FROM profiles';
+ my $visibleGroups;
- if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
- # Show only users in visible groups.
- $visibleGroups = $user->visible_groups_inherited;
+ if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
- if (scalar @$visibleGroups) {
- $query .= qq{, user_group_map AS ugm
+ # Show only users in visible groups.
+ $visibleGroups = $user->visible_groups_inherited;
+
+ if (scalar @$visibleGroups) {
+ $query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid
AND ugm.isbless = 0
AND } . $dbh->sql_in('ugm.group_id', $visibleGroups);
- }
- } elsif ($editusers || $user->can_bless($group->id) || $user->in_group('creategroups')) {
- $visibleGroups = 1;
- $query .= qq{, user_group_map AS ugm
+ }
+ }
+ elsif ($editusers
+ || $user->can_bless($group->id)
+ || $user->in_group('creategroups'))
+ {
+ $visibleGroups = 1;
+ $query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid
AND ugm.isbless = 0
};
- }
- if (!$visibleGroups) {
- ThrowUserError('group_not_visible', { group => $group });
- }
-
- my $grouplist = Bugzilla::Group->flatten_group_membership($group->id);
- $query .= ' AND ' . $dbh->sql_in('ugm.group_id', $grouplist);
-
- my $userids = $dbh->selectcol_arrayref($query);
- my $user_objects = Bugzilla::User->new_from_list($userids);
- my @users =
- map {{
- id => $self->type('int', $_->id),
- real_name => $self->type('string', $_->name),
- name => $self->type('string', $_->login),
- email => $self->type('string', $_->email),
- can_login => $self->type('boolean', $_->is_enabled),
- email_enabled => $self->type('boolean', $_->email_enabled),
- login_denied_text => $self->type('string', $_->disabledtext),
- }} @$user_objects;
-
- return \@users;
+ }
+ if (!$visibleGroups) {
+ ThrowUserError('group_not_visible', {group => $group});
+ }
+
+ my $grouplist = Bugzilla::Group->flatten_group_membership($group->id);
+ $query .= ' AND ' . $dbh->sql_in('ugm.group_id', $grouplist);
+
+ my $userids = $dbh->selectcol_arrayref($query);
+ my $user_objects = Bugzilla::User->new_from_list($userids);
+ my @users = map { {
+ id => $self->type('int', $_->id),
+ real_name => $self->type('string', $_->name),
+ name => $self->type('string', $_->login),
+ email => $self->type('string', $_->email),
+ can_login => $self->type('boolean', $_->is_enabled),
+ email_enabled => $self->type('boolean', $_->email_enabled),
+ login_denied_text => $self->type('string', $_->disabledtext),
+ } } @$user_objects;
+
+ return \@users;
}
1;
diff --git a/Bugzilla/WebService/JSON.pm b/Bugzilla/WebService/JSON.pm
index 5c28b20f4..f670d1fd9 100644
--- a/Bugzilla/WebService/JSON.pm
+++ b/Bugzilla/WebService/JSON.pm
@@ -39,7 +39,7 @@ sub decode {
}
}
-sub _build_json { JSON::MaybeXS->new }
+sub _build_json { JSON::MaybeXS->new }
# delegation all the json options to the real json encoder.
{
diff --git a/Bugzilla/WebService/Product.pm b/Bugzilla/WebService/Product.pm
index cdd8a0a92..0726c371d 100644
--- a/Bugzilla/WebService/Product.pm
+++ b/Bugzilla/WebService/Product.pm
@@ -20,24 +20,22 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Util qw(validate filter filter_wants);
use constant READ_ONLY => qw(
- get
- get_accessible_products
- get_enterable_products
- get_selectable_products
+ get
+ get_accessible_products
+ get_enterable_products
+ get_selectable_products
);
use constant PUBLIC_METHODS => qw(
- create
- get
- get_accessible_products
- get_enterable_products
- get_selectable_products
+ create
+ get
+ get_accessible_products
+ get_enterable_products
+ get_selectable_products
);
-use constant FIELD_MAP => {
- has_unconfirmed => 'allows_unconfirmed',
- is_open => 'isactive',
-};
+use constant FIELD_MAP =>
+ {has_unconfirmed => 'allows_unconfirmed', is_open => 'isactive',};
##################################################
# Add aliases here for method name compatibility #
@@ -47,256 +45,240 @@ BEGIN { *get_products = \&get }
# Get the ids of the products the user can search
sub get_selectable_products {
- Bugzilla->switch_to_shadow_db();
- return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]};
+ Bugzilla->switch_to_shadow_db();
+ return {ids => [map { $_->id } @{Bugzilla->user->get_selectable_products}]};
}
# Get the ids of the products the user can enter bugs against
sub get_enterable_products {
- Bugzilla->switch_to_shadow_db();
- return {ids => [map {$_->id} @{Bugzilla->user->get_enterable_products}]};
+ Bugzilla->switch_to_shadow_db();
+ return {ids => [map { $_->id } @{Bugzilla->user->get_enterable_products}]};
}
# Get the union of the products the user can search and enter bugs against.
sub get_accessible_products {
- Bugzilla->switch_to_shadow_db();
- return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
+ Bugzilla->switch_to_shadow_db();
+ return {ids => [map { $_->id } @{Bugzilla->user->get_accessible_products}]};
}
# Get a list of actual products, based on list of ids or names
our %FLAG_CACHE;
+
sub get {
- my ($self, $params) = validate(@_, 'ids', 'names', 'type');
- my $user = Bugzilla->user;
-
- Bugzilla->request_cache->{bz_etag_disable} = 1;
-
- defined $params->{ids} || defined $params->{names} || defined $params->{type}
- || ThrowCodeError("params_required", { function => "Product.get",
- params => ['ids', 'names', 'type'] });
-
- Bugzilla->switch_to_shadow_db();
-
- my $products = [];
- if (defined $params->{type}) {
- my %product_hash;
- foreach my $type (@{ $params->{type} }) {
- my $result = [];
- if ($type eq 'accessible') {
- $result = $user->get_accessible_products();
- }
- elsif ($type eq 'enterable') {
- $result = $user->get_enterable_products();
- }
- elsif ($type eq 'selectable') {
- $result = $user->get_selectable_products();
- }
- else {
- ThrowUserError('get_products_invalid_type',
- { type => $type });
- }
- map { $product_hash{$_->id} = $_ } @$result;
- }
- $products = [ values %product_hash ];
- }
- else {
- $products = $user->get_accessible_products;
+ my ($self, $params) = validate(@_, 'ids', 'names', 'type');
+ my $user = Bugzilla->user;
+
+ Bugzilla->request_cache->{bz_etag_disable} = 1;
+
+ defined $params->{ids}
+ || defined $params->{names}
+ || defined $params->{type}
+ || ThrowCodeError("params_required",
+ {function => "Product.get", params => ['ids', 'names', 'type']});
+
+ Bugzilla->switch_to_shadow_db();
+
+ my $products = [];
+ if (defined $params->{type}) {
+ my %product_hash;
+ foreach my $type (@{$params->{type}}) {
+ my $result = [];
+ if ($type eq 'accessible') {
+ $result = $user->get_accessible_products();
+ }
+ elsif ($type eq 'enterable') {
+ $result = $user->get_enterable_products();
+ }
+ elsif ($type eq 'selectable') {
+ $result = $user->get_selectable_products();
+ }
+ else {
+ ThrowUserError('get_products_invalid_type', {type => $type});
+ }
+ map { $product_hash{$_->id} = $_ } @$result;
}
+ $products = [values %product_hash];
+ }
+ else {
+ $products = $user->get_accessible_products;
+ }
- my @requested_products;
+ my @requested_products;
- if (defined $params->{ids}) {
- # Create a hash with the ids the user wants
- my %ids = map { $_ => 1 } @{$params->{ids}};
+ if (defined $params->{ids}) {
- # Return the intersection of this, by grepping the ids from
- # accessible products.
- push(@requested_products,
- grep { $ids{$_->id} } @$products);
- }
+ # Create a hash with the ids the user wants
+ my %ids = map { $_ => 1 } @{$params->{ids}};
- if (defined $params->{names}) {
- # Create a hash with the names the user wants
- my %names = map { lc($_) => 1 } @{$params->{names}};
-
- # Return the intersection of this, by grepping the names from
- # accessible products, union'ed with products found by ID to
- # avoid duplicates
- foreach my $product (grep { $names{lc $_->name} }
- @$products) {
- next if grep { $_->id == $product->id }
- @requested_products;
- push @requested_products, $product;
- }
- }
+ # Return the intersection of this, by grepping the ids from
+ # accessible products.
+ push(@requested_products, grep { $ids{$_->id} } @$products);
+ }
- # If we just requested a specific type of products without
- # specifying ids or names, then return the entire list.
- if (!defined $params->{ids} && !defined $params->{names}) {
- @requested_products = @$products;
- }
+ if (defined $params->{names}) {
+
+ # Create a hash with the names the user wants
+ my %names = map { lc($_) => 1 } @{$params->{names}};
- # Now create a result entry for each.
- local %FLAG_CACHE = ();
- my @products = map { $self->_product_to_hash($params, $_) }
- @requested_products;
- return { products => \@products };
+ # Return the intersection of this, by grepping the names from
+ # accessible products, union'ed with products found by ID to
+ # avoid duplicates
+ foreach my $product (grep { $names{lc $_->name} } @$products) {
+ next if grep { $_->id == $product->id } @requested_products;
+ push @requested_products, $product;
+ }
+ }
+
+ # If we just requested a specific type of products without
+ # specifying ids or names, then return the entire list.
+ if (!defined $params->{ids} && !defined $params->{names}) {
+ @requested_products = @$products;
+ }
+
+ # Now create a result entry for each.
+ local %FLAG_CACHE = ();
+ my @products = map { $self->_product_to_hash($params, $_) } @requested_products;
+ return {products => \@products};
}
sub create {
- my ($self, $params) = @_;
-
- Bugzilla->login(LOGIN_REQUIRED);
- Bugzilla->user->in_group('editcomponents')
- || ThrowUserError("auth_failure", { group => "editcomponents",
- action => "add",
- object => "products"});
- # Create product
- my $args = {
- name => $params->{name},
- description => $params->{description},
- version => $params->{version},
- defaultmilestone => $params->{default_milestone},
- # create_series has no default value.
- create_series => defined $params->{create_series} ?
- $params->{create_series} : 1
- };
- foreach my $field (qw(has_unconfirmed is_open classification)) {
- if (defined $params->{$field}) {
- my $name = FIELD_MAP->{$field} || $field;
- $args->{$name} = $params->{$field};
- }
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('editcomponents')
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "add", object => "products"});
+
+ # Create product
+ my $args = {
+ name => $params->{name},
+ description => $params->{description},
+ version => $params->{version},
+ defaultmilestone => $params->{default_milestone},
+
+ # create_series has no default value.
+ create_series => defined $params->{create_series}
+ ? $params->{create_series}
+ : 1
+ };
+ foreach my $field (qw(has_unconfirmed is_open classification)) {
+ if (defined $params->{$field}) {
+ my $name = FIELD_MAP->{$field} || $field;
+ $args->{$name} = $params->{$field};
}
- my $product = Bugzilla::Product->create($args);
- return { id => $self->type('int', $product->id) };
+ }
+ my $product = Bugzilla::Product->create($args);
+ return {id => $self->type('int', $product->id)};
}
sub _product_to_hash {
- my ($self, $params, $product) = @_;
-
- my $field_data = {
- id => $self->type('int', $product->id),
- name => $self->type('string', $product->name),
- description => $self->type('string', $product->description),
- is_active => $self->type('boolean', $product->is_active),
- default_milestone => $self->type('string', $product->default_milestone),
- has_unconfirmed => $self->type('boolean', $product->allows_unconfirmed),
- classification => $self->type('string', $product->classification->name),
- };
- if (filter_wants($params, 'components')) {
- $field_data->{components} = [map {
- $self->_component_to_hash($_, $params)
- } @{$product->components}];
- }
- if (filter_wants($params, 'versions')) {
- $field_data->{versions} = [map {
- $self->_version_to_hash($_, $params)
- } @{$product->versions}];
- }
- if (filter_wants($params, 'milestones')) {
- $field_data->{milestones} = [map {
- $self->_milestone_to_hash($_, $params)
- } @{$product->milestones}];
- }
- # BMO - add default hw/os
- $field_data->{default_platform} = $self->type('string', $product->default_platform);
- $field_data->{default_op_sys} = $self->type('string', $product->default_op_sys);
- # BMO - add default security group
- $field_data->{default_security_group} = $self->type('string', $product->default_security_group);
- return filter($params, $field_data);
+ my ($self, $params, $product) = @_;
+
+ my $field_data = {
+ id => $self->type('int', $product->id),
+ name => $self->type('string', $product->name),
+ description => $self->type('string', $product->description),
+ is_active => $self->type('boolean', $product->is_active),
+ default_milestone => $self->type('string', $product->default_milestone),
+ has_unconfirmed => $self->type('boolean', $product->allows_unconfirmed),
+ classification => $self->type('string', $product->classification->name),
+ };
+ if (filter_wants($params, 'components')) {
+ $field_data->{components}
+ = [map { $self->_component_to_hash($_, $params) } @{$product->components}];
+ }
+ if (filter_wants($params, 'versions')) {
+ $field_data->{versions}
+ = [map { $self->_version_to_hash($_, $params) } @{$product->versions}];
+ }
+ if (filter_wants($params, 'milestones')) {
+ $field_data->{milestones}
+ = [map { $self->_milestone_to_hash($_, $params) } @{$product->milestones}];
+ }
+
+ # BMO - add default hw/os
+ $field_data->{default_platform}
+ = $self->type('string', $product->default_platform);
+ $field_data->{default_op_sys} = $self->type('string', $product->default_op_sys);
+
+ # BMO - add default security group
+ $field_data->{default_security_group}
+ = $self->type('string', $product->default_security_group);
+ return filter($params, $field_data);
}
sub _component_to_hash {
- my ($self, $component, $params) = @_;
- my $field_data = filter $params, {
- id =>
- $self->type('int', $component->id),
- name =>
- $self->type('string', $component->name),
- description =>
- $self->type('string', $component->description),
- default_assigned_to =>
- $self->type('email', $component->default_assignee->login),
- default_qa_contact =>
- $self->type('email', $component->default_qa_contact->login),
- triage_owner =>
- $self->type('email', $component->triage_owner->login),
- sort_key => # sort_key is returned to match Bug.fields
- 0,
- is_active =>
- $self->type('boolean', $component->is_active),
- }, undef, 'components';
-
- if (filter_wants($params, 'flag_types', undef, 'components')) {
- $field_data->{flag_types} = {
- bug =>
- [map {
- $FLAG_CACHE{ $_->id } //= $self->_flag_type_to_hash($_)
- } @{$component->flag_types->{'bug'}}],
- attachment =>
- [map {
- $FLAG_CACHE{ $_->id } //= $self->_flag_type_to_hash($_)
- } @{$component->flag_types->{'attachment'}}],
- };
- }
+ my ($self, $component, $params) = @_;
+ my $field_data = filter $params, {
+ id => $self->type('int', $component->id),
+ name => $self->type('string', $component->name),
+ description => $self->type('string', $component->description),
+ default_assigned_to =>
+ $self->type('email', $component->default_assignee->login),
+ default_qa_contact =>
+ $self->type('email', $component->default_qa_contact->login),
+ triage_owner => $self->type('email', $component->triage_owner->login),
+ sort_key => # sort_key is returned to match Bug.fields
+ 0,
+ is_active => $self->type('boolean', $component->is_active),
+ },
+ undef, 'components';
+
+ if (filter_wants($params, 'flag_types', undef, 'components')) {
+ $field_data->{flag_types} = {
+ bug => [
+ map { $FLAG_CACHE{$_->id} //= $self->_flag_type_to_hash($_) }
+ @{$component->flag_types->{'bug'}}
+ ],
+ attachment => [
+ map { $FLAG_CACHE{$_->id} //= $self->_flag_type_to_hash($_) }
+ @{$component->flag_types->{'attachment'}}
+ ],
+ };
+ }
- return $field_data;
+ return $field_data;
}
sub _flag_type_to_hash {
- my ($self, $flag_type) = @_;
- return {
- id =>
- $self->type('int', $flag_type->id),
- name =>
- $self->type('string', $flag_type->name),
- description =>
- $self->type('string', $flag_type->description),
- cc_list =>
- $self->type('string', $flag_type->cc_list),
- sort_key =>
- $self->type('int', $flag_type->sortkey),
- is_active =>
- $self->type('boolean', $flag_type->is_active),
- is_requestable =>
- $self->type('boolean', $flag_type->is_requestable),
- is_requesteeble =>
- $self->type('boolean', $flag_type->is_requesteeble),
- is_multiplicable =>
- $self->type('boolean', $flag_type->is_multiplicable),
- grant_group =>
- $self->type('int', $flag_type->grant_group_id),
- request_group =>
- $self->type('int', $flag_type->request_group_id),
- };
+ my ($self, $flag_type) = @_;
+ return {
+ id => $self->type('int', $flag_type->id),
+ name => $self->type('string', $flag_type->name),
+ description => $self->type('string', $flag_type->description),
+ cc_list => $self->type('string', $flag_type->cc_list),
+ sort_key => $self->type('int', $flag_type->sortkey),
+ is_active => $self->type('boolean', $flag_type->is_active),
+ is_requestable => $self->type('boolean', $flag_type->is_requestable),
+ is_requesteeble => $self->type('boolean', $flag_type->is_requesteeble),
+ is_multiplicable => $self->type('boolean', $flag_type->is_multiplicable),
+ grant_group => $self->type('int', $flag_type->grant_group_id),
+ request_group => $self->type('int', $flag_type->request_group_id),
+ };
}
sub _version_to_hash {
- my ($self, $version, $params) = @_;
- return filter $params, {
- id =>
- $self->type('int', $version->id),
- name =>
- $self->type('string', $version->name),
- sort_key => # sort_key is returened to match Bug.fields
- 0,
- is_active =>
- $self->type('boolean', $version->is_active),
- }, undef, 'versions';
+ my ($self, $version, $params) = @_;
+ return filter $params, {
+ id => $self->type('int', $version->id),
+ name => $self->type('string', $version->name),
+ sort_key => # sort_key is returened to match Bug.fields
+ 0,
+ is_active => $self->type('boolean', $version->is_active),
+ },
+ undef, 'versions';
}
sub _milestone_to_hash {
- my ($self, $milestone, $params) = @_;
- return filter $params, {
- id =>
- $self->type('int', $milestone->id),
- name =>
- $self->type('string', $milestone->name),
- sort_key =>
- $self->type('int', $milestone->sortkey),
- is_active =>
- $self->type('boolean', $milestone->is_active),
- }, undef, 'milestones';
+ my ($self, $milestone, $params) = @_;
+ return filter $params,
+ {
+ id => $self->type('int', $milestone->id),
+ name => $self->type('string', $milestone->name),
+ sort_key => $self->type('int', $milestone->sortkey),
+ is_active => $self->type('boolean', $milestone->is_active),
+ },
+ undef, 'milestones';
}
1;
diff --git a/Bugzilla/WebService/Server.pm b/Bugzilla/WebService/Server.pm
index c4bd3e605..2aed48e22 100644
--- a/Bugzilla/WebService/Server.pm
+++ b/Bugzilla/WebService/Server.pm
@@ -22,88 +22,93 @@ use Module::Runtime qw(require_module);
use Try::Tiny;
sub handle_login {
- my ($self, $class, $method, $full_method) = @_;
- # Throw error if the supplied class does not exist or the method is private
- ThrowCodeError('unknown_method', {method => $full_method}) if (!$class or $method =~ /^_/);
-
- # We never want to create a new session unless the user is calling the
- # login method. Setting dont_persist_session makes
- # Bugzilla::Auth::_handle_login_result() skip calling persist_login().
- if ($full_method ne 'User.login') {
- Bugzilla->request_cache->{dont_persist_session} = 1;
- }
-
- try {
- require_module($class);
- }
- catch {
- ThrowCodeError('unknown_method', {method => $full_method});
- FATAL($_);
- };
- return if ($class->login_exempt($method)
- and !defined Bugzilla->input_params->{Bugzilla_login});
- Bugzilla->login();
-
- Bugzilla::Hook::process(
- 'webservice_before_call',
- { 'method' => $method, full_method => $full_method });
+ my ($self, $class, $method, $full_method) = @_;
+
+ # Throw error if the supplied class does not exist or the method is private
+ ThrowCodeError('unknown_method', {method => $full_method})
+ if (!$class or $method =~ /^_/);
+
+ # We never want to create a new session unless the user is calling the
+ # login method. Setting dont_persist_session makes
+ # Bugzilla::Auth::_handle_login_result() skip calling persist_login().
+ if ($full_method ne 'User.login') {
+ Bugzilla->request_cache->{dont_persist_session} = 1;
+ }
+
+ try {
+ require_module($class);
+ }
+ catch {
+ ThrowCodeError('unknown_method', {method => $full_method});
+ FATAL($_);
+ };
+ return
+ if ($class->login_exempt($method)
+ and !defined Bugzilla->input_params->{Bugzilla_login});
+ Bugzilla->login();
+
+ Bugzilla::Hook::process('webservice_before_call',
+ {'method' => $method, full_method => $full_method});
}
sub datetime_format_inbound {
- my ($self, $time) = @_;
-
- my $converted = datetime_from($time, Bugzilla->local_timezone);
- if (!defined $converted) {
- ThrowUserError('illegal_date', { date => $time });
- }
- $time = $converted->ymd() . ' ' . $converted->hms();
- return $time
+ my ($self, $time) = @_;
+
+ my $converted = datetime_from($time, Bugzilla->local_timezone);
+ if (!defined $converted) {
+ ThrowUserError('illegal_date', {date => $time});
+ }
+ $time = $converted->ymd() . ' ' . $converted->hms();
+ return $time;
}
sub datetime_format_outbound {
- my ($self, $date) = @_;
-
- return undef if (!defined $date or $date eq '');
-
- my $time = $date;
- if (blessed($date)) {
- # We expect this to mean we were sent a datetime object
- $time->set_time_zone('UTC');
- } else {
- # We always send our time in UTC, for consistency.
- # passed in value is likely a string, create a datetime object
- $time = datetime_from($date, 'UTC');
- }
- return $time->iso8601();
+ my ($self, $date) = @_;
+
+ return undef if (!defined $date or $date eq '');
+
+ my $time = $date;
+ if (blessed($date)) {
+
+ # We expect this to mean we were sent a datetime object
+ $time->set_time_zone('UTC');
+ }
+ else {
+ # We always send our time in UTC, for consistency.
+ # passed in value is likely a string, create a datetime object
+ $time = datetime_from($date, 'UTC');
+ }
+ return $time->iso8601();
}
# ETag support
sub bz_etag {
- my ($self, $data) = @_;
- my $cache = Bugzilla->request_cache;
-
- if (Bugzilla->request_cache->{bz_etag_disable}) {
- return undef;
+ my ($self, $data) = @_;
+ my $cache = Bugzilla->request_cache;
+
+ if (Bugzilla->request_cache->{bz_etag_disable}) {
+ return undef;
+ }
+ elsif (defined $data) {
+
+ # Serialize the data if passed a reference
+ local $Storable::canonical = 1;
+ $data = freeze($data) if ref $data;
+
+ # Wide characters cause md5_base64() to die.
+ utf8::encode($data) if utf8::is_utf8($data);
+
+ # Append content_type to the end of the data
+ # string as we want the etag to be unique to
+ # the content_type. We do not need this for
+ # XMLRPC as text/xml is always returned.
+ if (blessed($self) && $self->can('content_type')) {
+ $data .= $self->content_type if $self->content_type;
}
- elsif (defined $data) {
- # Serialize the data if passed a reference
- local $Storable::canonical = 1;
- $data = freeze($data) if ref $data;
-
- # Wide characters cause md5_base64() to die.
- utf8::encode($data) if utf8::is_utf8($data);
-
- # Append content_type to the end of the data
- # string as we want the etag to be unique to
- # the content_type. We do not need this for
- # XMLRPC as text/xml is always returned.
- if (blessed($self) && $self->can('content_type')) {
- $data .= $self->content_type if $self->content_type;
- }
-
- $cache->{'bz_etag'} = md5_base64($data);
- }
- return $cache->{'bz_etag'};
+
+ $cache->{'bz_etag'} = md5_base64($data);
+ }
+ return $cache->{'bz_etag'};
}
1;
diff --git a/Bugzilla/WebService/Server/JSONRPC.pm b/Bugzilla/WebService/Server/JSONRPC.pm
index 12a3143cc..ef00737ad 100644
--- a/Bugzilla/WebService/Server/JSONRPC.pm
+++ b/Bugzilla/WebService/Server/JSONRPC.pm
@@ -12,16 +12,17 @@ use strict;
use warnings;
use Bugzilla::WebService::Server;
-BEGIN {
- our @ISA = qw(Bugzilla::WebService::Server);
- if (eval { require JSON::RPC::Server::CGI }) {
- unshift(@ISA, 'JSON::RPC::Server::CGI');
- }
- else {
- require JSON::RPC::Legacy::Server::CGI;
- unshift(@ISA, 'JSON::RPC::Legacy::Server::CGI');
- }
+BEGIN {
+ our @ISA = qw(Bugzilla::WebService::Server);
+
+ if (eval { require JSON::RPC::Server::CGI }) {
+ unshift(@ISA, 'JSON::RPC::Server::CGI');
+ }
+ else {
+ require JSON::RPC::Legacy::Server::CGI;
+ unshift(@ISA, 'JSON::RPC::Legacy::Server::CGI');
+ }
}
use Bugzilla::Error;
@@ -40,88 +41,92 @@ use Bugzilla::WebService::JSON;
#####################################
sub new {
- my $class = shift;
- my $self = $class->SUPER::new(@_);
- Bugzilla->_json_server($self);
- $self->dispatch(WS_DISPATCH);
- $self->return_die_message(1);
- return $self;
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+ Bugzilla->_json_server($self);
+ $self->dispatch(WS_DISPATCH);
+ $self->return_die_message(1);
+ return $self;
}
sub create_json_coder {
- my $self = shift;
- my $json = Bugzilla::WebService::JSON->new;
- $json->allow_blessed(1);
- $json->convert_blessed(1);
- $json->allow_nonref(1);
- # This may seem a little backwards, but what this really means is
- # "don't convert our utf8 into byte strings, just leave it as a
- # utf8 string."
- $json->utf8(0) if Bugzilla->params->{'utf8'};
- return $json;
+ my $self = shift;
+ my $json = Bugzilla::WebService::JSON->new;
+ $json->allow_blessed(1);
+ $json->convert_blessed(1);
+ $json->allow_nonref(1);
+
+ # This may seem a little backwards, but what this really means is
+ # "don't convert our utf8 into byte strings, just leave it as a
+ # utf8 string."
+ $json->utf8(0) if Bugzilla->params->{'utf8'};
+ return $json;
}
# Override the JSON::RPC method to return our CGI object instead of theirs.
sub cgi { return Bugzilla->cgi; }
sub response_header {
- my $self = shift;
- # The HTTP body needs to be bytes (not a utf8 string) for recent
- # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
- # properly. $_[1] is the HTTP body content we're going to be sending.
- if (utf8::is_utf8($_[1])) {
- utf8::encode($_[1]);
- # Since we're going to just be sending raw bytes, we need to
- # set STDOUT to not expect utf8.
- disable_utf8();
- }
- return $self->SUPER::response_header(@_);
+ my $self = shift;
+
+ # The HTTP body needs to be bytes (not a utf8 string) for recent
+ # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
+ # properly. $_[1] is the HTTP body content we're going to be sending.
+ if (utf8::is_utf8($_[1])) {
+ utf8::encode($_[1]);
+
+ # Since we're going to just be sending raw bytes, we need to
+ # set STDOUT to not expect utf8.
+ disable_utf8();
+ }
+ return $self->SUPER::response_header(@_);
}
sub response {
- my ($self, $response) = @_;
- my $cgi = $self->cgi;
-
- # Implement JSONP.
- if (my $callback = $self->_bz_callback) {
- my $content = $response->content;
- if (blessed $content) {
- $content = $content->encode;
- }
- # Prepend the JSONP response with /**/ in order to protect
- # against possible encoding attacks (e.g., affecting Flash).
- $response->content("/**/$callback($content)");
- }
-
- # Use $cgi->header properly instead of just printing text directly.
- # This fixes various problems, including sending Bugzilla's cookies
- # properly.
- my $headers = $response->headers;
- my @header_args;
- foreach my $name ($headers->header_field_names) {
- my @values = $headers->header($name);
- $name =~ s/-/_/g;
- foreach my $value (@values) {
- push(@header_args, "-$name", $value);
- }
- }
-
- # ETag support
- my $etag = $self->bz_etag;
- if ($etag && $cgi->check_etag($etag)) {
- push(@header_args, "-ETag", $etag);
- print $cgi->header(-status => '304 Not Modified', @header_args);
- }
- else {
- push(@header_args, "-ETag", $etag) if $etag;
- print $cgi->header(-status => $response->code, @header_args);
- my $content = $response->content;
- if (blessed $content) {
- $content = $content->encode;
- utf8::encode($content);
- }
- print $content;
- }
+ my ($self, $response) = @_;
+ my $cgi = $self->cgi;
+
+ # Implement JSONP.
+ if (my $callback = $self->_bz_callback) {
+ my $content = $response->content;
+ if (blessed $content) {
+ $content = $content->encode;
+ }
+
+ # Prepend the JSONP response with /**/ in order to protect
+ # against possible encoding attacks (e.g., affecting Flash).
+ $response->content("/**/$callback($content)");
+ }
+
+ # Use $cgi->header properly instead of just printing text directly.
+ # This fixes various problems, including sending Bugzilla's cookies
+ # properly.
+ my $headers = $response->headers;
+ my @header_args;
+ foreach my $name ($headers->header_field_names) {
+ my @values = $headers->header($name);
+ $name =~ s/-/_/g;
+ foreach my $value (@values) {
+ push(@header_args, "-$name", $value);
+ }
+ }
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ if ($etag && $cgi->check_etag($etag)) {
+ push(@header_args, "-ETag", $etag);
+ print $cgi->header(-status => '304 Not Modified', @header_args);
+ }
+ else {
+ push(@header_args, "-ETag", $etag) if $etag;
+ print $cgi->header(-status => $response->code, @header_args);
+ my $content = $response->content;
+ if (blessed $content) {
+ $content = $content->encode;
+ utf8::encode($content);
+ }
+ print $content;
+ }
}
# The JSON-RPC 1.1 GET specification is not so great--you can't specify
@@ -133,70 +138,69 @@ sub response {
# Base64 encoded, because that is ridiculous and obnoxious for JavaScript
# clients.
sub retrieve_json_from_get {
- my $self = shift;
- my $cgi = $self->cgi;
-
- my %input;
-
- # Both version and id must be set before any errors are thrown.
- if ($cgi->param('version')) {
- $self->version(scalar $cgi->param('version'));
- $input{version} = $cgi->param('version');
- }
- else {
- $self->version('1.0');
- }
-
- # The JSON-RPC 2.0 spec says that any request that omits an id doesn't
- # want a response. However, in an HTTP GET situation, it's stupid to
- # expect all clients to specify some id parameter just to get a response,
- # so we don't require it.
- my $id;
- if (defined $cgi->param('id')) {
- $id = $cgi->param('id');
- }
- # However, JSON::RPC does require that an id exist in most cases, in
- # order to throw proper errors. We use the installation's urlbase as
- # the id, in this case.
- else {
- $id = Bugzilla->localconfig->{urlbase};
- }
- # Setting _bz_request_id here is required in case we throw errors early,
- # before _handle.
- $self->{_bz_request_id} = $input{id} = $id;
-
- # _bz_callback can throw an error, so we have to set it here, after we're
- # ready to throw errors.
- $self->_bz_callback(scalar $cgi->param('callback'));
-
- if (!$cgi->param('method')) {
- ThrowUserError('json_rpc_get_method_required');
- }
- $input{method} = $cgi->param('method');
-
- my $params;
- if (defined $cgi->param('params')) {
- local $@;
- $params = eval {
- $self->json->decode(scalar $cgi->param('params'))
- };
- if ($@) {
- ThrowUserError('json_rpc_invalid_params',
- { params => scalar $cgi->param('params'),
- err_msg => $@ });
- }
- }
- elsif (!$self->version or $self->version ne '1.1') {
- $params = [];
- }
- else {
- $params = {};
- }
-
- $input{params} = $params;
-
- my $json = $self->json->encode(\%input);
- return $json;
+ my $self = shift;
+ my $cgi = $self->cgi;
+
+ my %input;
+
+ # Both version and id must be set before any errors are thrown.
+ if ($cgi->param('version')) {
+ $self->version(scalar $cgi->param('version'));
+ $input{version} = $cgi->param('version');
+ }
+ else {
+ $self->version('1.0');
+ }
+
+ # The JSON-RPC 2.0 spec says that any request that omits an id doesn't
+ # want a response. However, in an HTTP GET situation, it's stupid to
+ # expect all clients to specify some id parameter just to get a response,
+ # so we don't require it.
+ my $id;
+ if (defined $cgi->param('id')) {
+ $id = $cgi->param('id');
+ }
+
+ # However, JSON::RPC does require that an id exist in most cases, in
+ # order to throw proper errors. We use the installation's urlbase as
+ # the id, in this case.
+ else {
+ $id = Bugzilla->localconfig->{urlbase};
+ }
+
+ # Setting _bz_request_id here is required in case we throw errors early,
+ # before _handle.
+ $self->{_bz_request_id} = $input{id} = $id;
+
+ # _bz_callback can throw an error, so we have to set it here, after we're
+ # ready to throw errors.
+ $self->_bz_callback(scalar $cgi->param('callback'));
+
+ if (!$cgi->param('method')) {
+ ThrowUserError('json_rpc_get_method_required');
+ }
+ $input{method} = $cgi->param('method');
+
+ my $params;
+ if (defined $cgi->param('params')) {
+ local $@;
+ $params = eval { $self->json->decode(scalar $cgi->param('params')) };
+ if ($@) {
+ ThrowUserError('json_rpc_invalid_params',
+ {params => scalar $cgi->param('params'), err_msg => $@});
+ }
+ }
+ elsif (!$self->version or $self->version ne '1.1') {
+ $params = [];
+ }
+ else {
+ $params = {};
+ }
+
+ $input{params} = $params;
+
+ my $json = $self->json->encode(\%input);
+ return $json;
}
#######################################
@@ -204,72 +208,76 @@ sub retrieve_json_from_get {
#######################################
sub type {
- my ($self, $type, $value) = @_;
-
- # This is the only type that does something special with undef.
- if ($type eq 'boolean') {
- return $value ? JSON::true : JSON::false;
- }
-
- return JSON::null if !defined $value;
-
- my $retval = $value;
-
- if ($type eq 'int') {
- $retval = int($value);
- }
- if ($type eq 'double') {
- $retval = 0.0 + $value;
- }
- elsif ($type eq 'string') {
- # Forces string context, so that JSON will make it a string.
- $retval = "$value";
- }
- elsif ($type eq 'dateTime') {
- # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
- $retval = $self->datetime_format_outbound($value);
- }
- elsif ($type eq 'base64') {
- utf8::encode($value) if utf8::is_utf8($value);
- $retval = encode_base64($value, '');
- }
- elsif ($type eq 'email' && Bugzilla->params->{'webservice_email_filter'}) {
- $retval = email_filter($value);
- }
-
- return $retval;
+ my ($self, $type, $value) = @_;
+
+ # This is the only type that does something special with undef.
+ if ($type eq 'boolean') {
+ return $value ? JSON::true : JSON::false;
+ }
+
+ return JSON::null if !defined $value;
+
+ my $retval = $value;
+
+ if ($type eq 'int') {
+ $retval = int($value);
+ }
+ if ($type eq 'double') {
+ $retval = 0.0 + $value;
+ }
+ elsif ($type eq 'string') {
+
+ # Forces string context, so that JSON will make it a string.
+ $retval = "$value";
+ }
+ elsif ($type eq 'dateTime') {
+
+ # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
+ $retval = $self->datetime_format_outbound($value);
+ }
+ elsif ($type eq 'base64') {
+ utf8::encode($value) if utf8::is_utf8($value);
+ $retval = encode_base64($value, '');
+ }
+ elsif ($type eq 'email' && Bugzilla->params->{'webservice_email_filter'}) {
+ $retval = email_filter($value);
+ }
+
+ return $retval;
}
sub datetime_format_outbound {
- my $self = shift;
- # YUI expects ISO8601 in UTC time; including TZ specifier
- return $self->SUPER::datetime_format_outbound(@_) . 'Z';
+ my $self = shift;
+
+ # YUI expects ISO8601 in UTC time; including TZ specifier
+ return $self->SUPER::datetime_format_outbound(@_) . 'Z';
}
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 ($self->request->method ne 'POST') {
- # 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 $path = $self->path_info;
- my $class = $self->{dispatch_path}->{$path};
- my $full_method = $self->_bz_method_name;
- $full_method =~ /^\S+\.(\S+)/;
- my $method = $1;
- $self->SUPER::handle_login($class, $method, $full_method);
+ 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 ($self->request->method ne 'POST') {
+
+ # 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 $path = $self->path_info;
+ my $class = $self->{dispatch_path}->{$path};
+ my $full_method = $self->_bz_method_name;
+ $full_method =~ /^\S+\.(\S+)/;
+ my $method = $1;
+ $self->SUPER::handle_login($class, $method, $full_method);
}
######################################
@@ -278,165 +286,165 @@ sub handle_login {
# Store the ID of the current call, because Bugzilla::Error will need it.
sub _handle {
- my $self = shift;
- my ($obj) = @_;
- $self->{_bz_request_id} = $obj->{id};
+ my $self = shift;
+ my ($obj) = @_;
+ $self->{_bz_request_id} = $obj->{id};
- my $result = $self->SUPER::_handle(@_);
+ my $result = $self->SUPER::_handle(@_);
- # Set the ETag if not already set in the webservice methods.
- my $etag = $self->bz_etag;
- if (!$etag && ref $result) {
- my $data = $self->json->decode($result)->{'result'};
- $self->bz_etag($data);
- }
+ # Set the ETag if not already set in the webservice methods.
+ my $etag = $self->bz_etag;
+ if (!$etag && ref $result) {
+ my $data = $self->json->decode($result)->{'result'};
+ $self->bz_etag($data);
+ }
- return $result;
+ return $result;
}
# Make all error messages returned by JSON::RPC go into the 100000
# range, and bring down all our errors into the normal range.
sub _error {
- my ($self, $id, $code) = (shift, shift, shift);
- # All JSON::RPC errors are less than 1000.
- if ($code < 1000) {
- $code += 100000;
- }
- # Bugzilla::Error adds 100,000 to all *our* errors, so
- # we know they came from us.
- elsif ($code > 100000) {
- $code -= 100000;
- }
-
- # We can't just set $_[1] because it's not always settable,
- # in JSON::RPC::Server.
- unshift(@_, $id, $code);
- my $json = $self->SUPER::_error(@_);
-
- # We want to always send the JSON-RPC 1.1 error format, although
- # If we're not in JSON-RPC 1.1, we don't need the silly "name" parameter.
- if (!$self->version or $self->version ne '1.1') {
- my $object = $self->json->decode($json);
- my $message = $object->{error};
- # Just assure that future versions of JSON::RPC don't change the
- # JSON-RPC 1.0 error format.
- if (!ref $message) {
- $object->{error} = {
- code => $code,
- message => $message,
- };
- $json = $self->json->encode($object);
- }
- }
- return $json;
+ my ($self, $id, $code) = (shift, shift, shift);
+
+ # All JSON::RPC errors are less than 1000.
+ if ($code < 1000) {
+ $code += 100000;
+ }
+
+ # Bugzilla::Error adds 100,000 to all *our* errors, so
+ # we know they came from us.
+ elsif ($code > 100000) {
+ $code -= 100000;
+ }
+
+ # We can't just set $_[1] because it's not always settable,
+ # in JSON::RPC::Server.
+ unshift(@_, $id, $code);
+ my $json = $self->SUPER::_error(@_);
+
+ # We want to always send the JSON-RPC 1.1 error format, although
+ # If we're not in JSON-RPC 1.1, we don't need the silly "name" parameter.
+ if (!$self->version or $self->version ne '1.1') {
+ my $object = $self->json->decode($json);
+ my $message = $object->{error};
+
+ # Just assure that future versions of JSON::RPC don't change the
+ # JSON-RPC 1.0 error format.
+ if (!ref $message) {
+ $object->{error} = {code => $code, message => $message,};
+ $json = $self->json->encode($object);
+ }
+ }
+ return $json;
}
# This handles dispatching our calls to the appropriate class based on
# the name of the method.
sub _find_procedure {
- my $self = shift;
+ my $self = shift;
- my $method = shift;
- $self->{_bz_method_name} = $method;
+ my $method = shift;
+ $self->{_bz_method_name} = $method;
- # This tricks SUPER::_find_procedure into finding the right class.
- $method =~ /^(\S+)\.(\S+)$/;
- $self->path_info($1);
- unshift(@_, $2);
+ # This tricks SUPER::_find_procedure into finding the right class.
+ $method =~ /^(\S+)\.(\S+)$/;
+ $self->path_info($1);
+ unshift(@_, $2);
- return $self->SUPER::_find_procedure(@_);
+ return $self->SUPER::_find_procedure(@_);
}
# This is a hacky way to do something right before methods are called.
# This is the last thing that JSON::RPC::Server::_handle calls right before
# the method is actually called.
sub _argument_type_check {
- my $self = shift;
- my $params = $self->SUPER::_argument_type_check(@_);
-
- # 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);
-
- # Now, convert dateTime fields on input.
- $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
- my ($class, $method) = ($1, $2);
- my $pkg = $self->{dispatch_path}->{$class};
- 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});
- }
- }
-
- # Update the params to allow for several convenience key/values
- # use for authentication
- fix_credentials($params, $self->cgi);
-
- Bugzilla->input_params($params);
-
- if ($self->request->method eq 'POST') {
- # CSRF is possible via XMLHttpRequest when the Content-Type header
- # is not application/json (for example: text/plain or
- # application/x-www-form-urlencoded).
- # application/json is the single official MIME type, per RFC 4627.
- my $content_type = $self->cgi->content_type;
- # The charset can be appended to the content type, so we use a regexp.
- if ($content_type !~ m{^application/json(-rpc)?(;.*)?$}i) {
- ThrowUserError('json_rpc_illegal_content_type',
- { content_type => $content_type });
- }
- }
- else {
- # When being called using GET, we don't allow calling
- # methods that can change data. This protects us against cross-site
- # request forgeries.
- if (!grep($_ eq $method, $pkg->READ_ONLY)) {
- ThrowUserError('json_rpc_post_only',
- { method => $self->_bz_method_name });
- }
- }
-
- # Only allowed methods to be used from our whitelist
- if (none { $_ eq $method} $pkg->PUBLIC_METHODS) {
- ThrowCodeError('unknown_method', { method => $self->_bz_method_name });
- }
-
- # 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;
+ my $self = shift;
+ my $params = $self->SUPER::_argument_type_check(@_);
+
+ # 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);
+
+ # Now, convert dateTime fields on input.
+ $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
+ my ($class, $method) = ($1, $2);
+ my $pkg = $self->{dispatch_path}->{$class};
+ 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});
+ }
+ }
+
+ # Update the params to allow for several convenience key/values
+ # use for authentication
+ fix_credentials($params, $self->cgi);
+
+ Bugzilla->input_params($params);
+
+ if ($self->request->method eq 'POST') {
+
+ # CSRF is possible via XMLHttpRequest when the Content-Type header
+ # is not application/json (for example: text/plain or
+ # application/x-www-form-urlencoded).
+ # application/json is the single official MIME type, per RFC 4627.
+ my $content_type = $self->cgi->content_type;
+
+ # The charset can be appended to the content type, so we use a regexp.
+ if ($content_type !~ m{^application/json(-rpc)?(;.*)?$}i) {
+ ThrowUserError('json_rpc_illegal_content_type',
+ {content_type => $content_type});
+ }
+ }
+ else {
+ # When being called using GET, we don't allow calling
+ # methods that can change data. This protects us against cross-site
+ # request forgeries.
+ if (!grep($_ eq $method, $pkg->READ_ONLY)) {
+ ThrowUserError('json_rpc_post_only', {method => $self->_bz_method_name});
+ }
+ }
+
+ # Only allowed methods to be used from our whitelist
+ if (none { $_ eq $method } $pkg->PUBLIC_METHODS) {
+ ThrowCodeError('unknown_method', {method => $self->_bz_method_name});
+ }
+
+ # 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;
}
##########################
@@ -445,22 +453,24 @@ sub _argument_type_check {
# _bz_method_name is stored by _find_procedure for later use.
sub _bz_method_name {
- return $_[0]->{_bz_method_name};
+ return $_[0]->{_bz_method_name};
}
sub _bz_callback {
- my ($self, $value) = @_;
- if (defined $value) {
- $value = trim($value);
- # We don't use \w because we don't want to allow Unicode here.
- if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
- ThrowUserError('json_rpc_invalid_callback', { callback => $value });
- }
- $self->{_bz_callback} = $value;
- # JSONP needs to be parsed by a JS parser, not by a JSON parser.
- $self->content_type('text/javascript');
+ my ($self, $value) = @_;
+ if (defined $value) {
+ $value = trim($value);
+
+ # We don't use \w because we don't want to allow Unicode here.
+ if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
+ ThrowUserError('json_rpc_invalid_callback', {callback => $value});
}
- return $self->{_bz_callback};
+ $self->{_bz_callback} = $value;
+
+ # JSONP needs to be parsed by a JS parser, not by a JSON parser.
+ $self->content_type('text/javascript');
+ }
+ return $self->{_bz_callback};
}
1;
diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm
index 5d8367410..781960c68 100644
--- a/Bugzilla/WebService/Server/REST.pm
+++ b/Bugzilla/WebService/Server/REST.pm
@@ -41,146 +41,146 @@ use Module::Runtime qw(require_module);
###########################
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.
- # If no accept header was sent or the content types specified were not
- # matched, we default to the first type in the whitelist.
- $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 });
+ my ($self) = @_;
+
+ # Determine how the data should be represented. We do this early so
+ # errors will also be returned with the proper content type.
+ # If no accept header was sent or the content types specified were not
+ # matched, we default to the first type in the whitelist.
+ $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);
}
- # 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 });
+ ThrowUserError("rest_invalid_resource",
+ {path => $self->cgi->path_info, method => $self->request->method});
+ }
- my $params = $self->_retrieve_json_params;
+ # 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});
- fix_credentials($params, $self->cgi);
+ my $params = $self->_retrieve_json_params;
- # Fix includes/excludes for each call
- rest_include_exclude($params);
+ fix_credentials($params, $self->cgi);
- # Set callback name if content-type is 'application/javascript'
- if ($params->{'callback'}
- || $self->content_type eq 'application/javascript')
- {
- $self->_bz_callback($params->{'callback'} || 'callback');
- }
+ # Fix includes/excludes for each call
+ rest_include_exclude($params);
- Bugzilla->input_params($params);
+ # Set callback name if content-type is 'application/javascript'
+ if ($params->{'callback'} || $self->content_type eq 'application/javascript') {
+ $self->_bz_callback($params->{'callback'} || 'callback');
+ }
- # 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 => Bugzilla->localconfig->{urlbase},
- method => $self->bz_method_name,
- params => $params
- };
+ Bugzilla->input_params($params);
- # Execute the handler
- my $result = $self->_handle($obj);
+ # 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 => Bugzilla->localconfig->{urlbase},
+ method => $self->bz_method_name,
+ params => $params
+ };
- if (!$self->error_response_header) {
- return $self->response(
- $self->response_header($self->bz_success_code || STATUS_OK, $result));
- }
+ # Execute the handler
+ my $result = $self->_handle($obj);
- $self->response($self->error_response_header);
+ 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) {
- # Content is in bytes at this point and needs to be converted
- # back to utf8 string.
- enable_utf8();
- utf8::decode($content) if !utf8::is_utf8($content);
- $json_data = $self->json->decode($content);
- }
-
- my $result = {};
- if (exists $json_data->{error}) {
- $result = $json_data->{error};
- $result->{error} = $self->type('boolean', 1);
-
- $result->{documentation} = Bugzilla->params->{docs_urlbase} . "api/";
- delete $result->{'name'}; # Remove JSONRPCError
- }
- elsif (exists $json_data->{result}) {
- $result = $json_data->{result};
- }
-
- Bugzilla::Hook::process('webservice_rest_response',
- { rpc => $self, result => \$result, response => $response });
-
- # Access Control
- my @allowed_headers = qw(accept content-type origin user-agent x-requested-with);
- foreach my $header (keys %{ API_AUTH_HEADERS() }) {
- # We want to lowercase and replace _ with -
- my $translated_header = $header;
- $translated_header =~ tr/A-Z_/a-z\-/;
- push(@allowed_headers, $translated_header);
- }
- $response->header("Access-Control-Allow-Origin", "*");
- $response->header("Access-Control-Allow-Headers", join(', ', @allowed_headers));
-
- # ETag support
- my $etag = $self->bz_etag;
- $self->bz_etag($result) if !$etag;
-
- # If accessing through web browser, then display in readable format
- if ($self->content_type eq 'text/html') {
- $result = $self->json->pretty->canonical->allow_nonref->encode($result);
-
- my $template = Bugzilla->template;
- $content = "";
- $result->encode if blessed $result;
- $template->process("rest.html.tmpl", { result => $result }, \$content)
- || ThrowTemplateError($template->error());
-
- $response->content_type('text/html');
- }
- else {
- $content = $self->json->encode($result);
- }
-
- utf8::encode($content) if utf8::is_utf8($content);
- disable_utf8();
-
- $response->content($content);
-
- $self->SUPER::response($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) {
+
+ # Content is in bytes at this point and needs to be converted
+ # back to utf8 string.
+ enable_utf8();
+ utf8::decode($content) if !utf8::is_utf8($content);
+ $json_data = $self->json->decode($content);
+ }
+
+ my $result = {};
+ if (exists $json_data->{error}) {
+ $result = $json_data->{error};
+ $result->{error} = $self->type('boolean', 1);
+
+ $result->{documentation} = Bugzilla->params->{docs_urlbase} . "api/";
+ delete $result->{'name'}; # Remove JSONRPCError
+ }
+ elsif (exists $json_data->{result}) {
+ $result = $json_data->{result};
+ }
+
+ Bugzilla::Hook::process('webservice_rest_response',
+ {rpc => $self, result => \$result, response => $response});
+
+ # Access Control
+ my @allowed_headers
+ = qw(accept content-type origin user-agent x-requested-with);
+ foreach my $header (keys %{API_AUTH_HEADERS()}) {
+
+ # We want to lowercase and replace _ with -
+ my $translated_header = $header;
+ $translated_header =~ tr/A-Z_/a-z\-/;
+ push(@allowed_headers, $translated_header);
+ }
+ $response->header("Access-Control-Allow-Origin", "*");
+ $response->header("Access-Control-Allow-Headers", join(', ', @allowed_headers));
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ $self->bz_etag($result) if !$etag;
+
+ # If accessing through web browser, then display in readable format
+ if ($self->content_type eq 'text/html') {
+ $result = $self->json->pretty->canonical->allow_nonref->encode($result);
+
+ my $template = Bugzilla->template;
+ $content = "";
+ $result->encode if blessed $result;
+ $template->process("rest.html.tmpl", {result => $result}, \$content)
+ || ThrowTemplateError($template->error());
+
+ $response->content_type('text/html');
+ }
+ else {
+ $content = $self->json->encode($result);
+ }
+
+ utf8::encode($content) if utf8::is_utf8($content);
+ disable_utf8();
+
+ $response->content($content);
+
+ $self->SUPER::response($response);
}
#######################################
@@ -188,21 +188,21 @@ sub response {
#######################################
sub handle_login {
- my $self = shift;
- my $class = $self->bz_class_name;
- my $method = $self->bz_method_name;
- my $full_method = $class . "." . $method;
- $full_method =~ s/^Bugzilla::WebService:://;
-
- # We never want to create a new session unless the user is calling the
- # login method. Setting dont_persist_session makes
- # Bugzilla::Auth::_handle_login_result() skip calling persist_login().
- if ($full_method ne 'User.login') {
- Bugzilla->request_cache->{dont_persist_session} = 1;
- }
-
- # Bypass JSONRPC::handle_login
- Bugzilla::WebService::Server->handle_login($class, $method, $full_method);
+ my $self = shift;
+ my $class = $self->bz_class_name;
+ my $method = $self->bz_method_name;
+ my $full_method = $class . "." . $method;
+ $full_method =~ s/^Bugzilla::WebService:://;
+
+ # We never want to create a new session unless the user is calling the
+ # login method. Setting dont_persist_session makes
+ # Bugzilla::Auth::_handle_login_result() skip calling persist_login().
+ if ($full_method ne 'User.login') {
+ Bugzilla->request_cache->{dont_persist_session} = 1;
+ }
+
+ # Bypass JSONRPC::handle_login
+ Bugzilla::WebService::Server->handle_login($class, $method, $full_method);
}
############################
@@ -212,79 +212,78 @@ sub handle_login {
# 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, @_);
- }
+ 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);
-
- # 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 $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);
+
+ # 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});
- }
+ }
+ 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();
+ # 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;
+ # 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;
- # Allow extensions to modify the params post login
- Bugzilla::Hook::process('webservice_rest_request',
- { rpc => $self, params => $params });
+ # Allow extensions to modify the params post login
+ Bugzilla::Hook::process('webservice_rest_request',
+ {rpc => $self, params => $params});
- if ($params_is_array) {
- $params = [$params];
- }
+ if ($params_is_array) {
+ $params = [$params];
+ }
- return $params;
+ return $params;
}
###################
@@ -292,46 +291,46 @@ sub _argument_type_check {
###################
sub bz_method_name {
- my ($self, $method) = @_;
- $self->{_bz_method_name} = $method if $method;
- return $self->{_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};
+ 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};
+ 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};
+ 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 [ sort { $a cmp $b } @{ $self->{_bz_rest_options} } ];
+ my ($self, $options) = @_;
+ $self->{_bz_rest_options} = $options if $options;
+ return [sort { $a cmp $b } @{$self->{_bz_rest_options}}];
}
sub rest_include_exclude {
- my ($params) = @_;
+ my ($params) = @_;
- if (exists $params->{'include_fields'} && !ref $params->{'include_fields'}) {
- $params->{'include_fields'} = [ split(/[\s+,]/, $params->{'include_fields'}) ];
- }
- if (exists $params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
- $params->{'exclude_fields'} = [ split(/[\s+,]/, $params->{'exclude_fields'}) ];
- }
+ if (exists $params->{'include_fields'} && !ref $params->{'include_fields'}) {
+ $params->{'include_fields'} = [split(/[\s+,]/, $params->{'include_fields'})];
+ }
+ if (exists $params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
+ $params->{'exclude_fields'} = [split(/[\s+,]/, $params->{'exclude_fields'})];
+ }
- return $params;
+ return $params;
}
##########################
@@ -339,187 +338,195 @@ sub rest_include_exclude {
##########################
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 parameters we were able to pull out of the path
- # based on the resource regexp and combine with the normal URL
- # parameters.
- if (my $rest_params = $self->bz_rest_params) {
- foreach my $param (keys %$rest_params) {
- # If the param does not already exist or if the
- # rest param is a single value, add it to the
- # global params.
- if (!exists $params->{$param} || !ref $rest_params->{$param}) {
- $params->{$param} = $rest_params->{$param};
- }
- # If rest_param is a list then add any extra values to the list
- elsif (ref $rest_params->{$param}) {
- my @extra_values = ref $params->{$param}
- ? @{ $params->{$param} }
- : ($params->{$param});
- $params->{$param}
- = [ uniq (@{ $rest_params->{$param} }, @extra_values) ];
- }
- }
+ my $self = shift;
+
+ # Make a copy of the current input_params rather than edit directly
+ my $params = {};
+ %{$params} = %{Bugzilla->input_params};
+
+ # First add any parameters we were able to pull out of the path
+ # based on the resource regexp and combine with the normal URL
+ # parameters.
+ if (my $rest_params = $self->bz_rest_params) {
+ foreach my $param (keys %$rest_params) {
+
+ # If the param does not already exist or if the
+ # rest param is a single value, add it to the
+ # global params.
+ if (!exists $params->{$param} || !ref $rest_params->{$param}) {
+ $params->{$param} = $rest_params->{$param};
+ }
+
+ # If rest_param is a list then add any extra values to the list
+ elsif (ref $rest_params->{$param}) {
+ my @extra_values
+ = ref $params->{$param} ? @{$params->{$param}} : ($params->{$param});
+ $params->{$param} = [uniq(@{$rest_params->{$param}}, @extra_values)];
+ }
+ }
+ }
+
+ # Any parameters passed in in the body of a non-GET request will override
+ # any parameters pull from the url path. Otherwise non-unique keys are
+ # combined.
+ if ($self->request->method ne 'GET') {
+ my $extra_params = {};
+
+ # We do this manually because CGI.pm doesn't understand JSON strings.
+ my $json = delete $params->{'POSTDATA'} || delete $params->{'PUTDATA'};
+ if ($json) {
+ eval { $extra_params = $self->json->utf8(0)->decode($json); };
+ if ($@) {
+ ThrowUserError('json_rpc_invalid_params', {err_msg => $@});
+ }
}
- # Any parameters passed in in the body of a non-GET request will override
- # any parameters pull from the url path. Otherwise non-unique keys are
- # combined.
- if ($self->request->method ne 'GET') {
- my $extra_params = {};
- # We do this manually because CGI.pm doesn't understand JSON strings.
- my $json = delete $params->{'POSTDATA'} || delete $params->{'PUTDATA'};
- if ($json) {
- eval { $extra_params = $self->json->utf8(0)->decode($json); };
- if ($@) {
- ThrowUserError('json_rpc_invalid_params', { err_msg => $@ });
- }
- }
-
- # Allow parameters in the query string if request was non-GET.
- # Note: parameters in query string body override any matching
- # parameters in the request body.
- foreach my $param ($self->cgi->url_param()) {
- $extra_params->{$param} = $self->cgi->url_param($param);
- }
-
- %{$params} = (%{$params}, %{$extra_params}) if %{$extra_params};
+ # Allow parameters in the query string if request was non-GET.
+ # Note: parameters in query string body override any matching
+ # parameters in the request body.
+ foreach my $param ($self->cgi->url_param()) {
+ $extra_params->{$param} = $self->cgi->url_param($param);
}
- return $params;
+ %{$params} = (%{$params}, %{$extra_params}) if %{$extra_params};
+ }
+
+ return $params;
}
sub preload {
- require_module($_) for values %{ WS_DISPATCH() };
+ require_module($_) for values %{WS_DISPATCH()};
}
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} }) {
- next if !$module->can('rest_resources');
- $resources->{$module} = $module->rest_resources;
- }
-
- Bugzilla::Hook::process('webservice_rest_resources',
- { rpc => $self, resources => $resources }) if Bugzilla::request_cache->{bzapi};
-
- # 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;
+ 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}}) {
+ next if !$module->can('rest_resources');
+ $resources->{$module} = $module->rest_resources;
+ }
+
+ Bugzilla::Hook::process('webservice_rest_resources',
+ {rpc => $self, resources => $resources})
+ if Bugzilla::request_cache->{bzapi};
+
+ # 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;
}
+ last if $handler_found;
+ }
- return $handler_found;
+ return $handler_found;
}
sub _best_content_type {
- my ($self, @types) = @_;
- return ($self->_simple_content_negotiation(@types))[0] || '*/*';
+ my ($self, @types) = @_;
+ return ($self->_simple_content_negotiation(@types))[0] || '*/*';
}
sub _simple_content_negotiation {
- my ($self, @types) = @_;
- my @accept_types = $self->_get_content_prefs();
- # Return the types as-is if no accept header sent, since sorting will be a no-op.
- if (!@accept_types) {
- return @types;
- }
- my $score = sub { $self->_score_type(shift, @accept_types) };
- return sort {$score->($b) <=> $score->($a)} @types;
+ my ($self, @types) = @_;
+ my @accept_types = $self->_get_content_prefs();
+
+ # Return the types as-is if no accept header sent, since sorting will be a no-op.
+ if (!@accept_types) {
+ return @types;
+ }
+ 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;
+ 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;
- }
+ 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;
+ # 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;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bug.pm b/Bugzilla/WebService/Server/REST/Resources/Bug.pm
index 26aec011c..34580368d 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Bug.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Bug.pm
@@ -15,177 +15,172 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bug;
BEGIN {
- *Bugzilla::WebService::Bug::rest_resources = \&_rest_resources;
-};
+ *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'
- }
- },
- qr{^/bug/possible_duplicates$}, {
- GET => {
- method => 'possible_duplicates'
- }
- },
- 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/(\d+)$}, {
- GET => {
- method => 'comments',
- params => sub {
- return { comment_ids => [ $_[0] ] };
- }
- }
- },
- qr{^/bug/comment/tags/([^/]+)$}, {
- GET => {
- method => 'search_comment_tags',
- params => sub {
- return { query => $_[0] };
- },
- },
- },
- qr{^/bug/comment/([^/]+)/tags$}, {
- PUT => {
- method => 'update_comment_tags',
- params => sub {
- return { comment_id => $_[0] };
- },
- },
- },
- qr{^/bug/comment/render$}, {
- POST => {
- method => 'render_comment',
- },
- },
- 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] ] };
- }
- },
- PUT => {
- method => 'update_attachment',
- params => sub {
- return { 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] };
- }
- }
- },
- qr{^/flag_types/([^/]+)/([^/]+)$}, {
- GET => {
- method => 'flag_types',
- params => sub {
- return { product => $_[0],
- component => $_[1] };
- }
- }
- },
- qr{^/flag_types/([^/]+)$}, {
- GET => {
- method => 'flag_types',
- params => sub {
- return { product => $_[0] };
- }
- }
+ my $rest_resources = [
+ qr{^/bug$},
+ {
+ GET => {method => 'search',},
+ POST => {method => 'create', status_code => STATUS_CREATED}
+ },
+ qr{^/bug/$},
+ {GET => {method => 'get'}},
+ qr{^/bug/possible_duplicates$},
+ {GET => {method => 'possible_duplicates'}},
+ 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/(\d+)$},
+ {
+ GET => {
+ method => 'comments',
+ params => sub {
+ return {comment_ids => [$_[0]]};
+ }
+ }
+ },
+ qr{^/bug/comment/tags/([^/]+)$},
+ {
+ GET => {
+ method => 'search_comment_tags',
+ params => sub {
+ return {query => $_[0]};
+ },
+ },
+ },
+ qr{^/bug/comment/([^/]+)/tags$},
+ {
+ PUT => {
+ method => 'update_comment_tags',
+ params => sub {
+ return {comment_id => $_[0]};
+ },
+ },
+ },
+ qr{^/bug/comment/render$},
+ {POST => {method => 'render_comment',},},
+ 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]]};
+ }
+ },
+ PUT => {
+ method => 'update_attachment',
+ params => sub {
+ return {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]};
+ }
+ }
+ },
+ qr{^/flag_types/([^/]+)/([^/]+)$},
+ {
+ GET => {
+ method => 'flag_types',
+ params => sub {
+ return {product => $_[0], component => $_[1]};
+ }
+ }
+ },
+ qr{^/flag_types/([^/]+)$},
+ {
+ GET => {
+ method => 'flag_types',
+ params => sub {
+ return {product => $_[0]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
index 12290e84e..72aa0d40f 100644
--- a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
@@ -12,36 +12,32 @@ use strict;
use warnings;
BEGIN {
- *Bugzilla::WebService::BugUserLastVisit::rest_resources = \&_rest_resources;
+ *Bugzilla::WebService::BugUserLastVisit::rest_resources = \&_rest_resources;
}
sub _rest_resources {
- return [
- # bug-id
- qr{^/bug_user_last_visit/(\d+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { ids => $_[0] };
- },
- },
- POST => {
- method => 'update',
- params => sub {
- return { ids => $_[0] };
- },
- },
+ return [
+ # bug-id
+ qr{^/bug_user_last_visit/(\d+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {ids => $_[0]};
},
- # no bug-id
- qr{^/bug_user_last_visit$}, {
- GET => {
- method => 'get',
- },
- POST => {
- method => 'update',
- },
+ },
+ POST => {
+ method => 'update',
+ params => sub {
+ return {ids => $_[0]};
},
- ];
+ },
+ },
+
+ # no bug-id
+ qr{^/bug_user_last_visit$},
+ {GET => {method => 'get',}, POST => {method => 'update',},},
+ ];
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm b/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
index 646355cd3..28872f698 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
@@ -15,48 +15,20 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bugzilla;
BEGIN {
- *Bugzilla::WebService::Bugzilla::rest_resources = \&_rest_resources;
-};
+ *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'
- }
- },
- qr{^/jobqueue_status$}, {
- GET => {
- method => 'jobqueue_status'
- }
- }
- ];
- return $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'}},
+ qr{^/jobqueue_status$}, {GET => {method => 'jobqueue_status'}}
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Classification.pm b/Bugzilla/WebService/Server/REST/Resources/Classification.pm
index f20278f55..88ba028ba 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Classification.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Classification.pm
@@ -15,22 +15,23 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Classification;
BEGIN {
- *Bugzilla::WebService::Classification::rest_resources = \&_rest_resources;
-};
+ *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] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/classification/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Elastic.pm b/Bugzilla/WebService/Server/REST/Resources/Elastic.pm
index 2f7c1eaa4..367dd9134 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Elastic.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Elastic.pm
@@ -15,16 +15,13 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Elastic;
BEGIN {
- *Bugzilla::WebService::Elastic::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Elastic::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/elastic/suggest_users$}, {
- GET => { method => 'suggest_users' },
- },
- ];
- return $rest_resources;
+ my $rest_resources
+ = [qr{^/elastic/suggest_users$}, {GET => {method => 'suggest_users'},},];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Group.pm b/Bugzilla/WebService/Server/REST/Resources/Group.pm
index 6e3d934eb..b6a1b9b34 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Group.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Group.pm
@@ -15,38 +15,35 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Group;
BEGIN {
- *Bugzilla::WebService::Group::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Group::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/group$}, {
- GET => {
- method => 'get'
- },
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/group/([^/]+)$}, {
- 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] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/group$},
+ {
+ GET => {method => 'get'},
+ POST => {method => 'create', success_code => STATUS_CREATED}
+ },
+ qr{^/group/([^/]+)$},
+ {
+ 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;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Product.pm b/Bugzilla/WebService/Server/REST/Resources/Product.pm
index 9ca6e3074..3222642c8 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Product.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Product.pm
@@ -17,53 +17,41 @@ use Bugzilla::WebService::Product;
use Bugzilla::Error;
BEGIN {
- *Bugzilla::WebService::Product::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Product::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/product_accessible$}, {
- GET => {
- method => 'get_accessible_products'
- }
- },
- qr{^/product_enterable$}, {
- GET => {
- method => 'get_enterable_products'
- }
- },
- qr{^/product_selectable$}, {
- GET => {
- method => 'get_selectable_products'
- }
- },
- qr{^/product$}, {
- GET => {
- method => 'get'
- },
- 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;
+ my $rest_resources = [
+ qr{^/product_accessible$},
+ {GET => {method => 'get_accessible_products'}},
+ qr{^/product_enterable$},
+ {GET => {method => 'get_enterable_products'}},
+ qr{^/product_selectable$},
+ {GET => {method => 'get_selectable_products'}},
+ qr{^/product$},
+ {
+ GET => {method => 'get'},
+ 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;
diff --git a/Bugzilla/WebService/Server/REST/Resources/User.pm b/Bugzilla/WebService/Server/REST/Resources/User.pm
index 6185237fb..ab5f78bde 100644
--- a/Bugzilla/WebService/Server/REST/Resources/User.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/User.pm
@@ -15,71 +15,54 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::User;
BEGIN {
- *Bugzilla::WebService::User::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::User::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/user/suggest$}, {
- GET => {
- method => 'suggest',
- },
- },
- qr{^/valid_login$}, {
- GET => {
- method => 'valid_login'
- }
- },
- qr{^/login$}, {
- GET => {
- method => 'login'
- }
- },
- qr{^/logout$}, {
- GET => {
- method => 'logout'
- }
- },
- 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] ] };
- }
- }
- },
- qr{^/user/mfa/([^/]+)/enroll$}, {
- GET => {
- method => 'mfa_enroll',
- params => sub {
- return { provider => $_[0] };
- }
- },
- },
- qr{^/whoami$}, {
- GET => {
- method => 'whoami'
- }
+ my $rest_resources = [
+ qr{^/user/suggest$},
+ {GET => {method => 'suggest',},},
+ qr{^/valid_login$},
+ {GET => {method => 'valid_login'}},
+ qr{^/login$},
+ {GET => {method => 'login'}},
+ qr{^/logout$},
+ {GET => {method => 'logout'}},
+ 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]]};
+ }
+ }
+ },
+ qr{^/user/mfa/([^/]+)/enroll$},
+ {
+ GET => {
+ method => 'mfa_enroll',
+ params => sub {
+ return {provider => $_[0]};
}
- ];
- return $rest_resources;
+ },
+ },
+ qr{^/whoami$},
+ {GET => {method => 'whoami'}}
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/XMLRPC.pm b/Bugzilla/WebService/Server/XMLRPC.pm
index 5ad50e91c..ca2d119de 100644
--- a/Bugzilla/WebService/Server/XMLRPC.pm
+++ b/Bugzilla/WebService/Server/XMLRPC.pm
@@ -23,99 +23,102 @@ use Bugzilla::Util;
use List::MoreUtils qw(none);
BEGIN {
- # Allow WebService methods to call XMLRPC::Lite's type method directly
- *Bugzilla::WebService::type = sub {
- my ($self, $type, $value) = @_;
- if ($type eq 'dateTime') {
- # This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
- # Our "base" implementation is in Bugzilla::WebService::Server.
- if (defined $value) {
- $value = Bugzilla::WebService::Server->datetime_format_outbound($value);
- $value =~ s/-//g;
- }
- else {
- my ($pkg, $file, $line) = caller;
- my $class = ref $self;
- ERROR("$class->type($type, undef) called from $pkg ($file line $line)");
- }
- }
- elsif ($type eq 'email') {
- $type = 'string';
- if (Bugzilla->params->{'webservice_email_filter'}) {
- $value = email_filter($value);
- }
- }
- return XMLRPC::Data->type($type)->value($value);
- };
-
- # Add support for ETags into XMLRPC WebServices
- *Bugzilla::WebService::bz_etag = sub {
- return Bugzilla::WebService::Server->bz_etag($_[1]);
- };
+ # Allow WebService methods to call XMLRPC::Lite's type method directly
+ *Bugzilla::WebService::type = sub {
+ my ($self, $type, $value) = @_;
+ if ($type eq 'dateTime') {
+
+ # This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
+ # Our "base" implementation is in Bugzilla::WebService::Server.
+ if (defined $value) {
+ $value = Bugzilla::WebService::Server->datetime_format_outbound($value);
+ $value =~ s/-//g;
+ }
+ else {
+ my ($pkg, $file, $line) = caller;
+ my $class = ref $self;
+ ERROR("$class->type($type, undef) called from $pkg ($file line $line)");
+ }
+ }
+ elsif ($type eq 'email') {
+ $type = 'string';
+ if (Bugzilla->params->{'webservice_email_filter'}) {
+ $value = email_filter($value);
+ }
+ }
+ return XMLRPC::Data->type($type)->value($value);
+ };
+
+ # Add support for ETags into XMLRPC WebServices
+ *Bugzilla::WebService::bz_etag = sub {
+ return Bugzilla::WebService::Server->bz_etag($_[1]);
+ };
}
sub initialize {
- my $self = shift;
- my %retval = $self->SUPER::initialize(@_);
- $retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
- $retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
- $retval{'dispatch_with'} = WS_DISPATCH;
- return %retval;
+ my $self = shift;
+ my %retval = $self->SUPER::initialize(@_);
+ $retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
+ $retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
+ $retval{'dispatch_with'} = WS_DISPATCH;
+ return %retval;
}
sub make_response {
- my $self = shift;
- my $cgi = Bugzilla->cgi;
-
- $self->SUPER::make_response(@_);
-
- # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
- # its cookies in Bugzilla::CGI, so we need to copy them over.
- foreach my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
- $self->response->headers->push_header('Set-Cookie', $cookie);
- }
-
- # Copy across security related headers from Bugzilla::CGI
- foreach my $header (split(/[\r\n]+/, $cgi->header)) {
- my ($name, $value) = $header =~ /^([^:]+): (.*)/;
- if (!$self->response->headers->header($name)) {
- $self->response->headers->header($name => $value);
- }
- }
-
- # ETag support
- my $etag = $self->bz_etag;
- if (!$etag) {
- my $data = $self->response->as_string;
- $etag = $self->bz_etag($data);
- }
-
- if ($etag && $cgi->check_etag($etag)) {
- $self->response->headers->push_header('ETag', $etag);
- $self->response->headers->push_header('status', '304 Not Modified');
- }
- elsif ($etag) {
- $self->response->headers->push_header('ETag', $etag);
+ my $self = shift;
+ my $cgi = Bugzilla->cgi;
+
+ $self->SUPER::make_response(@_);
+
+ # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
+ # its cookies in Bugzilla::CGI, so we need to copy them over.
+ foreach my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
+ $self->response->headers->push_header('Set-Cookie', $cookie);
+ }
+
+ # Copy across security related headers from Bugzilla::CGI
+ foreach my $header (split(/[\r\n]+/, $cgi->header)) {
+ my ($name, $value) = $header =~ /^([^:]+): (.*)/;
+ if (!$self->response->headers->header($name)) {
+ $self->response->headers->header($name => $value);
}
+ }
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ if (!$etag) {
+ my $data = $self->response->as_string;
+ $etag = $self->bz_etag($data);
+ }
+
+ if ($etag && $cgi->check_etag($etag)) {
+ $self->response->headers->push_header('ETag', $etag);
+ $self->response->headers->push_header('status', '304 Not Modified');
+ }
+ elsif ($etag) {
+ $self->response->headers->push_header('ETag', $etag);
+ }
}
sub handle_login {
- my ($self, $classes, $action, $uri, $method) = @_;
- my $class = $classes->{$uri};
- if (!$class) {
- ThrowCodeError('unknown_method', { method => $method eq 'methodName' ? '' : '.' . $method });
- }
- my $full_method = $uri . "." . $method;
- # Only allowed methods to be used from the module's whitelist
- my $file = $class;
- $file =~ s{::}{/}g;
- $file .= ".pm";
- require $file;
- if (none { $_ eq $method } $class->PUBLIC_METHODS) {
- ThrowCodeError('unknown_method', { method => $full_method });
- }
- $self->SUPER::handle_login($class, $method, $full_method);
- return;
+ my ($self, $classes, $action, $uri, $method) = @_;
+ my $class = $classes->{$uri};
+ if (!$class) {
+ ThrowCodeError('unknown_method',
+ {method => $method eq 'methodName' ? '' : '.' . $method});
+ }
+ my $full_method = $uri . "." . $method;
+
+ # Only allowed methods to be used from the module's whitelist
+ my $file = $class;
+ $file =~ s{::}{/}g;
+ $file .= ".pm";
+ require $file;
+ if (none { $_ eq $method } $class->PUBLIC_METHODS) {
+ ThrowCodeError('unknown_method', {method => $full_method});
+ }
+ $self->SUPER::handle_login($class, $method, $full_method);
+ return;
}
1;
@@ -124,6 +127,7 @@ sub handle_login {
# and also, in some cases, to more-usefully decode them.
package Bugzilla::XMLRPC::Deserializer;
use strict;
+
# We can't use "use base" because XMLRPC::Serializer doesn't return
# a true value.
use XMLRPC::Lite;
@@ -135,100 +139,111 @@ use Bugzilla::WebService::Util qw(fix_credentials);
use Scalar::Util qw(tainted);
sub new {
- my $self = shift->SUPER::new(@_);
- # Initialise XML::Parser to not expand references to entities, to prevent DoS
- require XML::Parser;
- my $parser = XML::Parser->new( NoExpand => 1, Handlers => { Default => sub {} } );
- $self->{_parser}->parser($parser, $parser);
- return $self;
+ my $self = shift->SUPER::new(@_);
+
+ # Initialise XML::Parser to not expand references to entities, to prevent DoS
+ require XML::Parser;
+ my $parser = XML::Parser->new(
+ NoExpand => 1,
+ Handlers => {
+ Default => sub { }
+ }
+ );
+ $self->{_parser}->parser($parser, $parser);
+ return $self;
}
sub deserialize {
- my $self = shift;
-
- # Only allow certain content types to protect against CSRF attacks
- my $content_type = lc($ENV{'CONTENT_TYPE'});
- # Remove charset, etc, if provided
- $content_type =~ s/^([^;]+);.*/$1/;
- if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
- ThrowUserError('xmlrpc_illegal_content_type',
- { content_type => $ENV{'CONTENT_TYPE'} });
- }
+ my $self = shift;
- my ($xml) = @_;
- my $som = $self->SUPER::deserialize(@_);
- if (tainted($xml)) {
- $som->{_bz_do_taint} = 1;
- }
- bless $som, 'Bugzilla::XMLRPC::SOM';
- my $params = $som->paramsin;
- # This allows positional parameters for Testopia.
- $params = {} if ref $params ne 'HASH';
+ # Only allow certain content types to protect against CSRF attacks
+ my $content_type = lc($ENV{'CONTENT_TYPE'});
+
+ # Remove charset, etc, if provided
+ $content_type =~ s/^([^;]+);.*/$1/;
+ if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
+ ThrowUserError('xmlrpc_illegal_content_type',
+ {content_type => $ENV{'CONTENT_TYPE'}});
+ }
+
+ my ($xml) = @_;
+ my $som = $self->SUPER::deserialize(@_);
+ if (tainted($xml)) {
+ $som->{_bz_do_taint} = 1;
+ }
+ bless $som, 'Bugzilla::XMLRPC::SOM';
+ my $params = $som->paramsin;
+
+ # This allows positional parameters for Testopia.
+ $params = {} if ref $params ne 'HASH';
- # Update the params to allow for several convenience key/values
- # use for authentication
- fix_credentials($params);
+ # Update the params to allow for several convenience key/values
+ # use for authentication
+ fix_credentials($params);
- Bugzilla->input_params($params);
+ Bugzilla->input_params($params);
- return $som;
+ return $som;
}
# Some method arguments need to be converted in some way, when they are input.
sub decode_value {
- my $self = shift;
- my ($type) = @{ $_[0] };
- my $value = $self->SUPER::decode_value(@_);
-
- # We only validate/convert certain types here.
- return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
-
- # Though the XML-RPC standard doesn't allow an empty <int>,
- # <double>,or <dateTime.iso8601>, we do, and we just say
- # "that's undef".
- if (grep($type eq $_, qw(int double dateTime))) {
- return undef if $value eq '';
- }
-
- my $validator = $self->_validation_subs->{$type};
- if (!$validator->($value)) {
- ThrowUserError('xmlrpc_invalid_value',
- { type => $type, value => $value });
- }
-
- # We convert dateTimes to a DB-friendly date format.
- if ($type eq 'dateTime.iso8601') {
- if ($value !~ /T.*[\-+Z]/i) {
- # The caller did not specify a timezone, so we assume UTC.
- # pass 'Z' specifier to datetime_from to force it
- $value = $value . 'Z';
- }
- $value = Bugzilla::WebService::Server::XMLRPC->datetime_format_inbound($value);
+ my $self = shift;
+ my ($type) = @{$_[0]};
+ my $value = $self->SUPER::decode_value(@_);
+
+ # We only validate/convert certain types here.
+ return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
+
+ # Though the XML-RPC standard doesn't allow an empty <int>,
+ # <double>,or <dateTime.iso8601>, we do, and we just say
+ # "that's undef".
+ if (grep($type eq $_, qw(int double dateTime))) {
+ return undef if $value eq '';
+ }
+
+ my $validator = $self->_validation_subs->{$type};
+ if (!$validator->($value)) {
+ ThrowUserError('xmlrpc_invalid_value', {type => $type, value => $value});
+ }
+
+ # We convert dateTimes to a DB-friendly date format.
+ if ($type eq 'dateTime.iso8601') {
+ if ($value !~ /T.*[\-+Z]/i) {
+
+ # The caller did not specify a timezone, so we assume UTC.
+ # pass 'Z' specifier to datetime_from to force it
+ $value = $value . 'Z';
}
+ $value = Bugzilla::WebService::Server::XMLRPC->datetime_format_inbound($value);
+ }
- return $value;
+ return $value;
}
sub _validation_subs {
- my $self = shift;
- return $self->{_validation_subs} if $self->{_validation_subs};
- # The only place that XMLRPC::Lite stores any sort of validation
- # regex is in XMLRPC::Serializer. We want to re-use those regexes here.
- my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
-
- # $lookup is a hash whose values are arrayrefs, and whose keys are the
- # names of types. The second item of each arrayref is a subroutine
- # that will do our validation for us.
- my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
- # Add a boolean validator
- $validators{'boolean'} = sub {$_[0] =~ /^[01]$/};
- # Some types have multiple names, or have a different name in
- # XMLRPC::Serializer than their standard XML-RPC name.
- $validators{'dateTime.iso8601'} = $validators{'dateTime'};
- $validators{'i4'} = $validators{'int'};
-
- $self->{_validation_subs} = \%validators;
- return \%validators;
+ my $self = shift;
+ return $self->{_validation_subs} if $self->{_validation_subs};
+
+ # The only place that XMLRPC::Lite stores any sort of validation
+ # regex is in XMLRPC::Serializer. We want to re-use those regexes here.
+ my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
+
+ # $lookup is a hash whose values are arrayrefs, and whose keys are the
+ # names of types. The second item of each arrayref is a subroutine
+ # that will do our validation for us.
+ my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
+
+ # Add a boolean validator
+ $validators{'boolean'} = sub { $_[0] =~ /^[01]$/ };
+
+ # Some types have multiple names, or have a different name in
+ # XMLRPC::Serializer than their standard XML-RPC name.
+ $validators{'dateTime.iso8601'} = $validators{'dateTime'};
+ $validators{'i4'} = $validators{'int'};
+
+ $self->{_validation_subs} = \%validators;
+ return \%validators;
}
1;
@@ -240,16 +255,16 @@ our @ISA = qw(XMLRPC::SOM);
use Bugzilla::WebService::Util qw(taint_data);
sub paramsin {
- my $self = shift;
- if (!$self->{bz_params_in}) {
- my @params = $self->SUPER::paramsin(@_);
- if ($self->{_bz_do_taint}) {
- taint_data(@params);
- }
- $self->{bz_params_in} = \@params;
+ my $self = shift;
+ if (!$self->{bz_params_in}) {
+ my @params = $self->SUPER::paramsin(@_);
+ if ($self->{_bz_do_taint}) {
+ taint_data(@params);
}
- my $params = $self->{bz_params_in};
- return wantarray ? @$params : $params->[0];
+ $self->{bz_params_in} = \@params;
+ }
+ my $params = $self->{bz_params_in};
+ return wantarray ? @$params : $params->[0];
}
1;
@@ -259,43 +274,46 @@ sub paramsin {
package Bugzilla::XMLRPC::Serializer;
use Scalar::Util qw(blessed);
use strict;
+
# We can't use "use base" because XMLRPC::Serializer doesn't return
# a true value.
use XMLRPC::Lite;
our @ISA = qw(XMLRPC::Serializer);
sub new {
- my $class = shift;
- my $self = $class->SUPER::new(@_);
- # This fixes UTF-8.
- $self->{'_typelookup'}->{'base64'} =
- [10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/},
- 'as_base64'];
- # This makes arrays work right even though we're a subclass.
- # (See http://rt.cpan.org//Ticket/Display.html?id=34514)
- $self->{'_encodingStyle'} = '';
- return $self;
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+
+ # This fixes UTF-8.
+ $self->{'_typelookup'}->{'base64'} = [
+ 10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/ },
+ 'as_base64'
+ ];
+
+ # This makes arrays work right even though we're a subclass.
+ # (See http://rt.cpan.org//Ticket/Display.html?id=34514)
+ $self->{'_encodingStyle'} = '';
+ return $self;
}
# Here the XMLRPC::Serializer is extended to use the XMLRPC nil extension.
sub encode_object {
- my $self = shift;
- my @encoded = $self->SUPER::encode_object(@_);
+ my $self = shift;
+ my @encoded = $self->SUPER::encode_object(@_);
- return $encoded[0]->[0] eq 'nil'
- ? ['value', {}, [@encoded]]
- : @encoded;
+ return $encoded[0]->[0] eq 'nil' ? ['value', {}, [@encoded]] : @encoded;
}
# Removes undefined values so they do not produce invalid XMLRPC.
sub envelope {
- my $self = shift;
- my ($type, $method, $data) = @_;
- # If the type isn't a successful response we don't want to change the values.
- if ($type eq 'response'){
- $data = _strip_undefs($data);
- }
- return $self->SUPER::envelope($type, $method, $data);
+ my $self = shift;
+ my ($type, $method, $data) = @_;
+
+ # If the type isn't a successful response we don't want to change the values.
+ if ($type eq 'response') {
+ $data = _strip_undefs($data);
+ }
+ return $self->SUPER::envelope($type, $method, $data);
}
# In an XMLRPC response we have to handle hashes of arrays, hashes, scalars,
@@ -303,57 +321,57 @@ sub envelope {
# The whole XMLRPC::Data object must be removed if its value key is undefined
# so it cannot be recursed like the other hash type objects.
sub _strip_undefs {
- my ($initial) = @_;
- if (ref $initial eq "HASH" || (blessed $initial && $initial->isa("HASH"))) {
- while (my ($key, $value) = each(%$initial)) {
- if ( !defined $value
- || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
- {
- # If the value is undefined remove it from the hash.
- delete $initial->{$key};
- }
- else {
- $initial->{$key} = _strip_undefs($value);
- }
- }
+ my ($initial) = @_;
+ if (ref $initial eq "HASH" || (blessed $initial && $initial->isa("HASH"))) {
+ while (my ($key, $value) = each(%$initial)) {
+ if (!defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value))
+ {
+ # If the value is undefined remove it from the hash.
+ delete $initial->{$key};
+ }
+ else {
+ $initial->{$key} = _strip_undefs($value);
+ }
}
- if (ref $initial eq "ARRAY" || (blessed $initial && $initial->isa("ARRAY"))) {
- for (my $count = 0; $count < scalar @{$initial}; $count++) {
- my $value = $initial->[$count];
- if ( !defined $value
- || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
- {
- # If the value is undefined remove it from the array.
- splice(@$initial, $count, 1);
- $count--;
- }
- else {
- $initial->[$count] = _strip_undefs($value);
- }
- }
+ }
+ if (ref $initial eq "ARRAY" || (blessed $initial && $initial->isa("ARRAY"))) {
+ for (my $count = 0; $count < scalar @{$initial}; $count++) {
+ my $value = $initial->[$count];
+ if (!defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value))
+ {
+ # If the value is undefined remove it from the array.
+ splice(@$initial, $count, 1);
+ $count--;
+ }
+ else {
+ $initial->[$count] = _strip_undefs($value);
+ }
}
- return $initial;
+ }
+ return $initial;
}
sub BEGIN {
- no strict 'refs';
- for my $type (qw(double i4 int dateTime)) {
- my $method = 'as_' . $type;
- *$method = sub {
- my ($self, $value) = @_;
- if (!defined($value)) {
- return as_nil();
- }
- else {
- my $super_method = "SUPER::$method";
- return $self->$super_method($value);
- }
- }
- }
+ no strict 'refs';
+ for my $type (qw(double i4 int dateTime)) {
+ my $method = 'as_' . $type;
+ *$method = sub {
+ my ($self, $value) = @_;
+ if (!defined($value)) {
+ return as_nil();
+ }
+ else {
+ my $super_method = "SUPER::$method";
+ return $self->$super_method($value);
+ }
+ }
+ }
}
sub as_nil {
- return ['nil', {}];
+ return ['nil', {}];
}
1;
diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm
index c569cf9d8..1e5127f8a 100644
--- a/Bugzilla/WebService/User.pm
+++ b/Bugzilla/WebService/User.pm
@@ -20,44 +20,38 @@ use Bugzilla::Group;
use Bugzilla::User;
use Bugzilla::Util qw(trim detaint_natural);
use Bugzilla::WebService::Util qw(filter filter_wants validate
- translate params_to_objects);
+ translate params_to_objects);
use Bugzilla::Hook;
use List::Util qw(first);
use Taint::Util qw(untaint);
# Don't need auth to login
-use constant LOGIN_EXEMPT => {
- login => 1,
- offer_account_by_email => 1,
-};
+use constant LOGIN_EXEMPT => {login => 1, offer_account_by_email => 1,};
use constant READ_ONLY => qw(
- get
- suggest
+ get
+ suggest
);
use constant PUBLIC_METHODS => qw(
- create
- get
- login
- logout
- offer_account_by_email
- update
- valid_login
- whoami
+ create
+ get
+ login
+ logout
+ offer_account_by_email
+ update
+ valid_login
+ whoami
);
-use constant MAPPED_FIELDS => {
- email => 'login',
- full_name => 'name',
- login_denied_text => 'disabledtext',
-};
+use constant MAPPED_FIELDS =>
+ {email => 'login', full_name => 'name', login_denied_text => 'disabledtext',};
use constant MAPPED_RETURNS => {
- login_name => 'email',
- realname => 'full_name',
- disabledtext => 'login_denied_text',
+ login_name => 'email',
+ realname => 'full_name',
+ disabledtext => 'login_denied_text',
};
##############
@@ -65,38 +59,38 @@ use constant MAPPED_RETURNS => {
##############
sub login {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- # Check to see if we are already logged in
- my $user = Bugzilla->user;
- if ($user->id) {
- return $self->_login_to_hash($user);
- }
+ # Check to see if we are already logged in
+ my $user = Bugzilla->user;
+ if ($user->id) {
+ return $self->_login_to_hash($user);
+ }
- # Username and password params are required
- foreach my $param ("login", "password") {
- (defined $params->{$param} || defined $params->{'Bugzilla_' . $param})
- || ThrowCodeError('param_required', { param => $param });
- }
+ # Username and password params are required
+ foreach my $param ("login", "password") {
+ (defined $params->{$param} || defined $params->{'Bugzilla_' . $param})
+ || ThrowCodeError('param_required', {param => $param});
+ }
- $user = Bugzilla->login();
- return $self->_login_to_hash($user);
+ $user = Bugzilla->login();
+ return $self->_login_to_hash($user);
}
sub logout {
- my $self = shift;
- Bugzilla->logout;
+ my $self = shift;
+ Bugzilla->logout;
}
sub valid_login {
- my ($self, $params) = @_;
- defined $params->{login}
- || ThrowCodeError('param_required', { param => 'login' });
- Bugzilla->login();
- if (Bugzilla->user->id && Bugzilla->user->login eq $params->{login}) {
- return $self->type('boolean', 1);
- }
- return $self->type('boolean', 0);
+ my ($self, $params) = @_;
+ defined $params->{login}
+ || ThrowCodeError('param_required', {param => 'login'});
+ Bugzilla->login();
+ if (Bugzilla->user->id && Bugzilla->user->login eq $params->{login}) {
+ return $self->type('boolean', 1);
+ }
+ return $self->type('boolean', 0);
}
#################
@@ -104,102 +98,100 @@ sub valid_login {
#################
sub offer_account_by_email {
- my $self = shift;
- my ($params) = @_;
- my $email = trim($params->{email})
- || ThrowCodeError('param_required', { param => 'email' });
-
- Bugzilla->user->check_account_creation_enabled;
- Bugzilla->user->check_and_send_account_creation_confirmation($email);
- return undef;
+ my $self = shift;
+ my ($params) = @_;
+ my $email = trim($params->{email})
+ || ThrowCodeError('param_required', {param => 'email'});
+
+ Bugzilla->user->check_account_creation_enabled;
+ Bugzilla->user->check_and_send_account_creation_confirmation($email);
+ return undef;
}
sub create {
- my $self = shift;
- my ($params) = @_;
-
- Bugzilla->user->in_group('editusers')
- || ThrowUserError("auth_failure", { group => "editusers",
- action => "add",
- object => "users"});
-
- my $email = trim($params->{email})
- || ThrowCodeError('param_required', { param => 'email' });
- my $realname = trim($params->{full_name});
- my $password = trim($params->{password}) || '*';
-
- my $user = Bugzilla::User->create({
- login_name => $email,
- realname => $realname,
- cryptpassword => $password
- });
-
- return { id => $self->type('int', $user->id) };
-}
+ my $self = shift;
+ my ($params) = @_;
-sub suggest {
- my ($self, $params) = @_;
+ Bugzilla->user->in_group('editusers')
+ || ThrowUserError("auth_failure",
+ {group => "editusers", action => "add", object => "users"});
- Bugzilla->switch_to_shadow_db();
+ my $email = trim($params->{email})
+ || ThrowCodeError('param_required', {param => 'email'});
+ my $realname = trim($params->{full_name});
+ my $password = trim($params->{password}) || '*';
- ThrowCodeError('params_required', { function => 'User.suggest', params => ['match'] })
- unless defined $params->{match};
-
- ThrowUserError('user_access_by_match_denied')
- unless Bugzilla->user->id;
-
- untaint($params->{match});
- my $s = $params->{match};
- trim($s);
- return { users => [] } if length($s) < 3;
+ my $user
+ = Bugzilla::User->create({
+ login_name => $email, realname => $realname, cryptpassword => $password
+ });
- my $dbh = Bugzilla->dbh;
- my @select = ('userid AS id', 'realname AS real_name', 'login_name AS name');
- my $order = 'last_seen_date DESC';
- my $where;
- state $have_mysql = $dbh->isa('Bugzilla::DB::Mysql');
+ return {id => $self->type('int', $user->id)};
+}
- if ($s =~ /^[:@](.+)$/s) {
- $where = $dbh->sql_prefix_match(nickname => $1);
+sub suggest {
+ my ($self, $params) = @_;
+
+ Bugzilla->switch_to_shadow_db();
+
+ ThrowCodeError('params_required',
+ {function => 'User.suggest', params => ['match']})
+ unless defined $params->{match};
+
+ ThrowUserError('user_access_by_match_denied') unless Bugzilla->user->id;
+
+ untaint($params->{match});
+ my $s = $params->{match};
+ trim($s);
+ return {users => []} if length($s) < 3;
+
+ my $dbh = Bugzilla->dbh;
+ my @select = ('userid AS id', 'realname AS real_name', 'login_name AS name');
+ my $order = 'last_seen_date DESC';
+ my $where;
+ state $have_mysql = $dbh->isa('Bugzilla::DB::Mysql');
+
+ if ($s =~ /^[:@](.+)$/s) {
+ $where = $dbh->sql_prefix_match(nickname => $1);
+ }
+ elsif ($s =~ /@/) {
+ $where = $dbh->sql_prefix_match(login_name => $s);
+ }
+ else {
+ if ($have_mysql && ($s =~ /[[:space:]]/ || $s =~ /[^[:ascii:]]/)) {
+ my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
+ push @select, "$match AS relevance";
+ $order = 'relevance DESC';
+ $where = $match;
}
- elsif ($s =~ /@/) {
- $where = $dbh->sql_prefix_match(login_name => $s);
+ elsif ($have_mysql && $s =~ /^[[:upper:]]/) {
+ my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
+ $where = join ' OR ', $match, $dbh->sql_prefix_match(nickname => $s),
+ $dbh->sql_prefix_match(login_name => $s);
}
else {
- if ($have_mysql && ( $s =~ /[[:space:]]/ || $s =~ /[^[:ascii:]]/ ) ) {
- my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
- push @select, "$match AS relevance";
- $order = 'relevance DESC';
- $where = $match;
- }
- elsif ($have_mysql && $s =~ /^[[:upper:]]/) {
- my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
- $where = join ' OR ',
- $match,
- $dbh->sql_prefix_match( nickname => $s ),
- $dbh->sql_prefix_match( login_name => $s );
- }
- else {
- $where = join ' OR ', $dbh->sql_prefix_match( nickname => $s ), $dbh->sql_prefix_match( login_name => $s );
- }
+ $where = join ' OR ', $dbh->sql_prefix_match(nickname => $s),
+ $dbh->sql_prefix_match(login_name => $s);
}
- $where = "($where) AND is_enabled = 1";
+ }
+ $where = "($where) AND is_enabled = 1";
- my $sql = 'SELECT ' . join(', ', @select) . " FROM profiles WHERE $where ORDER BY $order LIMIT 25";
- my $results = $dbh->selectall_arrayref($sql, { Slice => {} });
+ my $sql
+ = 'SELECT '
+ . join(', ', @select)
+ . " FROM profiles WHERE $where ORDER BY $order LIMIT 25";
+ my $results = $dbh->selectall_arrayref($sql, {Slice => {}});
- my @users = map {
- {
- id => $self->type(int => $_->{id}),
- real_name => $self->type(string => $_->{real_name}),
- name => $self->type(email => $_->{name}),
- }
- } @$results;
+ my @users = map { {
+ id => $self->type(int => $_->{id}),
+ real_name => $self->type(string => $_->{real_name}),
+ name => $self->type(email => $_->{name}),
+ } } @$results;
- Bugzilla::Hook::process('webservice_user_suggest',
- { webservice => $self, params => $params, users => \@users });
+ Bugzilla::Hook::process('webservice_user_suggest',
+ {webservice => $self, params => $params, users => \@users});
- return { users => \@users };
+ return {users => \@users};
}
# function to return user information by passing either user ids or
@@ -207,125 +199,132 @@ sub suggest {
# $call = $rpc->call( 'User.get', { ids => [1,2,3],
# names => ['testusera@redhat.com', 'testuserb@redhat.com'] });
sub get {
- my ($self, $params) = validate(@_, 'names', 'ids', 'match', 'group_ids', 'groups');
+ my ($self, $params)
+ = validate(@_, 'names', 'ids', 'match', 'group_ids', 'groups');
- Bugzilla->switch_to_shadow_db();
+ Bugzilla->switch_to_shadow_db();
- defined($params->{names}) || defined($params->{ids})
- || defined($params->{match})
- || ThrowCodeError('params_required',
- { function => 'User.get', params => ['ids', 'names', 'match'] });
+ defined($params->{names})
+ || defined($params->{ids})
+ || defined($params->{match})
+ || ThrowCodeError('params_required',
+ {function => 'User.get', params => ['ids', 'names', 'match']});
- my @user_objects;
- @user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }
- if $params->{names};
+ my @user_objects;
+ @user_objects = map { Bugzilla::User->check($_) } @{$params->{names}}
+ if $params->{names};
- # start filtering to remove duplicate user ids
- my %unique_users = map { $_->id => $_ } @user_objects;
- @user_objects = values %unique_users;
+ # start filtering to remove duplicate user ids
+ my %unique_users = map { $_->id => $_ } @user_objects;
+ @user_objects = values %unique_users;
- my @users;
+ my @users;
- # If the user is not logged in: Return an error if they passed any user ids.
- # Otherwise, return a limited amount of information based on login names.
- if (!Bugzilla->user->id){
- if ($params->{ids}){
- ThrowUserError("user_access_by_id_denied");
- }
- if ($params->{match}) {
- ThrowUserError('user_access_by_match_denied');
- }
- my $in_group = $self->_filter_users_by_group(
- \@user_objects, $params);
- @users = map { filter $params, {
- id => $self->type('int', $_->id),
- real_name => $self->type('string', $_->name),
- name => $self->type('email', $_->login),
- } } @$in_group;
-
- return { users => \@users };
+ # If the user is not logged in: Return an error if they passed any user ids.
+ # Otherwise, return a limited amount of information based on login names.
+ if (!Bugzilla->user->id) {
+ if ($params->{ids}) {
+ ThrowUserError("user_access_by_id_denied");
}
-
- my $obj_by_ids;
- $obj_by_ids = Bugzilla::User->new_from_list($params->{ids}) if $params->{ids};
-
- # obj_by_ids are only visible to the user if he can see
- # the otheruser, for non visible otheruser throw an error
- foreach my $obj (@$obj_by_ids) {
- if (Bugzilla->user->can_see_user($obj)){
- if (!$unique_users{$obj->id}) {
- push (@user_objects, $obj);
- $unique_users{$obj->id} = $obj;
- }
+ if ($params->{match}) {
+ ThrowUserError('user_access_by_match_denied');
+ }
+ my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
+ @users = map {
+ filter $params,
+ {
+ id => $self->type('int', $_->id),
+ real_name => $self->type('string', $_->name),
+ name => $self->type('email', $_->login),
}
- else {
- ThrowUserError('auth_failure', {reason => "not_visible",
- action => "access",
- object => "user",
- userid => $obj->id});
+ } @$in_group;
+
+ return {users => \@users};
+ }
+
+ my $obj_by_ids;
+ $obj_by_ids = Bugzilla::User->new_from_list($params->{ids}) if $params->{ids};
+
+ # obj_by_ids are only visible to the user if he can see
+ # the otheruser, for non visible otheruser throw an error
+ foreach my $obj (@$obj_by_ids) {
+ if (Bugzilla->user->can_see_user($obj)) {
+ if (!$unique_users{$obj->id}) {
+ push(@user_objects, $obj);
+ $unique_users{$obj->id} = $obj;
+ }
+ }
+ else {
+ ThrowUserError(
+ 'auth_failure',
+ {
+ reason => "not_visible",
+ action => "access",
+ object => "user",
+ userid => $obj->id
}
+ );
}
-
- # User Matching
- my $limit;
- if ($params->{limit}) {
- detaint_natural($params->{limit})
- || ThrowCodeError('param_must_be_numeric',
- { function => 'User.match', param => 'limit' });
- $limit = $limit ? min($params->{limit}, $limit) : $params->{limit};
+ }
+
+ # User Matching
+ my $limit;
+ if ($params->{limit}) {
+ detaint_natural($params->{limit})
+ || ThrowCodeError('param_must_be_numeric',
+ {function => 'User.match', param => 'limit'});
+ $limit = $limit ? min($params->{limit}, $limit) : $params->{limit};
+ }
+ my $exclude_disabled = $params->{'include_disabled'} ? 0 : 1;
+ foreach my $match_string (@{$params->{'match'} || []}) {
+ my $matched = Bugzilla::User::match($match_string, $limit, $exclude_disabled);
+ foreach my $user (@$matched) {
+ if (!$unique_users{$user->id}) {
+ push(@user_objects, $user);
+ $unique_users{$user->id} = $user;
+ }
}
- my $exclude_disabled = $params->{'include_disabled'} ? 0 : 1;
- foreach my $match_string (@{ $params->{'match'} || [] }) {
- my $matched = Bugzilla::User::match($match_string, $limit, $exclude_disabled);
- foreach my $user (@$matched) {
- if (!$unique_users{$user->id}) {
- push(@user_objects, $user);
- $unique_users{$user->id} = $user;
- }
- }
+ }
+
+ my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
+ foreach my $user (@$in_group) {
+ my $user_info = filter $params,
+ {
+ id => $self->type('int', $user->id),
+ real_name => $self->type('string', $user->name),
+ name => $self->type('email', $user->login),
+ email => $self->type('email', $user->email),
+ can_login => $self->type('boolean', $user->is_enabled ? 1 : 0),
+ };
+
+ if (Bugzilla->user->in_group('editusers')) {
+ $user_info->{email_enabled} = $self->type('boolean', $user->email_enabled);
+ $user_info->{login_denied_text} = $self->type('string', $user->disabledtext);
}
- my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
- foreach my $user (@$in_group) {
- my $user_info = filter $params, {
- id => $self->type('int', $user->id),
- real_name => $self->type('string', $user->name),
- name => $self->type('email', $user->login),
- email => $self->type('email', $user->email),
- can_login => $self->type('boolean', $user->is_enabled ? 1 : 0),
- };
-
- if (Bugzilla->user->in_group('editusers')) {
- $user_info->{email_enabled} = $self->type('boolean', $user->email_enabled);
- $user_info->{login_denied_text} = $self->type('string', $user->disabledtext);
- }
-
- if (Bugzilla->user->id == $user->id) {
- if (filter_wants($params, 'saved_searches')) {
- $user_info->{saved_searches} = [
- map { $self->_query_to_hash($_) } @{ $user->queries }
- ];
- }
- }
-
- if (filter_wants($params, 'groups')) {
- if (Bugzilla->user->id == $user->id || Bugzilla->user->in_group('editusers')) {
- $user_info->{groups} = [
- map { $self->_group_to_hash($_) } @{ $user->groups }
- ];
- }
- else {
- $user_info->{groups} = $self->_filter_bless_groups($user->groups);
- }
- }
+ if (Bugzilla->user->id == $user->id) {
+ if (filter_wants($params, 'saved_searches')) {
+ $user_info->{saved_searches}
+ = [map { $self->_query_to_hash($_) } @{$user->queries}];
+ }
+ }
- push(@users, $user_info);
+ if (filter_wants($params, 'groups')) {
+ if (Bugzilla->user->id == $user->id || Bugzilla->user->in_group('editusers')) {
+ $user_info->{groups} = [map { $self->_group_to_hash($_) } @{$user->groups}];
+ }
+ else {
+ $user_info->{groups} = $self->_filter_bless_groups($user->groups);
+ }
}
- Bugzilla::Hook::process('webservice_user_get',
- { webservice => $self, params => $params, users => \@users });
+ push(@users, $user_info);
+ }
- return { users => \@users };
+ Bugzilla::Hook::process('webservice_user_get',
+ {webservice => $self, params => $params, users => \@users});
+
+ return {users => \@users};
}
###############
@@ -333,145 +332,144 @@ sub get {
###############
sub update {
- my ($self, $params) = @_;
+ my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
- # Reject access if there is no sense in continuing.
- $user->in_group('editusers')
- || ThrowUserError("auth_failure", {group => "editusers",
- action => "edit",
- object => "users"});
+ # Reject access if there is no sense in continuing.
+ $user->in_group('editusers')
+ || ThrowUserError("auth_failure",
+ {group => "editusers", action => "edit", object => "users"});
- defined($params->{names}) || defined($params->{ids})
- || ThrowCodeError('params_required',
- { function => 'User.update', params => ['ids', 'names'] });
+ defined($params->{names})
+ || defined($params->{ids})
+ || ThrowCodeError('params_required',
+ {function => 'User.update', params => ['ids', 'names']});
- my $user_objects = params_to_objects($params, 'Bugzilla::User');
+ my $user_objects = params_to_objects($params, 'Bugzilla::User');
- my $values = translate($params, MAPPED_FIELDS);
+ my $values = translate($params, MAPPED_FIELDS);
- # We delete names and ids to keep only new values to set.
- delete $values->{names};
- delete $values->{ids};
+ # We delete names and ids to keep only new values to set.
+ delete $values->{names};
+ delete $values->{ids};
- $dbh->bz_start_transaction();
- foreach my $user (@$user_objects){
- $user->set_all($values);
- }
+ $dbh->bz_start_transaction();
+ foreach my $user (@$user_objects) {
+ $user->set_all($values);
+ }
- my %changes;
- foreach my $user (@$user_objects){
- my $returned_changes = $user->update();
- $changes{$user->id} = translate($returned_changes, MAPPED_RETURNS);
- }
- $dbh->bz_commit_transaction();
-
- my @result;
- foreach my $user (@$user_objects) {
- my %hash = (
- id => $user->id,
- changes => {},
- );
-
- foreach my $field (keys %{ $changes{$user->id} }) {
- my $change = $changes{$user->id}->{$field};
- # We normalize undef to an empty string, so that the API
- # stays consistent for things that can become empty.
- $change->[0] = '' if !defined $change->[0];
- $change->[1] = '' if !defined $change->[1];
- # We also flatten arrays (used by groups and blessed_groups)
- $change->[0] = join(',', @{$change->[0]}) if ref $change->[0];
- $change->[1] = join(',', @{$change->[1]}) if ref $change->[1];
-
- $hash{changes}{$field} = {
- removed => $self->type('string', $change->[0]),
- added => $self->type('string', $change->[1])
- };
- }
+ my %changes;
+ foreach my $user (@$user_objects) {
+ my $returned_changes = $user->update();
+ $changes{$user->id} = translate($returned_changes, MAPPED_RETURNS);
+ }
+ $dbh->bz_commit_transaction();
+
+ my @result;
+ foreach my $user (@$user_objects) {
+ my %hash = (id => $user->id, changes => {},);
+
+ foreach my $field (keys %{$changes{$user->id}}) {
+ my $change = $changes{$user->id}->{$field};
+
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things that can become empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
- push(@result, \%hash);
+ # We also flatten arrays (used by groups and blessed_groups)
+ $change->[0] = join(',', @{$change->[0]}) if ref $change->[0];
+ $change->[1] = join(',', @{$change->[1]}) if ref $change->[1];
+
+ $hash{changes}{$field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
}
- return { users => \@result };
+ push(@result, \%hash);
+ }
+
+ return {users => \@result};
}
sub _filter_users_by_group {
- my ($self, $users, $params) = @_;
- my ($group_ids, $group_names) = @$params{qw(group_ids groups)};
+ my ($self, $users, $params) = @_;
+ my ($group_ids, $group_names) = @$params{qw(group_ids groups)};
- # If no groups are specified, we return all users.
- return $users if (!$group_ids and !$group_names);
+ # If no groups are specified, we return all users.
+ return $users if (!$group_ids and !$group_names);
- my $user = Bugzilla->user;
+ my $user = Bugzilla->user;
- my @groups = map { Bugzilla::Group->check({ id => $_ }) }
- @{ $group_ids || [] };
+ my @groups = map { Bugzilla::Group->check({id => $_}) } @{$group_ids || []};
- if ($group_names) {
- foreach my $name (@$group_names) {
- my $group = Bugzilla::Group->check({ name => $name, _error => 'invalid_group_name' });
- $user->in_group($group) || ThrowUserError('invalid_group_name', { name => $name });
- push(@groups, $group);
- }
+ if ($group_names) {
+ foreach my $name (@$group_names) {
+ my $group
+ = Bugzilla::Group->check({name => $name, _error => 'invalid_group_name'});
+ $user->in_group($group)
+ || ThrowUserError('invalid_group_name', {name => $name});
+ push(@groups, $group);
}
+ }
- my @in_group = grep { $self->_user_in_any_group($_, \@groups) }
- @$users;
- return \@in_group;
+ my @in_group = grep { $self->_user_in_any_group($_, \@groups) } @$users;
+ return \@in_group;
}
sub _user_in_any_group {
- my ($self, $user, $groups) = @_;
- foreach my $group (@$groups) {
- return 1 if $user->in_group($group);
- }
- return 0;
+ my ($self, $user, $groups) = @_;
+ foreach my $group (@$groups) {
+ return 1 if $user->in_group($group);
+ }
+ return 0;
}
sub _filter_bless_groups {
- my ($self, $groups) = @_;
- my $user = Bugzilla->user;
+ my ($self, $groups) = @_;
+ my $user = Bugzilla->user;
- my @filtered_groups;
- foreach my $group (@$groups) {
- next unless ($user->in_group('editusers') || $user->can_bless($group->id));
- push(@filtered_groups, $self->_group_to_hash($group));
- }
+ my @filtered_groups;
+ foreach my $group (@$groups) {
+ next unless ($user->in_group('editusers') || $user->can_bless($group->id));
+ push(@filtered_groups, $self->_group_to_hash($group));
+ }
- return \@filtered_groups;
+ return \@filtered_groups;
}
sub _group_to_hash {
- my ($self, $group) = @_;
- my $item = {
- id => $self->type('int', $group->id),
- name => $self->type('string', $group->name),
- description => $self->type('string', $group->description),
- };
- return $item;
+ my ($self, $group) = @_;
+ my $item = {
+ id => $self->type('int', $group->id),
+ name => $self->type('string', $group->name),
+ description => $self->type('string', $group->description),
+ };
+ return $item;
}
sub _query_to_hash {
- my ($self, $query) = @_;
- my $item = {
- id => $self->type('int', $query->id),
- name => $self->type('string', $query->name),
- url => $self->type('string', $query->url),
- };
-
- return $item;
+ my ($self, $query) = @_;
+ my $item = {
+ id => $self->type('int', $query->id),
+ name => $self->type('string', $query->name),
+ url => $self->type('string', $query->url),
+ };
+
+ return $item;
}
sub _login_to_hash {
- my ($self, $user) = @_;
- my $item = { id => $self->type('int', $user->id) };
- if (my $login_token = $user->{_login_token}) {
- $item->{'token'} = $user->id . "-" . $login_token;
- }
- return $item;
+ my ($self, $user) = @_;
+ my $item = {id => $self->type('int', $user->id)};
+ if (my $login_token = $user->{_login_token}) {
+ $item->{'token'} = $user->id . "-" . $login_token;
+ }
+ return $item;
}
#
@@ -479,27 +477,27 @@ sub _login_to_hash {
#
sub mfa_enroll {
- my ($self, $params) = @_;
- my $provider_name = lc($params->{provider});
+ my ($self, $params) = @_;
+ my $provider_name = lc($params->{provider});
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- $user->set_mfa($provider_name);
- my $provider = $user->mfa_provider // die "Unknown MFA provider\n";
- return $provider->enroll_api();
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ $user->set_mfa($provider_name);
+ my $provider = $user->mfa_provider // die "Unknown MFA provider\n";
+ return $provider->enroll_api();
}
sub whoami {
- my ( $self, $params ) = @_;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- return filter(
- $params,
- {
- id => $self->type( 'int', $user->id ),
- real_name => $self->type( 'string', $user->name ),
- name => $self->type( 'email', $user->login ),
- mfa_status => $self->type( 'boolean', !!$user->mfa ),
- }
- );
+ my ($self, $params) = @_;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ return filter(
+ $params,
+ {
+ id => $self->type('int', $user->id),
+ real_name => $self->type('string', $user->name),
+ name => $self->type('email', $user->login),
+ mfa_status => $self->type('boolean', !!$user->mfa),
+ }
+ );
}
1;
diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm
index ce5586911..933d1b5f7 100644
--- a/Bugzilla/WebService/Util.pm
+++ b/Bugzilla/WebService/Util.pm
@@ -29,284 +29,292 @@ use base qw(Exporter);
require Test::Taint if ${^TAINT};
our @EXPORT_OK = qw(
- extract_flags
- filter
- filter_wants
- taint_data
- validate
- translate
- params_to_objects
- fix_credentials
+ extract_flags
+ filter
+ filter_wants
+ taint_data
+ validate
+ translate
+ params_to_objects
+ fix_credentials
);
sub extract_flags {
- my ($flags, $bug, $attachment) = @_;
- my (@new_flags, @old_flags);
+ my ($flags, $bug, $attachment) = @_;
+ my (@new_flags, @old_flags);
- my $flag_types = $attachment ? $attachment->flag_types : $bug->flag_types;
- my $current_flags = $attachment ? $attachment->flags : $bug->flags;
+ my $flag_types = $attachment ? $attachment->flag_types : $bug->flag_types;
+ my $current_flags = $attachment ? $attachment->flags : $bug->flags;
- # Copy the user provided $flags as we may call extract_flags more than
- # once when editing multiple bugs or attachments.
- my $flags_copy = dclone($flags);
+ # Copy the user provided $flags as we may call extract_flags more than
+ # once when editing multiple bugs or attachments.
+ my $flags_copy = dclone($flags);
- foreach my $flag (@$flags_copy) {
- my $id = $flag->{id};
- my $type_id = $flag->{type_id};
+ foreach my $flag (@$flags_copy) {
+ my $id = $flag->{id};
+ my $type_id = $flag->{type_id};
- my $new = delete $flag->{new};
- my $name = delete $flag->{name};
+ my $new = delete $flag->{new};
+ my $name = delete $flag->{name};
- if ($id) {
- my $flag_obj = grep($id == $_->id, @$current_flags);
- $flag_obj || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::Flag', id => $id });
- }
- elsif ($type_id) {
- my $type_obj = grep($type_id == $_->id, @$flag_types);
- $type_obj || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::FlagType', id => $type_id });
- if (!$new) {
- my @flag_matches = grep($type_id == $_->type->id, @$current_flags);
- @flag_matches > 1 && ThrowUserError('flag_not_unique',
- { value => $type_id });
- if (!@flag_matches) {
- delete $flag->{id};
- }
- else {
- delete $flag->{type_id};
- $flag->{id} = $flag_matches[0]->id;
- }
- }
+ if ($id) {
+ my $flag_obj = grep($id == $_->id, @$current_flags);
+ $flag_obj
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::Flag', id => $id});
+ }
+ elsif ($type_id) {
+ my $type_obj = grep($type_id == $_->id, @$flag_types);
+ $type_obj
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::FlagType', id => $type_id});
+ if (!$new) {
+ my @flag_matches = grep($type_id == $_->type->id, @$current_flags);
+ @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $type_id});
+ if (!@flag_matches) {
+ delete $flag->{id};
}
- elsif ($name) {
- my @type_matches = grep($name eq $_->name, @$flag_types);
- @type_matches > 1 && ThrowUserError('flag_type_not_unique',
- { value => $name });
- @type_matches || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::FlagType', name => $name });
- if ($new) {
- delete $flag->{id};
- $flag->{type_id} = $type_matches[0]->id;
- }
- else {
- my @flag_matches = grep($name eq $_->type->name, @$current_flags);
- @flag_matches > 1 && ThrowUserError('flag_not_unique', { value => $name });
- if (@flag_matches) {
- $flag->{id} = $flag_matches[0]->id;
- }
- else {
- delete $flag->{id};
- $flag->{type_id} = $type_matches[0]->id;
- }
- }
+ else {
+ delete $flag->{type_id};
+ $flag->{id} = $flag_matches[0]->id;
}
-
- if ($flag->{id}) {
- push(@old_flags, $flag);
+ }
+ }
+ elsif ($name) {
+ my @type_matches = grep($name eq $_->name, @$flag_types);
+ @type_matches > 1 && ThrowUserError('flag_type_not_unique', {value => $name});
+ @type_matches
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::FlagType', name => $name});
+ if ($new) {
+ delete $flag->{id};
+ $flag->{type_id} = $type_matches[0]->id;
+ }
+ else {
+ my @flag_matches = grep($name eq $_->type->name, @$current_flags);
+ @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $name});
+ if (@flag_matches) {
+ $flag->{id} = $flag_matches[0]->id;
}
else {
- push(@new_flags, $flag);
+ delete $flag->{id};
+ $flag->{type_id} = $type_matches[0]->id;
}
+ }
}
- return (\@old_flags, \@new_flags);
+ if ($flag->{id}) {
+ push(@old_flags, $flag);
+ }
+ else {
+ push(@new_flags, $flag);
+ }
+ }
+
+ return (\@old_flags, \@new_flags);
}
sub filter($$;$$) {
- my ($params, $hash, $types, $prefix) = @_;
- my %newhash = %$hash;
+ my ($params, $hash, $types, $prefix) = @_;
+ my %newhash = %$hash;
- foreach my $key (keys %$hash) {
- delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix);
- }
+ foreach my $key (keys %$hash) {
+ delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix);
+ }
- return \%newhash;
+ return \%newhash;
}
sub filter_wants($$;$$) {
- my ($params, $field, $types, $prefix) = @_;
-
- # Since this is operation is resource intensive, we will cache the results
- # This assumes that $params->{*_fields} doesn't change between calls
- my $cache = Bugzilla->request_cache->{filter_wants} ||= {};
- $field = "${prefix}.${field}" if $prefix;
-
- if (exists $cache->{$field}) {
- return $cache->{$field};
- }
-
- # Mimic old behavior if no types provided
- my %field_types = map { $_ => 1 } (ref $types ? @$types : ($types || 'default'));
-
- my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
- my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
-
- my %include_types;
- my %exclude_types;
-
- # Only return default fields if nothing is specified
- $include_types{default} = 1 if !%include;
-
- # Look for any field types requested
- foreach my $key (keys %include) {
- next if $key !~ /^_(.*)$/;
- $include_types{$1} = 1;
- delete $include{$key};
- }
- foreach my $key (keys %exclude) {
- next if $key !~ /^_(.*)$/;
- $exclude_types{$1} = 1;
- delete $exclude{$key};
- }
-
- # Explicit inclusion/exclusion
- return $cache->{$field} = 0 if $exclude{$field};
- return $cache->{$field} = 1 if $include{$field};
-
- # If the user has asked to include all or exclude all
- return $cache->{$field} = 0 if $exclude_types{'all'};
- return $cache->{$field} = 1 if $include_types{'all'};
-
- # If the user has not asked for any fields specifically or if the user has asked
- # for one or more of the field's types (and not excluded them)
- foreach my $type (keys %field_types) {
- return $cache->{$field} = 0 if $exclude_types{$type};
- return $cache->{$field} = 1 if $include_types{$type};
- }
-
- my $wants = 0;
- if ($prefix) {
- # Include the field if the parent is include (and this one is not excluded)
- $wants = 1 if $include{$prefix};
- }
- else {
- # We want to include this if one of the sub keys is included
- my $key = $field . '.';
- my $len = length($key);
- $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include;
- }
-
- return $cache->{$field} = $wants;
+ my ($params, $field, $types, $prefix) = @_;
+
+ # Since this is operation is resource intensive, we will cache the results
+ # This assumes that $params->{*_fields} doesn't change between calls
+ my $cache = Bugzilla->request_cache->{filter_wants} ||= {};
+ $field = "${prefix}.${field}" if $prefix;
+
+ if (exists $cache->{$field}) {
+ return $cache->{$field};
+ }
+
+ # Mimic old behavior if no types provided
+ my %field_types
+ = map { $_ => 1 } (ref $types ? @$types : ($types || 'default'));
+
+ my %include = map { $_ => 1 } @{$params->{'include_fields'} || []};
+ my %exclude = map { $_ => 1 } @{$params->{'exclude_fields'} || []};
+
+ my %include_types;
+ my %exclude_types;
+
+ # Only return default fields if nothing is specified
+ $include_types{default} = 1 if !%include;
+
+ # Look for any field types requested
+ foreach my $key (keys %include) {
+ next if $key !~ /^_(.*)$/;
+ $include_types{$1} = 1;
+ delete $include{$key};
+ }
+ foreach my $key (keys %exclude) {
+ next if $key !~ /^_(.*)$/;
+ $exclude_types{$1} = 1;
+ delete $exclude{$key};
+ }
+
+ # Explicit inclusion/exclusion
+ return $cache->{$field} = 0 if $exclude{$field};
+ return $cache->{$field} = 1 if $include{$field};
+
+ # If the user has asked to include all or exclude all
+ return $cache->{$field} = 0 if $exclude_types{'all'};
+ return $cache->{$field} = 1 if $include_types{'all'};
+
+ # If the user has not asked for any fields specifically or if the user has asked
+ # for one or more of the field's types (and not excluded them)
+ foreach my $type (keys %field_types) {
+ return $cache->{$field} = 0 if $exclude_types{$type};
+ return $cache->{$field} = 1 if $include_types{$type};
+ }
+
+ my $wants = 0;
+ if ($prefix) {
+
+ # Include the field if the parent is include (and this one is not excluded)
+ $wants = 1 if $include{$prefix};
+ }
+ else {
+ # We want to include this if one of the sub keys is included
+ my $key = $field . '.';
+ my $len = length($key);
+ $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include;
+ }
+
+ return $cache->{$field} = $wants;
}
sub taint_data {
- my @params = @_;
- return if !@params;
- # Though this is a private function, it hasn't changed since 2004 and
- # should be safe to use, and prevents us from having to write it ourselves
- # or require another module to do it.
- if (${^TAINT}) {
- Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
- Test::Taint::taint_deeply(\@params);
- }
+ my @params = @_;
+ return if !@params;
+
+ # Though this is a private function, it hasn't changed since 2004 and
+ # should be safe to use, and prevents us from having to write it ourselves
+ # or require another module to do it.
+ if (${^TAINT}) {
+ Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
+ Test::Taint::taint_deeply(\@params);
+ }
}
sub _delete_bad_keys {
- foreach my $item (@_) {
- next if ref $item ne 'HASH';
- foreach my $key (keys %$item) {
- # Making something a hash key always untaints it, in Perl.
- # However, we need to validate our argument names in some way.
- # We know that all hash keys passed in to the WebService will
- # match \w+, so we delete any key that doesn't match that.
- if ($key !~ /^[\w\.\-]+$/) {
- delete $item->{$key};
- }
- }
+ foreach my $item (@_) {
+ next if ref $item ne 'HASH';
+ foreach my $key (keys %$item) {
+
+ # Making something a hash key always untaints it, in Perl.
+ # However, we need to validate our argument names in some way.
+ # We know that all hash keys passed in to the WebService will
+ # match \w+, so we delete any key that doesn't match that.
+ if ($key !~ /^[\w\.\-]+$/) {
+ delete $item->{$key};
+ }
}
- return @_;
+ }
+ return @_;
}
-sub validate {
- my ($self, $params, @keys) = @_;
- my $cache_key = join('|', (caller(1))[3], sort @keys);
- # Type->of() is the same as Type[], used here because it is easier
- # to chain with plus_coercions.
- state $array_of_nonrefs = ArrayRef->of(Maybe[Value])->plus_coercions(
- Maybe[Value], q{ [ $_ ] },
- );
- state $type_cache = {};
- my $params_type = $type_cache->{$cache_key} //= do {
- my %fields = map { $_ => Optional[$array_of_nonrefs] } @keys;
- Maybe[ Dict[%fields, slurpy Any] ];
- };
-
- # If $params is defined but not a reference, then we weren't
- # sent any parameters at all, and we're getting @keys where
- # $params should be.
- return ($self, undef) if (defined $params and !ref $params);
-
- # If @keys is not empty then we convert any named
- # parameters that have scalar values to arrayrefs
- # that match.
- $params = $params_type->coerce($params);
- if (my $type_error = $params_type->validate($params)) {
- FATAL("validate() found type error: $type_error");
- ThrowUserError('invalid_params', { type_error => $type_error } ) if $type_error;
- }
-
- return ($self, $params);
+sub validate {
+ my ($self, $params, @keys) = @_;
+ my $cache_key = join('|', (caller(1))[3], sort @keys);
+
+ # Type->of() is the same as Type[], used here because it is easier
+ # to chain with plus_coercions.
+ state $array_of_nonrefs
+ = ArrayRef->of(Maybe [Value])->plus_coercions(Maybe [Value], q{ [ $_ ] },);
+ state $type_cache = {};
+ my $params_type = $type_cache->{$cache_key} //= do {
+ my %fields = map { $_ => Optional [$array_of_nonrefs] } @keys;
+ Maybe [Dict [%fields, slurpy Any]];
+ };
+
+ # If $params is defined but not a reference, then we weren't
+ # sent any parameters at all, and we're getting @keys where
+ # $params should be.
+ return ($self, undef) if (defined $params and !ref $params);
+
+ # If @keys is not empty then we convert any named
+ # parameters that have scalar values to arrayrefs
+ # that match.
+ $params = $params_type->coerce($params);
+ if (my $type_error = $params_type->validate($params)) {
+ FATAL("validate() found type error: $type_error");
+ ThrowUserError('invalid_params', {type_error => $type_error}) if $type_error;
+ }
+
+ return ($self, $params);
}
sub translate {
- my ($params, $mapped) = @_;
- my %changes;
- while (my ($key,$value) = each (%$params)) {
- my $new_field = $mapped->{$key} || $key;
- $changes{$new_field} = $value;
- }
- return \%changes;
+ my ($params, $mapped) = @_;
+ my %changes;
+ while (my ($key, $value) = each(%$params)) {
+ my $new_field = $mapped->{$key} || $key;
+ $changes{$new_field} = $value;
+ }
+ return \%changes;
}
sub params_to_objects {
- my ($params, $class) = @_;
- my (@objects, @objects_by_ids);
+ my ($params, $class) = @_;
+ my (@objects, @objects_by_ids);
- @objects = map { $class->check($_) }
- @{ $params->{names} } if $params->{names};
+ @objects = map { $class->check($_) } @{$params->{names}} if $params->{names};
- @objects_by_ids = map { $class->check({ id => $_ }) }
- @{ $params->{ids} } if $params->{ids};
+ @objects_by_ids = map { $class->check({id => $_}) } @{$params->{ids}}
+ if $params->{ids};
- push(@objects, @objects_by_ids);
- my %seen;
- @objects = grep { !$seen{$_->id}++ } @objects;
- return \@objects;
+ push(@objects, @objects_by_ids);
+ my %seen;
+ @objects = grep { !$seen{$_->id}++ } @objects;
+ return \@objects;
}
sub fix_credentials {
- my ($params, $cgi) = @_;
-
- # Allow user to pass in authentication details in X-Headers
- # This allows callers to keep credentials out of GET request query-strings
- if ($cgi) {
- foreach my $field (keys %{ API_AUTH_HEADERS() }) {
- next if exists $params->{API_AUTH_HEADERS->{$field}} || ($cgi->http($field) // '') eq '';
- $params->{API_AUTH_HEADERS->{$field}} = uri_unescape($cgi->http($field));
- }
+ my ($params, $cgi) = @_;
+
+ # Allow user to pass in authentication details in X-Headers
+ # This allows callers to keep credentials out of GET request query-strings
+ if ($cgi) {
+ foreach my $field (keys %{API_AUTH_HEADERS()}) {
+ next
+ if exists $params->{API_AUTH_HEADERS->{$field}}
+ || ($cgi->http($field) // '') eq '';
+ $params->{API_AUTH_HEADERS->{$field}} = uri_unescape($cgi->http($field));
}
-
- # Allow user to pass in login=foo&password=bar as a convenience
- # even if not calling GET /login. We also do not delete them as
- # GET /login requires "login" and "password".
- if (exists $params->{'login'} && exists $params->{'password'}) {
- $params->{'Bugzilla_login'} = delete $params->{'login'};
- $params->{'Bugzilla_password'} = delete $params->{'password'};
- }
- # Allow user to pass api_key=12345678 as a convenience which becomes
- # "Bugzilla_api_key" which is what the auth code looks for.
- if (exists $params->{api_key}) {
- $params->{Bugzilla_api_key} = delete $params->{api_key};
- }
- # Allow user to pass token=12345678 as a convenience which becomes
- # "Bugzilla_token" which is what the auth code looks for.
- if (exists $params->{'token'}) {
- $params->{'Bugzilla_token'} = delete $params->{'token'};
- }
-
- # Allow extensions to modify the credential data before login
- Bugzilla::Hook::process('webservice_fix_credentials', { params => $params });
+ }
+
+ # Allow user to pass in login=foo&password=bar as a convenience
+ # even if not calling GET /login. We also do not delete them as
+ # GET /login requires "login" and "password".
+ if (exists $params->{'login'} && exists $params->{'password'}) {
+ $params->{'Bugzilla_login'} = delete $params->{'login'};
+ $params->{'Bugzilla_password'} = delete $params->{'password'};
+ }
+
+ # Allow user to pass api_key=12345678 as a convenience which becomes
+ # "Bugzilla_api_key" which is what the auth code looks for.
+ if (exists $params->{api_key}) {
+ $params->{Bugzilla_api_key} = delete $params->{api_key};
+ }
+
+ # Allow user to pass token=12345678 as a convenience which becomes
+ # "Bugzilla_token" which is what the auth code looks for.
+ if (exists $params->{'token'}) {
+ $params->{'Bugzilla_token'} = delete $params->{'token'};
+ }
+
+ # Allow extensions to modify the credential data before login
+ Bugzilla::Hook::process('webservice_fix_credentials', {params => $params});
}
__END__