| // 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 java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.Config; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| @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.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; |
| } |
| } |