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 : }