diff options
Diffstat (limited to 'java/com/google/gerrit/server/restapi/project/SetParent.java')
-rw-r--r-- | java/com/google/gerrit/server/restapi/project/SetParent.java | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/restapi/project/SetParent.java b/java/com/google/gerrit/server/restapi/project/SetParent.java new file mode 100644 index 0000000000..d02d04a6c2 --- /dev/null +++ b/java/com/google/gerrit/server/restapi/project/SetParent.java @@ -0,0 +1,190 @@ +// Copyright (C) 2012 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.restapi.project; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import com.google.gerrit.extensions.api.projects.ParentInput; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.restapi.UnprocessableEntityException; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gerrit.server.config.AllUsersName; +import com.google.gerrit.server.config.ConfigKey; +import com.google.gerrit.server.config.ConfigUpdatedEvent; +import com.google.gerrit.server.config.ConfigUpdatedEvent.ConfigUpdateEntry; +import com.google.gerrit.server.config.ConfigUpdatedEvent.UpdateResult; +import com.google.gerrit.server.config.GerritConfigListener; +import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.git.meta.MetaDataUpdate; +import com.google.gerrit.server.permissions.GlobalPermission; +import com.google.gerrit.server.permissions.PermissionBackend; +import com.google.gerrit.server.permissions.PermissionBackendException; +import com.google.gerrit.server.permissions.ProjectPermission; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectConfig; +import com.google.gerrit.server.project.ProjectResource; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Config; + +@Singleton +public class SetParent + implements RestModifyView<ProjectResource, ParentInput>, GerritConfigListener { + private final ProjectCache cache; + private final PermissionBackend permissionBackend; + private final MetaDataUpdate.Server updateFactory; + private final AllProjectsName allProjects; + private final AllUsersName allUsers; + private volatile boolean allowProjectOwnersToChangeParent; + + @Inject + SetParent( + ProjectCache cache, + PermissionBackend permissionBackend, + MetaDataUpdate.Server updateFactory, + AllProjectsName allProjects, + AllUsersName allUsers, + @GerritServerConfig Config config) { + this.cache = cache; + this.permissionBackend = permissionBackend; + this.updateFactory = updateFactory; + this.allProjects = allProjects; + this.allUsers = allUsers; + this.allowProjectOwnersToChangeParent = + config.getBoolean("receive", "allowProjectOwnersToChangeParent", false); + } + + @Override + public String apply(ProjectResource rsrc, ParentInput input) + throws AuthException, ResourceConflictException, ResourceNotFoundException, + UnprocessableEntityException, IOException, PermissionBackendException, + BadRequestException { + return apply(rsrc, input, true); + } + + public String apply(ProjectResource rsrc, ParentInput input, boolean checkIfAdmin) + throws AuthException, ResourceConflictException, ResourceNotFoundException, + UnprocessableEntityException, IOException, PermissionBackendException, + BadRequestException { + IdentifiedUser user = rsrc.getUser().asIdentifiedUser(); + String parentName = + MoreObjects.firstNonNull(Strings.emptyToNull(input.parent), allProjects.get()); + validateParentUpdate(rsrc.getProjectState().getNameKey(), user, parentName, checkIfAdmin); + try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) { + ProjectConfig config = ProjectConfig.read(md); + Project project = config.getProject(); + project.setParentName(parentName); + + String msg = Strings.emptyToNull(input.commitMessage); + if (msg == null) { + msg = String.format("Changed parent to %s.\n", parentName); + } else if (!msg.endsWith("\n")) { + msg += "\n"; + } + md.setAuthor(user); + md.setMessage(msg); + config.commit(md); + cache.evict(rsrc.getProjectState().getProject()); + + Project.NameKey parent = project.getParent(allProjects); + requireNonNull(parent); + return parent.get(); + } catch (RepositoryNotFoundException notFound) { + throw new ResourceNotFoundException(rsrc.getName()); + } catch (ConfigInvalidException e) { + throw new ResourceConflictException( + String.format("invalid project.config: %s", e.getMessage())); + } + } + + public void validateParentUpdate( + Project.NameKey project, IdentifiedUser user, String newParent, boolean checkIfAdmin) + throws AuthException, ResourceConflictException, UnprocessableEntityException, + PermissionBackendException, BadRequestException { + if (checkIfAdmin) { + if (allowProjectOwnersToChangeParent) { + permissionBackend.user(user).project(project).check(ProjectPermission.WRITE_CONFIG); + } else { + permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER); + } + } + + if (project.equals(allUsers) && !allProjects.get().equals(newParent)) { + throw new BadRequestException( + String.format("%s must inherit from %s", allUsers.get(), allProjects.get())); + } + + if (project.equals(allProjects)) { + throw new ResourceConflictException("cannot set parent of " + allProjects.get()); + } + + if (allUsers.get().equals(newParent)) { + throw new ResourceConflictException( + String.format("Cannot inherit from '%s' project", allUsers.get())); + } + + newParent = Strings.emptyToNull(newParent); + if (newParent != null) { + ProjectState parent = cache.get(new Project.NameKey(newParent)); + if (parent == null) { + throw new UnprocessableEntityException("parent project " + newParent + " not found"); + } + + if (parent.getName().equals(project.get())) { + throw new ResourceConflictException("cannot set parent to self"); + } + + if (Iterables.tryFind( + parent.tree(), + p -> { + return p.getNameKey().equals(project); + }) + .isPresent()) { + throw new ResourceConflictException( + "cycle exists between " + project.get() + " and " + parent.getName()); + } + } + } + + @Override + public Multimap<UpdateResult, ConfigUpdateEntry> configUpdated(ConfigUpdatedEvent event) { + ConfigKey receiveSetParent = ConfigKey.create("receive", "allowProjectOwnersToChangeParent"); + if (!event.isValueUpdated(receiveSetParent)) { + return ConfigUpdatedEvent.NO_UPDATES; + } + try { + boolean enabled = + event.getNewConfig().getBoolean("receive", "allowProjectOwnersToChangeParent", false); + this.allowProjectOwnersToChangeParent = enabled; + } catch (IllegalArgumentException iae) { + return event.reject(receiveSetParent); + } + return event.accept(receiveSetParent); + } +} |