You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ReportSubmitter.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.ce.queue;
  21. import java.io.InputStream;
  22. import java.util.ArrayList;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Optional;
  26. import javax.annotation.Nullable;
  27. import org.sonar.api.resources.Qualifiers;
  28. import org.sonar.api.resources.Scopes;
  29. import org.sonar.api.server.ServerSide;
  30. import org.sonar.api.web.UserRole;
  31. import org.sonar.ce.queue.CeQueue;
  32. import org.sonar.ce.queue.CeTaskSubmit;
  33. import org.sonar.ce.task.CeTask;
  34. import org.sonar.db.DbClient;
  35. import org.sonar.db.DbSession;
  36. import org.sonar.db.ce.CeTaskTypes;
  37. import org.sonar.db.component.BranchDto;
  38. import org.sonar.db.component.ComponentDto;
  39. import org.sonar.db.permission.GlobalPermission;
  40. import org.sonar.server.common.almsettings.DevOpsProjectCreator;
  41. import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory;
  42. import org.sonar.server.component.ComponentCreationData;
  43. import org.sonar.server.common.component.ComponentUpdater;
  44. import org.sonar.server.exceptions.BadRequestException;
  45. import org.sonar.server.management.ManagedInstanceService;
  46. import org.sonar.server.common.permission.PermissionTemplateService;
  47. import org.sonar.server.common.project.ProjectCreator;
  48. import org.sonar.server.user.UserSession;
  49. import static java.lang.String.format;
  50. import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
  51. import static org.sonar.db.permission.GlobalPermission.SCAN;
  52. import static org.sonar.db.project.CreationMethod.SCANNER_API;
  53. import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG;
  54. import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
  55. @ServerSide
  56. public class ReportSubmitter {
  57. private final CeQueue queue;
  58. private final UserSession userSession;
  59. private final ProjectCreator projectCreator;
  60. private final ComponentUpdater componentUpdater;
  61. private final PermissionTemplateService permissionTemplateService;
  62. private final DbClient dbClient;
  63. private final BranchSupport branchSupport;
  64. private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory;
  65. private final ManagedInstanceService managedInstanceService;
  66. public ReportSubmitter(CeQueue queue, UserSession userSession, ProjectCreator projectCreator, ComponentUpdater componentUpdater,
  67. PermissionTemplateService permissionTemplateService, DbClient dbClient, BranchSupport branchSupport,
  68. DevOpsProjectCreatorFactory devOpsProjectCreatorFactory, ManagedInstanceService managedInstanceService) {
  69. this.queue = queue;
  70. this.userSession = userSession;
  71. this.projectCreator = projectCreator;
  72. this.componentUpdater = componentUpdater;
  73. this.permissionTemplateService = permissionTemplateService;
  74. this.dbClient = dbClient;
  75. this.branchSupport = branchSupport;
  76. this.devOpsProjectCreatorFactory = devOpsProjectCreatorFactory;
  77. this.managedInstanceService = managedInstanceService;
  78. }
  79. public CeTask submit(String projectKey, @Nullable String projectName, Map<String, String> characteristics, InputStream reportInput) {
  80. try (DbSession dbSession = dbClient.openSession(false)) {
  81. ComponentCreationData componentCreationData = null;
  82. // Note: when the main branch is analyzed, the characteristics may or may not have the branch name, so componentKey#isMainBranch is not
  83. // reliable!
  84. BranchSupport.ComponentKey componentKey = branchSupport.createComponentKey(projectKey, characteristics);
  85. Optional<ComponentDto> mainBranchComponentOpt = dbClient.componentDao().selectByKey(dbSession, componentKey.getKey());
  86. ComponentDto mainBranchComponent;
  87. if (mainBranchComponentOpt.isPresent()) {
  88. mainBranchComponent = mainBranchComponentOpt.get();
  89. validateProject(dbSession, mainBranchComponent, projectKey);
  90. } else {
  91. componentCreationData = createProject(projectKey, projectName, characteristics, dbSession, componentKey);
  92. mainBranchComponent = componentCreationData.mainBranchComponent();
  93. }
  94. BranchDto mainBranch = dbClient.branchDao().selectByUuid(dbSession, mainBranchComponent.branchUuid())
  95. .orElseThrow(() -> new IllegalStateException("Couldn't find the main branch of the project"));
  96. ComponentDto branchComponent;
  97. if (isMainBranch(componentKey, mainBranch)) {
  98. branchComponent = mainBranchComponent;
  99. } else if (componentKey.getBranchName().isPresent()) {
  100. branchComponent = dbClient.componentDao().selectByKeyAndBranch(dbSession, componentKey.getKey(), componentKey.getBranchName().get())
  101. .orElseGet(() -> branchSupport.createBranchComponent(dbSession, componentKey, mainBranchComponent, mainBranch));
  102. } else {
  103. branchComponent = dbClient.componentDao().selectByKeyAndPullRequest(dbSession, componentKey.getKey(), componentKey.getPullRequestKey().get())
  104. .orElseGet(() -> branchSupport.createBranchComponent(dbSession, componentKey, mainBranchComponent, mainBranch));
  105. }
  106. if (componentCreationData != null) {
  107. componentUpdater.commitAndIndex(dbSession, componentCreationData);
  108. } else {
  109. dbSession.commit();
  110. }
  111. checkScanPermission(branchComponent);
  112. return submitReport(dbSession, reportInput, branchComponent, mainBranch, characteristics);
  113. }
  114. }
  115. private static boolean isMainBranch(BranchSupport.ComponentKey componentKey, BranchDto mainBranch) {
  116. if (componentKey.isMainBranch()) {
  117. return true;
  118. }
  119. return componentKey.getBranchName().isPresent() && componentKey.getBranchName().get().equals(mainBranch.getKey());
  120. }
  121. private void checkScanPermission(ComponentDto project) {
  122. // this is a specific and inconsistent behavior. For legacy reasons, "technical users"
  123. // defined with global scan permission should be able to analyze a project even if
  124. // they don't have the direct permission on the project.
  125. // That means that dropping the permission on the project does not have any effects
  126. // if user has still the global permission
  127. if (!userSession.hasComponentPermission(UserRole.SCAN, project) && !userSession.hasPermission(GlobalPermission.SCAN)) {
  128. throw insufficientPrivilegesException();
  129. }
  130. }
  131. private void validateProject(DbSession dbSession, ComponentDto component, String rawProjectKey) {
  132. List<String> errors = new ArrayList<>();
  133. if (!Qualifiers.PROJECT.equals(component.qualifier()) || !Scopes.PROJECT.equals(component.scope())) {
  134. errors.add(format("Component '%s' is not a project", rawProjectKey));
  135. }
  136. if (!component.branchUuid().equals(component.uuid())) {
  137. // Project key is already used as a module of another project
  138. ComponentDto anotherBaseProject = dbClient.componentDao().selectOrFailByUuid(dbSession, component.branchUuid());
  139. errors.add(format("The project '%s' is already defined in SonarQube but as a module of project '%s'. "
  140. + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
  141. rawProjectKey, anotherBaseProject.getKey(), anotherBaseProject.getKey(), rawProjectKey));
  142. }
  143. if (!errors.isEmpty()) {
  144. throw BadRequestException.create(errors);
  145. }
  146. }
  147. private ComponentCreationData createProject(String projectKey, @Nullable String projectName, Map<String, String> characteristics,
  148. DbSession dbSession, BranchSupport.ComponentKey componentKey) {
  149. userSession.checkPermission(GlobalPermission.PROVISION_PROJECTS);
  150. DevOpsProjectCreator devOpsProjectCreator = devOpsProjectCreatorFactory.getDevOpsProjectCreator(dbSession, characteristics).orElse(null);
  151. throwIfCurrentUserWouldNotHaveScanPermission(projectKey, dbSession, devOpsProjectCreator);
  152. if (devOpsProjectCreator != null) {
  153. return devOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbSession, SCANNER_API_DEVOPS_AUTO_CONFIG, false, projectKey, projectName);
  154. }
  155. return projectCreator.createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey), null, SCANNER_API);
  156. }
  157. private void throwIfCurrentUserWouldNotHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectCreator devOpsProjectCreator) {
  158. if (!wouldCurrentUserHaveScanPermission(projectKey, dbSession, devOpsProjectCreator)) {
  159. throw insufficientPrivilegesException();
  160. }
  161. }
  162. private boolean wouldCurrentUserHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectCreator devOpsProjectCreator) {
  163. if (userSession.hasPermission(SCAN)) {
  164. return true;
  165. }
  166. if (managedInstanceService.isInstanceExternallyManaged() && devOpsProjectCreator != null) {
  167. return devOpsProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform();
  168. }
  169. return permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userSession.getUuid(), projectKey);
  170. }
  171. private CeTask submitReport(DbSession dbSession, InputStream reportInput, ComponentDto branch, BranchDto mainBranch, Map<String, String> characteristics) {
  172. CeTaskSubmit.Builder submit = queue.prepareSubmit();
  173. // the report file must be saved before submitting the task
  174. dbClient.ceTaskInputDao().insert(dbSession, submit.getUuid(), reportInput);
  175. dbSession.commit();
  176. submit.setType(CeTaskTypes.REPORT);
  177. submit.setComponent(CeTaskSubmit.Component.fromDto(branch.uuid(), mainBranch.getProjectUuid()));
  178. submit.setSubmitterUuid(userSession.getUuid());
  179. submit.setCharacteristics(characteristics);
  180. return queue.submit(submit.build());
  181. }
  182. }