diff options
author | Antoine Vigneau <antoine.vigneau@sonarsource.com> | 2023-07-03 11:43:58 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-07-18 20:03:21 +0000 |
commit | 5a07fe8d8a5ef407beef94fb410d65cb7d990c98 (patch) | |
tree | 57b9d55c065a694cf1300a55d577892fe93bd4ab | |
parent | 7eb69f2d9f83b568817107e8064a688246e94299 (diff) | |
download | sonarqube-5a07fe8d8a5ef407beef94fb410d65cb7d990c98.tar.gz sonarqube-5a07fe8d8a5ef407beef94fb410d65cb7d990c98.zip |
SONAR-19784 Do not allow visibility update on managed projects
6 files changed, 165 insertions, 14 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/entity/EntityDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/entity/EntityDto.java index 39932908c59..e6d20322ab5 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/entity/EntityDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/entity/EntityDto.java @@ -82,8 +82,12 @@ public class EntityDto { return Qualifiers.VIEW.equals(qualifier) || Qualifiers.SUBVIEW.equals(qualifier); } + public boolean isProject() { + return Qualifiers.PROJECT.equals(qualifier); + } + public boolean isProjectOrApp() { - return Qualifiers.APP.equals(qualifier) || Qualifiers.PROJECT.equals(qualifier); + return Qualifiers.APP.equals(qualifier) || isProject(); } @Override diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java b/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java index 98ca712eb60..9b9e109db0e 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java @@ -34,7 +34,7 @@ import static org.sonar.api.utils.Preconditions.checkState; @ServerSide @Priority(ManagedInstanceService.DELEGATING_INSTANCE_PRIORITY) -public class DelegatingManagedInstanceService implements ManagedInstanceService { +public class DelegatingManagedInstanceService implements ManagedInstanceService, ManagedProjectService { private static final IllegalStateException NOT_MANAGED_INSTANCE_EXCEPTION = new IllegalStateException("This instance is not managed."); private final Set<ManagedInstanceService> delegates; @@ -58,14 +58,14 @@ public class DelegatingManagedInstanceService implements ManagedInstanceService public Map<String, Boolean> getUserUuidToManaged(DbSession dbSession, Set<String> userUuids) { return findManagedInstanceService() .map(managedInstanceService -> managedInstanceService.getUserUuidToManaged(dbSession, userUuids)) - .orElse(returnNonManagedForAllGroups(userUuids)); + .orElse(returnNonManagedForAll(userUuids)); } @Override public Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids) { return findManagedInstanceService() .map(managedInstanceService -> managedInstanceService.getGroupUuidToManaged(dbSession, groupUuids)) - .orElse(returnNonManagedForAllGroups(groupUuids)); + .orElse(returnNonManagedForAll(groupUuids)); } @Override @@ -99,7 +99,20 @@ public class DelegatingManagedInstanceService implements ManagedInstanceService return managedInstanceServices.stream().collect(MoreCollectors.toOptional()); } - private static Map<String, Boolean> returnNonManagedForAllGroups(Set<String> resourcesUuid) { + private static Map<String, Boolean> returnNonManagedForAll(Set<String> resourcesUuid) { return resourcesUuid.stream().collect(toMap(identity(), any -> false)); } + + @Override + public boolean isProjectManaged(DbSession dbSession, String projectKey) { + return findManagedProjectService() + .map(managedProjectService -> managedProjectService.isProjectManaged(dbSession, projectKey)) + .orElse(false); + } + + private Optional<ManagedProjectService> findManagedProjectService() { + return findManagedInstanceService() + .filter(ManagedProjectService.class::isInstance) + .map(ManagedProjectService.class::cast); + } } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java b/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java new file mode 100644 index 00000000000..b3d765ba0aa --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java @@ -0,0 +1,28 @@ +/* + * 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.management; + +import org.sonar.db.DbSession; + +public interface ManagedProjectService { + + boolean isProjectManaged(DbSession dbSession, String projectKey); + +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java index f2ecf25fd4f..1aa7acd4187 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java @@ -21,6 +21,7 @@ package org.sonar.server.management; import java.util.Map; import java.util.Set; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -42,9 +43,9 @@ public class DelegatingManagedInstanceServiceTest { @Test public void getProviderName_whenNotManaged_shouldThrow() { - DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet()); + DelegatingManagedInstanceService managedInstanceService = noManagedInstanceService(); - assertThatThrownBy(() -> managedInstanceService.getProviderName()) + assertThatThrownBy(managedInstanceService::getProviderName) .isInstanceOf(IllegalStateException.class) .hasMessage("This instance is not managed."); } @@ -58,7 +59,7 @@ public class DelegatingManagedInstanceServiceTest { @Test public void isInstanceExternallyManaged_whenNoManagedInstanceService_returnsFalse() { - DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet()); + DelegatingManagedInstanceService managedInstanceService = noManagedInstanceService(); assertThat(managedInstanceService.isInstanceExternallyManaged()).isFalse(); } @@ -81,7 +82,7 @@ public class DelegatingManagedInstanceServiceTest { @Test public void getUserUuidToManaged_whenNoDelegates_setAllUsersAsNonManaged() { Set<String> userUuids = Set.of("a", "b"); - DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet()); + DelegatingManagedInstanceService managedInstanceService = noManagedInstanceService(); Map<String, Boolean> userUuidToManaged = managedInstanceService.getUserUuidToManaged(dbSession, userUuids); @@ -104,7 +105,7 @@ public class DelegatingManagedInstanceServiceTest { @Test public void getGroupUuidToManaged_whenNoDelegates_setAllUsersAsNonManaged() { Set<String> groupUuids = Set.of("a", "b"); - DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet()); + DelegatingManagedInstanceService managedInstanceService = noManagedInstanceService(); Map<String, Boolean> groupUuidToManaged = managedInstanceService.getGroupUuidToManaged(dbSession, groupUuids); @@ -200,7 +201,26 @@ public class DelegatingManagedInstanceServiceTest { return anotherManagedInstanceService; } - private static class NeverManagedInstanceService implements ManagedInstanceService { + @Test + public void isProjectManaged_whenManagedInstanceServices_shouldDelegatesToRightService() { + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(Set.of(new NeverManagedInstanceService(), new AlwaysManagedInstanceService())); + + assertThat(managedInstanceService.isProjectManaged(dbSession, "project_key")).isTrue(); + } + + @Test + public void isProjectManaged_whenManagedNoInstanceServices_returnsFalse() { + DelegatingManagedInstanceService managedInstanceService = noManagedInstanceService(); + + assertThat(managedInstanceService.isProjectManaged(dbSession, "project_key")).isFalse(); + } + + @NotNull + private static DelegatingManagedInstanceService noManagedInstanceService() { + return new DelegatingManagedInstanceService(emptySet()); + } + + private static class NeverManagedInstanceService implements ManagedInstanceService, ManagedProjectService { @Override public boolean isInstanceExternallyManaged() { @@ -236,9 +256,14 @@ public class DelegatingManagedInstanceServiceTest { public boolean isUserManaged(DbSession dbSession, String login) { return false; } + + @Override + public boolean isProjectManaged(DbSession dbSession, String projectKey) { + return false; + } } - private static class AlwaysManagedInstanceService implements ManagedInstanceService { + private static class AlwaysManagedInstanceService implements ManagedInstanceService, ManagedProjectService { @Override public boolean isInstanceExternallyManaged() { @@ -274,6 +299,11 @@ public class DelegatingManagedInstanceServiceTest { public boolean isUserManaged(DbSession dbSession, String login) { return true; } + + @Override + public boolean isProjectManaged(DbSession dbSession, String projectKey) { + return true; + } } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/UpdateVisibilityActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/UpdateVisibilityActionIT.java index 295b305a2ea..7c5bc1a594e 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/UpdateVisibilityActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/UpdateVisibilityActionIT.java @@ -20,11 +20,13 @@ package org.sonar.server.project.ws; import java.util.Arrays; +import java.util.Map; import java.util.Random; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import org.apache.commons.lang.NotImplementedException; import org.junit.Rule; import org.junit.Test; import org.sonar.api.config.Configuration; @@ -59,6 +61,9 @@ import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.management.DelegatingManagedInstanceService; +import org.sonar.server.management.ManagedInstanceService; +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; @@ -82,6 +87,7 @@ public class UpdateVisibilityActionIT { private static final String PARAM_PROJECT = "project"; private static final String PUBLIC = "public"; private static final String PRIVATE = "private"; + private static final String MANAGED_PROJECT_KEY = "managed_project_key"; private static final Set<String> GLOBAL_PERMISSIONS_NAME_SET = stream(GlobalPermission.values()).map(GlobalPermission::getKey) .collect(Collectors.toSet()); @@ -103,8 +109,10 @@ public class UpdateVisibilityActionIT { private final DbSession dbSession = dbTester.getSession(); private final TestIndexers projectIndexers = new TestIndexers(); 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); + private final UpdateVisibilityAction underTest = new UpdateVisibilityAction(dbClient, userSessionRule, projectIndexers, new SequenceUuidFactory(), configuration, + delegatingManagedInstanceService); private final WsActionTester ws = new WsActionTester(underTest); private final Random random = new Random(); @@ -301,6 +309,18 @@ public class UpdateVisibilityActionIT { } @Test + public void execute_throws_BadRequestException_if_entity_is_project_and_managed() { + ProjectDto project = dbTester.components().insertPublicProject(p -> p.setKey(MANAGED_PROJECT_KEY)).getProjectDto(); + request.setParam(PARAM_PROJECT, project.getKey()) + .setParam(PARAM_VISIBILITY, randomVisibility); + userSessionRule.addProjectPermission(UserRole.ADMIN, project); + + assertThatThrownBy(request::execute) + .isInstanceOf(BadRequestException.class) + .hasMessage("Cannot change visibility of a managed project"); + } + + @Test public void execute_changes_private_flag_of_specified_project_and_all_children_to_specified_new_visibility() { ProjectData project = randomPublicOrPrivateProject(); boolean initiallyPrivate = project.getProjectDto().isPrivate(); @@ -695,4 +715,51 @@ public class UpdateVisibilityActionIT { private ProjectData randomPublicOrPrivateProject() { return random.nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); } + + private static class ControllableManagedProjectService implements ManagedInstanceService, ManagedProjectService { + + @Override + public boolean isInstanceExternallyManaged() { + return true; + } + + @Override + public String getProviderName() { + throw new NotImplementedException(); + } + + @Override + public Map<String, Boolean> getUserUuidToManaged(DbSession dbSession, Set<String> userUuids) { + throw new NotImplementedException(); + } + + @Override + public Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids) { + throw new NotImplementedException(); + } + + @Override + public String getManagedUsersSqlFilter(boolean filterByManaged) { + throw new NotImplementedException(); + } + + @Override + public String getManagedGroupsSqlFilter(boolean filterByManaged) { + throw new NotImplementedException(); + } + + @Override + public boolean isUserManaged(DbSession dbSession, String login) { + throw new NotImplementedException(); + } + + @Override + public boolean isProjectManaged(DbSession dbSession, String projectKey) { + if (projectKey.equals(MANAGED_PROJECT_KEY)) { + return true; + } + return false; + } + } + } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java index ca2ea9bdffe..9c924d89ff5 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java @@ -36,6 +36,7 @@ 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.user.UserSession; import org.sonarqube.ws.client.project.ProjectsWsParameters; @@ -59,13 +60,16 @@ public class UpdateVisibilityAction implements ProjectsWsAction { private final Indexers indexers; private final UuidFactory uuidFactory; private final Configuration configuration; + private final DelegatingManagedInstanceService delegatingManagedInstanceService; - public UpdateVisibilityAction(DbClient dbClient, UserSession userSession, Indexers indexers, UuidFactory uuidFactory, Configuration configuration) { + public UpdateVisibilityAction(DbClient dbClient, UserSession userSession, Indexers indexers, UuidFactory uuidFactory, Configuration configuration, + DelegatingManagedInstanceService delegatingManagedInstanceService) { this.dbClient = dbClient; this.userSession = userSession; this.indexers = indexers; this.uuidFactory = uuidFactory; this.configuration = configuration; + this.delegatingManagedInstanceService = delegatingManagedInstanceService; } public void define(WebService.NewController context) { @@ -95,10 +99,15 @@ public class UpdateVisibilityAction implements ProjectsWsAction { 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(); |