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 setter_id { return $_[0]->{setter_id}; }
|
||||||
sub requestee_id { return $_[0]->{requestee_id}; }
|
sub requestee_id { return $_[0]->{requestee_id}; }
|
||||||
sub creation_date { return $_[0]->{creation_date}; }
|
sub creation_date { return $_[0]->{creation_date}; }
|
||||||
|
sub modification_date { return $_[0]->{modification_date}; }
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
#### Methods ####
|
#### Methods ####
|
||||||
|
|
|
@ -1007,7 +1007,11 @@ sub flag_types
|
||||||
|
|
||||||
sub allows_unconfirmed { return $_[0]->{allows_unconfirmed}; }
|
sub allows_unconfirmed { return $_[0]->{allows_unconfirmed}; }
|
||||||
sub description { return $_[0]->{description}; }
|
sub description { return $_[0]->{description}; }
|
||||||
|
sub isactive { return $_[0]->{isactive}; }
|
||||||
sub is_active { 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 votes_per_user { return $_[0]->{votesperuser}; }
|
||||||
sub max_votes_per_bug { return $_[0]->{maxvotesperbug}; }
|
sub max_votes_per_bug { return $_[0]->{maxvotesperbug}; }
|
||||||
sub votes_to_confirm { return $_[0]->{votestoconfirm}; }
|
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
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
# License Version 1.1 (the "License"); you may not use this file
|
# defined by the Mozilla Public License, v. 2.0.
|
||||||
# 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>
|
|
||||||
|
|
||||||
package Bugzilla::WebService::User;
|
package Bugzilla::WebService::User;
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
|
use warnings;
|
||||||
use base qw(Bugzilla::WebService);
|
use base qw(Bugzilla::WebService);
|
||||||
|
|
||||||
use Bugzilla;
|
use Bugzilla;
|
||||||
|
@ -27,8 +16,10 @@ use Bugzilla::Constants;
|
||||||
use Bugzilla::Error;
|
use Bugzilla::Error;
|
||||||
use Bugzilla::Group;
|
use Bugzilla::Group;
|
||||||
use Bugzilla::User;
|
use Bugzilla::User;
|
||||||
use Bugzilla::Util qw(trim);
|
use Bugzilla::Util qw(trim detaint_natural);
|
||||||
use Bugzilla::WebService::Util qw(filter validate);
|
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
|
# Don't need auth to login
|
||||||
use constant LOGIN_EXEMPT => {
|
use constant LOGIN_EXEMPT => {
|
||||||
|
@ -40,44 +31,65 @@ use constant READ_ONLY => qw(
|
||||||
get
|
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 #
|
# User Login #
|
||||||
##############
|
##############
|
||||||
|
|
||||||
sub login {
|
sub login {
|
||||||
my ($self, $params) = @_;
|
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
|
# Username and password params are required
|
||||||
foreach my $param ("login", "password") {
|
foreach my $param ("login", "password") {
|
||||||
defined $params->{$param}
|
(defined $params->{$param} || defined $params->{'Bugzilla_' . $param})
|
||||||
|| ThrowCodeError('param_required', { param => $param });
|
|| ThrowCodeError('param_required', { param => $param });
|
||||||
}
|
}
|
||||||
|
|
||||||
# Convert $remember from a boolean 0/1 value to a CGI-compatible one.
|
$user = Bugzilla->login();
|
||||||
if (defined($remember)) {
|
return $self->_login_to_hash($user);
|
||||||
$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) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub logout {
|
sub logout {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
Bugzilla->logout;
|
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' ],
|
# $call = $rpc->call( 'User.get', { match => [ 'testusera', 'testuserb' ],
|
||||||
# maxusermatches => 20, excludedisabled => 1 });
|
# maxusermatches => 20, excludedisabled => 1 });
|
||||||
sub get {
|
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
|
# Make them arrays if they aren't
|
||||||
if ($params->{names} && !ref $params->{names}) {
|
if ($params->{names} && !ref $params->{names}) {
|
||||||
|
@ -168,11 +182,11 @@ sub get {
|
||||||
}
|
}
|
||||||
my $in_group = $self->_filter_users_by_group(
|
my $in_group = $self->_filter_users_by_group(
|
||||||
\@user_objects, $params);
|
\@user_objects, $params);
|
||||||
@users = map {filter $params, {
|
@users = map { filter $params, {
|
||||||
id => $self->type('int', $_->id),
|
id => $self->type('int', $_->id),
|
||||||
real_name => $self->type('string', $_->name),
|
real_name => $self->type('string', $_->realname),
|
||||||
name => $self->type('string', $_->login),
|
name => $self->type('email', $_->login),
|
||||||
}} @$in_group;
|
} } @$in_group;
|
||||||
|
|
||||||
return { users => \@users };
|
return { users => \@users };
|
||||||
}
|
}
|
||||||
|
@ -180,7 +194,7 @@ sub get {
|
||||||
my $obj_by_ids;
|
my $obj_by_ids;
|
||||||
$obj_by_ids = Bugzilla::User->new_from_list($params->{ids}) if $params->{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
|
# the otheruser, for non visible otheruser throw an error
|
||||||
foreach my $obj (@$obj_by_ids) {
|
foreach my $obj (@$obj_by_ids) {
|
||||||
if (Bugzilla->user->can_see_user($obj)){
|
if (Bugzilla->user->can_see_user($obj)){
|
||||||
|
@ -198,10 +212,15 @@ sub get {
|
||||||
}
|
}
|
||||||
|
|
||||||
# User Matching
|
# User Matching
|
||||||
my $limit;
|
my $limit = Bugzilla->params->{maxusermatches};
|
||||||
if ($params->{'maxusermatches'}) {
|
if ($params->{limit}) {
|
||||||
$limit = $params->{'maxusermatches'} + 1;
|
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'} || [] }) {
|
foreach my $match_string (@{ $params->{'match'} || [] }) {
|
||||||
my $matched = Bugzilla::User::match_name($match_string, $limit, $params->{excludedisabled} && 1);
|
my $matched = Bugzilla::User::match_name($match_string, $limit, $params->{excludedisabled} && 1);
|
||||||
foreach my $user (@$matched) {
|
foreach my $user (@$matched) {
|
||||||
|
@ -212,34 +231,121 @@ sub get {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $in_group = $self->_filter_users_by_group(
|
my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
|
||||||
\@user_objects, $params);
|
foreach my $user (@$in_group) {
|
||||||
if (Bugzilla->user->in_group('editusers')) {
|
my $user_info = filter $params, {
|
||||||
@users =
|
id => $self->type('int', $user->id),
|
||||||
map {filter $params, {
|
real_name => $self->type('string', $user->realname),
|
||||||
id => $self->type('int', $_->id),
|
name => $self->type('email', $user->login),
|
||||||
real_name => $self->type('string', $_->realname),
|
email => $self->type('email', $user->email),
|
||||||
name => $self->type('string', $_->login),
|
can_login => $self->type('boolean', $user->is_enabled ? 1 : 0),
|
||||||
email => $self->type('string', $_->email),
|
};
|
||||||
can_login => $self->type('boolean', $_->is_enabled),
|
|
||||||
email_enabled => $self->type('boolean', $_->email_enabled),
|
if (Bugzilla->user->in_group('editusers')) {
|
||||||
login_denied_text => $self->type('string', $_->disabledtext),
|
$user_info->{email_enabled} = $self->type('boolean', $user->email_enabled);
|
||||||
}} @$in_group;
|
$user_info->{login_denied_text} = $self->type('string', $user->disabledtext);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
@users =
|
if (Bugzilla->user->id == $user->id) {
|
||||||
map {filter $params, {
|
if (filter_wants($params, 'saved_searches')) {
|
||||||
id => $self->type('int', $_->id),
|
$user_info->{saved_searches} = [
|
||||||
real_name => $self->type('string', $_->realname),
|
map { $self->_query_to_hash($_) } @{ $user->queries }
|
||||||
name => $self->type('string', $_->login),
|
];
|
||||||
email => $self->type('string', $_->email),
|
}
|
||||||
can_login => $self->type('boolean', $_->is_enabled),
|
if (filter_wants($params, 'saved_reports')) {
|
||||||
}} @$in_group;
|
$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 };
|
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 {
|
sub _filter_users_by_group {
|
||||||
my ($self, $users, $params) = @_;
|
my ($self, $users, $params) = @_;
|
||||||
my ($group_ids, $group_names) = @$params{qw(group_ids groups)};
|
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.
|
# If no groups are specified, we return all users.
|
||||||
return $users if (!$group_ids and !$group_names);
|
return $users if (!$group_ids and !$group_names);
|
||||||
|
|
||||||
my @groups = map { Bugzilla::Group->check({ id => $_ }) }
|
my $user = Bugzilla->user;
|
||||||
@{ $group_ids || [] };
|
my (@groups, %groups);
|
||||||
my @name_groups = map { Bugzilla::Group->check($_) }
|
|
||||||
@{ $group_names || [] };
|
|
||||||
push(@groups, @name_groups);
|
|
||||||
|
|
||||||
|
|
||||||
my @in_group = grep { $self->_user_in_any_group($_, \@groups) }
|
if ($group_ids) {
|
||||||
@$users;
|
@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;
|
return \@in_group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +391,58 @@ sub read_new_functionality {
|
||||||
return {status => 'ok'};
|
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;
|
1;
|
||||||
|
|
||||||
__END__
|
__END__
|
||||||
|
@ -295,13 +461,19 @@ log in/out using an existing account.
|
||||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
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
|
=over
|
||||||
|
|
||||||
|
@ -315,26 +487,23 @@ etc. This method logs in an user.
|
||||||
|
|
||||||
=over
|
=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<password> (string) - The user's password.
|
||||||
|
|
||||||
=item C<remember> (bool) B<Optional> - if the cookies returned by the
|
=item C<restrict_login> (bool) B<Optional> - If set to a true value,
|
||||||
call to login should expire with the session or not. In order for
|
the token returned by this method will only be valid from the IP address
|
||||||
this option to have effect the Bugzilla server must be configured to
|
which called this method.
|
||||||
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.
|
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
=item B<Returns>
|
=item B<Returns>
|
||||||
|
|
||||||
On success, a hash containing one item, C<id>, the numeric id of the
|
On success, a hash containing two items, C<id>, the numeric id of the
|
||||||
user that was logged in. A set of http cookies is also sent with the
|
user that was logged in, and a C<token> which can be passed in
|
||||||
response. These cookies must be sent along with any future requests
|
the parameters as authentication in other calls. The token can be sent
|
||||||
to the webservice, for the duration of the session.
|
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>
|
=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.
|
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
|
The ability to login with this account has been disabled. A reason may be
|
||||||
error.
|
specified with the error.
|
||||||
|
|
||||||
=item 305 (New Password Required)
|
=item 305 (New Password Required)
|
||||||
|
|
||||||
The current password is correct, but the user is asked to change
|
The current password is correct, but the user is asked to change
|
||||||
his password.
|
their password.
|
||||||
|
|
||||||
=item 50 (Param Required)
|
=item 50 (Param Required)
|
||||||
|
|
||||||
|
@ -360,11 +529,26 @@ A login or password parameter was not provided.
|
||||||
|
|
||||||
=back
|
=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
|
=back
|
||||||
|
|
||||||
=item C<logout>
|
=back
|
||||||
|
|
||||||
B<STABLE>
|
=head2 logout
|
||||||
|
|
||||||
|
B<DEPRECATED>
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
|
@ -380,13 +564,55 @@ Log out the user. Does nothing if there is no user logged in.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
=back
|
=head2 valid_login
|
||||||
|
|
||||||
=head2 Account Creation
|
B<DEPRECATED>
|
||||||
|
|
||||||
=over
|
=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>
|
B<STABLE>
|
||||||
|
|
||||||
|
@ -427,7 +653,7 @@ email address you specified. Account creation may be entirely disabled.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
=item C<create>
|
=head2 create
|
||||||
|
|
||||||
B<STABLE>
|
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
|
You must be logged in and have the C<editusers> privilege in order to
|
||||||
call this function.
|
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>
|
=item B<Params>
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
@ -486,17 +719,160 @@ password is under three characters.)
|
||||||
|
|
||||||
=item Error 503 (Password Too Long) removed in Bugzilla B<3.6>.
|
=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
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
=head2 User Info
|
=head2 update
|
||||||
|
|
||||||
|
B<EXPERIMENTAL>
|
||||||
|
|
||||||
=over
|
=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>
|
B<STABLE>
|
||||||
|
|
||||||
|
@ -506,6 +882,18 @@ B<STABLE>
|
||||||
|
|
||||||
Gets information about user accounts in Bugzilla.
|
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>
|
=item B<Params>
|
||||||
|
|
||||||
B<Note>: At least one of C<ids>, C<names>, or C<match> must be specified.
|
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
|
contains any one of the specified strings. Users that you cannot see will
|
||||||
not be included in the returned list.
|
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
|
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
|
each string, which defaults to 1000 but can be changed by the Bugzilla
|
||||||
administrator.
|
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
|
addresses from Bugzilla, and also to enforce the user visibility
|
||||||
restrictions that are implemented on some Bugzillas.)
|
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<group_ids> (array)
|
||||||
|
|
||||||
=item C<groups> (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
|
If these are specified, they limit the return value to users who are
|
||||||
in I<any> of the groups specified.
|
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
|
=back
|
||||||
|
|
||||||
=item B<Returns>
|
=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
|
into bugzilla, if empty then the user account is enabled. Otherwise it is
|
||||||
disabled/closed.
|
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
|
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
|
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>,
|
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
|
=back
|
||||||
|
|
||||||
|
@ -612,10 +1081,14 @@ C<real_name>, C<email>, and C<can_login> items.
|
||||||
|
|
||||||
=over
|
=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
|
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)
|
=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
|
Logged-out users cannot use the "ids" or "match" arguments to this
|
||||||
function.
|
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
|
=back
|
||||||
|
|
||||||
=item B<History>
|
=item B<History>
|
||||||
|
@ -637,7 +1115,16 @@ function.
|
||||||
|
|
||||||
=item C<group_ids> and C<groups> were added in Bugzilla B<4.0>.
|
=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
|
=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
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
# License Version 1.1 (the "License"); you may not use this file
|
# defined by the Mozilla Public License, v. 2.0.
|
||||||
# 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>
|
|
||||||
|
|
||||||
package Bugzilla::WebService::Util;
|
package Bugzilla::WebService::Util;
|
||||||
|
|
||||||
|
use 5.10.1;
|
||||||
use strict;
|
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
|
# We have to "require", not "use" this, because otherwise it tries to
|
||||||
# use features of Test::More during import().
|
# use features of Test::More during import().
|
||||||
require Test::Taint;
|
require Test::Taint;
|
||||||
|
|
||||||
our @EXPORT_OK = qw(
|
our @EXPORT_OK = qw(
|
||||||
|
extract_flags
|
||||||
filter
|
filter
|
||||||
filter_wants
|
filter_wants
|
||||||
taint_data
|
taint_data
|
||||||
validate
|
validate
|
||||||
|
translate
|
||||||
|
params_to_objects
|
||||||
|
fix_credentials
|
||||||
);
|
);
|
||||||
|
|
||||||
sub filter ($$) {
|
sub extract_flags {
|
||||||
my ($params, $hash) = @_;
|
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;
|
my %newhash = %$hash;
|
||||||
|
|
||||||
foreach my $key (keys %$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;
|
return \%newhash;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub filter_wants ($$) {
|
sub filter_wants($$;$$) {
|
||||||
my ($params, $field) = @_;
|
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 %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
|
||||||
my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
|
my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
|
||||||
|
|
||||||
if (defined $params->{include_fields}) {
|
my %include_types;
|
||||||
return 0 if !$include{$field};
|
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}) {
|
foreach my $key (keys %exclude) {
|
||||||
return 0 if $exclude{$field};
|
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 {
|
sub taint_data {
|
||||||
|
@ -77,8 +202,9 @@ sub _delete_bad_keys {
|
||||||
# Making something a hash key always untaints it, in Perl.
|
# Making something a hash key always untaints it, in Perl.
|
||||||
# However, we need to validate our argument names in some way.
|
# However, we need to validate our argument names in some way.
|
||||||
# We know that all hash keys passed in to the WebService will
|
# We know that all hash keys passed in to the WebService will
|
||||||
# match \w+, so we delete any key that doesn't match that.
|
# match \w+, contain '.' or '-', so we delete any key that
|
||||||
if ($key !~ /^\w+$/) {
|
# doesn't match that.
|
||||||
|
if ($key !~ /^[\w\.\-]+$/) {
|
||||||
delete $item->{$key};
|
delete $item->{$key};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,6 +234,56 @@ sub validate {
|
||||||
return ($self, $params);
|
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__
|
__END__
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
@ -129,25 +305,64 @@ internally in the WebService code.
|
||||||
|
|
||||||
=head1 METHODS
|
=head1 METHODS
|
||||||
|
|
||||||
=over
|
=head2 filter
|
||||||
|
|
||||||
=item C<filter>
|
|
||||||
|
|
||||||
This helps implement the C<include_fields> and C<exclude_fields> arguments
|
This helps implement the C<include_fields> and C<exclude_fields> arguments
|
||||||
of WebService methods. Given a hash (the second argument to this subroutine),
|
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
|
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>.
|
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
|
Returns C<1> if a filter would preserve the specified field when passing
|
||||||
a hash to L</filter>, C<0> otherwise.
|
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
|
methods. Currently it converts listed parameters into an array reference
|
||||||
if the client only passed a single scalar value. It modifies the parameters
|
if the client only passed a single scalar value. It modifies the parameters
|
||||||
hash in place so other parameters should be unaltered.
|
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
|
=back
|
||||||
|
|
|
@ -144,7 +144,7 @@ function userAutocomplete(hint, emptyOptions, loadAllOnEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
var u = window.location.href.replace(/[^\/]+$/, '');
|
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)
|
if (hint.input.value)
|
||||||
{
|
{
|
||||||
u += '20';
|
u += '20';
|
||||||
|
|
Loading…
Reference in New Issue