summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/User.pm176
-rw-r--r--Bugzilla/WebService/User.pm218
2 files changed, 392 insertions, 2 deletions
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index 259a7ea90..20cc061dd 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -177,12 +177,80 @@ sub super_user {
return $user;
}
+sub _update_groups {
+ my $self = shift;
+ my $group_changes = shift;
+ my $changes = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # Update group settings.
+ my $sth_add_mapping = $dbh->prepare(
+ qq{INSERT INTO user_group_map (
+ user_id, group_id, isbless, grant_type
+ ) VALUES (
+ ?, ?, ?, ?
+ )
+ });
+ my $sth_remove_mapping = $dbh->prepare(
+ qq{DELETE FROM user_group_map
+ WHERE user_id = ?
+ AND group_id = ?
+ AND isbless = ?
+ AND grant_type = ?
+ });
+
+ foreach my $is_bless (keys %$group_changes) {
+ my ($removed, $added) = @{$group_changes->{$is_bless}};
+
+ foreach my $group (@$removed) {
+ $sth_remove_mapping->execute(
+ $self->id, $group->id, $is_bless, GRANT_DIRECT
+ );
+ }
+ foreach my $group (@$added) {
+ $sth_add_mapping->execute(
+ $self->id, $group->id, $is_bless, GRANT_DIRECT
+ );
+ }
+
+ if (! $is_bless) {
+ my $query = qq{
+ INSERT INTO profiles_activity
+ (userid, who, profiles_when, fieldid, oldvalue, newvalue)
+ VALUES ( ?, ?, now(), ?, ?, ?)
+ };
+
+ $dbh->do(
+ $query, undef,
+ $self->id, Bugzilla->user->id,
+ get_field_id('bug_group'),
+ join(', ', map { $_->name } @$removed),
+ join(', ', map { $_->name } @$added)
+ );
+ }
+ else {
+ # XXX: should create profiles_activity entries for blesser changes.
+ }
+
+ Bugzilla->memcached->clear_config({ key => 'user_groups.' . $self->id });
+
+ my $type = $is_bless ? 'bless_groups' : 'groups';
+ $changes->{$type} = [
+ [ map { $_->name } @$removed ],
+ [ map { $_->name } @$added ],
+ ];
+ }
+}
+
sub update {
my $self = shift;
my $options = shift;
+ my $group_changes = delete $self->{_group_changes};
+
my $changes = $self->SUPER::update(@_);
my $dbh = Bugzilla->dbh;
+ $self->_update_groups($group_changes, $changes);
if (exists $changes->{login_name}) {
# Delete all the tokens related to the userid
@@ -298,6 +366,114 @@ sub set_disabledtext {
$_[0]->set('is_enabled', $_[1] ? 0 : 1);
}
+sub set_groups {
+ my $self = shift;
+ $self->_set_groups(GROUP_MEMBERSHIP, @_);
+}
+
+sub set_bless_groups {
+ my $self = shift;
+
+ # The person making the change needs to be in the editusers group
+ Bugzilla->user->in_group('editusers')
+ || ThrowUserError("auth_failure", {group => "editusers",
+ reason => "cant_bless",
+ action => "edit",
+ object => "users"});
+
+ $self->_set_groups(GROUP_BLESS, @_);
+}
+
+sub _set_groups {
+ my $self = shift;
+ my $is_bless = shift;
+ my $changes = shift;
+ my $dbh = Bugzilla->dbh;
+
+ use Data::Dumper;
+
+ # The person making the change is $user, $self is the person being changed
+ my $user = Bugzilla->user;
+
+ # Input is a hash of arrays. Key is 'set', 'add' or 'remove'. The array
+ # is a list of group ids and/or names.
+
+ # First turn the arrays into group objects.
+ $changes = $self->_set_groups_to_object($changes);
+
+ # Get a list of the groups the user currently is a member of
+ my $ids = $dbh->selectcol_arrayref(
+ q{SELECT DISTINCT group_id
+ FROM user_group_map
+ WHERE user_id = ? AND isbless = ? AND grant_type = ?},
+ undef, $self->id, $is_bless, GRANT_DIRECT);
+
+ my $current_groups = Bugzilla::Group->new_from_list($ids);
+ my $new_groups = dclone($current_groups);
+
+ # Record the changes
+ if (exists $changes->{set}) {
+ $new_groups = $changes->{set};
+
+ # We need to check the user has bless rights on the existing groups
+ # If they don't, then we need to add them back to new_groups
+ foreach my $group (@$current_groups) {
+ if (! $user->can_bless($group->id)) {
+ push @$new_groups, $group
+ unless grep { $_->id eq $group->id } @$new_groups;
+ }
+ }
+ }
+ else {
+ foreach my $group (@{$changes->{remove} // []}) {
+ @$new_groups = grep { $_->id ne $group->id } @$new_groups;
+ }
+ foreach my $group (@{$changes->{add} // []}) {
+ push @$new_groups, $group
+ unless grep { $_->id eq $group->id } @$new_groups;
+ }
+ }
+
+ # Stash the changes, so self->update can actually make them
+ my @diffs = diff_arrays($current_groups, $new_groups, 'id');
+ if (scalar(@{$diffs[0]}) || scalar(@{$diffs[1]})) {
+ $self->{_group_changes}{$is_bless} = \@diffs;
+ }
+}
+
+sub _set_groups_to_object {
+ my $self = shift;
+ my $changes = shift;
+ my $user = Bugzilla->user;
+
+ foreach my $key (keys %$changes) {
+ # Check we were given an array
+ unless (ref($changes->{$key}) eq 'ARRAY') {
+ ThrowCodeError(
+ 'param_invalid',
+ { param => $changes->{$key}, function => $key }
+ );
+ }
+
+ # Go through the array, and turn items into group objects
+ my @groups = ();
+ foreach my $value (@{$changes->{$key}}) {
+ my $type = $value =~ /^\d+$/ ? 'id' : 'name';
+ my $group = Bugzilla::Group->new({$type => $value});
+
+ if (! $group || ! $user->can_bless($group->id)) {
+ ThrowUserError('auth_failure',
+ { group => $value, reason => 'cant_bless',
+ action => 'edit', object => 'users' });
+ }
+ push @groups, $group;
+ }
+ $changes->{$key} = \@groups;
+ }
+
+ return $changes;
+}
+
sub update_last_seen_date {
my $self = shift;
return unless $self->id;
diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm
index 988ae3cd5..1e6de143c 100644
--- a/Bugzilla/WebService/User.pm
+++ b/Bugzilla/WebService/User.pm
@@ -28,7 +28,8 @@ use Bugzilla::Error;
use Bugzilla::Group;
use Bugzilla::User;
use Bugzilla::Util qw(trim);
-use Bugzilla::WebService::Util qw(filter filter_wants validate);
+use Bugzilla::WebService::Util qw(filter filter_wants validate
+ translate params_to_objects);
use Bugzilla::Hook;
use List::Util qw(first);
@@ -43,6 +44,20 @@ use constant READ_ONLY => qw(
get
);
+use constant MAPPED_FIELDS => {
+ email => 'login',
+ full_name => 'name',
+ login_denied_text => 'disabledtext',
+ email_enabled => 'disable_mail'
+};
+
+use constant MAPPED_RETURNS => {
+ login_name => 'email',
+ realname => 'full_name',
+ disabledtext => 'login_denied_text',
+ disable_mail => 'email_enabled'
+};
+
##############
# User Login #
##############
@@ -267,6 +282,76 @@ sub get {
return { users => \@users };
}
+###############
+# User Update #
+###############
+
+sub update {
+ my ($self, $params) = @_;
+
+ my $dbh = Bugzilla->dbh;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ # Reject access if there is no sense in continuing.
+ $user->in_group('editusers')
+ || ThrowUserError("auth_failure", {group => "editusers",
+ action => "edit",
+ object => "users"});
+
+ defined($params->{names}) || defined($params->{ids})
+ || ThrowCodeError('params_required',
+ { function => 'User.update', params => ['ids', 'names'] });
+
+ my $user_objects = params_to_objects($params, 'Bugzilla::User');
+
+ my $values = translate($params, MAPPED_FIELDS);
+
+ # We delete names and ids to keep only new values to set.
+ delete $values->{names};
+ delete $values->{ids};
+
+ $dbh->bz_start_transaction();
+ foreach my $user (@$user_objects){
+ $user->set_all($values);
+ }
+
+ my %changes;
+ foreach my $user (@$user_objects){
+ my $returned_changes = $user->update();
+ $changes{$user->id} = translate($returned_changes, MAPPED_RETURNS);
+ }
+ $dbh->bz_commit_transaction();
+
+ my @result;
+ foreach my $user (@$user_objects) {
+ my %hash = (
+ id => $user->id,
+ changes => {},
+ );
+
+ foreach my $field (keys %{ $changes{$user->id} }) {
+ my $change = $changes{$user->id}->{$field};
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things that can become empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
+ # We also flatten arrays (used by groups and blessed_groups)
+ $change->[0] = join(',', @{$change->[0]}) if ref $change->[0];
+ $change->[1] = join(',', @{$change->[1]}) if ref $change->[1];
+
+ $hash{changes}{$field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
+ }
+
+ push(@result, \%hash);
+ }
+
+ return { users => \@result };
+}
+
sub _filter_users_by_group {
my ($self, $users, $params) = @_;
my ($group_ids, $group_names) = @$params{qw(group_ids groups)};
@@ -487,7 +572,7 @@ for the provided username.
=back
-=head1 Account Creation
+=head1 Account Creation and Modification
=head2 offer_account_by_email
@@ -602,6 +687,135 @@ password is under three characters.)
=back
+=head2 update
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Updates user accounts in Bugzilla.
+
+=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<set_bless_groups>
+
+C<hash> - This is the same as set_groups, but affects what groups a user
+has direct membership to bless that group. It takes the same inputs as
+set_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
+
+=back
+
=head1 User Info
=head2 get