Merge WebServices from Bugzilla 5.0.1
parent
594e5ee476
commit
10988be74f
|
@ -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 ####
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue