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 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 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.organization.OrganizationDto;
  40. import org.sonar.db.permission.OrganizationPermission;
  41. import org.sonar.server.component.ComponentUpdater;
  42. import org.sonar.server.component.NewComponent;
  43. import org.sonar.server.exceptions.BadRequestException;
  44. import org.sonar.server.exceptions.NotFoundException;
  45. import org.sonar.server.permission.PermissionTemplateService;
  46. import org.sonar.server.user.UserSession;
  47. import static com.google.common.base.Preconditions.checkArgument;
  48. import static java.lang.String.format;
  49. import static org.apache.commons.lang.StringUtils.defaultIfBlank;
  50. import static org.sonar.server.component.NewComponent.newComponentBuilder;
  51. import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
  52. @ServerSide
  53. public class ReportSubmitter {
  54. private final CeQueue queue;
  55. private final UserSession userSession;
  56. private final ComponentUpdater componentUpdater;
  57. private final PermissionTemplateService permissionTemplateService;
  58. private final DbClient dbClient;
  59. private final BranchSupport branchSupport;
  60. public ReportSubmitter(CeQueue queue, UserSession userSession, ComponentUpdater componentUpdater,
  61. PermissionTemplateService permissionTemplateService, DbClient dbClient, BranchSupport branchSupport) {
  62. this.queue = queue;
  63. this.userSession = userSession;
  64. this.componentUpdater = componentUpdater;
  65. this.permissionTemplateService = permissionTemplateService;
  66. this.dbClient = dbClient;
  67. this.branchSupport = branchSupport;
  68. }
  69. /**
  70. * @throws NotFoundException if the organization with the specified key does not exist
  71. * @throws IllegalArgumentException if the organization with the specified key is not the organization of the specified project (when it already exists in DB)
  72. */
  73. public CeTask submit(String organizationKey, String projectKey, @Nullable String projectName, Map<String, String> characteristics, InputStream reportInput) {
  74. try (DbSession dbSession = dbClient.openSession(false)) {
  75. boolean projectCreated = false;
  76. OrganizationDto organizationDto = getOrganizationDtoOrFail(dbSession, organizationKey);
  77. // Note: when the main branch is analyzed, the characteristics may or may not have the branch name, so componentKey#isMainBranch is not reliable!
  78. BranchSupport.ComponentKey componentKey = branchSupport.createComponentKey(projectKey, characteristics);
  79. Optional<ComponentDto> mainBranchComponentOpt = dbClient.componentDao().selectByKey(dbSession, componentKey.getKey());
  80. ComponentDto mainBranchComponent;
  81. if (mainBranchComponentOpt.isPresent()) {
  82. mainBranchComponent = mainBranchComponentOpt.get();
  83. validateProject(dbSession, mainBranchComponent, projectKey);
  84. ensureOrganizationIsConsistent(mainBranchComponent, organizationDto);
  85. } else {
  86. mainBranchComponent = createProject(dbSession, organizationDto, componentKey.getMainBranchComponentKey(), projectName);
  87. projectCreated = true;
  88. }
  89. BranchDto mainBranch = dbClient.branchDao().selectByUuid(dbSession, mainBranchComponent.projectUuid())
  90. .orElseThrow(() -> new IllegalStateException("Couldn't find the main branch of the project"));
  91. ComponentDto branchComponent;
  92. if (isMainBranch(componentKey, mainBranch)) {
  93. branchComponent = mainBranchComponent;
  94. } else {
  95. branchComponent = dbClient.componentDao().selectByKey(dbSession, componentKey.getDbKey())
  96. .orElseGet(() -> branchSupport.createBranchComponent(dbSession, componentKey, organizationDto, mainBranchComponent, mainBranch));
  97. }
  98. if (projectCreated) {
  99. componentUpdater.commitAndIndex(dbSession, mainBranchComponent);
  100. } else {
  101. dbSession.commit();
  102. }
  103. checkScanPermission(branchComponent);
  104. return submitReport(dbSession, reportInput, branchComponent, characteristics);
  105. }
  106. }
  107. private static boolean isMainBranch(BranchSupport.ComponentKey componentKey, BranchDto mainBranch) {
  108. if (componentKey.isMainBranch()) {
  109. return true;
  110. }
  111. return componentKey.getBranchName().isPresent() && componentKey.getBranchName().get().equals(mainBranch.getKey());
  112. }
  113. private void checkScanPermission(ComponentDto project) {
  114. // this is a specific and inconsistent behavior. For legacy reasons, "technical users"
  115. // defined on an organization should be able to analyze a project even if
  116. // they don't have the direct permission on the project.
  117. // That means that dropping the permission on the project does not have any effects
  118. // if user has still the permission on the organization
  119. if (!userSession.hasComponentPermission(UserRole.SCAN, project) && !userSession.hasPermission(OrganizationPermission.SCAN)) {
  120. throw insufficientPrivilegesException();
  121. }
  122. }
  123. private OrganizationDto getOrganizationDtoOrFail(DbSession dbSession, String organizationKey) {
  124. return dbClient.organizationDao().selectByKey(dbSession, organizationKey)
  125. .orElseThrow(() -> new NotFoundException(format("Organization with key '%s' does not exist", organizationKey)));
  126. }
  127. private void validateProject(DbSession dbSession, ComponentDto component, String rawProjectKey) {
  128. List<String> errors = new ArrayList<>();
  129. if (!Qualifiers.PROJECT.equals(component.qualifier()) || !Scopes.PROJECT.equals(component.scope())) {
  130. errors.add(format("Component '%s' is not a project", rawProjectKey));
  131. }
  132. if (!component.projectUuid().equals(component.uuid())) {
  133. // Project key is already used as a module of another project
  134. ComponentDto anotherBaseProject = dbClient.componentDao().selectOrFailByUuid(dbSession, component.projectUuid());
  135. errors.add(format("The project '%s' is already defined in SonarQube but as a module of project '%s'. "
  136. + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
  137. rawProjectKey, anotherBaseProject.getKey(), anotherBaseProject.getKey(), rawProjectKey));
  138. }
  139. if (!errors.isEmpty()) {
  140. throw BadRequestException.create(errors);
  141. }
  142. }
  143. private static void ensureOrganizationIsConsistent(ComponentDto project, OrganizationDto organizationDto) {
  144. checkArgument(project.getOrganizationUuid().equals(organizationDto.getUuid()),
  145. "Organization of component with key '%s' does not match specified organization '%s'",
  146. project.getDbKey(), organizationDto.getKey());
  147. }
  148. private ComponentDto createProject(DbSession dbSession, OrganizationDto organization, BranchSupport.ComponentKey componentKey, @Nullable String projectName) {
  149. userSession.checkPermission(OrganizationPermission.PROVISION_PROJECTS);
  150. String userUuid = userSession.getUuid();
  151. boolean wouldCurrentUserHaveScanPermission = permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(
  152. dbSession, userUuid, componentKey.getDbKey());
  153. if (!wouldCurrentUserHaveScanPermission) {
  154. throw insufficientPrivilegesException();
  155. }
  156. boolean newProjectPrivate = dbClient.organizationDao().getNewProjectPrivate(dbSession, organization);
  157. NewComponent newProject = newComponentBuilder()
  158. .setOrganizationUuid(organization.getUuid())
  159. .setKey(componentKey.getKey())
  160. .setName(defaultIfBlank(projectName, componentKey.getKey()))
  161. .setQualifier(Qualifiers.PROJECT)
  162. .setPrivate(newProjectPrivate)
  163. .build();
  164. return componentUpdater.createWithoutCommit(dbSession, newProject, userUuid, c -> {
  165. });
  166. }
  167. private CeTask submitReport(DbSession dbSession, InputStream reportInput, ComponentDto project, Map<String, String> characteristics) {
  168. CeTaskSubmit.Builder submit = queue.prepareSubmit();
  169. // the report file must be saved before submitting the task
  170. dbClient.ceTaskInputDao().insert(dbSession, submit.getUuid(), reportInput);
  171. dbSession.commit();
  172. submit.setType(CeTaskTypes.REPORT);
  173. submit.setComponent(CeTaskSubmit.Component.fromDto(project));
  174. submit.setSubmitterUuid(userSession.getUuid());
  175. submit.setCharacteristics(characteristics);
  176. return queue.submit(submit.build());
  177. }
  178. }