]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19784 Synchronize visibility from GitHub
authorAurelien Poscia <aurelien.poscia@sonarsource.com>
Tue, 4 Jul 2023 08:32:44 +0000 (10:32 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 18 Jul 2023 20:03:21 +0000 (20:03 +0000)
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-server-common/src/main/java/org/sonar/server/project/VisibilityService.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/UpdateVisibilityActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index 0716d8aa40ab8efd37b698c75eadf650684eaafd..3ac0a9681693a0539c52ecdcae9da9ab5ad4ceac 100644 (file)
@@ -132,6 +132,7 @@ import org.sonar.server.platform.serverid.ServerIdChecksum;
 import org.sonar.server.plugins.InstalledPluginReferentialFactory;
 import org.sonar.server.plugins.ServerExtensionInstaller;
 import org.sonar.server.project.DefaultBranchNameResolver;
+import org.sonar.server.project.VisibilityService;
 import org.sonar.server.property.InternalPropertiesImpl;
 import org.sonar.server.qualitygate.QualityGateEvaluatorImpl;
 import org.sonar.server.qualitygate.QualityGateFinder;
@@ -441,6 +442,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       ProjectConfigurationFactory.class,
 
       DefaultBranchNameResolver.class,
+      VisibilityService.class,
       // webhooks
       new WebhookModule(),
 
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/project/VisibilityService.java b/server/sonar-server-common/src/main/java/org/sonar/server/project/VisibilityService.java
new file mode 100644 (file)
index 0000000..d9e57b1
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.project;
+
+import java.util.Optional;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.entity.EntityDto;
+import org.sonar.db.permission.GroupPermissionDto;
+import org.sonar.db.permission.UserPermissionDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserId;
+import org.sonar.server.es.Indexers;
+
+import static java.util.Collections.singletonList;
+import static java.util.Optional.ofNullable;
+import static org.sonar.api.utils.Preconditions.checkState;
+import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS;
+
+@ServerSide
+@ComputeEngineSide
+public class VisibilityService {
+  private final DbClient dbClient;
+  private final Indexers indexers;
+  private final UuidFactory uuidFactory;
+
+  public VisibilityService(DbClient dbClient,  Indexers indexers, UuidFactory uuidFactory) {
+    this.dbClient = dbClient;
+    this.indexers = indexers;
+    this.uuidFactory = uuidFactory;
+  }
+  public void changeVisibility(String entityUuid, boolean isPrivate) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      EntityDto entityDto = dbClient.entityDao().selectByUuid(dbSession, entityUuid)
+        .orElseThrow(() -> new IllegalStateException("Component must be a project, a portfolio or an application"));
+      changeVisibility(entityDto, isPrivate);
+    }
+  }
+
+  public void changeVisibility(EntityDto entityDto, boolean isPrivate) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      checkNoPendingTasks(dbSession, entityDto);
+      if (isPrivate != entityDto.isPrivate()) {
+        setPrivateForRootComponentUuid(dbSession, entityDto, isPrivate);
+        if (isPrivate) {
+          updatePermissionsToPrivate(dbSession, entityDto);
+        } else {
+          updatePermissionsToPublic(dbSession, entityDto);
+        }
+        indexers.commitAndIndexEntities(dbSession, singletonList(entityDto), Indexers.EntityEvent.PERMISSION_CHANGE);
+      }
+    }
+  }
+  private void checkNoPendingTasks(DbSession dbSession, EntityDto entityDto) {
+    //This check likely can be removed when we remove the column 'private' from components table
+    checkState(noPendingTask(dbSession, entityDto.getKey()), "Component visibility can't be changed as long as it has background task(s) pending or in progress");
+  }
+
+  private boolean noPendingTask(DbSession dbSession, String entityKey) {
+    EntityDto entityDto = dbClient.entityDao().selectByKey(dbSession, entityKey).orElseThrow(() -> new IllegalStateException("Can't find entity " + entityKey));
+    return dbClient.ceQueueDao().selectByEntityUuid(dbSession, entityDto.getUuid()).isEmpty();
+  }
+
+  private void setPrivateForRootComponentUuid(DbSession dbSession, EntityDto entity, boolean newIsPrivate) {
+    Optional<BranchDto> branchDto = dbClient.branchDao().selectMainBranchByProjectUuid(dbSession, entity.getUuid());
+    String branchUuid = branchDto.isPresent() ? branchDto.get().getUuid() : entity.getUuid();
+    dbClient.componentDao().setPrivateForBranchUuid(dbSession, branchUuid, newIsPrivate, entity.getKey(), entity.getQualifier(), entity.getName());
+
+    if (entity.isProjectOrApp()) {
+      dbClient.projectDao().updateVisibility(dbSession, entity.getUuid(), newIsPrivate);
+
+      dbClient.branchDao().selectByProjectUuid(dbSession, entity.getUuid()).stream()
+        .filter(branch -> !branch.isMain())
+        .forEach(branch -> dbClient.componentDao().setPrivateForBranchUuidWithoutAuditLog(dbSession, branch.getUuid(), newIsPrivate));
+    } else {
+      dbClient.portfolioDao().updateVisibilityByPortfolioUuid(dbSession, entity.getUuid(), newIsPrivate);
+    }
+  }
+
+  private void updatePermissionsToPrivate(DbSession dbSession, EntityDto entity) {
+    // delete project permissions for group AnyOne
+    dbClient.groupPermissionDao().deleteByEntityUuidForAnyOne(dbSession, entity);
+    // grant UserRole.CODEVIEWER and UserRole.USER to any group or user with at least one permission on project
+    PUBLIC_PERMISSIONS.forEach(permission -> {
+      dbClient.groupPermissionDao().selectGroupUuidsWithPermissionOnEntityBut(dbSession, entity.getUuid(), permission)
+        .forEach(group -> insertProjectPermissionOnGroup(dbSession, entity, permission, group));
+      dbClient.userPermissionDao().selectUserIdsWithPermissionOnEntityBut(dbSession, entity.getUuid(), permission)
+        .forEach(userUuid -> insertProjectPermissionOnUser(dbSession, entity, permission, userUuid));
+    });
+  }
+
+  private void insertProjectPermissionOnUser(DbSession dbSession, EntityDto entity, String permission, UserId userId) {
+    dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto(Uuids.create(), permission, userId.getUuid(), entity.getUuid()),
+      entity, userId, null);
+  }
+
+  private void insertProjectPermissionOnGroup(DbSession dbSession, EntityDto entity, String permission, String groupUuid) {
+    String groupName = ofNullable(dbClient.groupDao().selectByUuid(dbSession, groupUuid)).map(GroupDto::getName).orElse(null);
+    dbClient.groupPermissionDao().insert(dbSession, new GroupPermissionDto()
+      .setUuid(uuidFactory.create())
+      .setEntityUuid(entity.getUuid())
+      .setGroupUuid(groupUuid)
+      .setGroupName(groupName)
+      .setRole(permission)
+      .setEntityName(entity.getName()), entity, null);
+  }
+
+  private void updatePermissionsToPublic(DbSession dbSession, EntityDto entity) {
+    PUBLIC_PERMISSIONS.forEach(permission -> {
+      // delete project group permission for UserRole.CODEVIEWER and UserRole.USER
+      dbClient.groupPermissionDao().deleteByEntityAndPermission(dbSession, permission, entity);
+      // delete project user permission for UserRole.CODEVIEWER and UserRole.USER
+      dbClient.userPermissionDao().deleteEntityPermissionOfAnyUser(dbSession, permission, entity);
+    });
+  }
+}
index 7c5bc1a594e8b7dc73306aea859e62b3ee132936..861b74b42b3d2b20bfba0b34fca3c7def32f7e8b 100644 (file)
@@ -67,6 +67,7 @@ import org.sonar.server.management.ManagedProjectService;
 import org.sonar.server.permission.PermissionService;
 import org.sonar.server.permission.PermissionServiceImpl;
 import org.sonar.server.permission.index.FooIndexDefinition;
+import org.sonar.server.project.VisibilityService;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -111,8 +112,8 @@ public class UpdateVisibilityActionIT {
   private final Configuration configuration = mock(Configuration.class);
   private final DelegatingManagedInstanceService delegatingManagedInstanceService = new DelegatingManagedInstanceService(Set.of(new ControllableManagedProjectService()));
 
-  private final UpdateVisibilityAction underTest = new UpdateVisibilityAction(dbClient, userSessionRule, projectIndexers, new SequenceUuidFactory(), configuration,
-    delegatingManagedInstanceService);
+  private final VisibilityService visibilityService = new VisibilityService(dbClient, projectIndexers, new SequenceUuidFactory());
+  private final UpdateVisibilityAction underTest = new UpdateVisibilityAction(dbClient, userSessionRule, configuration, visibilityService, delegatingManagedInstanceService);
   private final WsActionTester ws = new WsActionTester(underTest);
 
   private final Random random = new Random();
@@ -281,7 +282,7 @@ public class UpdateVisibilityActionIT {
   }
 
   @Test
-  public void execute_throws_BadRequestException_if_specified_component_has_pending_tasks() {
+  public void execute_throws_IllegalStateException_if_specified_component_has_pending_tasks() {
     ProjectData project = randomPublicOrPrivateProject();
     IntStream.range(0, 1 + Math.abs(random.nextInt(5)))
       .forEach(i -> insertPendingTask(project.getMainBranchDto()));
@@ -290,12 +291,12 @@ public class UpdateVisibilityActionIT {
     userSessionRule.addProjectPermission(UserRole.ADMIN, project.getProjectDto());
 
     assertThatThrownBy(request::execute)
-      .isInstanceOf(BadRequestException.class)
+      .isInstanceOf(IllegalStateException.class)
       .hasMessage("Component visibility can't be changed as long as it has background task(s) pending or in progress");
   }
 
   @Test
-  public void execute_throws_BadRequestException_if_main_component_of_specified_component_has_in_progress_tasks() {
+  public void execute_throws_IllegalStateException_if_main_component_of_specified_component_has_in_progress_tasks() {
     ProjectData project = randomPublicOrPrivateProject();
     IntStream.range(0, 1 + Math.abs(random.nextInt(5)))
       .forEach(i -> insertInProgressTask(project.getMainBranchDto()));
@@ -304,7 +305,7 @@ public class UpdateVisibilityActionIT {
     userSessionRule.addProjectPermission(UserRole.ADMIN, project.getProjectDto());
 
     assertThatThrownBy(request::execute)
-      .isInstanceOf(BadRequestException.class)
+      .isInstanceOf(IllegalStateException.class)
       .hasMessage("Component visibility can't be changed as long as it has background task(s) pending or in progress");
   }
 
index 9c924d89ff58e0ff584a02c742aec44e5291dcd1..f8275601eb6faec7cab1c4528ccf5a6a603b62ca 100644 (file)
  */
 package org.sonar.server.project.ws;
 
-import java.util.Optional;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.component.BranchDto;
 import org.sonar.db.entity.EntityDto;
-import org.sonar.db.permission.GroupPermissionDto;
-import org.sonar.db.permission.UserPermissionDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserId;
-import org.sonar.server.es.Indexers;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.management.DelegatingManagedInstanceService;
 import org.sonar.server.project.Visibility;
+import org.sonar.server.project.VisibilityService;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.client.project.ProjectsWsParameters;
 
-import static java.util.Collections.singletonList;
-import static java.util.Optional.ofNullable;
 import static org.sonar.api.CoreProperties.CORE_ALLOW_PERMISSION_MANAGEMENT_FOR_PROJECT_ADMINS_DEFAULT_VALUE;
 import static org.sonar.api.CoreProperties.CORE_ALLOW_PERMISSION_MANAGEMENT_FOR_PROJECT_ADMINS_PROPERTY;
 import static org.sonar.api.web.UserRole.ADMIN;
-import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS;
 import static org.sonar.server.exceptions.BadRequestException.checkRequest;
 import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
@@ -57,19 +46,17 @@ public class UpdateVisibilityAction implements ProjectsWsAction {
 
   private final DbClient dbClient;
   private final UserSession userSession;
-  private final Indexers indexers;
-  private final UuidFactory uuidFactory;
   private final Configuration configuration;
+  private final VisibilityService visibilityService;
   private final DelegatingManagedInstanceService delegatingManagedInstanceService;
 
-  public UpdateVisibilityAction(DbClient dbClient, UserSession userSession, Indexers indexers, UuidFactory uuidFactory, Configuration configuration,
-    DelegatingManagedInstanceService delegatingManagedInstanceService) {
+  public UpdateVisibilityAction(DbClient dbClient, UserSession userSession, Configuration configuration,
+    VisibilityService visibilityService, DelegatingManagedInstanceService delegatingManagedInstanceService) {
     this.dbClient = dbClient;
     this.userSession = userSession;
-    this.indexers = indexers;
-    this.uuidFactory = uuidFactory;
     this.configuration = configuration;
     this.delegatingManagedInstanceService = delegatingManagedInstanceService;
+    this.visibilityService = visibilityService;
   }
 
   public void define(WebService.NewController context) {
@@ -91,23 +78,6 @@ public class UpdateVisibilityAction implements ProjectsWsAction {
       .setRequired(true);
   }
 
-  private void validateRequest(DbSession dbSession, EntityDto entityDto) {
-    boolean isGlobalAdmin = userSession.isSystemAdministrator();
-    boolean isProjectAdmin = userSession.hasEntityPermission(ADMIN, entityDto);
-    boolean allowChangingPermissionsByProjectAdmins = configuration.getBoolean(CORE_ALLOW_PERMISSION_MANAGEMENT_FOR_PROJECT_ADMINS_PROPERTY)
-      .orElse(CORE_ALLOW_PERMISSION_MANAGEMENT_FOR_PROJECT_ADMINS_DEFAULT_VALUE);
-    if (!isProjectAdmin || (!isGlobalAdmin && !allowChangingPermissionsByProjectAdmins)) {
-      throw insufficientPrivilegesException();
-    }
-    checkRequest(notManagedProject(dbSession, entityDto), "Cannot change visibility of a managed project");
-    //This check likely can be removed when we remove the column 'private' from components table
-    checkRequest(noPendingTask(dbSession, entityDto.getKey()), "Component visibility can't be changed as long as it has background task(s) pending or in progress");
-  }
-
-  private boolean notManagedProject(DbSession dbSession, EntityDto entityDto) {
-    return !entityDto.isProject() || !delegatingManagedInstanceService.isProjectManaged(dbSession, entityDto.getKey());
-  }
-
   @Override
   public void handle(Request request, Response response) throws Exception {
     userSession.checkLoggedIn();
@@ -120,76 +90,25 @@ public class UpdateVisibilityAction implements ProjectsWsAction {
         .orElseThrow(() -> BadRequestException.create("Component must be a project, a portfolio or an application"));
 
       validateRequest(dbSession, entityDto);
-      if (changeToPrivate != entityDto.isPrivate()) {
-        setPrivateForRootComponentUuid(dbSession, entityDto, changeToPrivate);
-        if (changeToPrivate) {
-          updatePermissionsToPrivate(dbSession, entityDto);
-        } else {
-          updatePermissionsToPublic(dbSession, entityDto);
-        }
-        indexers.commitAndIndexEntities(dbSession, singletonList(entityDto), Indexers.EntityEvent.PERMISSION_CHANGE);
-      }
-
+      visibilityService.changeVisibility(entityDto, changeToPrivate);
       response.noContent();
     }
   }
 
-  private void setPrivateForRootComponentUuid(DbSession dbSession, EntityDto entity, boolean newIsPrivate) {
-    Optional<BranchDto> branchDto = dbClient.branchDao().selectMainBranchByProjectUuid(dbSession, entity.getUuid());
-    String branchUuid = branchDto.isPresent() ? branchDto.get().getUuid() : entity.getUuid();
-    dbClient.componentDao().setPrivateForBranchUuid(dbSession, branchUuid, newIsPrivate, entity.getKey(), entity.getQualifier(), entity.getName());
-
-    if (entity.isProjectOrApp()) {
-      dbClient.projectDao().updateVisibility(dbSession, entity.getUuid(), newIsPrivate);
-
-      dbClient.branchDao().selectByProjectUuid(dbSession, entity.getUuid()).stream()
-        .filter(branch -> !branch.isMain())
-        .forEach(branch -> dbClient.componentDao().setPrivateForBranchUuidWithoutAuditLog(dbSession, branch.getUuid(), newIsPrivate));
-    } else {
-      dbClient.portfolioDao().updateVisibilityByPortfolioUuid(dbSession, entity.getUuid(), newIsPrivate);
+  private void validateRequest(DbSession dbSession, EntityDto entityDto) {
+    boolean isGlobalAdmin = userSession.isSystemAdministrator();
+    boolean isProjectAdmin = userSession.hasEntityPermission(ADMIN, entityDto);
+    boolean allowChangingPermissionsByProjectAdmins = configuration.getBoolean(CORE_ALLOW_PERMISSION_MANAGEMENT_FOR_PROJECT_ADMINS_PROPERTY)
+      .orElse(CORE_ALLOW_PERMISSION_MANAGEMENT_FOR_PROJECT_ADMINS_DEFAULT_VALUE);
+    if (!isProjectAdmin || (!isGlobalAdmin && !allowChangingPermissionsByProjectAdmins)) {
+      throw insufficientPrivilegesException();
     }
+    checkRequest(notManagedProject(dbSession, entityDto), "Cannot change visibility of a managed project");
   }
 
-  private boolean noPendingTask(DbSession dbSession, String entityKey) {
-    EntityDto entityDto = dbClient.entityDao().selectByKey(dbSession, entityKey).orElseThrow(() -> new IllegalStateException("Can't find entity " + entityKey));
-    return dbClient.ceQueueDao().selectByEntityUuid(dbSession, entityDto.getUuid()).isEmpty();
-  }
-
-  private void updatePermissionsToPrivate(DbSession dbSession, EntityDto entity) {
-    // delete project permissions for group AnyOne
-    dbClient.groupPermissionDao().deleteByEntityUuidForAnyOne(dbSession, entity);
-    // grant UserRole.CODEVIEWER and UserRole.USER to any group or user with at least one permission on project
-    PUBLIC_PERMISSIONS.forEach(permission -> {
-      dbClient.groupPermissionDao().selectGroupUuidsWithPermissionOnEntityBut(dbSession, entity.getUuid(), permission)
-        .forEach(group -> insertProjectPermissionOnGroup(dbSession, entity, permission, group));
-      dbClient.userPermissionDao().selectUserIdsWithPermissionOnEntityBut(dbSession, entity.getUuid(), permission)
-        .forEach(userUuid -> insertProjectPermissionOnUser(dbSession, entity, permission, userUuid));
-    });
-  }
-
-  private void insertProjectPermissionOnUser(DbSession dbSession, EntityDto entity, String permission, UserId userId) {
-    dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto(Uuids.create(), permission, userId.getUuid(), entity.getUuid()),
-      entity, userId, null);
-  }
-
-  private void insertProjectPermissionOnGroup(DbSession dbSession, EntityDto entity, String permission, String groupUuid) {
-    String groupName = ofNullable(dbClient.groupDao().selectByUuid(dbSession, groupUuid)).map(GroupDto::getName).orElse(null);
-    dbClient.groupPermissionDao().insert(dbSession, new GroupPermissionDto()
-      .setUuid(uuidFactory.create())
-      .setEntityUuid(entity.getUuid())
-      .setGroupUuid(groupUuid)
-      .setGroupName(groupName)
-      .setRole(permission)
-      .setEntityName(entity.getName()), entity, null);
-  }
 
-  private void updatePermissionsToPublic(DbSession dbSession, EntityDto entity) {
-    PUBLIC_PERMISSIONS.forEach(permission -> {
-      // delete project group permission for UserRole.CODEVIEWER and UserRole.USER
-      dbClient.groupPermissionDao().deleteByEntityAndPermission(dbSession, permission, entity);
-      // delete project user permission for UserRole.CODEVIEWER and UserRole.USER
-      dbClient.userPermissionDao().deleteEntityPermissionOfAnyUser(dbSession, permission, entity);
-    });
+  private boolean notManagedProject(DbSession dbSession, EntityDto entityDto) {
+    return !entityDto.isProject() || !delegatingManagedInstanceService.isProjectManaged(dbSession, entityDto.getKey());
   }
 
 }
index dfc05cadc6a29363d1689dce25b254c4e3f2971b..687029da316aa6c4940aab09d056297078803125 100644 (file)
@@ -188,6 +188,7 @@ import org.sonar.server.plugins.ws.UninstallAction;
 import org.sonar.server.plugins.ws.UpdatesAction;
 import org.sonar.server.project.DefaultBranchNameResolver;
 import org.sonar.server.project.ProjectQGChangeEventListener;
+import org.sonar.server.project.VisibilityService;
 import org.sonar.server.project.ws.ProjectsWsModule;
 import org.sonar.server.projectanalysis.ws.ProjectAnalysisWsModule;
 import org.sonar.server.projectlink.ws.ProjectLinksModule;
@@ -419,6 +420,7 @@ public class PlatformLevel4 extends PlatformLevel {
       GroupPermissionChanger.class,
       CheckAnyonePermissionsAtStartup.class,
       CheckLanguageSpecificParamsAtStartup.class,
+      VisibilityService.class,
 
       // components
       new BranchWsModule(),