Add Group.{add,remove}_{members,managers} web services

Also move user_group_map manipulation code into Bugzilla::Group
classes
Vitaliy Filippov 2015-11-16 16:20:37 +03:00
parent bd69dca4a1
commit 6e9ee91dcf
8 changed files with 247 additions and 90 deletions

View File

@ -373,6 +373,123 @@ sub remove_from_db
$dbh->bz_commit_transaction();
}
sub add_users
{
my $self = shift;
my ($users, $isbless) = @_;
return if !@$users;
add_user_groups([ map { { group => $self, user => $_ } } @$users ], $isbless);
}
sub remove_users
{
my $self = shift;
my ($users, $isbless) = @_;
return if !@$users;
remove_user_groups([ map { { group => $self, user => $_ } } @$users ], $isbless);
}
# Add members or blessers to this group
# Bugzilla::Group::add_user_groups([ { user => Bugzilla::User or int id, group => Bugzilla::Group }, ... ], $isbless = 0 or 1)
sub add_user_groups
{
shift if $_[0] eq __PACKAGE__;
my ($rows, $isbless) = @_;
return if !@$rows;
# Filter duplicates
my $g = {};
for my $row (@$rows)
{
$g->{int(ref $row->{user} ? $row->{user}->id : $row->{user})}->{int($row->{group}->id)} = $row;
}
$isbless = $isbless ? 1 : 0;
my $dbh = Bugzilla->dbh;
# Filter already existing members
for my $row (@{ $dbh->selectall_arrayref(
"SELECT user_id, group_id FROM user_group_map WHERE (user_id, group_id, grant_type, isbless) IN (".
join(', ', map {
my $uid = $_;
map { "($uid, $_, ".GRANT_DIRECT.", $isbless)" } keys %{$g->{$uid}};
} keys %$g).") FOR UPDATE", undef
) || [] })
{
delete $g->{$row->[0]}->{$row->[1]};
delete $g->{$row->[0]} if !%{$g->{$row->[0]}};
}
return if !%$g;
# Apply update
$dbh->do(
"INSERT INTO user_group_map (user_id, group_id, grant_type, isbless) VALUES ".
join(', ', map {
my $uid = $_;
map { "($uid, $_, ".GRANT_DIRECT.", $isbless)" } keys %{$g->{$uid}};
} keys %$g)
);
# Record profiles_activity entries
my $cur_userid = Bugzilla->user->id;
my $group_fldid = Bugzilla->get_field('bug_group')->id;
if (!$isbless)
{
# FIXME: should create profiles_activity entries for blesser changes.
$dbh->do(
"INSERT INTO profiles_activity (userid, who, profiles_when, fieldid, oldvalue, newvalue) VALUES ".
join(', ', map {
my $uid = $_;
join(', ', map { "($uid, $cur_userid, NOW(), $group_fldid, '', ".$dbh->quote($_->{group}->name).")" } values %{$g->{$uid}})
} keys %$g)
);
}
}
# Remove members or blessers from this group - arguments same as in add_user_groups()
sub remove_user_groups
{
shift if $_[0] eq __PACKAGE__;
my ($rows, $isbless) = @_;
return if !@$rows;
# Filter duplicates
$isbless = $isbless ? 1 : 0;
my $dbh = Bugzilla->dbh;
# Remember group objects
my $g = { map { $_->{group}->id => $_->{group} } @$rows };
# Filter already deleted members
my $del = {};
for my $row (@{ $dbh->selectall_arrayref(
"SELECT user_id, group_id FROM user_group_map WHERE (user_id, group_id, grant_type, isbless) IN (".
join(', ', map {
my $uid = int(ref $_->{user} ? $_->{user}->id : $_->{user});
my $gid = int($_->{group}->id);
"($uid, $gid, ".GRANT_DIRECT.", $isbless)";
} @$rows).") FOR UPDATE", undef
) || [] })
{
push @{$del->{$row->[0]}}, $row->[1];
}
return if !%$del;
# Apply update
$dbh->do(
"DELETE FROM user_group_map WHERE (user_id, group_id, grant_type, isbless) IN (".
join(', ', map {
my $uid = $_;
map { "($uid, $_, ".GRANT_DIRECT.", $isbless)" } @{$del->{$uid}};
} keys %$del).")"
);
# Record profiles_activity entries
my $cur_userid = Bugzilla->user->id;
my $group_fldid = Bugzilla->get_field('bug_group')->id;
if (!$isbless)
{
# FIXME: should create profiles_activity entries for blesser changes.
$dbh->do(
"INSERT INTO profiles_activity (userid, who, profiles_when, fieldid, oldvalue, newvalue) VALUES ".
join(', ', map {
my $uid = $_;
map { "($uid, $cur_userid, NOW(), $group_fldid, ".$dbh->quote($g->{$_}->name).", '')" } @{$del->{$uid}};
} keys %$del)
);
}
}
# Add missing entries in bug_group_map for bugs created while
# a mandatory group was disabled and which is now enabled again.
sub _enforce_mandatory

View File

@ -17,7 +17,7 @@ use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field;
use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Util qw(extract_flags filter filter_wants validate translate);
use Bugzilla::WebService::Util qw(extract_flags filter filter_wants validate translate user_to_hash);
use Bugzilla::Bug;
use Bugzilla::BugMail;
use Bugzilla::Util qw(trick_taint trim diff_arrays detaint_natural);
@ -1137,7 +1137,7 @@ sub _bug_to_hash {
}
if (filter_wants $params, 'assigned_to') {
$item{'assigned_to'} = $self->type('email', $bug->assigned_to->login);
$item{'assigned_to_detail'} = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to');
$item{'assigned_to_detail'} = user_to_hash($self, $bug->assigned_to, $params);
}
if (filter_wants $params, 'blocks') {
my @blocks = map { $self->type('int', $_) } @{ $bug->blocked };
@ -1152,14 +1152,14 @@ sub _bug_to_hash {
if (filter_wants $params, 'cc') {
my @cc = map { $self->type('email', $_) } @{ $bug->cc };
$item{'cc'} = \@cc;
$item{'cc_detail'} = [ map { $self->_user_to_hash($_, $params, undef, 'cc') } @{ $bug->cc_users } ];
$item{'cc_detail'} = [ map { user_to_hash($self, $_, $params) } @{ $bug->cc_users } ];
}
if (filter_wants $params, 'creation_time') {
$item{'creation_time'} = $self->type('dateTime', $bug->creation_ts);
}
if (filter_wants $params, 'creator') {
$item{'creator'} = $self->type('email', $bug->reporter->login);
$item{'creator_detail'} = $self->_user_to_hash($bug->reporter, $params, undef, 'creator');
$item{'creator_detail'} = user_to_hash($self, $bug->reporter, $params);
}
if (filter_wants $params, 'depends_on') {
my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson };
@ -1192,7 +1192,7 @@ sub _bug_to_hash {
my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
$item{'qa_contact'} = $self->type('email', $qa_login);
if ($bug->qa_contact) {
$item{'qa_contact_detail'} = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact');
$item{'qa_contact_detail'} = user_to_hash($self, $bug->qa_contact, $params);
}
}
if (filter_wants $params, 'see_also') {
@ -1253,17 +1253,6 @@ sub _bug_to_hash {
return \%item;
}
sub _user_to_hash {
my ($self, $user, $filters, $types, $prefix) = @_;
my $item = filter $filters, {
id => $self->type('int', $user->id),
real_name => $self->type('string', $user->realname),
name => $self->type('email', $user->login),
email => $self->type('email', $user->email),
}, $types, $prefix;
return $item;
}
sub _attachment_to_hash {
my ($self, $attach, $filters, $types, $prefix) = @_;

View File

@ -13,12 +13,16 @@ use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Util qw(validate translate params_to_objects);
use Bugzilla::WebService::Util qw(validate translate params_to_objects user_to_hash);
use constant PUBLIC_METHODS => qw(
create
get
update
add_members
remove_members
add_managers
remove_managers
);
use constant MAPPED_RETURNS => {
@ -146,6 +150,92 @@ sub get {
return { groups => \@groups };
}
sub add_members
{
my $self = shift;
$self->_update_users(0, 1, @_);
}
sub remove_members
{
my $self = shift;
$self->_update_users(0, 0, @_);
}
sub add_managers
{
my $self = shift;
$self->_update_users(1, 1, @_);
}
sub remove_managers
{
my $self = shift;
$self->_update_users(1, 0, @_);
}
sub _update_users
{
my ($self, $isbless, $add, $params) = @_;
my $dbh = Bugzilla->dbh;
defined($params->{names}) || defined($params->{ids})
|| ThrowCodeError('params_required',
{ function => 'Group.update', params => ['ids', 'names'] });
my @group_objects;
if ($params->{usernames})
{
push @group_objects, @{ Bugzilla::Group->match({ name => $params->{names} }) };
}
if ($params->{userids})
{
push @group_objects, @{ Bugzilla::Group->match({ id => $params->{ids} }) };
}
Bugzilla->login(LOGIN_REQUIRED);
if ($isbless)
{
Bugzilla->user->in_group('editusers')
|| ThrowUserError("auth_failure", {
group => "editusers",
action => "edit",
object => "group"
});
}
else
{
Bugzilla->user->in_group('creategroups')
|| !grep { !Bugzilla->user->can_bless($_->id) } @group_objects
|| ThrowUserError("auth_failure", {
group => "creategroups",
action => "edit",
object => "group"
});
}
my @user_objects;
if ($params->{usernames})
{
push @user_objects, @{ Bugzilla::User->match({ login_name => $params->{usernames} }) };
}
if ($params->{userids})
{
push @user_objects, @{ Bugzilla::User->match({ id => $params->{userids} }) };
}
@user_objects = grep { Bugzilla->user->can_see_user($_) } @user_objects;
@user_objects || ThrowCodeError('params_required', { function => 'Group.update', params => ['userids', 'usernames'] });
$add = $add ? 'add_user_groups' : 'remove_user_groups';
Bugzilla::Group->$add(
[ map { my $grp = $_; map { { group => $grp, user => $_ } } @user_objects } @group_objects ], $isbless
);
return {
groups => [ map { $self->_group_to_hash({}, $_) } @group_objects ],
users => [ map { user_to_hash($self, $_, {}) } @user_objects ],
};
}
sub _group_to_hash {
my ($self, $params, $group) = @_;
my $user = Bugzilla->user;

View File

@ -17,7 +17,7 @@ use Bugzilla::Error;
use Bugzilla::Group;
use Bugzilla::User;
use Bugzilla::Util qw(trim detaint_natural);
use Bugzilla::WebService::Util qw(filter filter_wants validate translate params_to_objects);
use Bugzilla::WebService::Util qw(filter filter_wants validate translate params_to_objects user_to_hash);
use List::Util qw(first min);
@ -240,13 +240,7 @@ sub get {
my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
foreach my $user (@$in_group) {
my $user_info = filter $params, {
id => $self->type('int', $user->id),
real_name => $self->type('string', $user->realname),
name => $self->type('email', $user->login),
email => $self->type('email', $user->email),
can_login => $self->type('boolean', $user->is_enabled ? 1 : 0),
};
my $user_info = user_to_hash($self, $user);
if (Bugzilla->user->in_group('editusers')) {
$user_info->{email_enabled} = $self->type('boolean', $user->email_enabled);

View File

@ -31,6 +31,7 @@ our @EXPORT_OK = qw(
validate
translate
params_to_objects
user_to_hash
fix_credentials
);
@ -260,6 +261,17 @@ sub params_to_objects {
return \@objects;
}
sub user_to_hash {
my ($ws, $user, $params) = @_;
return filter $params, {
id => $ws->type('int', $user->id),
real_name => $ws->type('string', $user->realname),
name => $ws->type('email', $user->login),
email => $ws->type('email', $user->email),
can_login => $ws->type('boolean', $user->is_enabled ? 1 : 0),
};
}
sub fix_credentials {
my ($params) = @_;
# Allow user to pass in login=foo&password=bar as a convenience

View File

@ -302,6 +302,7 @@ if ($action eq 'confirm_remove')
if ($action eq 'remove_regexp')
{
check_token_data($token, 'remove_group_members');
# remove all explicit users from the group with
# gid = $ARGS->{group} that match the regular expression
# stored in the DB for that group or all of them period
@ -310,25 +311,14 @@ if ($action eq 'remove_regexp')
my $regexp = CheckGroupRegexp($ARGS->{regexp});
$dbh->bz_start_transaction();
my $users = $group->members_direct();
my $sth_delete = $dbh->prepare("DELETE FROM user_group_map WHERE user_id = ? AND isbless = 0 AND group_id = ?");
my @deleted;
foreach my $member (@$users)
{
if ($regexp eq '' || $member->login =~ m/$regexp/i)
{
$sth_delete->execute($member->id, $group->id);
push @deleted, $member;
}
}
my $del = [ grep { $_->login =~ m/$regexp/is } @{ $group->members_direct } ];
$group->remove_users($del, 0);
$dbh->bz_commit_transaction();
$vars->{users} = \@deleted;
$vars->{users} = $del;
$vars->{regexp} = $regexp;
Bugzilla::Hook::process('editgroups-post_remove_regexp', { deleted => \@deleted });
Bugzilla::Hook::process('editgroups-post_remove_regexp', { deleted => $del });
Bugzilla::Views::refresh_some_views();
delete_token($token);

View File

@ -279,22 +279,12 @@ elsif ($action eq 'update')
$changes = $otherUser->update();
}
# Update group settings.
my $sth_add_mapping = $dbh->prepare(
"INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) VALUES (?, ?, ?, ?)"
);
my $sth_remove_mapping = $dbh->prepare(
"DELETE FROM user_group_map WHERE user_id=? AND group_id=? AND isbless=? AND grant_type=?"
);
my @groupsAddedTo;
my @groupsRemovedFrom;
my @groupsGrantedRightsToBless;
my @groupsDeniedRightsToBless;
# Regard only groups the user is allowed to bless and skip all others silently.
# FIXME: checking for existence of each user_group_map entry would allow to
# display a friendlier error message on page reloads.
userDataToVars($otherUserID);
my $permissions = $vars->{permissions};
foreach my $blessable (@{$user->bless_groups()})
@ -308,13 +298,11 @@ elsif ($action eq 'update')
{
if (!$groupid)
{
$sth_remove_mapping->execute($otherUserID, $id, 0, GRANT_DIRECT);
push @groupsRemovedFrom, $name;
push @groupsRemovedFrom, $blessable;
}
else
{
$sth_add_mapping->execute($otherUserID, $id, 0, GRANT_DIRECT);
push @groupsAddedTo, $name;
push @groupsAddedTo, $blessable;
}
}
@ -327,27 +315,20 @@ elsif ($action eq 'update')
{
if (!$groupid)
{
$sth_remove_mapping->execute($otherUserID, $id, 1, GRANT_DIRECT);
push @groupsDeniedRightsToBless, $name;
push @groupsDeniedRightsToBless, $blessable;
}
else
{
$sth_add_mapping->execute($otherUserID, $id, 1, GRANT_DIRECT);
push @groupsGrantedRightsToBless, $name;
push @groupsGrantedRightsToBless, $blessable;
}
}
}
}
if (@groupsAddedTo || @groupsRemovedFrom)
{
$dbh->do(
"INSERT INTO profiles_activity (userid, who, profiles_when, fieldid, oldvalue, newvalue)".
" VALUES (?, ?, NOW(), ?, ?, ?)", undef,
$otherUserID, $userid, Bugzilla->get_field('bug_group')->id,
join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)
);
}
# FIXME: should create profiles_activity entries for blesser changes.
Bugzilla::Group::add_user_groups([ map { { group => $_, user => $otherUser } } @groupsAddedTo ]);
Bugzilla::Group::remove_user_groups([ map { { group => $_, user => $otherUser } } @groupsRemovedFrom ]);
Bugzilla::Group::add_user_groups([ map { { group => $_, user => $otherUser } } @groupsGrantedRightsToBless ], 1);
Bugzilla::Group::remove_user_groups([ map { { group => $_, user => $otherUser } } @groupsDeniedRightsToBless ], 1);
$dbh->bz_commit_transaction();

View File

@ -54,30 +54,14 @@ if (@add_members || @add_bless || @rm_members || @rm_bless)
} };
for (\@add_members, \@add_bless)
{
@$_ = map { $users->{lc $_} ? $users->{lc $_}->id : ThrowUserError('invalid_username', { name => $_ }) } @$_;
}
if (@add_members || @add_bless)
{
# FIXME Use object method instead of direct DB query
Bugzilla->dbh->do(
"INSERT IGNORE INTO user_group_map (user_id, group_id, grant_type, isbless) VALUES ".
join(', ', ("(?, ?, ?, ?)") x (@add_members + @add_bless)), undef,
(map { $_, $vars->{group}->id, GRANT_DIRECT, 0 } @add_members),
(map { $_, $vars->{group}->id, GRANT_DIRECT, 1 } @add_bless)
);
@$_ = map { $users->{lc $_} || ThrowUserError('invalid_username', { name => $_ }) } @$_;
}
$vars->{group}->add_users(\@add_members, 0);
$vars->{group}->add_users(\@add_bless, 1);
}
if (@rm_members || @rm_bless)
{
# FIXME Use object method instead of direct DB query
trick_taint($_) for @rm_members, @rm_bless;
Bugzilla->dbh->do(
"DELETE FROM user_group_map WHERE group_id=? AND grant_type=? AND (user_id, isbless) IN (".
join(', ', ("(?, ?)") x (@rm_members + @rm_bless)).")", undef,
$vars->{group}->id, GRANT_DIRECT,
(map { int($_), 0 } @rm_members), (map { int($_), 1 } @rm_bless)
);
}
trick_taint($_) for @rm_members, @rm_bless;
$vars->{group}->remove_users(\@rm_members, 0);
$vars->{group}->remove_users(\@rm_bless, 1);
if (@add_members || @rm_members)
{
Bugzilla::Hook::process('editusersingroup-post_add', {