LCOV - code coverage report
Current view: top level - server/restapi/group - CreateGroup.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 81 92 88.0 %
Date: 2022-11-19 15:00:39 Functions: 5 8 62.5 %

          Line data    Source code
       1             : // Copyright (C) 2013 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.restapi.group;
      16             : 
      17             : import com.google.common.base.MoreObjects;
      18             : import com.google.common.base.Strings;
      19             : import com.google.common.collect.ImmutableSet;
      20             : import com.google.gerrit.common.Nullable;
      21             : import com.google.gerrit.common.data.GlobalCapability;
      22             : import com.google.gerrit.entities.Account;
      23             : import com.google.gerrit.entities.AccountGroup;
      24             : import com.google.gerrit.entities.GroupDescription;
      25             : import com.google.gerrit.entities.InternalGroup;
      26             : import com.google.gerrit.exceptions.DuplicateKeyException;
      27             : import com.google.gerrit.extensions.annotations.RequiresCapability;
      28             : import com.google.gerrit.extensions.api.groups.GroupInput;
      29             : import com.google.gerrit.extensions.client.ListGroupsOption;
      30             : import com.google.gerrit.extensions.common.GroupInfo;
      31             : import com.google.gerrit.extensions.restapi.AuthException;
      32             : import com.google.gerrit.extensions.restapi.BadRequestException;
      33             : import com.google.gerrit.extensions.restapi.IdString;
      34             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      35             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      36             : import com.google.gerrit.extensions.restapi.Response;
      37             : import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
      38             : import com.google.gerrit.extensions.restapi.TopLevelResource;
      39             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      40             : import com.google.gerrit.extensions.restapi.Url;
      41             : import com.google.gerrit.server.GerritPersonIdent;
      42             : import com.google.gerrit.server.IdentifiedUser;
      43             : import com.google.gerrit.server.UserInitiated;
      44             : import com.google.gerrit.server.account.CreateGroupArgs;
      45             : import com.google.gerrit.server.account.GroupCache;
      46             : import com.google.gerrit.server.account.GroupUuid;
      47             : import com.google.gerrit.server.config.GerritServerConfig;
      48             : import com.google.gerrit.server.group.GroupResolver;
      49             : import com.google.gerrit.server.group.GroupResource;
      50             : import com.google.gerrit.server.group.InternalGroupDescription;
      51             : import com.google.gerrit.server.group.SystemGroupBackend;
      52             : import com.google.gerrit.server.group.db.GroupDelta;
      53             : import com.google.gerrit.server.group.db.GroupsUpdate;
      54             : import com.google.gerrit.server.group.db.InternalGroupCreation;
      55             : import com.google.gerrit.server.notedb.Sequences;
      56             : import com.google.gerrit.server.permissions.PermissionBackendException;
      57             : import com.google.gerrit.server.plugincontext.PluginSetContext;
      58             : import com.google.gerrit.server.util.time.TimeUtil;
      59             : import com.google.gerrit.server.validators.GroupCreationValidationListener;
      60             : import com.google.gerrit.server.validators.ValidationException;
      61             : import com.google.inject.Inject;
      62             : import com.google.inject.Provider;
      63             : import com.google.inject.Singleton;
      64             : import java.io.IOException;
      65             : import java.time.ZoneId;
      66             : import java.util.ArrayList;
      67             : import java.util.Collection;
      68             : import java.util.Collections;
      69             : import java.util.List;
      70             : import java.util.Locale;
      71             : import java.util.Optional;
      72             : import org.eclipse.jgit.errors.ConfigInvalidException;
      73             : import org.eclipse.jgit.lib.Config;
      74             : import org.eclipse.jgit.lib.PersonIdent;
      75             : 
      76             : @RequiresCapability(GlobalCapability.CREATE_GROUP)
      77             : @Singleton
      78             : public class CreateGroup
      79             :     implements RestCollectionCreateView<TopLevelResource, GroupResource, GroupInput> {
      80             :   private final Provider<IdentifiedUser> self;
      81             :   private final ZoneId serverZoneId;
      82             :   private final Provider<GroupsUpdate> groupsUpdateProvider;
      83             :   private final GroupCache groupCache;
      84             :   private final GroupResolver groups;
      85             :   private final GroupJson json;
      86             :   private final PluginSetContext<GroupCreationValidationListener> groupCreationValidationListeners;
      87             :   private final AddMembers addMembers;
      88             :   private final SystemGroupBackend systemGroupBackend;
      89             :   private final boolean defaultVisibleToAll;
      90             :   private final Sequences sequences;
      91             : 
      92             :   @Inject
      93             :   CreateGroup(
      94             :       Provider<IdentifiedUser> self,
      95             :       @GerritPersonIdent Provider<PersonIdent> serverIdent,
      96             :       @UserInitiated Provider<GroupsUpdate> groupsUpdateProvider,
      97             :       GroupCache groupCache,
      98             :       GroupResolver groups,
      99             :       GroupJson json,
     100             :       PluginSetContext<GroupCreationValidationListener> groupCreationValidationListeners,
     101             :       AddMembers addMembers,
     102             :       SystemGroupBackend systemGroupBackend,
     103             :       @GerritServerConfig Config cfg,
     104         149 :       Sequences sequences) {
     105         149 :     this.self = self;
     106         149 :     this.serverZoneId = serverIdent.get().getZoneId();
     107         149 :     this.groupsUpdateProvider = groupsUpdateProvider;
     108         149 :     this.groupCache = groupCache;
     109         149 :     this.groups = groups;
     110         149 :     this.json = json;
     111         149 :     this.groupCreationValidationListeners = groupCreationValidationListeners;
     112         149 :     this.addMembers = addMembers;
     113         149 :     this.systemGroupBackend = systemGroupBackend;
     114         149 :     this.defaultVisibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false);
     115         149 :     this.sequences = sequences;
     116         149 :   }
     117             : 
     118             :   public CreateGroup addOption(ListGroupsOption o) {
     119           0 :     json.addOption(o);
     120           0 :     return this;
     121             :   }
     122             : 
     123             :   public CreateGroup addOptions(Collection<ListGroupsOption> o) {
     124           0 :     json.addOptions(o);
     125           0 :     return this;
     126             :   }
     127             : 
     128             :   @Override
     129             :   public Response<GroupInfo> apply(TopLevelResource resource, IdString id, GroupInput input)
     130             :       throws AuthException, BadRequestException, UnprocessableEntityException,
     131             :           ResourceConflictException, IOException, ConfigInvalidException, ResourceNotFoundException,
     132             :           PermissionBackendException {
     133          29 :     String name = id.get();
     134          29 :     if (input == null) {
     135           0 :       input = new GroupInput();
     136             :     }
     137          29 :     if (input.name != null && !name.equals(input.name)) {
     138           0 :       throw new BadRequestException("name must match URL");
     139             :     }
     140             : 
     141          29 :     AccountGroup.UUID ownerUuid = owner(input);
     142          29 :     CreateGroupArgs args = new CreateGroupArgs();
     143          29 :     args.setGroupName(name);
     144          29 :     args.uuid = Strings.isNullOrEmpty(input.uuid) ? null : AccountGroup.UUID.parse(input.uuid);
     145          29 :     if (args.uuid != null) {
     146           1 :       if (!args.uuid.isInternalGroup()) {
     147           1 :         throw new BadRequestException(String.format("invalid group UUID '%s'", args.uuid.get()));
     148             :       }
     149           1 :       if (groupCache.get(args.uuid).isPresent()) {
     150           1 :         throw new ResourceConflictException(
     151           1 :             String.format("group with UUID '%s' already exists", args.uuid.get()));
     152             :       }
     153             :     }
     154          29 :     args.groupDescription = Strings.emptyToNull(input.description);
     155          29 :     args.visibleToAll = MoreObjects.firstNonNull(input.visibleToAll, defaultVisibleToAll);
     156          29 :     args.ownerGroupUuid = ownerUuid;
     157          29 :     if (input.members != null && !input.members.isEmpty()) {
     158          18 :       List<Account.Id> members = new ArrayList<>();
     159          18 :       for (String nameOrEmailOrId : input.members) {
     160          18 :         Account a = addMembers.findAccount(nameOrEmailOrId);
     161          18 :         if (!a.isActive()) {
     162           0 :           throw new UnprocessableEntityException(
     163           0 :               String.format("Account Inactive: %s", nameOrEmailOrId));
     164             :         }
     165          18 :         members.add(a.id());
     166          18 :       }
     167          18 :       args.initialMembers = members;
     168          18 :     } else {
     169          15 :       args.initialMembers =
     170          15 :           ownerUuid == null
     171          13 :               ? Collections.singleton(self.get().getAccountId())
     172          15 :               : Collections.emptySet();
     173             :     }
     174             : 
     175             :     try {
     176          29 :       groupCreationValidationListeners.runEach(
     177           0 :           l -> l.validateNewGroup(args), ValidationException.class);
     178           0 :     } catch (ValidationException e) {
     179           0 :       throw new ResourceConflictException(e.getMessage(), e);
     180          29 :     }
     181             : 
     182          29 :     return Response.created(json.format(new InternalGroupDescription(createGroup(args))));
     183             :   }
     184             : 
     185             :   @Nullable
     186             :   private AccountGroup.UUID owner(GroupInput input) throws UnprocessableEntityException {
     187          29 :     if (input.ownerId != null) {
     188           9 :       GroupDescription.Internal d = groups.parseInternal(Url.decode(input.ownerId));
     189           9 :       return d.getGroupUUID();
     190             :     }
     191          28 :     return null;
     192             :   }
     193             : 
     194             :   private InternalGroup createGroup(CreateGroupArgs createGroupArgs)
     195             :       throws ResourceConflictException, IOException, ConfigInvalidException {
     196             : 
     197          29 :     String nameLower = createGroupArgs.getGroupName().toLowerCase(Locale.US);
     198             : 
     199          29 :     for (String name : systemGroupBackend.getNames()) {
     200          29 :       if (name.toLowerCase(Locale.US).equals(nameLower)) {
     201           1 :         throw new ResourceConflictException("group '" + name + "' already exists");
     202             :       }
     203          29 :     }
     204             : 
     205          29 :     for (String name : systemGroupBackend.getReservedNames()) {
     206          29 :       if (name.toLowerCase(Locale.US).equals(nameLower)) {
     207           1 :         throw new ResourceConflictException("group name '" + name + "' is reserved");
     208             :       }
     209          29 :     }
     210             : 
     211          29 :     AccountGroup.Id groupId = AccountGroup.id(sequences.nextGroupId());
     212          29 :     AccountGroup.UUID uuid =
     213          29 :         MoreObjects.firstNonNull(
     214             :             createGroupArgs.uuid,
     215          29 :             GroupUuid.make(
     216          29 :                 createGroupArgs.getGroupName(),
     217          29 :                 self.get().newCommitterIdent(TimeUtil.now(), serverZoneId)));
     218             :     InternalGroupCreation groupCreation =
     219          29 :         InternalGroupCreation.builder()
     220          29 :             .setGroupUUID(uuid)
     221          29 :             .setNameKey(createGroupArgs.getGroup())
     222          29 :             .setId(groupId)
     223          29 :             .build();
     224             :     GroupDelta.Builder groupDeltaBuilder =
     225          29 :         GroupDelta.builder().setVisibleToAll(createGroupArgs.visibleToAll);
     226          29 :     if (createGroupArgs.ownerGroupUuid != null) {
     227           9 :       Optional<InternalGroup> ownerGroup = groupCache.get(createGroupArgs.ownerGroupUuid);
     228           9 :       ownerGroup.map(InternalGroup::getGroupUUID).ifPresent(groupDeltaBuilder::setOwnerGroupUUID);
     229             :     }
     230          29 :     if (createGroupArgs.groupDescription != null) {
     231           3 :       groupDeltaBuilder.setDescription(createGroupArgs.groupDescription);
     232             :     }
     233          29 :     groupDeltaBuilder.setMemberModification(
     234          29 :         members -> ImmutableSet.copyOf(createGroupArgs.initialMembers));
     235             :     try {
     236          29 :       return groupsUpdateProvider.get().createGroup(groupCreation, groupDeltaBuilder.build());
     237           1 :     } catch (DuplicateKeyException e) {
     238           1 :       throw new ResourceConflictException(
     239           1 :           "group '" + createGroupArgs.getGroupName() + "' already exists", e);
     240             :     }
     241             :   }
     242             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750