blob: e2eec41deb4d1a1e32a5dab1a48d6397db555828 [file] [log] [blame]
// Copyright (C) 2015 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.googlesource.gerrit.plugins.importer;
import static com.google.gerrit.reviewdb.client.AccountGroup.isInternalGroup;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.PreconditionFailedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.CreateGroupArgs;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.googlesource.gerrit.plugins.importer.ImportGroup.Input;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@RequiresCapability(ImportCapability.ID)
class ImportGroup implements RestModifyView<ConfigResource, Input> {
public static class Input {
public String from;
public String user;
public String pass;
public boolean importOwnerGroup;
public boolean importIncludedGroups;
}
interface Factory {
ImportGroup create(AccountGroup.NameKey group);
}
private static Logger log = LoggerFactory.getLogger(ImportGroup.class);
private final Config cfg;
private final ReviewDb db;
private final AccountUtil accountUtil;
private final AccountCache accountCache;
private final GroupCache groupCache;
private final GroupIncludeCache groupIncludeCache;
private final DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners;
private final ImportGroup.Factory importGroupFactory;
private final GerritApi.Factory apiFactory;
private final AccountGroup.NameKey group;
private GerritApi api;
@Inject
ImportGroup(
@GerritServerConfig Config cfg,
ReviewDb db,
AccountUtil accountUtil,
AccountCache accountCache,
GroupCache groupCache,
GroupIncludeCache groupIncludeCache,
DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners,
ImportGroup.Factory importGroupFactory,
GerritApi.Factory apiFactory,
@Assisted AccountGroup.NameKey group) {
this.cfg = cfg;
this.db = db;
this.accountUtil = accountUtil;
this.groupCache = groupCache;
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
this.groupCreationValidationListeners = groupCreationValidationListeners;
this.importGroupFactory = importGroupFactory;
this.apiFactory = apiFactory;
this.group = group;
}
@Override
public Response<String> apply(ConfigResource rsrc, Input input)
throws NoSuchAccountException, OrmException, IOException,
RestApiException, ConfigInvalidException {
GroupInfo groupInfo;
this.api = apiFactory.create(input.from, input.user, input.pass);
groupInfo = api.getGroup(group.get());
validate(input, groupInfo);
createGroup(input, groupInfo);
return Response.<String> ok("OK");
}
private void validate(Input input, GroupInfo groupInfo)
throws IOException, OrmException, NoSuchAccountException,
RestApiException, ConfigInvalidException {
if (!isInternalGroup(new AccountGroup.UUID(groupInfo.id))) {
throw new MethodNotAllowedException(String.format(
"Group with name %s is not an internal group and cannot be imported",
groupInfo.name));
}
if (getGroupByUUID(groupInfo.id) != null) {
throw new ResourceConflictException(String.format(
"Group with UUID %s already exists", groupInfo.id));
}
if (!groupInfo.id.equals(groupInfo.ownerId))
if (!input.importOwnerGroup && getGroupByUUID(groupInfo.ownerId) == null) {
throw new PreconditionFailedException(String.format(
"Owner group %s with UUID %s does not exist",
getGroupName(groupInfo.ownerId), groupInfo.ownerId));
}
if (groupInfo.members != null) {
for (AccountInfo member : groupInfo.members) {
try {
accountUtil.resolveUser(api, member);
} catch (NoSuchAccountException e) {
throw new PreconditionFailedException(e.getMessage());
}
}
}
if (!input.importIncludedGroups) {
if (groupInfo.includes != null) {
for (GroupInfo include : groupInfo.includes) {
if (getGroupByUUID(include.id) == null) {
throw new PreconditionFailedException(String.format(
"Included group %s with UUID %s does not exist",
getGroupName(include.id), include.id));
}
}
}
}
for (GroupCreationValidationListener l : groupCreationValidationListeners) {
try {
l.validateNewGroup(toCreateGroupArgs(groupInfo));
} catch (ValidationException e) {
throw new ResourceConflictException(e.getMessage(), e);
}
}
}
private AccountGroup getGroupByName(String groupName) {
return groupCache.get(new AccountGroup.NameKey(groupName));
}
private AccountGroup getGroupByUUID(String uuid) {
return groupCache.get(new AccountGroup.UUID(uuid));
}
private CreateGroupArgs toCreateGroupArgs(GroupInfo groupInfo)
throws IOException, OrmException, NoSuchAccountException,
RestApiException, ConfigInvalidException {
CreateGroupArgs args = new CreateGroupArgs();
args.setGroupName(groupInfo.name);
args.groupDescription = groupInfo.description;
args.visibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false);
if (!groupInfo.ownerId.equals(groupInfo.id)) {
args.ownerGroupId = getGroupByUUID(groupInfo.ownerId).getId();
}
Set<Account.Id> initialMembers = new HashSet<>();
for (AccountInfo member : groupInfo.members) {
initialMembers.add(accountUtil.resolveUser(api, member));
}
args.initialMembers = initialMembers;
return args;
}
private AccountGroup createGroup(Input input, GroupInfo info)
throws OrmException, NoSuchAccountException, IOException,
RestApiException, ConfigInvalidException {
String uniqueName = getUniqueGroupName(info.name);
if (!info.name.equals(uniqueName)) {
log.warn(String.format("Group %s with UUID %s is imported with name %s",
info.name, info.id, uniqueName));
info.name = uniqueName;
}
AccountGroup group = createAccountGroup(info);
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 ResourceConflictException(info.name);
}
db.accountGroups().insert(Collections.singleton(group));
groupCache.evict(group);
if (!info.id.equals(info.ownerId)) {
if (getGroupByUUID(info.ownerId) == null) {
if (isInternalGroup(new AccountGroup.UUID(info.ownerId))) {
String ownerGroupName = getGroupName(info.ownerId);
if (input.importOwnerGroup) {
importGroupFactory.create(new AccountGroup.NameKey(ownerGroupName))
.apply(new ConfigResource(), input);
} else {
throw new IllegalStateException(String.format(
"Cannot set non-existing group %s as owner of group %s.",
ownerGroupName, info.name));
}
}
}
group.setOwnerGroupUUID(new AccountGroup.UUID(info.ownerId));
db.accountGroups().upsert(Collections.singleton(group));
}
addMembers(group.getId(), info.members);
addGroups(input, group.getId(), info.name, info.includes);
groupCache.evict(group);
return group;
}
private String getUniqueGroupName(String name) {
return getUniqueGroupName(name, false);
}
private String getUniqueGroupName(String name, boolean appendIndex) {
if (getGroupByName(name) == null) {
return name;
}
if (appendIndex) {
int i = 0;
while (true) {
String groupName = String.format("%s-%d", name, ++i);
if (getGroupByName(groupName) == null) {
return groupName;
}
}
}
return getUniqueGroupName(String.format("%s_imported", name), true);
}
private AccountGroup createAccountGroup(GroupInfo info) throws OrmException {
AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
AccountGroup.UUID uuid = new AccountGroup.UUID(info.id);
AccountGroup group =
new AccountGroup(new AccountGroup.NameKey(info.name), groupId, uuid);
group.setVisibleToAll(cfg.getBoolean("groups", "newGroupsVisibleToAll",
false));
group.setDescription(info.description);
return group;
}
private void addMembers(AccountGroup.Id groupId, List<AccountInfo> members)
throws OrmException, NoSuchAccountException, IOException,
RestApiException, ConfigInvalidException {
List<AccountGroupMember> memberships = new ArrayList<>();
for (AccountInfo member : members) {
Account.Id userId = accountUtil.resolveUser(api, member);
AccountGroupMember membership =
new AccountGroupMember(new AccountGroupMember.Key(userId, groupId));
memberships.add(membership);
}
db.accountGroupMembers().insert(memberships);
for (AccountInfo member : members) {
accountCache.evict(accountUtil.resolveUser(api, member));
}
}
private void addGroups(Input input, AccountGroup.Id groupId, String groupName,
List<GroupInfo> includedGroups)
throws NoSuchAccountException, OrmException, IOException,
RestApiException, ConfigInvalidException {
List<AccountGroupById> includeList = new ArrayList<>();
for (GroupInfo includedGroup : includedGroups) {
if (isInternalGroup(new AccountGroup.UUID(includedGroup.id))) {
if (getGroupByUUID(includedGroup.id) == null) {
String includedGroupName = getGroupName(includedGroup.id);
if (input.importIncludedGroups) {
importGroupFactory.create(
new AccountGroup.NameKey(includedGroupName)).apply(
new ConfigResource(), input);
} else {
throw new IllegalStateException(String.format(
"Cannot include non-existing group %s into group %s.",
includedGroupName, groupName));
}
}
}
AccountGroup.UUID memberUUID = new AccountGroup.UUID(includedGroup.id);
AccountGroupById groupInclude =
new AccountGroupById(new AccountGroupById.Key(groupId, memberUUID));
includeList.add(groupInclude);
}
db.accountGroupById().insert(includeList);
for (GroupInfo member : includedGroups) {
groupIncludeCache.evictParentGroupsOf(new AccountGroup.UUID(member.id));
}
}
private String getGroupName(String uuid) throws BadRequestException,
IOException, OrmException {
return api.getGroup(uuid).name;
}
}