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;
ProjectConfigurationFactory.class,
DefaultBranchNameResolver.class,
+ VisibilityService.class,
// webhooks
new WebhookModule(),
--- /dev/null
+/*
+ * 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);
+ });
+ }
+}
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;
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();
}
@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()));
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()));
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");
}
*/
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;
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) {
.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();
.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());
}
}
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;
GroupPermissionChanger.class,
CheckAnyonePermissionsAtStartup.class,
CheckLanguageSpecificParamsAtStartup.class,
+ VisibilityService.class,
// components
new BranchWsModule(),