Add extension point to allow plugins to validate group creation

By implementing this extension point plugins can validate the creation
of new groups based on input arguments. E.g. a plugin could enforce
a certain name scheme for the group names.

To match the extension point for validating project creations a
CreateGroupArgs class was added that contains the arguments for the
group creation.

Change-Id: Id5a5909028d57ce588ed8b17d9435958d37668c1
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin 2014-01-22 20:16:20 +01:00
parent de3bfb2fb5
commit 42b7b4e61d
7 changed files with 171 additions and 57 deletions

View File

@ -43,6 +43,17 @@ input arguments.
E.g. a plugin could use this to enforce a certain name scheme for
project names.
[[new-group-validation]]
== New group validation
Plugins implementing the `GroupCreationValidationListener` interface
can perform additional validation on group creation based on the
input arguments.
E.g. a plugin could use this to enforce a certain name scheme for
group names.
GERRIT
------

View File

@ -0,0 +1,45 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import java.util.Collection;
public class CreateGroupArgs {
private AccountGroup.NameKey groupName;
public String groupDescription;
public boolean visibleToAll;
public AccountGroup.Id ownerGroupId;
public Collection<? extends Account.Id> initialMembers;
public Collection<? extends AccountGroup.UUID> initialGroups;
public AccountGroup.NameKey getGroup() {
return groupName;
}
public String getGroupName() {
return groupName != null ? groupName.get() : null;
}
public void setGroupName(String n) {
groupName = n != null ? new AccountGroup.NameKey(n) : null;
}
public void setGroupName(AccountGroup.NameKey n) {
groupName = n;
}
}

View File

@ -30,6 +30,7 @@ import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.PersonIdent;
@ -41,7 +42,7 @@ import java.util.List;
public class PerformCreateGroup {
public interface Factory {
PerformCreateGroup create();
PerformCreateGroup create(CreateGroupArgs createGroupArgs);
}
private final ReviewDb db;
@ -50,89 +51,75 @@ public class PerformCreateGroup {
private final IdentifiedUser currentUser;
private final PersonIdent serverIdent;
private final GroupCache groupCache;
private final CreateGroupArgs createGroupArgs;
@Inject
PerformCreateGroup(final ReviewDb db, final AccountCache accountCache,
final GroupIncludeCache groupIncludeCache,
final IdentifiedUser currentUser,
@GerritPersonIdent final PersonIdent serverIdent,
final GroupCache groupCache) {
PerformCreateGroup(ReviewDb db, AccountCache accountCache,
GroupIncludeCache groupIncludeCache, IdentifiedUser currentUser,
@GerritPersonIdent PersonIdent serverIdent, GroupCache groupCache,
@Assisted CreateGroupArgs createGroupArgs) {
this.db = db;
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
this.currentUser = currentUser;
this.serverIdent = serverIdent;
this.groupCache = groupCache;
this.createGroupArgs = createGroupArgs;
}
/**
* Creates a new group.
*
* @param groupName the name for the new group
* @param groupDescription the description of the new group, {@code null}
* if no description
* @param visibleToAll {@code true} to make the group visible to all
* registered users, if {@code false} the group is only visible to
* the group owners and Gerrit administrators
* @param ownerGroupId the group that should own the new group, if
* {@code null} the new group will own itself
* @param initialMembers initial members to be added to the new group
* @param initialGroups initial groups to include in the new group
* @return the id of the new group
* @return the new group
* @throws OrmException is thrown in case of any data store read or write
* error
* @throws NameAlreadyUsedException is thrown in case a group with the given
* name already exists
* @throws PermissionDeniedException user cannot create a group.
*/
public AccountGroup createGroup(final String groupName,
final String groupDescription, final boolean visibleToAll,
final AccountGroup.Id ownerGroupId,
final Collection<? extends Account.Id> initialMembers,
final Collection<? extends AccountGroup.UUID> initialGroups)
throws OrmException, NameAlreadyUsedException, PermissionDeniedException {
public AccountGroup createGroup() throws OrmException,
NameAlreadyUsedException, PermissionDeniedException {
if (!currentUser.getCapabilities().canCreateGroup()) {
throw new PermissionDeniedException(String.format(
"%s does not have \"Create Group\" capability.",
currentUser.getUserName()));
}
final AccountGroup.Id groupId =
new AccountGroup.Id(db.nextAccountGroupId());
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
final AccountGroup.UUID uuid = GroupUUID.make(groupName,
AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
AccountGroup.UUID uuid = GroupUUID.make(
createGroupArgs.getGroupName(),
currentUser.newCommitterIdent(
serverIdent.getWhen(),
serverIdent.getTimeZone()));
final AccountGroup group = new AccountGroup(nameKey, groupId, uuid);
group.setVisibleToAll(visibleToAll);
if (ownerGroupId != null) {
AccountGroup ownerGroup = groupCache.get(ownerGroupId);
AccountGroup group =
new AccountGroup(createGroupArgs.getGroup(), groupId, uuid);
group.setVisibleToAll(createGroupArgs.visibleToAll);
if (createGroupArgs.ownerGroupId != null) {
AccountGroup ownerGroup = groupCache.get(createGroupArgs.ownerGroupId);
if (ownerGroup != null) {
group.setOwnerGroupUUID(ownerGroup.getGroupUUID());
}
}
if (groupDescription != null) {
group.setDescription(groupDescription);
if (createGroupArgs.groupDescription != null) {
group.setDescription(createGroupArgs.groupDescription);
}
final AccountGroupName gn = new AccountGroupName(group);
AccountGroupName gn = new AccountGroupName(group);
// first insert the group name to validate that the group name hasn't
// already been used to create another group
try {
db.accountGroupNames().insert(Collections.singleton(gn));
} catch (OrmDuplicateKeyException e) {
throw new NameAlreadyUsedException(groupName);
throw new NameAlreadyUsedException(createGroupArgs.getGroupName());
}
db.accountGroups().insert(Collections.singleton(group));
addMembers(groupId, initialMembers);
addMembers(groupId, createGroupArgs.initialMembers);
if (initialGroups != null) {
addGroups(groupId, initialGroups);
if (createGroupArgs.initialGroups != null) {
addGroups(groupId, createGroupArgs.initialGroups);
groupIncludeCache.evictMembersOf(uuid);
}
groupCache.onCreateGroup(nameKey);
groupCache.onCreateGroup(createGroupArgs.getGroup());
return group;
}

View File

@ -118,6 +118,7 @@ import com.google.gerrit.server.ssh.SshAddressesModule;
import com.google.gerrit.server.tools.ToolsCatalog;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
import com.google.gerrit.server.validators.ProjectCreationValidationListener;
import com.google.inject.Inject;
import com.google.inject.TypeLiteral;
@ -251,6 +252,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), CommitValidationListener.class);
DynamicSet.setOf(binder(), MergeValidationListener.class);
DynamicSet.setOf(binder(), ProjectCreationValidationListener.class);
DynamicSet.setOf(binder(), GroupCreationValidationListener.class);
DynamicItem.itemOf(binder(), AvatarProvider.class);
DynamicSet.setOf(binder(), LifecycleListener.class);
DynamicSet.setOf(binder(), TopMenu.class);

View File

@ -22,6 +22,7 @@ import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@ -32,10 +33,13 @@ import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.CreateGroupArgs;
import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.CreateGroup.Input;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@ -54,7 +58,7 @@ public class CreateGroup implements RestModifyView<TopLevelResource, Input> {
public String ownerId;
}
static interface Factory {
public static interface Factory {
CreateGroup create(@Assisted String name);
}
@ -62,17 +66,20 @@ public class CreateGroup implements RestModifyView<TopLevelResource, Input> {
private final GroupsCollection groups;
private final PerformCreateGroup.Factory op;
private final GroupJson json;
private final DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners;
private final boolean defaultVisibleToAll;
private final String name;
@Inject
CreateGroup(Provider<IdentifiedUser> self, GroupsCollection groups,
PerformCreateGroup.Factory performCreateGroupFactory, GroupJson json,
DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners,
@GerritServerConfig Config cfg, @Assisted String name) {
this.self = self;
this.groups = groups;
this.op = performCreateGroupFactory;
this.json = json;
this.groupCreationValidationListeners = groupCreationValidationListeners;
this.defaultVisibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false);
this.name = name;
}
@ -91,15 +98,24 @@ public class CreateGroup implements RestModifyView<TopLevelResource, Input> {
AccountGroup.Id ownerId = owner(input);
AccountGroup group;
try {
group = op.create().createGroup(
name,
Strings.emptyToNull(input.description),
Objects.firstNonNull(input.visibleToAll, defaultVisibleToAll),
ownerId,
ownerId == null
? Collections.singleton(self.get().getAccountId())
: Collections.<Account.Id> emptySet(),
null);
CreateGroupArgs args = new CreateGroupArgs();
args.setGroupName(name);
args.groupDescription = Strings.emptyToNull(input.description);
args.visibleToAll = Objects.firstNonNull(input.visibleToAll, defaultVisibleToAll);
args.ownerGroupId = ownerId;
args.initialMembers = ownerId == null
? Collections.singleton(self.get().getAccountId())
: Collections.<Account.Id> emptySet();
for (GroupCreationValidationListener l : groupCreationValidationListeners) {
try {
l.validateNewGroup(args);
} catch (ValidationException e) {
throw new ResourceConflictException(e.getMessage(), e);
}
}
group = op.create(args).createGroup();
} catch (PermissionDeniedException e) {
throw new AuthException(e.getMessage());
} catch (NameAlreadyUsedException e) {

View File

@ -0,0 +1,35 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.validators;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.server.account.CreateGroupArgs;
/**
* Listener to provide validation on group creation.
*/
@ExtensionPoint
public interface GroupCreationValidationListener {
/**
* Group creation validation.
*
* Invoked by Gerrit just before a new group is going to be created.
*
* @param args arguments for the group creation
* @throws ValidationException if validation fails
*/
public void validateNewGroup(CreateGroupArgs args)
throws ValidationException;
}

View File

@ -18,9 +18,13 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.CreateGroupArgs;
import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
@ -69,15 +73,29 @@ final class CreateGroupCommand extends SshCommand {
@Inject
private PerformCreateGroup.Factory performCreateGroupFactory;
@Inject
private DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners;
@Override
protected void run() throws Failure, OrmException {
try {
performCreateGroupFactory.create().createGroup(groupName,
groupDescription,
visibleToAll,
ownerGroupId,
initialMembers,
initialGroups);
CreateGroupArgs args = new CreateGroupArgs();
args.setGroupName(groupName);
args.groupDescription = groupDescription;
args.visibleToAll = visibleToAll;
args.ownerGroupId = ownerGroupId;
args.initialMembers = initialMembers;
args.initialGroups = initialGroups;
for (GroupCreationValidationListener l : groupCreationValidationListeners) {
try {
l.validateNewGroup(args);
} catch (ValidationException e) {
die(e);
}
}
performCreateGroupFactory.create(args).createGroup();
} catch (PermissionDeniedException e) {
throw die(e);