LCOV - code coverage report
Current view: top level - server/restapi/project - SetParent.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 56 70 80.0 %
Date: 2022-11-19 15:00:39 Functions: 8 8 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2012 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.project;
      16             : 
      17             : import static java.util.Objects.requireNonNull;
      18             : 
      19             : import com.google.common.base.MoreObjects;
      20             : import com.google.common.base.Strings;
      21             : import com.google.common.collect.Iterables;
      22             : import com.google.common.collect.Multimap;
      23             : import com.google.gerrit.entities.Project;
      24             : import com.google.gerrit.extensions.api.projects.ParentInput;
      25             : import com.google.gerrit.extensions.restapi.AuthException;
      26             : import com.google.gerrit.extensions.restapi.BadRequestException;
      27             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      28             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      29             : import com.google.gerrit.extensions.restapi.Response;
      30             : import com.google.gerrit.extensions.restapi.RestModifyView;
      31             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      32             : import com.google.gerrit.server.IdentifiedUser;
      33             : import com.google.gerrit.server.config.AllProjectsName;
      34             : import com.google.gerrit.server.config.AllUsersName;
      35             : import com.google.gerrit.server.config.ConfigKey;
      36             : import com.google.gerrit.server.config.ConfigUpdatedEvent;
      37             : import com.google.gerrit.server.config.ConfigUpdatedEvent.ConfigUpdateEntry;
      38             : import com.google.gerrit.server.config.ConfigUpdatedEvent.UpdateResult;
      39             : import com.google.gerrit.server.config.GerritConfigListener;
      40             : import com.google.gerrit.server.config.GerritServerConfig;
      41             : import com.google.gerrit.server.git.meta.MetaDataUpdate;
      42             : import com.google.gerrit.server.permissions.GlobalPermission;
      43             : import com.google.gerrit.server.permissions.PermissionBackend;
      44             : import com.google.gerrit.server.permissions.PermissionBackendException;
      45             : import com.google.gerrit.server.permissions.ProjectPermission;
      46             : import com.google.gerrit.server.project.ProjectCache;
      47             : import com.google.gerrit.server.project.ProjectConfig;
      48             : import com.google.gerrit.server.project.ProjectResource;
      49             : import com.google.gerrit.server.project.ProjectState;
      50             : import com.google.inject.Inject;
      51             : import com.google.inject.Provider;
      52             : import com.google.inject.Singleton;
      53             : import java.io.IOException;
      54             : import org.eclipse.jgit.errors.ConfigInvalidException;
      55             : import org.eclipse.jgit.errors.RepositoryNotFoundException;
      56             : import org.eclipse.jgit.lib.Config;
      57             : 
      58             : @Singleton
      59             : public class SetParent
      60             :     implements RestModifyView<ProjectResource, ParentInput>, GerritConfigListener {
      61             :   private final ProjectCache cache;
      62             :   private final PermissionBackend permissionBackend;
      63             :   private final Provider<MetaDataUpdate.Server> updateFactory;
      64             :   private final AllProjectsName allProjects;
      65             :   private final AllUsersName allUsers;
      66             :   private final ProjectConfig.Factory projectConfigFactory;
      67             :   private volatile boolean allowProjectOwnersToChangeParent;
      68             : 
      69             :   @Inject
      70             :   SetParent(
      71             :       ProjectCache cache,
      72             :       PermissionBackend permissionBackend,
      73             :       Provider<MetaDataUpdate.Server> updateFactory,
      74             :       AllProjectsName allProjects,
      75             :       AllUsersName allUsers,
      76             :       ProjectConfig.Factory projectConfigFactory,
      77         146 :       @GerritServerConfig Config config) {
      78         146 :     this.cache = cache;
      79         146 :     this.permissionBackend = permissionBackend;
      80         146 :     this.updateFactory = updateFactory;
      81         146 :     this.allProjects = allProjects;
      82         146 :     this.allUsers = allUsers;
      83         146 :     this.projectConfigFactory = projectConfigFactory;
      84         146 :     this.allowProjectOwnersToChangeParent =
      85         146 :         config.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
      86         146 :   }
      87             : 
      88             :   @Override
      89             :   public Response<String> apply(ProjectResource rsrc, ParentInput input)
      90             :       throws AuthException, ResourceConflictException, ResourceNotFoundException,
      91             :           UnprocessableEntityException, IOException, PermissionBackendException,
      92             :           BadRequestException {
      93           2 :     return Response.ok(apply(rsrc, input, true));
      94             :   }
      95             : 
      96             :   public String apply(ProjectResource rsrc, ParentInput input, boolean checkIfAdmin)
      97             :       throws AuthException, ResourceConflictException, ResourceNotFoundException,
      98             :           UnprocessableEntityException, IOException, PermissionBackendException,
      99             :           BadRequestException {
     100           2 :     IdentifiedUser user = rsrc.getUser().asIdentifiedUser();
     101           2 :     String parentName =
     102           2 :         MoreObjects.firstNonNull(Strings.emptyToNull(input.parent), allProjects.get());
     103           2 :     validateParentUpdate(rsrc.getProjectState().getNameKey(), user, parentName, checkIfAdmin);
     104           2 :     try (MetaDataUpdate md = updateFactory.get().create(rsrc.getNameKey())) {
     105           2 :       ProjectConfig config = projectConfigFactory.read(md);
     106           2 :       config.updateProject(p -> p.setParent(parentName));
     107             : 
     108           2 :       String msg = Strings.emptyToNull(input.commitMessage);
     109           2 :       if (msg == null) {
     110           2 :         msg = String.format("Changed parent to %s.\n", parentName);
     111           0 :       } else if (!msg.endsWith("\n")) {
     112           0 :         msg += "\n";
     113             :       }
     114           2 :       md.setAuthor(user);
     115           2 :       md.setMessage(msg);
     116           2 :       config.commit(md);
     117           2 :       cache.evictAndReindex(rsrc.getProjectState().getProject());
     118             : 
     119           2 :       Project.NameKey parent = config.getProject().getParent(allProjects);
     120           2 :       requireNonNull(parent);
     121           2 :       return parent.get();
     122           0 :     } catch (RepositoryNotFoundException notFound) {
     123           0 :       throw new ResourceNotFoundException(rsrc.getName(), notFound);
     124           0 :     } catch (ConfigInvalidException e) {
     125           0 :       throw new ResourceConflictException(
     126           0 :           String.format("invalid project.config: %s", e.getMessage()));
     127             :     }
     128             :   }
     129             : 
     130             :   public void validateParentUpdate(
     131             :       Project.NameKey project, IdentifiedUser user, String newParent, boolean checkIfAdmin)
     132             :       throws AuthException, ResourceConflictException, UnprocessableEntityException,
     133             :           PermissionBackendException, BadRequestException {
     134           2 :     if (checkIfAdmin) {
     135           2 :       if (allowProjectOwnersToChangeParent) {
     136           1 :         permissionBackend.user(user).project(project).check(ProjectPermission.WRITE_CONFIG);
     137             :       } else {
     138           2 :         permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
     139             :       }
     140             :     }
     141             : 
     142           2 :     if (project.equals(allUsers) && !allProjects.get().equals(newParent)) {
     143           1 :       throw new BadRequestException(
     144           1 :           String.format("%s must inherit from %s", allUsers.get(), allProjects.get()));
     145             :     }
     146             : 
     147           2 :     if (project.equals(allProjects)) {
     148           1 :       throw new ResourceConflictException("cannot set parent of " + allProjects.get());
     149             :     }
     150             : 
     151           2 :     if (allUsers.get().equals(newParent)) {
     152           1 :       throw new ResourceConflictException(
     153           1 :           String.format("Cannot inherit from '%s' project", allUsers.get()));
     154             :     }
     155             : 
     156           2 :     newParent = Strings.emptyToNull(newParent);
     157           2 :     if (newParent != null) {
     158           2 :       Project.NameKey newParentNameKey = Project.nameKey(newParent);
     159           2 :       ProjectState parent =
     160             :           cache
     161           2 :               .get(newParentNameKey)
     162           2 :               .orElseThrow(
     163             :                   () ->
     164           1 :                       new UnprocessableEntityException(
     165             :                           "parent project " + newParentNameKey + " not found"));
     166             : 
     167           2 :       if (parent.getName().equals(project.get())) {
     168           1 :         throw new ResourceConflictException("cannot set parent to self");
     169             :       }
     170             : 
     171           2 :       if (Iterables.tryFind(parent.tree(), p -> p.getNameKey().equals(project)).isPresent()) {
     172           1 :         throw new ResourceConflictException(
     173           1 :             "cycle exists between " + project.get() + " and " + parent.getName());
     174             :       }
     175             :     }
     176           2 :   }
     177             : 
     178             :   @Override
     179             :   public Multimap<UpdateResult, ConfigUpdateEntry> configUpdated(ConfigUpdatedEvent event) {
     180           1 :     ConfigKey receiveSetParent = ConfigKey.create("receive", "allowProjectOwnersToChangeParent");
     181           1 :     if (!event.isValueUpdated(receiveSetParent)) {
     182           1 :       return ConfigUpdatedEvent.NO_UPDATES;
     183             :     }
     184             :     try {
     185           0 :       boolean enabled =
     186           0 :           event.getNewConfig().getBoolean("receive", "allowProjectOwnersToChangeParent", false);
     187           0 :       this.allowProjectOwnersToChangeParent = enabled;
     188           0 :     } catch (IllegalArgumentException iae) {
     189           0 :       return event.reject(receiveSetParent);
     190           0 :     }
     191           0 :     return event.accept(receiveSetParent);
     192             :   }
     193             : }

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