Merge WebServices from Bugzilla 5.0.1

3col
Vitaliy Filippov 2015-10-23 12:40:36 +03:00
parent 594e5ee476
commit 10988be74f
11 changed files with 5182 additions and 572 deletions

View File

@ -150,6 +150,7 @@ sub status { return $_[0]->{status}; }
sub setter_id { return $_[0]->{setter_id}; }
sub requestee_id { return $_[0]->{requestee_id}; }
sub creation_date { return $_[0]->{creation_date}; }
sub modification_date { return $_[0]->{modification_date}; }
###############################
#### Methods ####

View File

@ -1007,7 +1007,11 @@ sub flag_types
sub allows_unconfirmed { return $_[0]->{allows_unconfirmed}; }
sub description { return $_[0]->{description}; }
sub isactive { return $_[0]->{isactive}; }
sub is_active { return $_[0]->{isactive}; }
sub votesperuser { return $_[0]->{votesperuser}; }
sub maxvotesperbug { return $_[0]->{maxvotesperbug}; }
sub votestoconfirm { return $_[0]->{votestoconfirm}; }
sub votes_per_user { return $_[0]->{votesperuser}; }
sub max_votes_per_bug { return $_[0]->{maxvotesperbug}; }
sub votes_to_confirm { return $_[0]->{votestoconfirm}; }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,212 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::WebService::Classification;
use strict;
use warnings;
use base qw (Bugzilla::WebService);
use Bugzilla::Classification;
use Bugzilla::Error;
use Bugzilla::WebService::Util qw(filter validate params_to_objects);
use constant READ_ONLY => qw(
get
);
use constant PUBLIC_METHODS => qw(
get
);
sub get {
my ($self, $params) = validate(@_, 'names', 'ids');
defined $params->{names} || defined $params->{ids}
|| ThrowCodeError('params_required', { function => 'Classification.get',
params => ['names', 'ids'] });
my $user = Bugzilla->user;
Bugzilla->params->{'useclassification'}
|| $user->in_group('editclassifications')
|| ThrowUserError('auth_classification_not_enabled');
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 @classifications = map { $self->_classification_to_hash($_, $params) } @classification_objs;
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 ],
};
}
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';
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Classification - The Classification API
=head1 DESCRIPTION
This part of the Bugzilla API allows you to deal with the available Classifications.
You will be able to get information about them as well as manipulate them.
=head1 METHODS
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
Although the data input and output is the same for JSONRPC, XMLRPC and REST,
the directions for how to access the data via REST is noted in each method
where applicable.
=head1 Classification Retrieval
=head2 get
B<EXPERIMENTAL>
=over
=item B<Description>
Returns a hash containing information about a set of classifications.
=item B<REST>
To return information on a single classification:
GET /rest/classification/<classification_id_or_name>
The returned data format will be the same as below.
=item B<Params>
In addition to the parameters below, this method also accepts the
standard L<include_fields|Bugzilla::WebService/include_fields> and
L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
You could get classifications info by supplying their names and/or ids.
So, this method accepts the following parameters:
=over
=item C<ids>
An array of classification ids.
=item C<names>
An array of classification names.
=back
=item B<Returns>
A hash with the key C<classifications> and an array of hashes as the corresponding value.
Each element of the array represents a classification that the user is authorized to see
and has the following keys:
=over
=item C<id>
C<int> The id of the classification.
=item C<name>
C<string> The name of the classification.
=item C<description>
C<string> The description of the classificaion.
=item C<sort_key>
C<int> The value which determines the order the classification is sorted.
=item C<products>
An array of hashes. The array contains the products the user is authorized to
access within the classification. Each hash has the following keys:
=over
=item C<name>
C<string> The name of the product.
=item C<id>
C<int> The id of the product.
=item C<description>
C<string> The description of the product.
=back
=back
=item B<Errors>
=over
=item 900 (Classification not enabled)
Classification is not enabled on this installation.
=back
=item B<History>
=over
=item Added in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back
=back

View File

@ -0,0 +1,153 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::WebService::Component;
use 5.10.1;
use strict;
use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Component;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Util qw(translate params_to_objects validate);
use constant PUBLIC_METHODS => qw(
create
);
use constant MAPPED_FIELDS => {
default_assignee => 'initialowner',
default_qa_contact => 'initialqacontact',
default_cc => 'initial_cc',
is_open => 'isactive',
};
sub create {
my ($self, $params) = @_;
my $user = Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('editcomponents')
|| scalar @{ $user->get_products_by_permission('editcomponents') }
|| ThrowUserError('auth_failure', { group => 'editcomponents',
action => 'edit',
object => 'components' });
my $product = $user->check_can_admin_product($params->{product});
# Translate the fields
my $values = translate($params, MAPPED_FIELDS);
$values->{product} = $product;
# Create the component and return the newly created id.
my $component = Bugzilla::Component->create($values);
return { id => $self->type('int', $component->id) };
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Component - The Component API
=head1 DESCRIPTION
This part of the Bugzilla API allows you to deal with the available product components.
You will be able to get information about them as well as manipulate them.
=head1 METHODS
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
=head1 Component Creation and Modification
=head2 create
B<EXPERIMENTAL>
=over
=item B<Description>
This allows you to create a new component in Bugzilla.
=item B<Params>
Some params must be set, or an error will be thrown. These params are
marked B<Required>.
=over
=item C<name>
B<Required> C<string> The name of the new component.
=item C<product>
B<Required> C<string> The name of the product that the component must be
added to. This product must already exist, and the user have the necessary
permissions to edit components for it.
=item C<description>
B<Required> C<string> The description of the new component.
=item C<default_assignee>
B<Required> C<string> The login name of the default assignee of the component.
=item C<default_cc>
C<array> An array of strings with each element representing one login name of the default CC list.
=item C<default_qa_contact>
C<string> The login name of the default QA contact for the component.
=item C<is_open>
C<boolean> 1 if you want to enable the component for bug creations. 0 otherwise. Default is 1.
=back
=item B<Returns>
A hash with one key: C<id>. This will represent the ID of the newly-added
component.
=item B<Errors>
=over
=item 304 (Authorization Failure)
You are not authorized to create a new component.
=item 1200 (Component already exists)
The name that you specified for the new component already exists in the
specified product.
=back
=item B<History>
=over
=item Added in Bugzilla B<5.0>.
=back
=back

View File

@ -0,0 +1,833 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::WebService::FlagType;
use strict;
use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Component;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::FlagType;
use Bugzilla::Product;
use Bugzilla::Util qw(trim);
use List::MoreUtils qw(uniq);
use constant PUBLIC_METHODS => qw(
create
get
update
);
sub get {
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 create {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
Bugzilla->user->in_group('editcomponents')
|| scalar(@{$user->get_products_by_permission('editcomponents')})
|| ThrowUserError("auth_failure", { group => "editcomponents",
action => "add",
object => "flagtypes" });
$params->{name} || ThrowCodeError('param_required', { param => 'name' });
$params->{description} || ThrowCodeError('param_required', { param => 'description' });
my %args = (
sortkey => 1,
name => undef,
inclusions => ['0:0'], # Default to __ALL__:__ALL__
cc_list => '',
description => undef,
is_requestable => 'on',
exclusions => [],
is_multiplicable => 'on',
request_group => '',
is_active => 'on',
is_specifically_requestable => 'on',
target_type => 'bug',
grant_group => '',
);
foreach my $key (keys %args) {
$args{$key} = $params->{$key} if defined($params->{$key});
}
$args{name} = trim($params->{name});
$args{description} = trim($params->{description});
# Is specifically requestable is actually is_requesteeable
if (exists $args{is_specifically_requestable}) {
$args{is_requesteeble} = delete $args{is_specifically_requestable};
}
# Default is on for the tickbox flags.
# If the user has set them to 'off' then undefine them so the flags are not ticked
foreach my $arg_name (qw(is_requestable is_multiplicable is_active is_requesteeble)) {
if (defined($args{$arg_name}) && ($args{$arg_name} eq '0')) {
$args{$arg_name} = undef;
}
}
# Process group inclusions and exclusions
$args{inclusions} = _process_lists($params->{inclusions}) if defined $params->{inclusions};
$args{exclusions} = _process_lists($params->{exclusions}) if defined $params->{exclusions};
my $flagtype = Bugzilla::FlagType->create(\%args);
return { id => $self->type('int', $flagtype->id) };
}
sub update {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('editcomponents')
|| scalar(@{$user->get_products_by_permission('editcomponents')})
|| ThrowUserError("auth_failure", { group => "editcomponents",
action => "edit",
object => "flagtypes" });
defined($params->{names}) || defined($params->{ids})
|| ThrowCodeError('params_required',
{ function => 'FlagType.update', params => ['ids', 'names'] });
# Get the list of unique flag type ids we are updating
my @flag_type_ids = defined($params->{ids}) ? @{$params->{ids}} : ();
if (defined $params->{names}) {
push @flag_type_ids, map { $_->id }
@{ Bugzilla::FlagType::match({ name => $params->{names} }) };
}
@flag_type_ids = uniq @flag_type_ids;
# We delete names and ids to keep only new values to set.
delete $params->{names};
delete $params->{ids};
# Process group inclusions and exclusions
# We removed them from $params because these are handled differently
my $inclusions = _process_lists(delete $params->{inclusions}) if defined $params->{inclusions};
my $exclusions = _process_lists(delete $params->{exclusions}) if defined $params->{exclusions};
$dbh->bz_start_transaction();
my %changes = ();
foreach my $flag_type_id (@flag_type_ids) {
my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_type_id);
if ($can_fully_edit) {
$flagtype->set_all($params);
}
elsif (scalar keys %$params) {
ThrowUserError('flag_type_not_editable', { flagtype => $flagtype });
}
# Process the clusions
foreach my $type ('inclusions', 'exclusions') {
my $clusions = $type eq 'inclusions' ? $inclusions : $exclusions;
next if not defined $clusions;
my @extra_clusions = ();
if (!$user->in_group('editcomponents')) {
my $products = $user->get_products_by_permission('editcomponents');
# Bring back the products the user cannot edit.
foreach my $item (values %{$flagtype->$type}) {
my ($prod_id, $comp_id) = split(':', $item);
push(@extra_clusions, $item) unless grep { $_->id == $prod_id } @$products;
}
}
$flagtype->set_clusions({
$type => [@$clusions, @extra_clusions],
});
}
my $returned_changes = $flagtype->update();
$changes{$flagtype->id} = {
name => $flagtype->name,
changes => $returned_changes,
};
}
$dbh->bz_commit_transaction();
my @result;
foreach my $flag_type_id (keys %changes) {
my %hash = (
id => $self->type('int', $flag_type_id),
name => $self->type('string', $changes{$flag_type_id}{name}),
changes => {},
);
foreach my $field (keys %{ $changes{$flag_type_id}{changes} }) {
my $change = $changes{$flag_type_id}{changes}{$field};
$hash{changes}{$field} = {
removed => $self->type('string', $change->[0]),
added => $self->type('string', $change->[1])
};
}
push(@result, \%hash);
}
return { flagtypes => \@result };
}
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;
}
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) {
push @$result, $comp_id;
}
else {
return [ '' ];
}
}
}
$result = Bugzilla::Component->match({ product_id => $product_id, name => $result });
return $result;
}
sub _process_lists {
my $list = shift;
my $user = Bugzilla->user;
my @products;
if ($user->in_group('editcomponents')) {
@products = Bugzilla::Product->get_all;
}
else {
@products = @{$user->get_products_by_permission('editcomponents')};
}
my @component_list;
foreach my $item (@$list) {
# A hash with products as the key and component names as the values
if(ref($item) eq 'HASH') {
while (my ($product_name, $component_names) = each %$item) {
my $product = Bugzilla::Product->check({name => $product_name});
unless (grep { $product->name eq $_->name } @products) {
ThrowUserError('product_access_denied', { name => $product_name });
}
my @component_ids;
foreach my $comp_name (@$component_names) {
my $component = Bugzilla::Component->check({product => $product, name => $comp_name});
ThrowCodeError('param_invalid', { param => $comp_name}) unless defined $component;
push @component_list, $product->id . ':' . $component->id;
}
}
}
elsif(!ref($item)) {
# These are whole products
my $product = Bugzilla::Product->check({name => $item});
unless (grep { $product->name eq $_->name } @products) {
ThrowUserError('product_access_denied', { name => $item });
}
push @component_list, $product->id . ':0';
}
else {
# The user has passed something invalid
ThrowCodeError('param_invalid', { param => $item });
}
}
return \@component_list;
}
1;
__END__
=head1 NAME
Bugzilla::WebService::FlagType - API for creating flags.
=head1 DESCRIPTION
This part of the Bugzilla API allows you to create new flags
=head1 METHODS
See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>,
and B<EXPERIMENTAL> mean, and for more description about error codes.
=head2 Get Flag Types
=over
=item C<get> B<UNSTABLE>
=item B<Description>
Get information about valid flag types that can be set for bugs and attachments.
=item B<REST>
You have several options for retreiving information about flag types. The first
part is the request method and the rest is the related path needed.
To get information about all flag types for a product:
GET /rest/flag_type/<product>
To get information about flag_types for a product and component:
GET /rest/flag_type/<product>/<component>
The returned data format is the same as below.
=item B<Params>
You must pass a product name and an optional component name.
=over
=item C<product> (string) - The name of a valid product.
=item C<component> (string) - An optional valid component name associated with the product.
=back
=item B<Returns>
A hash containing two keys, C<bug> and C<attachment>. Each key value is an array of hashes,
containing the following keys:
=over
=item C<id>
C<int> An integer id uniquely identifying this flag type.
=item C<name>
C<string> The name for the flag type.
=item C<type>
C<string> The target of the flag type which is either C<bug> or C<attachment>.
=item C<description>
C<string> The description of the flag type.
=item C<values>
C<array> An array of string values that the user can set on the flag type.
=item C<is_requesteeble>
C<boolean> Users can ask specific other users to set flags of this type.
=item C<is_multiplicable>
C<boolean> Multiple flags of this type can be set for the same bug or attachment.
=back
=item B<Errors>
=over
=item 106 (Product Access Denied)
Either the product does not exist or you don't have access to it.
=item 51 (Invalid Component)
The component provided does not exist in the product.
=back
=item B<History>
=over
=item Added in Bugzilla B<5.0>.
=back
=back
=head2 Create Flag
=over
=item C<create> B<UNSTABLE>
=item B<Description>
Creates a new FlagType
=item B<REST>
POST /rest/flag_type
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
At a minimum the following two arguments must be supplied:
=over
=item C<name> (string) - The name of the new Flag Type.
=item C<description> (string) - A description for the Flag Type object.
=back
=item B<Returns>
C<int> flag_id
The ID of the new FlagType object is returned.
=item B<Params>
=over
=item name B<required>
C<string> A short name identifying this type.
=item description B<required>
C<string> A comprehensive description of this type.
=item inclusions B<optional>
An array of strings or a hash containing product names, and optionally
component names. If you provide a string, the flag type will be shown on
all bugs in that product. If you provide a hash, the key represents the
product name, and the value is the components of the product to be included.
For example:
[ 'FooProduct',
{
BarProduct => [ 'C1', 'C3' ],
BazProduct => [ 'C7' ]
}
]
This flag will be added to B<All> components of I<FooProduct>,
components C1 and C3 of I<BarProduct>, and C7 of I<BazProduct>.
=item exclusions B<optional>
An array of strings or hashes containing product names. This uses the same
fromat as inclusions.
This will exclude the flag from all products and components specified.
=item sortkey B<optional>
C<int> A number between 1 and 32767 by which this type will be sorted when
displayed to users in a list; ignore if you don't care what order the types
appear in or if you want them to appear in alphabetical order.
=item is_active B<optional>
C<boolean> Flag of this type appear in the UI and can be set. Default is B<true>.
=item is_requestable B<optional>
C<boolean> Users can ask for flags of this type to be set. Default is B<true>.
=item cc_list B<optional>
C<array> An array of strings. If the flag type is requestable, who should
receive e-mail notification of requests. This is an array of e-mail addresses
which do not need to be Bugzilla logins.
=item is_specifically_requestable B<optional>
C<boolean> Users can ask specific other users to set flags of this type as
opposed to just asking the wind. Default is B<true>.
=item is_multiplicable B<optional>
C<boolean> Multiple flags of this type can be set on the same bug. Default is B<true>.
=item grant_group B<optional>
C<string> The group allowed to grant/deny flags of this type (to allow all
users to grant/deny these flags, select no group). Default is B<no group>.
=item request_group B<optional>
C<string> If flags of this type are requestable, the group allowed to request
them (to allow all users to request these flags, select no group). Note that
the request group alone has no effect if the grant group is not defined!
Default is B<no group>.
=back
=item B<Errors>
=over
=item 51 (Group Does Not Exist)
The group name you entered does not exist, or you do not have access to it.
=item 105 (Unknown component)
The component does not exist for this product.
=item 106 (Product Access Denied)
Either the product does not exist or you don't have editcomponents privileges
to it.
=item 501 (Illegal Email Address)
One of the e-mail address in the CC list is invalid. An e-mail in the CC
list does NOT need to be a valid Bugzilla user.
=item 1101 (Flag Type Name invalid)
You must specify a non-blank name for this flag type. It must
no contain spaces or commas, and must be 50 characters or less.
=item 1102 (Flag type must have description)
You must specify a description for this flag type.
=item 1103 (Flag type CC list is invalid
The CC list must be 200 characters or less.
=item 1104 (Flag Type Sort Key Not Valid)
The sort key is not a valid number.
=item 1105 (Flag Type Not Editable)
This flag type is not available for the products you can administer. Therefore
you can not edit attributes of the flag type, other than the inclusion and
exclusion list.
=back
=item B<History>
=over
=item Added in Bugzilla B<5.0>.
=back
=back
=head2 update
B<EXPERIMENTAL>
=over
=item B<Description>
This allows you to update a flag type in Bugzilla.
=item B<REST>
PUT /rest/flag_type/<product_id_or_name>
The params to include in the PUT body as well as the returned data format,
are the same as below. The C<ids> and C<names> params will be overridden as
it is pulled from the URL path.
=item B<Params>
B<Note:> The following parameters specify which products you are updating.
You must set one or both of these parameters.
=over
=item C<ids>
C<array> of C<int>s. Numeric ids of the flag types that you wish to update.
=item C<names>
C<array> of C<string>s. Names of the flag types that you wish to update. If
many flag types have the same name, this will change ALL of them.
=back
B<Note:> The following parameters specify the new values you want to set for
the products you are updating.
=over
=item name
C<string> A short name identifying this type.
=item description
C<string> A comprehensive description of this type.
=item inclusions B<optional>
An array of strings or a hash containing product names, and optionally
component names. If you provide a string, the flag type will be shown on
all bugs in that product. If you provide a hash, the key represents the
product name, and the value is the components of the product to be included.
for example
[ 'FooProduct',
{
BarProduct => [ 'C1', 'C3' ],
BazProduct => [ 'C7' ]
}
]
This flag will be added to B<All> components of I<FooProduct>,
components C1 and C3 of I<BarProduct>, and C7 of I<BazProduct>.
=item exclusions B<optional>
An array of strings or hashes containing product names.
This uses the same fromat as inclusions.
This will exclude the flag from all products and components specified.
=item sortkey
C<int> A number between 1 and 32767 by which this type will be sorted when
displayed to users in a list; ignore if you don't care what order the types
appear in or if you want them to appear in alphabetical order.
=item is_active
C<boolean> Flag of this type appear in the UI and can be set.
=item is_requestable
C<boolean> Users can ask for flags of this type to be set.
=item cc_list
C<array> An array of strings. If the flag type is requestable, who should
receive e-mail notification of requests. This is an array of e-mail addresses
which do not need to be Bugzilla logins.
=item is_specifically_requestable
C<boolean> Users can ask specific other users to set flags of this type as
opposed to just asking the wind.
=item is_multiplicable
C<boolean> Multiple flags of this type can be set on the same bug.
=item grant_group
C<string> The group allowed to grant/deny flags of this type (to allow all
users to grant/deny these flags, select no group).
=item request_group
C<string> If flags of this type are requestable, the group allowed to request
them (to allow all users to request these flags, select no group). Note that
the request group alone has no effect if the grant group is not defined!
=back
=item B<Returns>
A C<hash> with a single field "flagtypes". This points to an array of hashes
with the following fields:
=over
=item C<id>
C<int> The id of the product that was updated.
=item C<name>
C<string> The name of the product that was updated.
=item C<changes>
C<hash> The changes that were actually done on this product. The keys are
the names of the fields that were changed, and the values are a hash
with two keys:
=over
=item C<added>
C<string> The value that this field was changed to.
=item C<removed>
C<string> The value that was previously set in this field.
=back
Note that booleans will be represented with the strings '1' and '0'.
Here's an example of what a return value might look like:
{
products => [
{
id => 123,
changes => {
name => {
removed => 'FooFlagType',
added => 'BarFlagType'
},
is_requestable => {
removed => '1',
added => '0',
}
}
}
]
}
=back
=item B<Errors>
=over
=item 51 (Group Does Not Exist)
The group name you entered does not exist, or you do not have access to it.
=item 105 (Unknown component)
The component does not exist for this product.
=item 106 (Product Access Denied)
Either the product does not exist or you don't have editcomponents privileges
to it.
=item 501 (Illegal Email Address)
One of the e-mail address in the CC list is invalid. An e-mail in the CC
list does NOT need to be a valid Bugzilla user.
=item 1101 (Flag Type Name invalid)
You must specify a non-blank name for this flag type. It must
no contain spaces or commas, and must be 50 characters or less.
=item 1102 (Flag type must have description)
You must specify a description for this flag type.
=item 1103 (Flag type CC list is invalid
The CC list must be 200 characters or less.
=item 1104 (Flag Type Sort Key Not Valid)
The sort key is not a valid number.
=item 1105 (Flag Type Not Editable)
This flag type is not available for the products you can administer. Therefore
you can not edit attributes of the flag type, other than the inclusion and
exclusion list.
=back
=item B<History>
=over
=item Added in Bugzilla B<5.0>.
=back
=back

View File

@ -0,0 +1,605 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::WebService::Group;
use strict;
use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Util qw(validate translate params_to_objects);
use constant PUBLIC_METHODS => qw(
create
get
update
);
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) };
}
sub update {
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);
}
return { groups => \@result };
}
sub get {
my ($self, $params) = validate(@_, 'ids', 'names', 'type');
Bugzilla->login(LOGIN_REQUIRED);
# 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');
}
Bugzilla->switch_to_shadow_db();
my $groups = [];
if (defined $params->{ids}) {
# Get the groups by id
$groups = Bugzilla::Group->new_from_list($params->{ids});
}
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;
}
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 };
}
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;
}
sub _get_group_membership {
my ($self, $group, $params) = @_;
my $user = Bugzilla->user;
my %users_only;
my $dbh = Bugzilla->dbh;
my $editusers = $user->in_group('editusers');
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 (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
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;
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Group - The API for creating, changing, and getting
information about Groups.
=head1 DESCRIPTION
This part of the Bugzilla API allows you to create Groups and
get information about them.
=head1 METHODS
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
Although the data input and output is the same for JSONRPC, XMLRPC and REST,
the directions for how to access the data via REST is noted in each method
where applicable.
=head1 Group Creation and Modification
=head2 create
B<UNSTABLE>
=over
=item B<Description>
This allows you to create a new group in Bugzilla.
=item B<REST>
POST /rest/group
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
Some params must be set, or an error will be thrown. These params are
marked B<Required>.
=over
=item C<name>
B<Required> C<string> A short name for this group. Must be unique. This
is not usually displayed in the user interface, except in a few places.
=item C<description>
B<Required> C<string> A human-readable name for this group. Should be
relatively short. This is what will normally appear in the UI as the
name of the group.
=item C<user_regexp>
C<string> A regular expression. Any user whose Bugzilla username matches
this regular expression will automatically be granted membership in this group.
=item C<is_active>
C<boolean> C<True> if new group can be used for bugs, C<False> if this
is a group that will only contain users and no bugs will be restricted
to it.
=item C<icon_url>
C<string> A URL pointing to a small icon used to identify the group.
This icon will show up next to users' names in various parts of Bugzilla
if they are in this group.
=back
=item B<Returns>
A hash with one element, C<id>. This is the id of the newly-created group.
=item B<Errors>
=over
=item 800 (Empty Group Name)
You must specify a value for the C<name> field.
=item 801 (Group Exists)
There is already another group with the same C<name>.
=item 802 (Group Missing Description)
You must specify a value for the C<description> field.
=item 803 (Group Regexp Invalid)
You specified an invalid regular expression in the C<user_regexp> field.
=back
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 update
B<UNSTABLE>
=over
=item B<Description>
This allows you to update a group in Bugzilla.
=item B<REST>
PUT /rest/group/<group_name_or_id>
The params to include in the PUT body as well as the returned data format,
are the same as below. The C<ids> param will be overridden as it is pulled
from the URL path.
=item B<Params>
At least C<ids> or C<names> must be set, or an error will be thrown.
=over
=item C<ids>
B<Required> C<array> Contain ids of groups to update.
=item C<names>
B<Required> C<array> Contain names of groups to update.
=item C<name>
C<string> A new name for group.
=item C<description>
C<string> A new description for groups. This is what will appear in the UI
as the name of the groups.
=item C<user_regexp>
C<string> A new regular expression for email. Will automatically grant
membership to these groups to anyone with an email address that matches
this perl regular expression.
=item C<is_active>
C<boolean> Set if groups are active and eligible to be used for bugs.
True if bugs can be restricted to this group, false otherwise.
=item C<icon_url>
C<string> A URL pointing to an icon that will appear next to the name of
users who are in this group.
=back
=item B<Returns>
A C<hash> with a single field "groups". This points to an array of hashes
with the following fields:
=over
=item C<id>
C<int> The id of the group that was updated.
=item C<changes>
C<hash> The changes that were actually done on this group. The keys are
the names of the fields that were changed, and the values are a hash
with two keys:
=over
=item C<added>
C<string> The values that were added to this field,
possibly a comma-and-space-separated list if multiple values were added.
=item C<removed>
C<string> The values that were removed from this field, possibly a
comma-and-space-separated list if multiple values were removed.
=back
=back
=item B<Errors>
The same as L</create>.
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head1 Group Information
=head2 get
B<UNSTABLE>
=over
=item B<Description>
Returns information about L<Bugzilla::Group|Groups>.
=item B<REST>
To return information about a specific group by C<id> or C<name>:
GET /rest/group/<group_id_or_name>
You can also return information about more than one specific group
by using the following in your query string:
GET /rest/group?ids=1&ids=2&ids=3 or GET /group?names=ProductOne&names=Product2
the returned data format is same as below.
=item B<Params>
If neither ids or names is passed, and you are in the creategroups or
editusers group, then all groups will be retrieved. Otherwise, only groups
that you have bless privileges for will be returned.
=over
=item C<ids>
C<array> Contain ids of groups to update.
=item C<names>
C<array> Contain names of groups to update.
=item C<membership>
C<boolean> Set to 1 then a list of members of the passed groups' names and
ids will be returned.
=back
=item B<Returns>
If the user is a member of the "creategroups" group they will receive
information about all groups or groups matching the criteria that they passed.
You have to be in the creategroups group unless you're requesting membership
information.
If the user is not a member of the "creategroups" group, but they are in the
"editusers" group or have bless privileges to the groups they require
membership information for, the is_active, is_bug_group and user_regexp values
are not supplied.
The return value will be a hash containing group names as the keys, each group
name will point to a hash that describes the group and has the following items:
=over
=item id
C<int> The unique integer ID that Bugzilla uses to identify this group.
Even if the name of the group changes, this ID will stay the same.
=item name
C<string> The name of the group.
=item description
C<string> The description of the group.
=item is_bug_group
C<int> Whether this groups is to be used for bug reports or is only administrative specific.
=item user_regexp
C<string> A regular expression that allows users to be added to this group if their login matches.
=item is_active
C<int> Whether this group is currently active or not.
=item users
C<array> An array of hashes, each hash contains a user object for one of the
members of this group, only returned if the user sets the C<membership>
parameter to 1, the user hash has the following items:
=over
=item id
C<int> The id of the user.
=item real_name
C<string> The actual name of the user.
=item email
C<string> The email address of the user.
=item name
C<string> The login name of the user. Note that in some situations this is
different than their email.
=item can_login
C<boolean> A boolean value to indicate if the user can login into bugzilla.
=item email_enabled
C<boolean> A boolean value to indicate if bug-related mail will be sent
to the user or not.
=item disabled_text
C<string> A text field that holds the reason for disabling a user from logging
into bugzilla, if empty then the user account is enabled otherwise it is
disabled/closed.
=back
=back
=item B<Errors>
=over
=item 51 (Invalid Object)
A non existing group name was passed to the function, as a result no
group object existed for that invalid name.
=item 805 (Cannot view groups)
Logged-in users are not authorized to edit bugzilla groups as they are not
members of the creategroups group in bugzilla, or they are not authorized to
access group member's information as they are not members of the "editusers"
group or can bless the group.
=back
=item B<History>
=over
=item This function was added in Bugzilla B<5.0>.
=back
=back
=cut

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,14 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Mads Bondo Dydensborg <mbd@dbc.dk>
# Noura Elhawary <nelhawar@redhat.com>
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::WebService::User;
use strict;
use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla;
@ -27,8 +16,10 @@ use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Group;
use Bugzilla::User;
use Bugzilla::Util qw(trim);
use Bugzilla::WebService::Util qw(filter validate);
use Bugzilla::Util qw(trim detaint_natural);
use Bugzilla::WebService::Util qw(filter filter_wants validate translate params_to_objects);
use List::Util qw(first min);
# Don't need auth to login
use constant LOGIN_EXEMPT => {
@ -40,44 +31,65 @@ use constant READ_ONLY => qw(
get
);
use constant PUBLIC_METHODS => qw(
create
get
login
logout
offer_account_by_email
update
valid_login
);
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',
};
##############
# User Login #
##############
sub login {
my ($self, $params) = @_;
my $remember = $params->{remember};
# 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->{$param} || defined $params->{'Bugzilla_' . $param})
|| ThrowCodeError('param_required', { param => $param });
}
# Convert $remember from a boolean 0/1 value to a CGI-compatible one.
if (defined($remember)) {
$remember = $remember? 'on': '';
}
else {
# Use Bugzilla's default if $remember is not supplied.
$remember =
Bugzilla->params->{'rememberlogin'} eq 'defaulton'? 'on': '';
}
# Make sure the CGI user info class works if necessary.
my $input_params = Bugzilla->input_params;
$input_params->{'Bugzilla_login'} = $params->{login};
$input_params->{'Bugzilla_password'} = $params->{password};
$input_params->{'Bugzilla_remember'} = $remember;
Bugzilla->login();
return { id => $self->type('int', Bugzilla->user->id) };
$user = Bugzilla->login();
return $self->_login_to_hash($user);
}
sub logout {
my $self = shift;
Bugzilla->logout;
return undef;
}
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);
}
#################
@ -128,7 +140,9 @@ sub create {
# $call = $rpc->call( 'User.get', { match => [ 'testusera', 'testuserb' ],
# maxusermatches => 20, excludedisabled => 1 });
sub get {
my ($self, $params) = validate(@_, 'names', 'ids');
my ($self, $params) = validate(@_, 'names', 'ids', 'match', 'group_ids', 'groups');
Bugzilla->switch_to_shadow_db();
# Make them arrays if they aren't
if ($params->{names} && !ref $params->{names}) {
@ -168,11 +182,11 @@ sub get {
}
my $in_group = $self->_filter_users_by_group(
\@user_objects, $params);
@users = map {filter $params, {
@users = map { filter $params, {
id => $self->type('int', $_->id),
real_name => $self->type('string', $_->name),
name => $self->type('string', $_->login),
}} @$in_group;
real_name => $self->type('string', $_->realname),
name => $self->type('email', $_->login),
} } @$in_group;
return { users => \@users };
}
@ -180,7 +194,7 @@ sub get {
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
# obj_by_ids are only visible to the user if they can see
# the otheruser, for non visible otheruser throw an error
foreach my $obj (@$obj_by_ids) {
if (Bugzilla->user->can_see_user($obj)){
@ -198,10 +212,15 @@ sub get {
}
# User Matching
my $limit;
if ($params->{'maxusermatches'}) {
$limit = $params->{'maxusermatches'} + 1;
my $limit = Bugzilla->params->{maxusermatches};
if ($params->{limit}) {
detaint_natural($params->{limit})
|| ThrowCodeError('param_must_be_numeric',
{ function => 'Bugzilla::WebService::User::match',
param => 'limit' });
$limit = $limit ? min($params->{limit}, $limit) : $params->{limit};
}
foreach my $match_string (@{ $params->{'match'} || [] }) {
my $matched = Bugzilla::User::match_name($match_string, $limit, $params->{excludedisabled} && 1);
foreach my $user (@$matched) {
@ -212,34 +231,121 @@ sub get {
}
}
my $in_group = $self->_filter_users_by_group(
\@user_objects, $params);
if (Bugzilla->user->in_group('editusers')) {
@users =
map {filter $params, {
id => $self->type('int', $_->id),
real_name => $self->type('string', $_->realname),
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),
}} @$in_group;
}
else {
@users =
map {filter $params, {
id => $self->type('int', $_->id),
real_name => $self->type('string', $_->realname),
name => $self->type('string', $_->login),
email => $self->type('string', $_->email),
can_login => $self->type('boolean', $_->is_enabled),
}} @$in_group;
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->realname),
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, 'saved_reports')) {
$user_info->{saved_reports} = [
map { $self->_report_to_hash($_) } @{ $user->reports }
];
}
}
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);
}
}
push(@users, $user_info);
}
return { users => \@users };
}
###############
# User Update #
###############
sub update {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
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"});
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 $values = translate($params, MAPPED_FIELDS);
# 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);
}
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])
};
}
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)};
@ -247,15 +353,23 @@ sub _filter_users_by_group {
# If no groups are specified, we return all users.
return $users if (!$group_ids and !$group_names);
my @groups = map { Bugzilla::Group->check({ id => $_ }) }
@{ $group_ids || [] };
my @name_groups = map { Bugzilla::Group->check($_) }
@{ $group_names || [] };
push(@groups, @name_groups);
my $user = Bugzilla->user;
my (@groups, %groups);
my @in_group = grep { $self->_user_in_any_group($_, \@groups) }
@$users;
if ($group_ids) {
@groups = map { Bugzilla::Group->check({ id => $_ }) } @$group_ids;
$groups{$_->id} = $_ foreach @groups;
}
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 });
$groups{$group->id} = $group;
}
}
@groups = values %groups;
my @in_group = grep { $self->_user_in_any_group($_, \@groups) } @$users;
return \@in_group;
}
@ -277,6 +391,58 @@ sub read_new_functionality {
return {status => 'ok'};
}
sub _filter_bless_groups {
my ($self, $groups) = @_;
my $user = Bugzilla->user;
my @filtered_groups;
foreach my $group (@$groups) {
next unless $user->can_bless($group->id);
push(@filtered_groups, $self->_group_to_hash($group));
}
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;
}
sub _query_to_hash {
my ($self, $query) = @_;
my $item = {
id => $self->type('int', $query->id),
name => $self->type('string', $query->name),
query => $self->type('string', $query->query),
};
return $item;
}
sub _report_to_hash {
my ($self, $report) = @_;
my $item = {
id => $self->type('int', $report->id),
name => $self->type('string', $report->name),
query => $self->type('string', $report->query),
};
return $item;
}
sub _login_to_hash {
my ($self, $user) = @_;
my $item = { id => $self->type('int', $user->id) };
if ($user->{_login_token}) {
$item->{'token'} = $user->id . "-" . $user->{_login_token};
}
return $item;
}
1;
__END__
@ -295,13 +461,19 @@ log in/out using an existing account.
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
=head2 Logging In and Out
Although the data input and output is the same for JSONRPC, XMLRPC and REST,
the directions for how to access the data via REST is noted in each method
where applicable.
=over
=head1 Logging In and Out
=item C<login>
These method are now deprecated, and will be removed in the release after
Bugzilla 5.0. The correct way of use these REST and RPC calls is noted in
L<Bugzilla::WebService>
B<STABLE>
=head2 login
B<DEPRECATED>
=over
@ -315,26 +487,23 @@ etc. This method logs in an user.
=over
=item C<login> (string) - The user's login name.
=item C<login> (string) - The user's login name.
=item C<password> (string) - The user's password.
=item C<remember> (bool) B<Optional> - if the cookies returned by the
call to login should expire with the session or not. In order for
this option to have effect the Bugzilla server must be configured to
allow the user to set this option - the Bugzilla parameter
I<rememberlogin> must be set to "defaulton" or
"defaultoff". Addionally, the client application must implement
management of cookies across sessions.
=item C<restrict_login> (bool) B<Optional> - If set to a true value,
the token returned by this method will only be valid from the IP address
which called this method.
=back
=item B<Returns>
On success, a hash containing one item, C<id>, the numeric id of the
user that was logged in. A set of http cookies is also sent with the
response. These cookies must be sent along with any future requests
to the webservice, for the duration of the session.
On success, a hash containing two items, C<id>, the numeric id of the
user that was logged in, and a C<token> which can be passed in
the parameters as authentication in other calls. The token can be sent
along with any future requests to the webservice, for the duration of the
session, i.e. till L<User.logout|/logout> is called.
=item B<Errors>
@ -344,15 +513,15 @@ to the webservice, for the duration of the session.
The username does not exist, or the password is wrong.
=item 301 (Account Disabled)
=item 301 (Login Disabled)
The account has been disabled. A reason may be specified with the
error.
The ability to login with this account has been disabled. A reason may be
specified with the error.
=item 305 (New Password Required)
The current password is correct, but the user is asked to change
his password.
their password.
=item 50 (Param Required)
@ -360,11 +529,26 @@ A login or password parameter was not provided.
=back
=item B<History>
=over
=item C<remember> was removed in Bugzilla B<5.0> as this method no longer
creates a login cookie.
=item C<restrict_login> was added in Bugzilla B<5.0>.
=item C<token> was added in Bugzilla B<4.4.3>.
=item This function will be removed in the release after Bugzilla 5.0, in favour of API keys.
=back
=item C<logout>
=back
B<STABLE>
=head2 logout
B<DEPRECATED>
=over
@ -380,13 +564,55 @@ Log out the user. Does nothing if there is no user logged in.
=back
=back
=head2 valid_login
=head2 Account Creation
B<DEPRECATED>
=over
=item C<offer_account_by_email>
=item B<Description>
This method will verify whether a client's cookies or current login
token is still valid or have expired. A valid username must be provided
as well that matches.
=item B<Params>
=over
=item C<login>
The login name that matches the provided cookies or token.
=item C<token>
(string) Persistent login token current being used for authentication (optional).
Cookies passed by client will be used before the token if both provided.
=back
=item B<Returns>
Returns true/false depending on if the current cookies or token are valid
for the provided username.
=item B<Errors> (none)
=item B<History>
=over
=item Added in Bugzilla B<5.0>.
=item This function will be removed in the release after Bugzilla 5.0, in favour of API keys.
=back
=back
=head1 Account Creation and Modification
=head2 offer_account_by_email
B<STABLE>
@ -427,7 +653,7 @@ email address you specified. Account creation may be entirely disabled.
=back
=item C<create>
=head2 create
B<STABLE>
@ -443,6 +669,13 @@ actually receive an email. This function does not check that.
You must be logged in and have the C<editusers> privilege in order to
call this function.
=item B<REST>
POST /rest/user
The params to include in the POST body as well as the returned data format,
are the same as below.
=item B<Params>
=over
@ -486,17 +719,160 @@ password is under three characters.)
=item Error 503 (Password Too Long) removed in Bugzilla B<3.6>.
=back
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head2 User Info
=head2 update
B<EXPERIMENTAL>
=over
=item C<get>
=item B<Description>
Updates user accounts in Bugzilla.
=item B<REST>
PUT /rest/user/<user_id_or_name>
The params to include in the PUT body as well as the returned data format,
are the same as below. The C<ids> and C<names> params are overridden as they
are pulled from the URL path.
=item B<Params>
=over
=item C<ids>
C<array> Contains ids of user to update.
=item C<names>
C<array> Contains email/login of user to update.
=item C<full_name>
C<string> The new name of the user.
=item C<email>
C<string> The email of the user. Note that email used to login to bugzilla.
Also note that you can only update one user at a time when changing the
login name / email. (An error will be thrown if you try to update this field
for multiple users at once.)
=item C<password>
C<string> The password of the user.
=item C<email_enabled>
C<boolean> A boolean value to enable/disable sending bug-related mail to the user.
=item C<login_denied_text>
C<string> A text field that holds the reason for disabling a user from logging
into bugzilla, if empty then the user account is enabled otherwise it is
disabled/closed.
=item C<groups>
C<hash> These specify the groups that this user is directly a member of.
To set these, you should pass a hash as the value. The hash may contain
the following fields:
=over
=item C<add> An array of C<int>s or C<string>s. The group ids or group names
that the user should be added to.
=item C<remove> An array of C<int>s or C<string>s. The group ids or group names
that the user should be removed from.
=item C<set> An array of C<int>s or C<string>s. An exact set of group ids
and group names that the user should be a member of. NOTE: This does not
remove groups from the user where the person making the change does not
have the bless privilege for.
If you specify C<set>, then C<add> and C<remove> will be ignored. A group in
both the C<add> and C<remove> list will be added. Specifying a group that the
user making the change does not have bless rights will generate an error.
=back
=item C<bless_groups>
C<hash> - This is the same as groups, but affects what groups a user
has direct membership to bless that group. It takes the same inputs as
groups.
=back
=item B<Returns>
A C<hash> with a single field "users". This points to an array of hashes
with the following fields:
=over
=item C<id>
C<int> The id of the user that was updated.
=item C<changes>
C<hash> The changes that were actually done on this user. The keys are
the names of the fields that were changed, and the values are a hash
with two keys:
=over
=item C<added>
C<string> The values that were added to this field,
possibly a comma-and-space-separated list if multiple values were added.
=item C<removed>
C<string> The values that were removed from this field, possibly a
comma-and-space-separated list if multiple values were removed.
=back
=back
=item B<Errors>
=over
=item 51 (Bad Login Name)
You passed an invalid login name in the "names" array.
=item 304 (Authorization Required)
Logged-in users are not authorized to edit other users.
=back
=item B<History>
=over
=item REST API call added in Bugzilla B<5.0>.
=back
=back
=head1 User Info
=head2 get
B<STABLE>
@ -506,6 +882,18 @@ B<STABLE>
Gets information about user accounts in Bugzilla.
=item B<REST>
To get information about a single user:
GET /rest/user/<user_id_or_name>
To search for users by name, group using URL params same as below:
GET /rest/user
The returned data format is the same as below.
=item B<Params>
B<Note>: At least one of C<ids>, C<names>, or C<match> must be specified.
@ -538,9 +926,6 @@ Bugzilla itself. Users will be returned whose real name or login name
contains any one of the specified strings. Users that you cannot see will
not be included in the returned list.
Some Bugzilla installations have user-matching turned off, in which
case you will only be returned exact matches.
Most installations have a limit on how many matches are returned for
each string, which defaults to 1000 but can be changed by the Bugzilla
administrator.
@ -550,6 +935,13 @@ if they try. (This is to make it harder for spammers to harvest email
addresses from Bugzilla, and also to enforce the user visibility
restrictions that are implemented on some Bugzillas.)
=item C<limit> (int)
Limit the number of users matched by the C<match> parameter. If value
is greater than the system limit, the system limit will be used. This
parameter is only used when user matching using the C<match> parameter
is being performed.
=item C<group_ids> (array)
=item C<groups> (array)
@ -559,6 +951,14 @@ C<groups> is an array of names of groups that a user can be in.
If these are specified, they limit the return value to users who are
in I<any> of the groups specified.
=item C<include_disabled> (boolean)
By default, when using the C<match> parameter, disabled users are excluded
from the returned results unless their full username is identical to the
match string. Setting C<include_disabled> to C<true> will include disabled
users in the returned results even if their username doesn't fully match
the input string.
=back
=item B<Returns>
@ -601,10 +1001,79 @@ C<string> A text field that holds the reason for disabling a user from logging
into bugzilla, if empty then the user account is enabled. Otherwise it is
disabled/closed.
=item groups
C<array> An array of group hashes the user is a member of. If the currently
logged in user is querying their own account or is a member of the 'editusers'
group, the array will contain all the groups that the user is a
member of. Otherwise, the array will only contain groups that the logged in
user can bless. Each hash describes the group and contains the following items:
=over
=item id
C<int> The group id
=item name
C<string> The name of the group
=item description
C<string> The description for the group
=back
=item saved_searches
C<array> An array of hashes, each of which represents a user's saved search and has
the following keys:
=over
=item id
C<int> An integer id uniquely identifying the saved search.
=item name
C<string> The name of the saved search.
=item query
C<string> The CGI parameters for the saved search.
=back
=item saved_reports
C<array> An array of hashes, each of which represents a user's saved report and has
the following keys:
=over
=item id
C<int> An integer id uniquely identifying the saved report.
=item name
C<string> The name of the saved report.
=item query
C<string> The CGI parameters for the saved report.
=back
B<Note>: If you are not logged in to Bugzilla when you call this function, you
will only be returned the C<id>, C<name>, and C<real_name> items. If you are
logged in and not in editusers group, you will only be returned the C<id>, C<name>,
C<real_name>, C<email>, and C<can_login> items.
C<real_name>, C<email>, C<can_login>, and C<groups> items. The groups returned are
filtered based on your permission to bless each group.
The C<saved_searches> and C<saved_reports> items are only returned if you are
querying your own account, even if you are in the editusers group.
=back
@ -612,10 +1081,14 @@ C<real_name>, C<email>, and C<can_login> items.
=over
=item 51 (Bad Login Name or Group Name)
=item 51 (Bad Login Name or Group ID)
You passed an invalid login name in the "names" array or a bad
group name/id in the C<groups>/C<group_ids> arguments.
group ID in the C<group_ids> argument.
=item 52 (Invalid Parameter)
The value used must be an integer greater than zero.
=item 304 (Authorization Required)
@ -627,6 +1100,11 @@ wanted to get information about by user id.
Logged-out users cannot use the "ids" or "match" arguments to this
function.
=item 804 (Invalid Group Name)
You passed a group name in the C<groups> argument which either does not
exist or you do not belong to it.
=back
=item B<History>
@ -637,7 +1115,16 @@ function.
=item C<group_ids> and C<groups> were added in Bugzilla B<4.0>.
=back
=item C<include_disabled> was added in Bugzilla B<4.0>. Default
behavior for C<match> was changed to only return enabled accounts.
=item Error 804 has been added in Bugzilla 4.0.9 and 4.2.4. It's now
illegal to pass a group name you don't belong to.
=item C<groups>, C<saved_searches>, and C<saved_reports> were added
in Bugzilla B<4.4>.
=item REST API call added in Bugzilla B<5.0>.
=back

View File

@ -1,63 +1,188 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2008
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::WebService::Util;
use 5.10.1;
use strict;
use base qw(Exporter);
use warnings;
use Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::Error;
use Storable qw(dclone);
use parent qw(Exporter);
# We have to "require", not "use" this, because otherwise it tries to
# use features of Test::More during import().
require Test::Taint;
our @EXPORT_OK = qw(
extract_flags
filter
filter_wants
taint_data
validate
translate
params_to_objects
fix_credentials
);
sub filter ($$) {
my ($params, $hash) = @_;
sub extract_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;
# 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};
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;
}
}
}
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;
}
}
}
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;
foreach my $key (keys %$hash) {
delete $newhash{$key} if !filter_wants($params, $key);
delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix);
}
return \%newhash;
}
sub filter_wants ($$) {
my ($params, $field) = @_;
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'} || [] };
if (defined $params->{include_fields}) {
return 0 if !$include{$field};
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};
}
if (defined $params->{exclude_fields}) {
return 0 if $exclude{$field};
foreach my $key (keys %exclude) {
next if $key !~ /^_(.*)$/;
$exclude_types{$1} = 1;
delete $exclude{$key};
}
return 1;
# 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 {
@ -77,8 +202,9 @@ sub _delete_bad_keys {
# 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+$/) {
# match \w+, contain '.' or '-', so we delete any key that
# doesn't match that.
if ($key !~ /^[\w\.\-]+$/) {
delete $item->{$key};
}
}
@ -108,6 +234,56 @@ sub validate {
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;
}
sub params_to_objects {
my ($params, $class) = @_;
my (@objects, @objects_by_ids);
@objects = map { $class->check($_) }
@{ $params->{names} } if $params->{names};
@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;
}
sub fix_credentials {
my ($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__
=head1 NAME
@ -129,25 +305,64 @@ internally in the WebService code.
=head1 METHODS
=over
=item C<filter>
=head2 filter
This helps implement the C<include_fields> and C<exclude_fields> arguments
of WebService methods. Given a hash (the second argument to this subroutine),
this will remove any keys that are I<not> in C<include_fields> and then remove
any keys that I<are> in C<exclude_fields>.
=item C<filter_wants>
An optional third option can be passed that prefixes the field name to allow
filtering of data two or more levels deep.
For example, if you want to filter out the C<id> key/value in components returned
by Product.get, you would use the value C<component.id> in your C<exclude_fields>
list.
=head2 filter_wants
Returns C<1> if a filter would preserve the specified field when passing
a hash to L</filter>, C<0> otherwise.
=item C<validate>
=head2 validate
This helps in the validation of parameters passed into the WebSerice
This helps in the validation of parameters passed into the WebService
methods. Currently it converts listed parameters into an array reference
if the client only passed a single scalar value. It modifies the parameters
hash in place so other parameters should be unaltered.
=head2 translate
WebService methods frequently take parameters with different names than
the ones that we use internally in Bugzilla. This function takes a hashref
that has field names for keys and returns a hashref with those keys renamed
according to the mapping passed in with the second parameter (which is also
a hashref).
=head2 params_to_objects
Creates objects of the type passed in as the second parameter, using the
parameters passed to a WebService method (the first parameter to this function).
Helps make life simpler for WebService methods that internally create objects
via both "ids" and "names" fields. Also de-duplicates objects that were loaded
by both "ids" and "names". Returns an arrayref of objects.
=head2 fix_credentials
Allows for certain parameters related to authentication such as Bugzilla_login,
Bugzilla_password, and Bugzilla_token to have shorter named equivalents passed in.
This function converts the shorter versions to their respective internal names.
=head2 extract_flags
Subroutine that takes a list of hashes that are potential flag changes for
both bugs and attachments. Then breaks the list down into two separate lists
based on if the change is to add a new flag or to update an existing flag.
=head1 B<Methods in need of POD>
=over
=item taint_data
=back

View File

@ -144,7 +144,7 @@ function userAutocomplete(hint, emptyOptions, loadAllOnEmpty)
}
var u = window.location.href.replace(/[^\/]+$/, '');
u += 'xml.cgi?method=User.get&output=json&excludedisabled=1&maxusermatches=';
u += 'xml.cgi?method=User.get&output=json&excludedisabled=1&include_fields=id&include_fields=real_name&include_fields=email&maxusermatches=';
if (hint.input.value)
{
u += '20';