diff options
author | klaudio-sinani-sonarsource <92299827+klaudio-sinani-sonarsource@users.noreply.github.com> | 2022-01-11 12:49:37 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-01-20 20:02:43 +0000 |
commit | 110208dd8037b2cbf63df1ae17fb523c057d2046 (patch) | |
tree | b1503539eb1e3cbdf51cf729f29dabcc77be7163 /server/sonar-webserver-auth/src | |
parent | 5dd324c60877c4445460bde30ab3e3cc25fc7a7f (diff) | |
download | sonarqube-110208dd8037b2cbf63df1ae17fb523c057d2046.tar.gz sonarqube-110208dd8037b2cbf63df1ae17fb523c057d2046.zip |
SONAR-15877 Flag portfolios that contain inaccessible components (#5239)
Diffstat (limited to 'server/sonar-webserver-auth/src')
13 files changed, 217 insertions, 0 deletions
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java index 7bc12dd1ef4..c14babcfdc4 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java @@ -51,6 +51,11 @@ public class SafeModeUserSession extends AbstractUserSession { return false; } + @Override + protected boolean hasPortfolioChildProjectsPermission(String permission, String portfolioUuid) { + return false; + } + @CheckForNull @Override public String getLogin() { diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java index 9fec50125fc..29914eb211f 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java @@ -131,6 +131,15 @@ public abstract class AbstractUserSession implements UserSession { } @Override + public final boolean hasPortfolioChildProjectsPermission(String permission, ComponentDto portfolio) { + if (isRoot()) { + return true; + } + + return hasPortfolioChildProjectsPermission(permission, portfolio.uuid()); + } + + @Override public final boolean hasComponentUuidPermission(String permission, String componentUuid) { if (isRoot()) { return true; @@ -147,6 +156,8 @@ public abstract class AbstractUserSession implements UserSession { protected abstract boolean hasChildProjectsPermission(String permission, String applicationUuid); + protected abstract boolean hasPortfolioChildProjectsPermission(String permission, String portfolioUuid); + @Override public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) { if (isRoot()) { diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java index ed2770c08c0..c4a504d7a7b 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java @@ -133,6 +133,11 @@ public final class DoPrivileged { } @Override + protected boolean hasPortfolioChildProjectsPermission(String permission, String applicationUuid) { + return true; + } + + @Override public boolean isSystemAdministrator() { return true; } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java index 0131f2ab492..7acb465d77d 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java @@ -19,6 +19,7 @@ */ package org.sonar.server.user; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -27,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.resources.Qualifiers; @@ -181,6 +183,22 @@ public class ServerUserSession extends AbstractUserSession { .allMatch(Boolean::valueOf); } + @Override + protected boolean hasPortfolioChildProjectsPermission(String permission, String portfolioUuid) { + if (permissionsByProjectUuid == null) { + permissionsByProjectUuid = new HashMap<>(); + } + + Set<ComponentDto> portfolioHierarchyComponents = resolvePortfolioHierarchyComponents(portfolioUuid); + + Set<String> portfolioHierarchyComponentUuids = portfolioHierarchyComponents.stream().map(ComponentDto::getCopyComponentUuid).collect(Collectors.toSet()); + + return portfolioHierarchyComponentUuids + .stream() + .map(uuid -> hasPermission(permission, uuid)) + .allMatch(Boolean::valueOf); + } + private boolean hasPermission(String permission, String projectUuid) { Set<String> projectPermissions = permissionsByProjectUuid.computeIfAbsent(projectUuid, this::loadProjectPermissions); return projectPermissions.contains(permission); @@ -219,6 +237,41 @@ public class ServerUserSession extends AbstractUserSession { } } + private List<ComponentDto> getDirectChildComponents(String portfolioUuid) { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.componentDao().selectDescendants(dbSession, ComponentTreeQuery.builder() + .setBaseUuid(portfolioUuid) + .setQualifiers(Arrays.asList(Qualifiers.PROJECT, Qualifiers.SUBVIEW)) + .setStrategy(Strategy.CHILDREN).build()); + } + } + + private Set<ComponentDto> resolvePortfolioHierarchyComponents(String parentComponentUuid) { + Set<ComponentDto> portfolioHierarchyProjects = new HashSet<>(); + + resolvePortfolioHierarchyComponents(parentComponentUuid, portfolioHierarchyProjects); + + return portfolioHierarchyProjects; + } + + private void resolvePortfolioHierarchyComponents(String parentComponentUuid, Set<ComponentDto> hierarchyChildComponents) { + List<ComponentDto> childComponents = getDirectChildComponents(parentComponentUuid); + + if (childComponents.isEmpty()) { + return; + } + + childComponents.forEach(c -> { + if (c.getCopyComponentUuid() != null) { + hierarchyChildComponents.add(c); + } + + if (Qualifiers.SUBVIEW.equals(c.qualifier())) { + resolvePortfolioHierarchyComponents(c.uuid(), hierarchyChildComponents); + } + }); + } + private Set<GlobalPermission> loadGlobalPermissions() { Set<String> permissionKeys; try (DbSession dbSession = dbClient.openSession(false)) { diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java index 96096ccefe1..3dec0032d96 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java @@ -199,6 +199,11 @@ public class ThreadLocalUserSession implements UserSession { } @Override + public boolean hasPortfolioChildProjectsPermission(String permission, ComponentDto portfolio) { + return get().hasPortfolioChildProjectsPermission(permission, portfolio); + } + + @Override public boolean hasComponentUuidPermission(String permission, String componentUuid) { return get().hasComponentUuidPermission(permission, componentUuid); } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java index 61992db77cc..5350e578539 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java @@ -201,6 +201,8 @@ public interface UserSession { boolean hasChildProjectsPermission(String permission, ProjectDto component); + boolean hasPortfolioChildProjectsPermission(String permission, ComponentDto component); + /** * Using {@link #hasComponentPermission(String, ComponentDto)} is recommended * because it does not have to load project if the referenced component diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java index 23ea9457b94..80b2055f1d9 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java @@ -47,5 +47,6 @@ public class SafeModeUserSessionTest { assertThat(underTest.hasPermissionImpl(GlobalPermission.ADMINISTER)).isFalse(); assertThat(underTest.hasProjectUuidPermission(UserRole.USER, "foo")).isFalse(); assertThat(underTest.hasChildProjectsPermission(UserRole.USER, "foo")).isFalse(); + assertThat(underTest.hasPortfolioChildProjectsPermission(UserRole.USER, "foo")).isFalse(); } } diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java index 17dcd97dddb..6faaecd5fb1 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java @@ -52,6 +52,7 @@ public class DoPrivilegedTest { assertThat(catcher.userSession.isSystemAdministrator()).isTrue(); assertThat(catcher.userSession.shouldResetPassword()).isFalse(); assertThat(catcher.userSession.hasChildProjectsPermission(USER, new ComponentDto())).isTrue(); + assertThat(catcher.userSession.hasPortfolioChildProjectsPermission(USER, new ComponentDto())).isTrue(); // verify session in place after task is done assertThat(threadLocalUserSession.get()).isSameAs(session); diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java index ddebf91b08d..8fdd4ed8d83 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java @@ -45,6 +45,7 @@ import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.db.component.ComponentTesting.newChildComponent; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newProjectCopy; +import static org.sonar.db.component.ComponentTesting.newSubPortfolio; import static org.sonar.db.permission.GlobalPermission.ADMINISTER; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; import static org.sonar.db.permission.GlobalPermission.SCAN; @@ -396,6 +397,107 @@ public class ServerUserSessionTest { } @Test + public void test_hasPortfolioChildProjectsPermission_for_logged_in_user() { + ComponentDto project1 = db.components().insertPublicProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + ComponentDto project3 = db.components().insertPrivateProject(); + ComponentDto project4 = db.components().insertPrivateProject(); + + UserDto user = db.users().insertUser(); + UserSession session = newUserSession(user); + + ComponentDto portfolio = db.components().insertPrivatePortfolio(); + ComponentDto subPortfolio = db.components().insertComponent(newSubPortfolio(portfolio)); + + // Add public project1 to private portfolio + db.components().addPortfolioProject(portfolio, project1); + db.components().insertComponent(newProjectCopy(project1, portfolio)); + + assertThat(session.hasPortfolioChildProjectsPermission(USER, portfolio)).isTrue(); + + // Add private project2 with USER permissions to private portfolio + db.users().insertProjectPermissionOnUser(user, USER, project2); + db.components().addPortfolioProject(portfolio, project2); + db.components().insertComponent(newProjectCopy(project2, portfolio)); + + assertThat(session.hasPortfolioChildProjectsPermission(USER, portfolio)).isTrue(); + + // Add private project4 with USER permissions to sub-portfolio + db.users().insertProjectPermissionOnUser(user, USER, project4); + db.components().addPortfolioProject(subPortfolio, project4); + db.components().insertComponent(newProjectCopy(project4, subPortfolio)); + db.components().addPortfolioReference(portfolio, subPortfolio.uuid()); + + assertThat(session.hasPortfolioChildProjectsPermission(USER, portfolio)).isTrue(); + + // Add private project3 without permissions to private portfolio + db.components().addPortfolioProject(portfolio, project3); + db.components().insertComponent(newProjectCopy(project3, portfolio)); + + assertThat(session.hasChildProjectsPermission(USER, portfolio)).isFalse(); + } + + @Test + public void test_hasPortfolioChildProjectsPermission_for_anonymous_user() { + ComponentDto project = db.components().insertPrivateProject(); + + db.users().insertPermissionOnAnyone(USER); + + ComponentDto portfolio = db.components().insertPrivatePortfolio(); + + db.components().addPortfolioProject(portfolio, project); + // add computed project + db.components().insertComponent(newProjectCopy(project, portfolio)); + + UserSession session = newAnonymousSession(); + assertThat(session.hasPortfolioChildProjectsPermission(USER, portfolio)).isFalse(); + } + + @Test + public void hasPortfolioChildProjectsPermission_keeps_cache_of_permissions_of_logged_in_user() { + ComponentDto project = db.components().insertPrivateProject(); + + UserDto user = db.users().insertUser(); + db.users().insertProjectPermissionOnUser(user, USER, project); + + ComponentDto portfolio = db.components().insertPrivatePortfolio(); + db.components().addPortfolioProject(portfolio, project); + // add computed project + db.components().insertComponent(newProjectCopy(project, portfolio)); + + UserSession session = newUserSession(user); + + // feed the cache + assertThat(session.hasPortfolioChildProjectsPermission(USER, portfolio)).isTrue(); + + // change permissions without updating the cache + db.users().deletePermissionFromUser(project, user, USER); + assertThat(session.hasPortfolioChildProjectsPermission(USER, portfolio)).isTrue(); + + // cache is refreshed when user logs in again + session = newUserSession(user); + assertThat(session.hasPortfolioChildProjectsPermission(USER, portfolio)).isFalse(); + } + + @Test + public void hasPortfolioChildProjectsPermission_keeps_cache_of_permissions_of_anonymous_user() { + db.users().insertPermissionOnAnyone(USER); + + ComponentDto project = db.components().insertPublicProject(); + ComponentDto portfolio = db.components().insertPublicPortfolio(); + db.components().addPortfolioProject(portfolio, project); + + UserSession session = newAnonymousSession(); + + // feed the cache + assertThat(session.hasChildProjectsPermission(USER, portfolio)).isTrue(); + + // change privacy of the project without updating the cache + db.getDbClient().componentDao().setPrivateForRootComponentUuidWithoutAudit(db.getSession(), project.uuid(), true); + assertThat(session.hasChildProjectsPermission(USER, portfolio)).isTrue(); + } + + @Test public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_without_permissions() { ComponentDto publicProject = db.components().insertPublicProject(); diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java index 22dc9cc8745..1ef29694dd6 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java @@ -24,6 +24,7 @@ import org.junit.Before; import org.junit.Test; import org.sonar.api.resources.Qualifiers; import org.sonar.db.component.ComponentDto; +import org.sonar.db.portfolio.PortfolioDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.GroupTesting; @@ -72,6 +73,7 @@ public class ThreadLocalUserSessionTest { assertThat(threadLocalUserSession.getGroups()).extracting(GroupDto::getUuid).containsOnly(group.getUuid()); assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ComponentDto())).isFalse(); assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ProjectDto())).isFalse(); + assertThat(threadLocalUserSession.hasPortfolioChildProjectsPermission(USER, new ComponentDto())).isFalse(); assertThat(threadLocalUserSession.hasProjectPermission(USER, new ProjectDto().getUuid())).isFalse(); } @@ -96,6 +98,7 @@ public class ThreadLocalUserSessionTest { assertThat(threadLocalUserSession.getGroups()).extracting(GroupDto::getUuid).containsOnly(group.getUuid()); assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ComponentDto())).isTrue(); assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ProjectDto())).isTrue(); + assertThat(threadLocalUserSession.hasPortfolioChildProjectsPermission(USER, new ComponentDto())).isTrue(); assertThat(threadLocalUserSession.hasProjectPermission(USER, new ProjectDto().getUuid())).isTrue(); } diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AbstractMockUserSession.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AbstractMockUserSession.java index b563be3bd23..a224c1a11d3 100644 --- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AbstractMockUserSession.java +++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AbstractMockUserSession.java @@ -45,6 +45,7 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> private final Set<GlobalPermission> permissions = new HashSet<>(); private final Map<String, String> projectUuidByComponentUuid = new HashMap<>(); private final Map<String, Set<String>> applicationProjects = new HashMap<>(); + private final Map<String, Set<String>> portfolioProjects = new HashMap<>(); private final Set<String> projectPermissions = new HashSet<>(); private boolean systemAdministrator = false; private boolean resetPassword = false; @@ -133,6 +134,19 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> return clazz.cast(this); } + public T registerPortfolioProjects(PortfolioDto portfolio, ProjectDto... portfolioProjects) { + registerPortfolios(portfolio); + registerProjects(portfolioProjects); + + Set<String> portfolioProjectsUuid = Arrays.stream(portfolioProjects) + .map(ProjectDto::getUuid) + .collect(Collectors.toSet()); + + this.portfolioProjects.put(portfolio.getUuid(), portfolioProjectsUuid); + + return clazz.cast(this); + } + public T addProjectPermission(String permission, ComponentDto... components) { Arrays.stream(components).forEach(component -> { checkArgument( @@ -189,6 +203,13 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> .allMatch(projectUuid -> projectPermissions.contains(permission) && projectUuidByPermission.get(permission).contains(projectUuid)); } + @Override + protected boolean hasPortfolioChildProjectsPermission(String permission, String portfolioUuid) { + return portfolioProjects.containsKey(portfolioUuid) && portfolioProjects.get(portfolioUuid) + .stream() + .allMatch(projectUuid -> projectPermissions.contains(permission) && projectUuidByPermission.get(permission).contains(projectUuid)); + } + public T setSystemAdministrator(boolean b) { this.systemAdministrator = b; return clazz.cast(this); diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java index 7141fea1111..c762d013731 100644 --- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java +++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java @@ -277,6 +277,11 @@ public class UserSessionRule implements TestRule, UserSession { } @Override + public boolean hasPortfolioChildProjectsPermission(String permission, ComponentDto component) { + return currentUserSession.hasPortfolioChildProjectsPermission(permission, component); + } + + @Override public boolean hasComponentUuidPermission(String permission, String componentUuid) { return currentUserSession.hasComponentUuidPermission(permission, componentUuid); } diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java index cf3a2336dff..67b1e5ba512 100644 --- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java +++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java @@ -127,6 +127,9 @@ public class TestUserSessionFactory implements UserSessionFactory { } @Override + protected boolean hasPortfolioChildProjectsPermission(String permission, String portfolioUuid) { throw notImplemented(); } + + @Override public boolean isSystemAdministrator() { throw notImplemented(); } |